deckscape/src/DocVBT.m3


 Copyright 1996 Digital Equipment Corporation.              
 Distributed only by permission.                            
                                                            
 Last modified on Mon Aug 19 22:13:45 PDT 1996 by mhb       

<* PRAGMA LL *>

MODULE DocVBT;

IMPORT Env, Fmt, FormsVBT, Rsrc, MyBundle, WSObjectVBT, VBT, Web, WebVBT,
       Thread, DeckVBT, WorkspaceVBT, FVTypes, SourceVBT, Text, TextList,
       FreeDocVBT, AnyEvent, Point, Rect, Options;

<* FATAL ANY *>

TYPE
  MyWebVBT = WebVBT.T OBJECT
    doc: T;
    fork: BOOLEAN;
    mu: MUTEX;
    cond: Thread.Condition;
    done: BOOLEAN;
  OVERRIDES
    hotlink := HotLink;
    ready := Ready;
  END;

REVEAL T = FormsVBT.T BRANDED OBJECT
    owner: WSObjectVBT.T := NIL;
    webvbt: MyWebVBT;
  OVERRIDES
    realize := Realize;
  END;

TYPE
  Source = FVTypes.FVSource OBJECT
    doc: T;
  OVERRIDES
    hit := Hit;
  END;

PROCEDURE Realize (doc: T; type, name: TEXT) : VBT.T
  RAISES {FormsVBT.Error} =
  BEGIN
    IF Text.Equal (name, "source") THEN
      RETURN NEW (Source, doc := doc)
    ELSE
      RETURN FormsVBT.T.realize (doc, type, name)
    END;
  END Realize;

PROCEDURE NewDoc (): T =
  VAR
    doc      := NEW(T);
    path     := Rsrc.BuildPath("$DeckScapePATH", MyBundle.Get());
    delete   := NEW(FormsVBT.Closure, apply := Delete);
    hotlist  := NEW(FormsVBT.Closure, apply := Hotlist);
    reparent := NEW(FormsVBT.Closure, apply := Reparent);
  BEGIN
    EVAL FormsVBT.T.initFromRsrc(doc, "Doc.fv", path, TRUE);
    FormsVBT.Attach(doc, "killButton", delete);
    FormsVBT.Attach(doc, "hotlistButton", hotlist);
    FormsVBT.Attach(doc, "source", reparent);
    doc.webvbt := NEW(MyWebVBT, doc:=doc).init();
    FormsVBT.PutGeneric(doc, "gen", doc.webvbt);
    RETURN doc;
  END NewDoc;

PROCEDURE NewFromPage (page: Web.Page; base: TEXT): T =
  VAR
    doc    := NewDoc();
    webvbt := doc.webvbt;
  BEGIN
    FormsVBT.PutText(doc, "docName", base);
    webvbt.fork := TRUE;
    webvbt.fromText(
      style := GetCurrentStyle(), zippers := GetCurrentZippers(),
      contents := page.contents, contentType := page.header.contentType,
      contentSubType := page.header.contentSubType, url := base);
    RETURN doc;
  END NewFromPage;

PROCEDURE NewFromURL (url: TEXT := NIL; reload := FALSE; fork := TRUE): T =
  VAR
    doc    := NewDoc();
    webvbt := doc.webvbt;
  BEGIN
    IF url = NIL THEN url := DefaultHomeURL END;
    FormsVBT.PutText(doc, "docName", url);
    webvbt.fork := fork;
    IF fork THEN
      webvbt.fetch(url, reload := reload, style := GetCurrentStyle(),
                   zippers := GetCurrentZippers());
    ELSE
      webvbt.mu := NEW(MUTEX);
      webvbt.cond := NEW(Thread.Condition);
      webvbt.done := FALSE;
      LOCK webvbt.mu DO
        webvbt.fetch(url, reload := reload, style := GetCurrentStyle(),
                     zippers := GetCurrentZippers());
        WHILE NOT webvbt.done DO Thread.Wait(webvbt.mu, webvbt.cond) END
      END
    END;
    RETURN doc;
  END NewFromURL;
Bug: GetCurrent... procedures need to lock VBT.mu; they cannot be NewFromURL is called sometime with VBT.mu lock (e.g., WorkspaceVBT.NewDeck) and sometime without (e.g., DoExpand).

PROCEDURE GetCurrentZippers (): BOOLEAN =
  BEGIN
    RETURN Options.zippers
  END GetCurrentZippers;

PROCEDURE GetCurrentStyle (): WebVBT.Style =
  BEGIN
    IF Options.fgImages THEN
      RETURN WebVBT.Style.Normal
    ELSE
      RETURN WebVBT.Style.Background
    END
  END GetCurrentStyle;

