;;;-*- Mode:LISP; Package: (browser global); Base:10.; fonts: cptfontb -*- ;;; ;;; $header: /ct/browser/browser.l,v 1.5 84/02/23 18:58:21 jmiller Exp $ ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; BROWSER (formerly DB-WALKER) ;;; ;;; James R. Miller May 3, 1983 ;;; ;;; ;;; ;;; Ungeneralized to use simple flavor representation for text ;;; ;;; nodes only -- jrm, 2/1/84 ;;; ;;; ;;; ;;; modifications by John Shelton to interface to ;;; ;;; Tutor Module 5-May-83 ;;; ;;; ;;; ;;; Generalized to handle arbitrary node graphs ;;; ;;; by Tutor Group 12 May to 14 May 83 ;;; ;;; ;;; ;;; Further generalized for standalone operation, next/previous ;;; ;;; operation, automatic selection of menu fonts: jrm, 5/20-24/83 ;;; ;;; ;;; ;;; Menu labels, multiple documents added: jrm, 5/25/83 ;;; ;;; ;;; ;;; This file is part of a proprietary software project. Source ;;; ;;; code and documentation describing implementation details are ;;; ;;; available on a confidential, non-disclosure basis only. These ;;; ;;; materials, including this file in particular, are trade secrets ;;; ;;; of Computer * Thought Corporation. ;;; ;;; ;;; ;;; (c) Copyright 1982 and 1983, Computer * Thought Corporation. ;;; ;;; All Rights Reserved. ;;; ;;; ;;; ;;; Reference materials: ;;; ;;; Foderaro and Sklower, The FRANZ LISP Manual, September 1981. ;;; ;;; Weinreb and Moon, LISP MACHINE MANUAL, Symbolics, July 1981. ;;; ;;; Charniak et al., 1980. Artificial Intelligence Programming. ;;; ;;; Miller, 1982. The C*T Ada Tutor: Guide to the Implementation. ;;; ;;; The following code assumes familiarity with these materials. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Ensure presence of needed files. ;;; Assumes ct_load and some suitable file-map are present (eval-when (load eval compile) ;;Necessary files: (ct_load 'user:charmac) ;CT char set extensions. (ct_load 'user:aip) ;AIP macros pkg. (ct_load 'user:compat) ;Franz/LM compat pkg. (ct_load 'user:ctflav) ;flavor tools (ct_load 'user:menufix) ;John's menu fix: (ct_load 'user:leftmenu) ;John's left-justified-menu stuff (ct_load 'user:protect) ;PROTECT software ;;Load the browser/view window fonts (ct_load 'user:brchars) (ct_load 'user:cttr12i) (ct_load 'user:cthl12) (ct_load 'user:cthl12i) (ct_load 'user:cthl12b) ;;Use the new version of VIEW, which, at this point, is not in the filemap. (ct_load 'user:newview) ;;Load fonts not on 3600... (ct_load 'user:ct18) (ct_load 'user:ct18b) (ct_load 'user:25fr3) (ct_load 'user:cttr18) (ct_load 'user:cttr18b) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Compiler Declarations and Global Variables -- (declare (special *all-known-documents* *browser-window* *xref-menu* *old-browser-menu* *db-walker-types* user:*browser-release* *browser-release-string*)) ;;;Globals: ;;;*DB-WALKER-TYPES contains the description of db-walker types. (setq *db-walker-types* '((documents (:children "Documents:")) (document (:parent "Up to Document Collection:") (:children "Down to Chapters:")) (chapter (:parent "Up to Containing Document:") (:children "Down to Sections:") (:priornode "Back to Previous Chapter:") (:nextnode "Forward to Next Chapter:")) (section (:parent "Up to Containing Chapter:") (:children "Down to Subsections:") (:priornode "Back to Previous Section:") (:nextnode "Forward to Next Section:")) (subsection (:parent "Up to Containing Section:") (:priornode "Back to Previous Subsection:") (:nextnode "Forward to Next Subsection:") (:children "Down to Paragraphs:")) (paragraph (:parent "Up to Containing Subsection:") (:priornode "Back to Previous Paragraph:") (:nextnode "Forward to Next Paragraph:")))) ;;;*FONT-SIZE-LIST*: (defvar *font-size-list* '((12 fonts:tr10b fonts:tr10) (16 fonts:tr12b fonts:tr12) (20 fonts:cttr18b fonts:cttr18)) "An a-list of fonts and sizes, used by COMPUTE-LARGEST-POSSIBLE-FONT to find the largest font capable of showing a certain amount of information. The first font is bold (for menu entries); the second is normal (for menu headers. This should ultimately be replaced with italics).") (defvar *naughty_window* (tv:make-window 'tv:window ':character-height 10. ':width 700. ':font-map '(fonts:cptfontb) ':save-bits nil ':deexposed-typeout-action ':permit ':label '(:top :string "Warning Window" :font fonts:cptfontb))) ;;;*BROWSER-RELEASE(-STRING)*: The string is automatically updated by ;;;RCS; USER:*BROWSER-RELEASE* then gets the number following the "$Revision: " ;;;part of the string. Use SETQs for the moment (at least) to make sure ;;;this thing works. (setq *browser-release-string* "$Revision: 1.39 $") ;;;EXTRACT-REVISION-NUMBER: given a RCS revision string, skip past the ;;;"$Revision:" label (to avoid package complaints) and read and return ;;;the revision number. Semi-ugly, but it'll do. Define this in an ;;;EVAL-WHEN so the function will be defined when the file is compiled, ;;;and USER:*BROWSER-RELEASE* is given its value. Note that for this to ;;;work properly, you should first check the file in (so that the revision ;;;number gets updated) and THEN compile the version in LATEST. (eval-when (load eval compile) (defun extract-revision-number (string) (read-from-string (with-input-from-string (str string) (loop for i from 1 to 100000 until (eq (send str ':tyi) #/:) finally (return (substring string i))))))) (setq user:*browser-release* (extract-revision-number *browser-release-string*)) ;;;*MANUAL-DIRECTORY-ALIST*: (defvar *manual-directory-alist* nil "An a-list of document types and host/directory locations. This global can be changed by a site, and the system will look in the appropriate place at runtime.") ;;;*DOCUMENT-TYPE-HIERARCHY*: (defvar *document-type-hierarchy* '(top-of-tree documents document chapter section subsection paragraph) "The types of document type entries known to the system, in top-to-bottom order.") ;;;LEFT-MOMENTARY-MENU: A momentary menu with left-justified items (defflavor left-momentary-menu () (tv:left-menu-mixin tv:momentary-menu)) ;;;XREF-MENU: The cross-reference menu (setq *xref-menu* (tv:make-window 'left-momentary-menu ':borders 2 ':font-map '(fonts:ct18b) ':label '(:string "Pick a cross-reference, or move away to stay where you are" :font fonts:ct18))) ;;;OLD-BROWSER-MENU: The menu for selecting old browsers (defvar *old-browser-menu* (tv:make-window 'left-momentary-menu ':borders 2 ':font-map '(fonts:ct18b) ':label '(:string "Pick an old browser to return to, or move away to stay in the current browser." :font fonts:ct18))) ;;;This change to the BEFORE CHOOSE method has the effect of centering a ;;;popped-up menu around the mouse's Y position, but not the X position. ;;;This repairs the rather annoying trait of the mouse getting yanked out of ;;;position in the browser's vertical menus -- after you leave the menu ;;;(either by selecting or moving off), the mouse cursor will still be near ;;;the menu items that you were just working with. Obviously, the code is ;;;adapted from TV:BASIC-MOMENTARY-MENU. tv:(DEFMETHOD (browser:left-MOMENTARY-MENU :BEFORE :CHOOSE) () (COND ((NOT EXPOSED-P) (MULTIPLE-VALUE-BIND (X-OFF Y-OFF) (SHEET-CALCULATE-OFFSETS SUPERIOR MOUSE-SHEET) (MULTIPLE-VALUE-BIND (X Y) (FUNCALL-SELF ':CENTER-AROUND (- MOUSE-X X-OFF) (- MOUSE-Y Y-OFF)) x ;keep compiler messages quiet (MOUSE-WARP mouse-x ;used to be (+ X X-OFF) (+ Y Y-OFF)))) ;; Expose self, and seize the mouse. (WITH-MOUSE-GRABBED (FUNCALL-SELF ':EXPOSE) (COND ((NEQ SELF (LOWEST-SHEET-UNDER-POINT MOUSE-SHEET MOUSE-X MOUSE-Y NIL ':EXPOSED)) (FUNCALL-SELF ':DEACTIVATE) (*THROW 'ABORT NIL))))))) ;;;*DOCUMENT-LONG-LABELS*: (defvar *document-long-labels* nil "This holds the labels used in the 'You are now at ...' part of the menu.") ;;;*DOCUMENT-SHORT-LABELS*: (defvar *document-short-labels* nil "This holds the labels used in the MAJOR-SUBHEADS menu.") ;;;Menu flavor definitions and changes: ;;;LEFT-COMMAND-MENU: A menu with items left-justified (defflavor left-command-menu () (tv:pop-up-notification-mixin tv:line-truncating-mixin tv:left-menu-mixin tv:command-menu-pane #+LMI tv:stream-mixin #+LMI tv:select-mixin) (:default-init-plist :truncate-line-out-flag 1)) ;;;LEFT-COMMAND-MENU-WITH-TYPEOUT supports BREAK/RESUME in the middle of ;;;a browser session. (defflavor left-command-menu-with-typeout () (left-command-menu tv:window-with-typeout-mixin #+LMI tv:stream-mixin #+LMI tv:select-mixin) (:default-init-plist :typeout-window '(tv:typeout-window :deexposed-typeout-action (:expose-for-typeout) :io-buffer nil))) (defmethod (left-command-menu-with-typeout :after :init) (&rest ignore) (send (send self ':typeout-window) ':set-io-buffer (send self ':io-buffer))) ;;;UNSCROLLING-COMMAND-MENU-PANE is a command menu that doesn't scroll; do ;;;this by defining away the SCROLL-BAR method. (defflavor unscrolling-command-menu-pane () (tv:line-truncating-mixin tv:command-menu-pane #+LMI tv:stream-mixin) (:default-init-plist :truncate-line-out-flag 1)) (defmethod (unscrolling-command-menu-pane :scroll-bar-p) () nil) ;;;SCROLLING-COMMAND-MENU-PANE is a command menu that does scroll (defflavor scrolling-command-menu-pane () (tv:line-truncating-mixin tv:command-menu-pane #+LMI tv:stream-mixin) (:default-init-plist :truncate-line-out-flag 1)) ;;;Internal macros (that have to be defined here): ;;;INTERNED-GENSYM: ********************************************************** ;;;Intern a gensym'ed symbol (defmacro interned-gensym (&optional arg) (cond (arg `(intern (gensym ,arg))) (t `(intern (gensym))))) ;;;BROWSER-ENVIRONMENT: ****************************************************** ;;;A flavor describing a browser environment -- for now, this is little more ;;;than a record (defflavor browser-environment ((browser) (name) (number) (current-node) (nodestack)) () :gettable-instance-variables :settable-instance-variables :initable-instance-variables) ;;;AFTER INIT: Set up the NUMBER pointer to this environment. (defmethod (browser-environment :after :init) (&rest ignore) (send self ':set-number (1+ (length (send browser ':existing-browsers)))) (user:protect 'browser)) ;;; Database walker: ********************************************************** (defflavor browser ((current-node) (previous-node) (browser-selection-menu) (subhead-menu) (nodestack-menu) (node-text-menu) (textwindow) (nodestack) (major-subheads) (current-browser-environment) (existing-browsers) (refresh-the-screen-p)) ;if t, force a refresh (tv:any-tyi-mixin tv:process-mixin tv:select-mixin tv:stream-mixin tv:bordered-constraint-frame-with-shared-io-buffer) (:default-init-plist :save-bits t) (:documentation :special-purpose "The Browser Window") :settable-instance-variables :gettable-instance-variables :initable-instance-variables) (defmethod (browser :before :init) (&rest ignore) ;;Initialize stuff (setq tv:process '(browser-top-level :regular-pdl-size 8000 :special-pdl-size 6000) tv:panes `((browser-selection-menu unscrolling-command-menu-pane :item-list nil :borders 2 :columns 1 :label (:string "Browser selection" :font fonts:cptfontb)) (subhead-menu scrolling-command-menu-pane :item-list nil :borders 2 :columns 1 :label (:string "Documents" :font fonts:cptfontb)) (nodestack-menu left-command-menu :borders 2 :item-list nil :label (:string "Previous positions" :font fonts:cptfontb)) (message-window tv:window-pane :borders 2 :font-map (fonts:metsi)) (textwindow user:viewing_frame :borders 2) (node-text-menu left-command-menu-with-typeout :item-list nil :borders 2 :label (:string ,(format nil "The Computer * Thought Documentation Browser: Version ~a" user:*browser-release*) :font fonts:tr12b :bottom))) tv:constraints '((command-and-node-window (topstrip node-text-menu) ((topstrip :horizontal (175) (nodestack-menu subhead-and-selection) ((nodestack-menu 0.7) (subhead-and-selection :vertical (0.3) (browser-selection-menu subhead-menu) ((browser-selection-menu 0.4) (subhead-menu 0.6)))))) ((node-text-menu :even))) (messages-only (message-window) ((message-window :even))) (text-display-only (textwindow) ((textwindow :even)))))) (defmethod (browser :after :init) (&rest ignore) (setq browser-selection-menu (send self ':get-pane 'browser-selection-menu)) (setq subhead-menu (send self ':get-pane 'subhead-menu)) (setq nodestack-menu (send self ':get-pane 'nodestack-menu)) (setq textwindow (send self ':get-pane 'textwindow)) (setq node-text-menu (send self ':get-pane 'node-text-menu))) ;;;(BROWSER :BROWSER-TOP-LEVEL): *************************************** ;;;The top-level function and method. (BROWSER-TOP-LEVEL) is called when ;;;the process is started up or when ABORT is hit during execution. (defun browser-top-level (terminal-io) (send terminal-io ':browser-top-level)) (defmethod (browser :browser-top-level) () ;;Just in case... (setq base 10. ibase 10. *nopoint t) ;;Select the browser and walk it, starting at *ALL-KNOWN-DOCUMENTS*. (setq *browser-window* self) ;;Clear PREVIOUS-NODE, so that the screen will still get redrawn ;;after an abort. (setq previous-node nil) (send self ':select) (send self ':walk-database *all-known-documents*)) ;;;(BROWSER :CLEAR-SCREEN): ****************************************** ;;;Clear the three menu panes of the db walker. (defmethod (browser :clear-screen) () (loop for pane in '(nodestack-menu subhead-menu node-text-menu) do (send self ':send-pane pane ':set-item-list nil))) ;;;(BROWSER :MENU-SELECT): ********************************** ;;;Get a menu item from any of the three menus. (defmethod (browser :after :select) (&rest ignore) (send node-text-menu ':select)) (defmethod (browser :menu-select) () (send terminal-io ':clear-input) (send node-text-menu ':select) (loop as response = (send terminal-io ':any-tyi) doing ;;Return the three values RAWMENUITEM, ITEM-PNAME, and MENU-PANE). (cond ((listp response) (return (second response) (string-trim '(#\sp) (first (second response))) (fourth response))) ((memq response '(#\c-L #\clear-screen)) (send self ':refresh))))) (comment ;;; Oh yuck. Need to do these to make Shift-Right-Click work. ;;; Won't do them for now, but we may want to add them later. ;;; Soley & JRM, 8/15/84 (DEFMETHOD (browser:left-command-menu :MOUSE-BUTTONS) (BD X Y) BD X Y ;;ignored, we don't care where the ;;mouse is, the :MOUSE-MOVES method ;;took care of that (LET ((BUTTONS (MOUSE-BUTTON-ENCODE BD))) (COND ((= BUTTONS #\MOUSE-R-2) (MOUSE-CALL-SYSTEM-MENU)) (CURRENT-ITEM ;Any button, select item. (SETQ LAST-ITEM CURRENT-ITEM CHOSEN-ITEM CURRENT-ITEM) (COND ((AND (LISTP CHOSEN-ITEM) ( (LENGTH CHOSEN-ITEM) 3) (EQ (SECOND CHOSEN-ITEM) ':BUTTONS)) (SETQ CHOSEN-ITEM (NTH (1- (HAULONG BD)) (THIRD CHOSEN-ITEM)))))) ((AND ( X (SHEET-INSIDE-LEFT)) (< X (SHEET-INSIDE-RIGHT)) ( Y (SHEET-INSIDE-TOP)) (< Y (SHEET-INSIDE-BOTTOM)))) (T ;; Here, clicked on the window, but outside of the window proper. ;; Send a :MOUSE-CLICK message so things like margin regions can ;; work. (FUNCALL-SELF ':MOUSE-CLICK BD X Y))))) (DEFMETHOD (browser:left-command-menu-with-typeout :MOUSE-BUTTONS) (BD X Y) BD X Y ;;ignored, we don't care where the ;;mouse is, the :MOUSE-MOVES method ;;took care of that (LET ((BUTTONS (MOUSE-BUTTON-ENCODE BD))) (COND ((= BUTTONS #\MOUSE-R-2) (MOUSE-CALL-SYSTEM-MENU)) (CURRENT-ITEM ;Any button, select item. (SETQ LAST-ITEM CURRENT-ITEM CHOSEN-ITEM CURRENT-ITEM) (COND ((AND (LISTP CHOSEN-ITEM) ( (LENGTH CHOSEN-ITEM) 3) (EQ (SECOND CHOSEN-ITEM) ':BUTTONS)) (SETQ CHOSEN-ITEM (NTH (1- (HAULONG BD)) (THIRD CHOSEN-ITEM)))))) ((AND ( X (SHEET-INSIDE-LEFT)) (< X (SHEET-INSIDE-RIGHT)) ( Y (SHEET-INSIDE-TOP)) (< Y (SHEET-INSIDE-BOTTOM)))) (T ;; Here, clicked on the window, but outside of the window proper. ;; Send a :MOUSE-CLICK message so things like margin regions can ;; work. (FUNCALL-SELF ':MOUSE-CLICK BD X Y))))) ...end of commented-out mouse-buttons methods ) ;;;(BROWSER :SELECT-A-NEW-BROWSER) ******************************************* ;;;Pop up a menu describing the browser environments in existence, and ;;;let the user select one. Don't show the browser corresponding to the ;;;current position. (defmethod (browser :select-a-new-browser) () (let ((browser-list (loop for item in existing-browsers unless (equal (send item ':name) (send current-browser-environment ':name)) collect (list (send (send item ':current-node) ':longlabel) item))) (response)) (cond ;;If there's only one other browser, go to it, making sure the screen ;;is re-displayed. ((= (length browser-list) 1) (setq refresh-the-screen-p t) (second (first browser-list))) ;;If there is more than one browser, build a menu list of the ;;existing browsers... (t (send *old-browser-menu* ':set-item-list (loop for item in (reverse browser-list) with docname = nil do (setq docname (send (send (second item) ':current-node) ':documentname)) collect `(,(format nil "Browser ~d: ~a~a~a" (send (second item) ':number) (get-document-abbr docname) (cond (docname ": ") (t "")) (first item)) :value ,(send (second item) ':name)))) ;;...pop up a menu of the items, and look for the selected item ;;on BROWSER-LIST; return the selected node. (cond ((setq response (send *old-browser-menu* ':choose)) (loop for item in browser-list when (equal response (send (second item) ':name)) do (setq refresh-the-screen-p t) (return (second item)))) ;;If :CHOOSE returns NIL -- if no selection was made -- ;;return NIL, and don't bother redrawing the screen. (t (setq refresh-the-screen-p nil) nil)))))) ;;;(BROWSER :SET-UP-MENUS) ******************************************* ;;;Set up the window menus to correspond to the user's current position ;;;and history in the system. (defmethod (browser :set-up-menus) (node nodecont) ;;NODESTACK-MENU contains the nodes visited in this incarnation of the ;;database walker. Notice that the list of fonts passed to ;;BUILD-FONTED-MENU-LIST is faked (i.e., the bold font is supposed to be ;;first) so that the items in the nodestack menu will be in uppercase. (send nodestack-menu ':set-item-list (build-fonted-menu-list (loop for node in nodestack collect (list (string-append (get-document-abbr (send node ':documentname)) (cond ((send node ':documentname) ": ") (t "")) (send node ':shortlabel)) ':value (send node ':shortlabel))) '(fonts:cttr18 fonts:cttr18b))) ;;...SUBHEAD-MENU gets the accessible major topics. Recompute them, ;;using the names of the network's first-level children, each time ;;in case they have changed. (send subhead-menu ':set-item-list (cond ((setq major-subheads (loop for child in (send *all-known-documents* ':children) collect (list (send child ':documentname) (get-document-abbr (send child ':name)) (send child ':longlabel) child))) (loop with subheadlabel = nil for subhead in major-subheads do (setq subheadlabel (second subhead)) unless (equal subheadlabel "") collect `(,subheadlabel :value ,subheadlabel :font fonts:cttr18b :documentation ,(format nil "Browse through ~a (Version ~a)" (third subhead) (get (first subhead) 'release-number))))))) ;;BROWSER-SELECTION-MENU lets the user drop into a fresh browser and ;;return to other existing browsers (if any exist). (send browser-selection-menu ':set-item-list `(("Create New Browser" :value "Create New Browser" :font fonts:cttr18b :documentation "Drop into a new browser, starting at the current position.") ;;If more than one browser exists, allow the selection of them. ,@(cond ((> (length existing-browsers) 1) `(("Select Old Browser" :value "Select Old Browser" :font fonts:cttr18b :documentation "Select one of the browsers created earlier in this session" )))))) ;;...and NODE-TEXT-MENU contains the nodes that can be visited or files ;;that can be viewed. (send node-text-menu ':set-item-list (create-topic-menu node nodecont node-text-menu current-browser-environment))) ;;;(BROWSER WALK-DATABASE): ***************************************** ;;;Given a pointer to a database structure, let the user wander around in ;;;this structure as he likes. (defmethod (browser :walk-database) (tree) ;;The master control loop: (loop with instructions-and-node = nil initially ;;Clear the menu system and set it up for deexposed-typeout. (send self ':clear-screen) (send self ':set-deexposed-typeout-action 'permit) ;;Start walking the tree at either CURRENT-NODE or TREE. ;;(CURRENT-NODE will have a value when re-entering this ;;method after hitting ABORT). (cond ((null current-node) (setq current-node tree))) ;;Initialize EXISTING-BROWSERS and CURRENT-BROWSER-ENVIRONMENT, but ;;be careful about this, since we can get here either by starting ;;a fresh browser or by hitting ABORT during execution. Only do the ;;following when a fresh browser is being built -- when there isn't ;;anything in CURRENT-BROWSER-ENVIRONMENT. (cond ((null current-browser-environment) ;;Create EXISTING-BROWSERS as this browser environment, and ;;set up CURRENT-BROWSER-NAME as this environment's name. (setq existing-browsers (list (setq current-browser-environment (make-instance 'browser-environment ':browser self ':name (interned-gensym 'b) ':current-node current-node ':nodestack nil)))))) ;;Start the (infinite) loop: do ;;De-expose the typeout window if it happens to be up, and refresh ;;the browser. (if (eq tv:selected-window (send node-text-menu ':typeout-window)) (progn (send (send node-text-menu ':typeout-window) ':deexpose) (send self ':refresh))) ;;The main loop: ;;Send CURRENT-NODE a SHOW-YOURSELF message -- this will ;;display the 3-pane menu and return one of the following three ;;things: ;; ;; (DISPLAY ): Switch the node menu to show ;; (SHOW-TEXT-FILE ): Show 's text file ;; (BUILD ): push the current environment onto ;; EXISTING-BROWSERS and start up a fresh ;; browser displaying (setq instructions-and-node (send current-node ':show-yourself self)) ;;Save the current node in PREVIOUS-NODE before... (setq previous-node current-node) ;;...checking INSTRUCTIONS-AND-NODE's first element for what to do: (setq current-node (selectq (first instructions-and-node) ;;DISPLAY: CURRENT-NODE becomes the node returned by the ;;SHOW-YOURSELF message. Update the nodestack so that ;;We can get back to here by clicking on the top of the ;;node stack. (display (send self ':update-nodestack (second instructions-and-node)) (second instructions-and-node)) ;;SHOW-TEXT-FILE: Show the text file corresponding to this ;;node, as well as any other text files that the user may ;;jump to. Keep doing this until SHOW-YOUR-TEXT-FILE ;;returns (DISPLAY ) -- then becomes the ;;new node to be shown. Also, update the nodestack so that ;;We can get back to here by clicking on the top of the ;;node stack. (show-text-file (loop with next-display = nil and node-to-display = current-node do (setq next-display (send node-to-display ':show-your-text-file self)) (setq node-to-display (second next-display)) until (eq (first next-display) 'display) finally #+LMI (let ((main-browser-menu (send self :get-pane 'node-text-menu))) (send self :set-selection-substitute main-browser-menu) (send main-browser-menu :select)) (unless (eq current-node node-to-display) (send self ':update-nodestack current-node)) (return node-to-display))) ;;BUILD: Save the CURRENT-NODE and the current NODESTACK ;;on EXISTING-BROWSERS and display the node returned by the ;;SHOW-YOURSELF message. (build (loop for b in existing-browsers when (equal (send current-browser-environment ':name) (send b ':name)) do (send b ':set-current-node current-node) (send b ':set-nodestack nodestack)) (push (setq current-browser-environment (make-instance 'browser-environment ':browser self ':name (interned-gensym 'b) ':current-node current-node ':nodestack nil)) existing-browsers) (setq nodestack nil) ;;Set REFRESH-THE-SCREEN-P to T so that the new browser ;;will be re-displayed at the top of the loop. (setq refresh-the-screen-p t) ;;Return the node pointed to by the new browser. (second instructions-and-node)) ;;SELECT: the second element of this list is a browser ;;environment; switch to it if it's not the same as the ;;current one. Otherwise, stay where you are. Before doing ;;all this, though, modify the current environment's ;;representation on EXISTING-BROWSERS by replacing it with ;;the current state of this environment. (select (let ((newenv (second instructions-and-node))) (cond ((null newenv) current-node) (t (loop for b in existing-browsers when (equal (send current-browser-environment ':name) (send b ':name)) do (send b ':set-current-node current-node) (send b ':set-nodestack nodestack)) (setq current-browser-environment newenv nodestack (send newenv ':nodestack)) (send newenv ':current-node))))) ;;Error trap... (otherwise (break `("SHOW-YOURSELF returned funny value: " ,instructions-and-node))))))) ;;;(BROWSER :UPDATE-NODESTACK): ************************************ ;;;Update the nodestack after a selection. (defmethod (browser :update-nodestack) (new-node) ;;If the newly selected node is on NODESTACK, remove it before doing ;;anything else. (cond ((member new-node nodestack) (setq nodestack (delete new-node nodestack)))) ;;Add NEW-NODE to NODESTACK, trimming the list if necessary. (setq nodestack (cond ((member current-node nodestack) (cons current-node (delete current-node nodestack))) (t (remove nil (firstn 7 (cons current-node nodestack))))))) ;;;Flavor definitions for document nodes ;;;DOCUMENT-NODE -- the things the network is made out of. (defflavor document-node ((name nil) (type nil) (documentname nil) (longlabel nil) (shortlabel nil) (parent nil) (children nil) (priornode nil) (nextnode nil) (xrefs nil) (readable t) ;;default: all nodes are readable (startingbyte 0) (endingbyte 0)) () :gettable-instance-variables :settable-instance-variables :initable-instance-variables) ;;;(DOCUMENT-NODE :FIND-NODECONTENTS): *************************************** ;;;Retrieves the type information for this node from *DB-WALKER-TYPES* and ;;;puts together lists of the form (:CHILDREN ()) for each ;;;such entry. (defmethod (document-node :find-nodecontents) () (let* ((type-entry (cdr (assq type *db-walker-types*)))) ;;Create a menu entry of the form (:CHILDREN ) from ;;each element in TYPE-ENTRY, whose elements are of the form ;;(:CHILDREN