home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #27 / NN_1992_27.iso / spool / comp / lang / lisp / mcl / 1613 < prev    next >
Encoding:
Text File  |  1992-11-18  |  17.8 KB  |  480 lines

  1. Newsgroups: comp.lang.lisp.mcl
  2. Path: sparky!uunet!spool.mu.edu!agate!boulder!cambridge.apple.com!apple!ames!elroy.jpl.nasa.gov!swrinde!zaphod.mps.ohio-state.edu!darwin.sura.net!wupost!cs.uiuc.edu!m.cs.uiuc.edu!m.cs.uiuc.edu!liberte
  3. From: liberte@cs.uiuc.edu (Daniel LaLiberte)
  4. Subject: Re: I Want my own framework...
  5. Message-ID: <722100096.26088@news.Colorado.EDU>
  6. Lines: 463
  7. Sender: news
  8. Organization: University of Illinois, Urbana-Champaign, Dept CS
  9. References: <721941749.8349526@AppleLink.Apple.COM>
  10. Distribution: co
  11. Date: 17 Nov 92 17:39:02 GMT
  12. Approved: news
  13. X-Note1: mail msgid was <LIBERTE.92Nov17113902@aspen.cs.uiuc.edu>
  14. X-Note2: message-id generated by recnews
  15. Lines: 463
  16.  
  17. One thing I needed in the direction of a MCL application framework was
  18. support for multiple applications within the one MCL environment.  I
  19. succeeded in getting what I needed, and perhaps it will be useful for
  20. others, so I include it below.  I havent submitted it to the archive
  21. yet because there is still more work to do on it, but I don't have
  22. time to do that work now, so it is time to release it as is and let
  23. others have a try at it.
  24.  
  25. Dan LaLiberte
  26. liberte@cs.uiuc.edu
  27. (Join the League for Programming Freedom: lpf@uunet.uu.net)
  28. "If we don't succeed, we run the risk of failure.
  29.         -- Vice President Dan Quayle."
  30. --------------
  31. ;;; -*- Mode: LISP; Package: (CL-USER); Syntax:Common-Lisp; Lowercase: Yes -*-
  32.  
  33. #| multi-application class and window hooks
  34.  
  35. Daniel LaLiberte (liberte@ncsa.uiuc.edu)
  36. National Center for Supercomputing Applications
  37. University of Illinois, Urbana-Champaign
  38. August 1992.
  39.  
  40. This file defines a multi-application class which is a subclass of the MCL
  41. application class.   The MCL application object is promoted to a
  42. multi-application.  *application* always holds the current multi-application
  43. object.  (Hereafter, "application" means "multi-application".)
  44. All applications, stored in the list *applications*, are added
  45. to the Apple menu under the About ... item.
  46.  
  47. To use this package, load or eval it, preferrably soon after starting up
  48. MCL.  You can define subclasses of multi-application, or use it as is.
  49. Call make-instance with the :name of your application,
  50. and an :about-action function to display the about dialog.
  51. This application is made the current, active application.
  52. While your application is current, define its menus, open any windows, etc. 
  53. Don't bother changing the apple menu because multi-application does that
  54. in set-apple-menu-applications.  There is an example at the bottom of this
  55. file.
  56.  
  57. When you click on any window that is not in your application, your
  58. application will be suspended and the other application, or the mcl 
  59. application will be resumed.  Call application-quit to terminate the 
  60. use of an application.
  61.  
  62. There are three ways in which a application can be activated
  63. (or resumed).  When one is created, it is automatically activated
  64. by the initialize-instance method.  The other two times are when a 
  65. window in an application is selected, or when the application's name is
  66. selected from the Apple menu.   An application may also be resumed
  67. programmatically by calling application-select.  There is no need to
  68. call application-suspend; the current application is automatically
  69. suspended when another is resumed.
  70.  
  71. When an application is suspended, the current menus are saved; when
  72. the application is later resumed, these menus are restored.  The Apple
  73. menu is always updated to list the current applications.
  74.  
  75. When a window is deactivated, either another window will be activated
  76. in the same or different application, or there are no more windows.
  77. (If a window is being closed, another window in the same application
  78. should be activated rather than the next one on the global window list.
  79. This is not yet implemented.)
  80. If another window in the same application is selected, then the
  81. current application need not be suspended and resumed
  82.  
  83. The hooks into the window methods are done through advise so that we dont
  84. redefine the kernel methods, and dont get redefined by someone elses methods.
  85. Advise works for all the hooks except for initialize-instance. 
  86.  
  87. Windoids cause lots of extra activation events, so menus will flash more than
  88. needed.  One way to reduce this, and it should be done anyway, is to require
  89. applications that use windoids to hide them when the application
  90. is suspended and show them when resumed.  (Could/should this be done 
  91. automatically for windoids?)
  92.  
  93. When switching applications is via the apple menu,
  94. if the current window is in a different application, it needs to
  95. be deactivated and one of the windows in the selected application should
  96. be activated, preferrably the last one that was active.  
  97. What if there are no windows in the application?  Then
  98. the menus for the new application would apply to a window in a different
  99. application - this could be very confusing.  So currently I just
  100. deactivate the front window (from the previous application) and leave
  101. none selected.  However, there is a bad side effect: the deactivated 
  102. window is not selectable until some other window is selected.  (How to fix?
  103. I think window-selection needs to work also if the front-window is inactive.)
  104.  
  105. Future work
  106. ===========
  107. to do on an as needed basis:
  108. (the problems noted above...)
  109.  
  110. The Windows menu should be limited to windows in the current application.
  111. Manually executing code for an application is cumbersome.  I do something
  112. like:  (progn (application-select my-app)
  113.               (set-menubar my-menus))
  114.  
  115. Looks like a bug when an error occurs during window creation.
  116. The Listener gets selected but the application is not swapped.
  117.  
  118. When using Interface Tools, a dialog should be designed in MCL, and
  119. used in the application that it is associated with.
  120. Perhaps IFT should be its own application.  Menus are edited while being
  121. used; a debatable policy.  A menubar could otherwise be associated
  122. with an application.
  123.  
  124. How all this relates to apple-events, I dont know.  Ideally, all the
  125. applications should be available to receive events even if not currently
  126. active, and they should be able to send and receive events between them
  127. as well as outside applications.
  128.  
  129. Need to think more about application quitting.  Could automatically
  130. query to confirm quitting.  Could ask about unsaved windows and abort the
  131. quit if user cancels.
  132.  
  133. Windoids still mess up the window selection business.  Sometimes a
  134. window is selected automatically but its application is not resumed.
  135.  
  136. Could replace the window-list with the application specific window list.
  137. I don't know what problems this might cause though.  Perhaps quitting from
  138. MCL wouldnt close all windows.  That reminds me, quitting from MCL should
  139. first quit all the multi-applications.
  140.  
  141. Could provide services to applications, like a Windows menu with just
  142. its windows in it.
  143.  
  144. Bug: Suspending and resuming the MCL application (not the multi-application)
  145. by clicking on a window in a different multi-application
  146. doesn't catch the window select event to swap menus.
  147.  
  148. |#
  149.  
  150. ;; To fix some window event anomalies, you need the following patch.
  151. ;; multi-application will work without it, but sometimes the wrong
  152. ;; window will be selected, or the menus may be switched incorrectly.
  153. ;; This patch should become part of MCL 2.0.
  154. ;; (load "ccl:patches;reactivate-window-patch")
  155.  
  156. (defvar *applications* nil "List of applications.")
  157.  
  158. (defvar *window-application-alist* nil 
  159.   "Association list from windows to applications.")
  160.  
  161.  
  162. (defclass multi-application (application)
  163.   ((application-name
  164.     :accessor application-name)
  165.    (about-application-action
  166.     :accessor about-application-action)
  167.  
  168.     (saved-menus
  169.     :documentation "Menus that were active when the application 
  170. was last suspended."
  171.     :initform nil
  172.     :accessor saved-menus)
  173.  
  174.    (visible-windoids 
  175.     :documentation "List of all windoids visible last time app was suspended."
  176.     :initform nil
  177.     :accessor visible-windoids)
  178.    )
  179.  
  180.   (:documentation
  181.    "Representation of application specific info.")
  182.   )
  183.  
  184. (defmethod print-object ((object multi-application) stream)
  185.   (format stream "#<~s ~s>"
  186.           (class-name (class-of object)) 
  187.           (application-name object)))
  188.           
  189.  
  190. ;;##########################################
  191. ;; Make the mcl application into a multi-application.
  192.  
  193. (defvar *mcl-application* *application*
  194.   "Remember the one application object that MCL creates.")
  195.  
  196. (change-class *mcl-application* (find-class 'multi-application))
  197.  
  198. ;; Do what initialize-instance would have done.
  199. (setf (application-name *mcl-application*) "MCL")
  200. (setf (about-application-action *mcl-application*) #'ccl::about-ccl)
  201. (push *mcl-application* *applications*)
  202. ;; (set-menubar *default-menubar*)
  203.  
  204. ;;##########################################
  205.  
  206. (defmethod initialize-instance :after 
  207.   ((new-appl multi-application) &rest rest &key name about-action)
  208.   "Create an application object, making it the current application."
  209.   (declare (ignore rest))
  210.   (setf (application-name new-appl) name)
  211.   (setf (about-application-action new-appl) about-action)
  212.   (format t "~%new application: ~s" new-appl)
  213.   (push new-appl *applications*)
  214.   (application-resume new-appl)
  215.   (format t "~%current application: ~s" *application*)
  216.   new-appl)
  217.  
  218. (defmethod application-quit ((appl multi-application))
  219.   "Call this to terminate an application.
  220. This closes all the applications windows.
  221. APPL need not eq *application*, but if so, another application is resumed."
  222.   ;; Close all its windows.
  223.   (map-windows #'(lambda (w)
  224.                    (if (eq appl (cdr (assoc w *window-application-alist*)))
  225.                      (window-close w)))
  226.                :include-invisibles t :include-windoids t)
  227.   (setq *applications* (delete appl *applications*))
  228.   (if (eq appl *application*)
  229.     ;; If current application is being quit, resume some other application.
  230.     (application-resume (car *applications*))
  231.     ;; Else update apple menu anyway.
  232.     (set-apple-menu-applications))
  233.   )
  234.  
  235. (defun quit-current-application ()
  236.   "Utility function to call application-quit on *application*."
  237.   ;;(format t "~%Current application: ~s" *application*)
  238.   (APPLICATION-QUIT *APPLICATION*))
  239.  
  240.  
  241. (defmethod front-window-in-application 
  242.            ((appl multi-application)
  243.             &key class include-invisibles include-windoids)
  244.   "Return the front window in the application, if any."
  245.   (let ((f #'(lambda (w)
  246.                (let ((some-appl (cdr (assoc w *window-application-alist*))))
  247.                  (if (or (eq appl some-appl)
  248.                          (and (null some-appl) (eq appl *mcl-application*)))
  249.                    (return-from front-window-in-application w))))))
  250.     (declare (dynamic-extent f))
  251.     (map-windows f :class class :include-invisibles include-invisibles 
  252.                  :include-windoids include-windoids)))
  253.  
  254.  
  255. (defmethod select-front-window-in-application ((appl multi-application))
  256.   "Select the front window in the application, if any.
  257. Otherwise, deactivate selected window."
  258.   ;; Should only be used by the current application since window-select
  259.   ;; doesnt ensure that the application is current.
  260.   (let ((w (front-window-in-application appl)))
  261.     (if w
  262.       (window-select w)
  263.       (when ccl::*selected-window*
  264.         (view-deactivate-event-handler ccl::*selected-window*)
  265.         ))))
  266.  
  267.  
  268. (defmethod application-select ((appl multi-application))
  269.   ;; Resume the application and select the front window in the application.
  270.   (application-resume appl)
  271.   (select-front-window-in-application appl))
  272.  
  273.  
  274. (defmethod application-resume ((appl multi-application))
  275.   "Resume the application.
  276. Set the menubar to the state the menus were in last time the 
  277. application was active."
  278.   ;; Don't select a window because the application might have been
  279.   ;; resumed because some other window in the applicsation was selected.
  280.   (unless (eq *application* appl)
  281.     ;;(format t "~%resume: ~s current: ~s" appl *application*)
  282.     (application-suspend *application*)
  283.     (setf *application* appl)
  284.     (set-menubar (saved-menus appl)))
  285.   (set-apple-menu-applications)
  286.   ;;    (format t "~%menus: ~s" (menubar))
  287.   )
  288.  
  289. (defmethod application-suspend ((appl multi-application))
  290.   "Suspend the application.
  291. Save the current menubar away for when the application is resumed.
  292. This is only called by application-resume when a different application
  293. is about to be activated."
  294.   ;; *application* remains as it is until changed by activation of another appl.
  295.   ;; Also could hide visible windoids of the application.
  296.   ;; because windoids really mess up this multi-application scheme
  297.   ;; since they repeatedly activate all windoids and the front window.
  298.   ;; Patch 2 to MCL 2.0f fixes this.
  299.   (setf (saved-menus appl) (menubar))
  300.   ;; Deactivate the front window.
  301.   ;; Bad side effect: this window becomes unselectable until something
  302.   ;; else has been selected.  It's even worse if there is only one window.
  303.   ;; Patch 2 to MCL 2.0f fixes these problems.
  304.   (let ((w (front-window-in-application appl)))
  305.     (when w (view-deactivate-event-handler w))))
  306.  
  307.  
  308. (defmacro with-application (application &rest body)
  309.   `(let ((current-application *application*))
  310.      (application-resume ,application)
  311.      (unwind-protect
  312.        ,@body
  313.        (application-resume current-application))))
  314.  
  315.  
  316. (defun set-apple-menu-applications ()
  317.   ;; This could be a method on the application...
  318.   (LET ((APPLE-MENU *APPLE-MENU*))
  319.     (menu-enable APPLE-MENU)
  320.     (APPLY #'REMOVE-MENU-ITEMS APPLE-MENU (MENU-ITEMS APPLE-MENU))
  321.     (APPLY #'ADD-MENU-ITEMS
  322.            APPLE-MENU
  323.            (MAKE-INSTANCE 'MENU-ITEM 
  324.              :MENU-ITEM-TITLE 
  325.              (format nil "About ~a..." (application-name *application*))
  326.              :MENU-ITEM-ACTION 
  327.              #'(lambda nil (funcall (about-application-action *application*))))
  328.  
  329.            (mapcar #'(lambda (appl) 
  330.                        (MAKE-INSTANCE 'MENU-ITEM
  331.                          :MENU-ITEM-TITLE
  332.                          (application-name appl)
  333.                          :MENU-ITEM-ACTION
  334.                          #'(LAMBDA NIL (application-select appl))))
  335.                    (remove *application* *applications*)))
  336.     (add-menu-items  
  337.      apple-menu
  338.      (MAKE-INSTANCE 'MENU-ITEM :MENU-ITEM-TITLE "-" :DISABLED T))))
  339.  
  340. ;;;===================
  341. ;;; Handle window initialization, close, selection, etc
  342.  
  343. ;; I'd like to use advise instead of defining methods
  344. ;; because someone else might override the same method, but 
  345. ;; advisingdoesnt work for some methods, and it's pretty messy.
  346.  
  347. (defmethod initialize-instance :before ((w window) &rest rest)
  348.   (declare (ignore rest))
  349.   (push (cons w *application*) *window-application-alist*)
  350.   )
  351.  
  352. (defmethod window-close :around ((w window))
  353.   (let* ((window-appl (assoc w *window-application-alist*))
  354.          (appl (window-application w))
  355.          (was-current (eq appl *application*)))
  356.    ;;(format t "~%closing: ~s" w)
  357.    (when window-appl
  358.      (setf *window-application-alist* 
  359.            (delete window-appl *window-application-alist*)))
  360.    (call-next-method)
  361.    ;; Reselect the application, if it was the current one.
  362.    ;; It would be better to avoid the change in the first place.
  363.    (when was-current
  364.      (application-select appl))
  365.    ))
  366.  
  367.  
  368. (defmethod window-application ((w window))
  369.   (or (cdr (assoc w *window-application-alist*))
  370.       *mcl-application*))
  371.  
  372. (advise 
  373.  (:method window-select-event-handler (window))
  374.  (let* ((w (car arglist))
  375.         (appl (window-application w)))
  376.    ;;(format t "~%advise select: ~s appl: ~s" w appl)
  377.    (application-resume appl)
  378.    )
  379.  :name :multi-application
  380.  :when :before)
  381.  
  382.  
  383. ;; Around advise doesnt get called when a window is closed.
  384. (defmethod view-activate-event-handler :around ((w window))
  385.   ;;(format t "~%before activation: ~s" w)
  386.   (let ((appl (or (cdr (assoc w *window-application-alist*))
  387.                   *mcl-application*))
  388.         (was-active (window-active-p w)))
  389.     (call-next-method)
  390.     (when (and (not was-active) (window-active-p w))
  391.       (application-resume appl)))
  392.   ;;(format t "~%after activation: ~s" w)
  393.   )
  394.  
  395.  
  396. '(defmethod view-deactivate-event-handler :around ((w window))
  397.   ;;(format t "~%before deactivation: ~s" w)
  398.   (call-next-method)
  399.   ;;(format t "~%after deactivation: ~s" w)
  400.   )
  401.  
  402. #| #####################################################################
  403. Instead of the ugly advise calls above, I would like a new defining form
  404. that might be called defadvise.  The spec would *include*
  405. argument names which should be extracted and bound to the advise arglist.
  406.  
  407. (defmacro defadvise (spec advise-name when &rest body)
  408.   "Advise on spec."
  409.   `(advise
  410.     ,(clean-up spec)
  411.     (progv ,(args-of spec) arglist
  412.       ,@body)
  413.     :name ,advise-name
  414.     :when ,when))
  415.  
  416. The progv probably doesnt do a general binding job.
  417. Some failsafe checking should be done, and a undefadvise is also needed.
  418. I don't know enough CL to write the clean-up and args-of routines.
  419. But it could be used like:
  420.  
  421. (defadvise (initialize-instance ((w window) &rest rest))
  422.   :multi-application :before 
  423.   (push (cons w *application*) *window-application-alist*)
  424.   )
  425.  
  426. ##################################################################### |# 
  427.  
  428. (provide :multi-application)
  429.  
  430. #|
  431.  
  432. ;;; Example application
  433.  
  434. (defun about-foo ()
  435.   (MAKE-INSTANCE 'DIALOG
  436.   :WINDOW-TYPE
  437.   :DOCUMENT
  438.   :WINDOW-TITLE
  439.   "About Foo"
  440.   :VIEW-POSITION
  441.   #@(109 101)
  442.   :VIEW-SIZE
  443.   #@(269 115)
  444.   :VIEW-FONT
  445.   '("Chicago" 12 :SRCOR :PLAIN)
  446.   :VIEW-SUBVIEWS
  447.   (LIST (MAKE-DIALOG-ITEM
  448.           'STATIC-TEXT-DIALOG-ITEM
  449.           #@(38 18)
  450.           #@(178 16)
  451.           "This dialog is about Foo"
  452.           'NIL)
  453.         (MAKE-DIALOG-ITEM
  454.           'BUTTON-DIALOG-ITEM
  455.           #@(95 67)
  456.           #@(62 16)
  457.           "OK"
  458.           #'(LAMBDA (ITEM) ITEM (WINDOW-CLOSE (FRONT-WINDOW)))
  459.           :DEFAULT-BUTTON
  460.           T))))
  461.  
  462. (PROGN
  463.   (setq foo-application
  464.         (make-instance 'multi-application
  465.           :name "Foo" 
  466.           :about-action 'about-foo))
  467.  
  468.   (SET-MENUBAR (LIST (MAKE-INSTANCE 'MENU
  469.                        :MENU-TITLE
  470.                        "Foo"
  471.                        :MENU-ITEMS
  472.                        (LIST (MAKE-INSTANCE 'MENU-ITEM
  473.                                :MENU-ITEM-TITLE
  474.                                "Quit"
  475.                                :MENU-ITEM-ACTION
  476.                                'quit-current-application))))))
  477.  
  478.  
  479. |#
  480.