PROCEDURE Ready (w: MyWebVBT; ct: CARDINAL) =
  BEGIN
    IF NOT w.fork AND ct = 0 THEN
      LOCK w.mu DO w.done := TRUE; Thread.Broadcast(w.cond); END
    END
  END Ready;

PROCEDURE Copy (doc: T): T =
  BEGIN
    RETURN NewFromPage(doc.webvbt.page, doc.webvbt.url);
  END Copy;

PROCEDURE SetOwner (doc: T; owner: VBT.T) =
  BEGIN
    doc.owner := owner;
  END SetOwner;

PROCEDURE GetOwner (doc: T): VBT.T =
  BEGIN
    RETURN doc.owner
  END GetOwner;

PROCEDURE GetPage (doc: T): Web.Page =
  BEGIN
    RETURN doc.webvbt.page;
  END GetPage;

PROCEDURE GetTitle (doc: T): TEXT =
  BEGIN
    TYPECASE doc.webvbt.page OF
    | NULL => RETURN "fetching..."
    | WebVBT.HTMLPage (p) => RETURN p.html.title
    | WebVBT.ImagePage => RETURN "<image>"
    ELSE
      RETURN "<????>"
    END;
  END GetTitle;

PROCEDURE Hit (                      s     : Source;
                                     target: VBT.T;
               <* UNUSED *> READONLY cd    : VBT.PositionRec):
  BOOLEAN =
  VAR owner := s.doc.owner;
  BEGIN
    IF ISTYPE(owner, DeckVBT.T) THEN
      (* a DocVBT inside a DeckVBT can go into the Workspace
         and into a DeckVBT other than its owner *)
      IF ISTYPE(target, WorkspaceVBT.Target) THEN
        RETURN TRUE
      ELSE
        RETURN target # DeckVBT.GetTarget (owner)
      END
    ELSE
      (* a DocVBT inside a FreeDocVBT can go into any DeckVBT,
         but not into the Workspace *)
      RETURN ISTYPE (target, DeckVBT.Target)
    END
  END Hit;

PROCEDURE Reparent (<*UNUSED*> cl  : FormsVBT.Closure;
                               fv  : FormsVBT.T;
                    <*UNUSED*> name: TEXT;
                    <*UNUSED*> time: VBT.TimeStamp     ) =
  VAR
    doc    := NARROW(fv, T);
    source := NARROW(FormsVBT.GetVBT(fv, "source"), Source);
    target := SourceVBT.GetTarget(source);
    owner  := source.doc.owner;
    newDeck   : DeckVBT.T;
    newFreeDoc: FreeDocVBT.T;
  BEGIN
    TYPECASE owner OF
    | DeckVBT.T (deck) =>
        (* a DocVBT in a DeckVBT; the target is either a
           Workspace or a DeckVBT *)
        IF ISTYPE(target, WorkspaceVBT.Target) THEN
          DeckVBT.RemDoc(deck, doc);
          newFreeDoc :=
            FreeDocVBT.New(DeckVBT.GetTitle(deck) & " [DOC]");
          VAR dom := VBT.Domain(deck);
              nw := LocateMouse(fv);
              hor := Rect.HorSize(dom);
              ver := Rect.VerSize(dom);
              r := Rect.FromEdges (nw.h, nw.h+hor, nw.v, nw.v+ver);
          BEGIN
            WorkspaceVBT.AddFreeDoc(deck.getWorkspace(), newFreeDoc, r);
          END;
          DeckVBT.AddFreeDoc(deck, newFreeDoc);
          FreeDocVBT.AddDoc(newFreeDoc, doc);
        ELSE
          DeckVBT.RemDoc(deck, doc);
          newDeck := NARROW(target, DeckVBT.Target).deck;
          DeckVBT.AddDoc(newDeck, doc);
        END
    | FreeDocVBT.T (freeDoc) =>
        (* a DocVBT in a FreeDocVBT; the target must be a
           DeckVBT *)
        FreeDocVBT.RemDoc(freeDoc, doc);
        newDeck := NARROW(target, DeckVBT.Target).deck;
        DeckVBT.AddDoc(newDeck, doc);
    ELSE                         <* ASSERT FALSE *>
    END;
  END Reparent;

PROCEDURE LocateMouse (fv: FormsVBT.T): Point.T =
  BEGIN
    TYPECASE FormsVBT.GetTheEvent(fv) OF
    | AnyEvent.Mouse (m) =>
        WITH cp = m.mouse.cp DO
          IF NOT cp.offScreen THEN RETURN cp.pt END
        END
    ELSE
    END;
    RETURN Point.Origin
  END LocateMouse;

PROCEDURE Delete (<*UNUSED*> cl  : FormsVBT.Closure;
                             fv  : FormsVBT.T;
                  <*UNUSED*> name: TEXT;
                  <*UNUSED*> time: VBT.TimeStamp     ) =
  VAR doc: T := fv;
  BEGIN
    doc.owner.remDoc(doc);
  END Delete;

PROCEDURE Hotlist (<*UNUSED*> cl  : FormsVBT.Closure;
                              fv  : FormsVBT.T;
                   <*UNUSED*> name: TEXT;
                   <*UNUSED*> time: VBT.TimeStamp     ) =
  VAR doc: T := fv;
       ws: WorkspaceVBT.T := doc.owner.getWorkspace();
  BEGIN
    DeckVBT.AddDoc(ws.hotlist, Copy(doc));
  END Hotlist;

PROCEDURE Search (doc: T; text: TEXT): BOOLEAN =
  BEGIN
    RETURN doc.webvbt.search(text)
  END Search;

PROCEDURE Reload (doc: T): T =
  BEGIN
    RETURN NewFromURL(doc.webvbt.url, reload := TRUE)
  END Reload;

TYPE
  Link = REF RECORD
    label, url: TEXT;
    next: Link;
  END;

TYPE
  ExpandClosure = Thread.Closure OBJECT
                    (* READONLY by threads: *)
                    doc     : T;
                    deck    : DeckVBT.T;
                    numLinks: INTEGER;
                    (* protected by mu: *)
                    mu   : MUTEX;
                    links: TextList.T;  (* NIL'd once processed *)
                  OVERRIDES
                    apply := DoExpand;
                  END;

PROCEDURE Expand (doc: T): VBT.T =
  VAR
    links := doc.webvbt.getLinks();
    l     := links;
    ct    := 0;
    mu    := NEW(MUTEX);
  BEGIN
    WHILE l # NIL DO INC(ct); l := l.tail END;
    WITH deck = DeckVBT.New("Expanding " & Fmt.Int(ct) & " links") DO
      LOCK mu DO
        FOR th := 1 TO NumberOfExpansionThreads DO
          EVAL Thread.Fork(NEW(ExpandClosure, doc := doc, deck := deck,
                               numLinks := ct, mu := mu, links := links));
        END
      END;
      RETURN deck
    END
  END Expand;

PROCEDURE DoExpand (cl: ExpandClosure): REFANY =
  VAR
    doc   := cl.doc;
    deck  := cl.deck;
    links := cl.links;
  VAR
    l     : TextList.T;
    url   : TEXT;
    newURL: TEXT;
    newDoc: T;
  BEGIN
    LOOP
      LOCK cl.mu DO
        l := links;
        WHILE l # NIL AND l.head = NIL DO l := l.tail END;
        IF l = NIL THEN EXIT END;
        url := l.head;
        l.head := NIL;
      END;
      newURL := Web.AbsoluteURL(url, doc.webvbt.url);
      newDoc := NewFromURL(newURL, fork := FALSE);
      LOCK VBT.mu DO DeckVBT.AddDoc(deck, newDoc, FALSE) END
    END;
    LOCK VBT.mu DO DeckVBT.SetTitle(deck, "Expanded"); END;
    RETURN NIL;
  END DoExpand;

PROCEDURE HotLink (w: MyWebVBT; url: TEXT; READONLY cd: VBT.MouseRec) =
  VAR
    expandedURL    := Web.AbsoluteURL(url, w.url);
    newDoc     : T;
  BEGIN
    IF VBT.Modifier.Control IN cd.modifiers THEN
      VAR
        deck    := WhichDeck(w.doc);
        docList := DeckVBT.DocList(deck, includeFreeDocs := FALSE);
      BEGIN
        WHILE docList # NIL DO
          VAR doc: T := docList.head;
          BEGIN
            IF Text.Equal(expandedURL, doc.webvbt.url) THEN
              DeckVBT.SetTopDoc(deck, DeckVBT.IndexOfDoc(deck, doc));
              RETURN
            END
          END;
          docList := docList.tail
        END
      END
    END;
    newDoc := NewFromURL(expandedURL);
    w.doc.owner.addDoc(newDoc);
  END HotLink;

PROCEDURE WhichDeck (doc: T): DeckVBT.T =
  VAR owner := doc.owner;
  BEGIN
    IF ISTYPE (owner, DeckVBT.T) THEN
      RETURN owner
    ELSIF ISTYPE (owner, FreeDocVBT.T) THEN
      RETURN FreeDocVBT.GetDeck (owner)
    ELSE <* ASSERT FALSE *>
    END
  END WhichDeck;

BEGIN
  DefaultHomeURL := Env.Get("WWW_HOME");
  IF DefaultHomeURL = NIL OR Text.Empty(DefaultHomeURL) THEN
      DefaultHomeURL := "https://www.research.digital.com/"
  END;
END DocVBT.