Path: utzoo!utgpu!jarvis.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!ucbvax!hplabs!hplabsz!mayer From: mayer@hplabsz.HPL.HP.COM (Niels Mayer) Newsgroups: comp.windows.x Subject: Re: Motif from LISP Message-ID: <4851@hplabsz.HPL.HP.COM> Date: 21 Feb 90 02:39:05 GMT References: <9002201746.AA02540@rice-chex> Reply-To: mayer@hplabs.hp.com (Niels Mayer) Organization: Hewlett-Packard Labs, Software Technology Lab, Palo Alto, CA. Lines: 445 Summary: Expires: Sender: Followup-To: ... warning WINTERP plug ahead ... In article <9002201746.AA02540@rice-chex> gillett@AI.MIT.EDU (Walter E. Gillett) writes: >We're building a product in Common LISP and would like to be Motif-compliant. >I've only heard of two LISP-based X toolkits, CLIM and CLIO, neither one of >which is currently available and both of which are policy-free. I have yet to see something as big and gnarly as Motif interfaced in a clean, non-kludgy fashion to Common Lisp, though I've talked to some people that are attempting it. The biggest problem is that it's not easy building up lisp data structures inside the code interfacing C to Common Lisp, nor is it easy to rip apart arbitrarily complex Common Lisp data structures for use by C interface code. Consider also that you want to implement callbacks in Common Lisp, and that you will have added a bevy of new atomic types to Common Lisp that need to be garbage collected, printed, read, etc. Combine that with the fact that you probably want to change Lisp's read-eval-print loop so that you can handle lisp input and X-events "simultanously" (so the system can be interactive). That's alot of tweaking to be done at very low levels of the Common Lisp system. The fact that most CL systems don't come with source code doesn't help this process either, and even if they did, such lisp systems are only implemented in C at the kernel level, the rest is in lisp anyways -- this makes for a real tangle.... Yes, I know that CL's have "foreign function interfaces" but they are not enough for data-intensive C<-->Lisp interfacing as required by the Xtoolkit, Motif, etc. >Transforming >a policy-free interface into a Motif-compliant interface sounds like a lot of >unnecessary work. Does anyone have any suggestions for a solution? For starters, you should use the Motif toolkit. You will generate endless headaches for yourself and your customers if you try to use the Motif external specification to come up with a Motif lookalike. >How hard >is it to use the C-based Motif toolkit from LISP via foreign function calls >(from Franz Allegro CL or Lucid CL)? I think it's HARD, and it's especially hard to make it clean, robust, and fast... I chose a different way, because I knew that Common Lisp would have given us trouble from the outset. We're building a "groupware toolkit" and we needed Lisp as a prototyping environment, as well as a customizable delivery environment for system integrators and end-users to tailor the application to local practices. We opted against common lisp for various reasons: (1) those CL's aren't free, and our prototypes and applications must be distributable within HP without "hassles" (Motif doesn't count, since it's part of HPUX). (2) we can't (reasonably) get source to product Lisp systems so as to alter them in drastic ways needed for use by Xt/Motif; (3) Common Lisp is big, and slow, it will cause your machine to thrash and swap out all your other applications (Common Lisp is a wonderful prototyping platform, but is terrible for delivering applications to be run on a unix workstation). If the above hasn't scared you off, then you may be insterested in WINTERP, which uses XLISP, and provides a nice OO interface to the Motif widget classes. XLISP is a lisp interpreter written by David Betz, it is implemented entirely in C, which makes it easy to do hybrid programming in both C and Lisp. This allowed me to build my beta-version of WINTERP from scratch in less than 1/2 man-year (including time spent to learn XLISP, Motif, vacations, groupware project design, and time to do "real work" (aka overhead)). If nothing else, you can use WINTERP to see what sorts of things are required in interfacing Motif to Lisp. The following is a quick summary of WINTERP: ---------- WINTERP is an interpretive, interactive environment for rapid prototyping and delivering customizable applications using the OSF Motif UI Toolkit. The name "WINTERP" stands for Widget INTERPreter, and that's exactly what WINTERP is -- an interpretive language that allows programmers to interactively create interfaces using the capabilities of the Motif widgets and the X11 toolkit intrinsics. WINTERP is also designed to enable the delivery of Lisp-customizable applications without incurring the costs of using an environment such as Common Lisp. As a delivery platform, WINTERP improves on UIL in that it allows both the look, feel, and functionality of an application to be tailored and extended with a real programming language, rather than just a layout language. As an application prototyping environment, WINTERP is akin to a graphical gnuemacs in that it uses a mini-lisp interpreter (XLISP) to glue together various C-Implemented primitive operations. In gnuemacs, these operations consist mostly of manipulating text within editor buffers -- in fact text-buffers are a "first-class" type in gnuemacs. WINTERP makes the widget a first class type, and represents them as real objects using the XLISP object system (a smalltalk-like object system). The syntax resulting from marrying XLISP objects and Motif widgets is cleaner than UIL+C, straight C, etc. The OOP interface to the widgets allows for new lisp-implemented "methods" to be added to existing widget classes. Widget functionality can also be specialized by subclassing existing widget classes in Lisp. Furthermore, since WINTERP is interactive, you can try out small changes incrementally, and modify the look and functionality of an application on-the-fly. A very useful interactive primitive, 'get_moused_widget' serves as a base for building "direct manipulation builder" style extensions to WINTERP. WINTERP contains an interface to gnuemacs' lisp-mode which allows you to type lisp forms into a gnuemacs editor buffer, and issue a simple command to send a form off to winterp's lisp-listener for evaluation. WINTERP's serverized lisp listener also allows other programs to send commands to the interpreter. This feature enables a cheap built-in RPC mechanism for inter-program communication. WINTERP is available now. It was released on the MIT X11r4 contrib tape which means that it has no copyright restrictions -- it may be used in products. WINTERP is quite portable -- people have reported that it runs on HP9000s3xx (680xx), HP9000s8xx (HP PA-RISC), HP Apollo, Sun3, Sun 4, MIPS, DEC, etc. The latest version of WINTERP, including sources, documentation, and a large number of examples is available via anonymous ftp from expo.lcs.mit.edu in directory contrib/winterp, file winterp.tar.Z. -------------------- Here's some example winterp code that implements a simple mh-mail browser using a WINTERP-subclassed xmList widget: ------------------------------------------------------------------------------ ; -*-Lisp-*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; File: mail-browser.lsp ; RCS: $Header: mail-br.lsp,v 1.1 89/11/25 04:00:24 mayer Exp $ ; Description: A simple MH mail browser written mostly to show the power of ; subclassing the Motif list widget in WINTERP. Load this file ; to get a browser of the last 30 MH messages in your inbox. ; This assumes that (1) you have MH, (2) you have folder +inbox, ; (3) "scan" is on your $PATH. (4) various other things I forgot. ; Author: Niels Mayer, HPLabs ; Created: Mon Nov 20 18:13:23 1989 ; Modified: Sat Nov 25 01:11:51 1989 (Niels Mayer) mayer@hplnpm ; Language: Lisp ; Package: N/A ; Status: X11r4 contrib tape release ; ; (c) Copyright 1989, Hewlett-Packard Company. ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Make a subclass of XM_LIST_WIDGET_CLASS which holds an additional ;; instance variable 'items'. 'items' is an array of arbitrary objects ;; (BROWSER_OBJECT) to be displayed in a browser made from the list widget. ;; ;; BROWSER-OBJECT can be any arbitrary xlisp object that respond to ;; the messages :display_string and :default_action: ;; ;; Message :display_string must return a string which is used as the ;; textual representation of the object in the browser display. ;; ;; Message :default_action is sent to the object whenever the ;; list widget's default action, a double-click, is performed on the item ;; corresponding to the object. ;; (setq List_Browser (send Class :new '(items) ;new instance vars '() ;no class vars XM_LIST_WIDGET_CLASS)) ;superclass ;; ;; We override the XM_LIST_WIDGET_CLASS's object initializer ;; so that we can process the items list and hand off the ;; browser items to the list widget. ;; ;; (send List_Browser :new ) ;; is a list of BROWSER_OBJECTs as described above. ;; -- these are the arguments that ;; will be passed on to the list widget ;; (send List_Browser :answer :isnew '(items_list &rest args) '( (let* ( (items_end_idx (length items_list)) (display_items (make-array items_end_idx))) ;; initialize the 'items' instance variable so that it ;; holds all the BROWSER_OBJECTs passed in (setq items (make-array items_end_idx)) ;create the array (do ( ;copy elts from list to array (i 0 (1+ i)) (elts items_list (cdr elts))) ;; loop till no more elts ((null elts)) ;; loop body (setf (aref items i) (car elts)) (setf (aref display_items i) (send (car elts) :display_string)) ) ;; initialize the widget, passing in the browser items. (apply 'send-super `(:isnew ,@args :xmn_selection_policy :browse_select :xmn_items ,display_items :xmn_item_count ,items_end_idx )) ) ;; set up a callback on the list widget initialized above such that ;; a double click on the browser-item will send the message ;; :default_action to the BROWSER_OBJECT. (send-super :set_callback :xmn_default_action_callback '(callback_item_position) '((send (aref items (1- callback_item_position)) :default_action)) ) ) ) ;; ;; override methods on XM_LIST_WIDGET_CLASS so that they work properly ;; with the list browser. Note that all other list methods work fine ;; on the list browser ;; (send List_Browser :answer :ADD_ITEM '(item position) '( (setq items (array-insert-pos items (1- position) item)) (send-super :add_item (send item :display_string) position) ) ) (send List_Browser :answer :ADD_ITEM_UNSELECTED '(item position) '( (setq items (array-insert-pos items (1- position) item)) (send-super :add_item_unselected (send item :display_string) position) ) ) (send List_Browser :answer :DELETE_ITEM '(item) '( ;; this is too lame to implement... requires that we compare ;; item with the result of :display_string done on every element ;; of ivar 'items' (error "Message :DELETE_ITEM not supported in List_Browser") ) ) (send List_Browser :answer :DELETE_POS '(position) '( (setq items (array-delete-pos items (1- position))) (send-super :delete_pos position) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Define a BROWSER_OBJECT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Each BROWSER_OBJECT holds the information summarizing one mail message. ;; the information is split up into individual fields because we may want ;; to be able to sort on one field, or search for matches on one field. ;; (setq Mail_Message_Class (send Class :new '(folder num anno month date no-date size sender subject) )) ;; this string is passed to the mh 'scan' and 'inc' commands to determine ;; the formatting of the output of the message info summary. Each entry ;; here corresponds to an instance variable in Mail_Message_Class (setq FOLDER_SCAN_FORMAT (strcat "%(msg)" ;output the message number "%<{replied}A%|" ;IF msg answered output "A" ELSE "%<{forwarded}F%|" ;IF msg forwarded output "F" ELSE "%<{resent}R%|" ;IF msg redisted output "R" ELSE "%<{printed}P%|" ;IF msg printed output "P" " %>%>%>%>" ;ELSE output " " "%02(mon{date})/%02(mday{date})" ;output mon/date "%<{date} %|*%>" ;IF no date output "*" else " " "%(size) " ;output the message's size "%<(mymbox{from})To:%14(friendly{to})%|" ;IF my message, output "To: " "%17(friendly{from})%> " ;ELSE output sender field "%{subject}<<" ;output subject followed by ">>" "%{body}" ;output beginning of body, limited by SCAN_OUTPUT_WIDTH ) ) ;; this method will read a single line summary of a mail message as produced ;; by the mh 'scan' or 'inc' commands and sets the instance variables in the ;; BROWSER_OBJECT to the individual fields of the message summary. (send Mail_Message_Class :answer :read-msg-info '(pipe fldr) '( (if (and (setq folder fldr) (setq num (fscanf-fixnum pipe "%ld")) (setq anno (fscanf-string pipe "%c")) (setq month (fscanf-fixnum pipe "%2ld")) (setq date (fscanf-fixnum pipe "/%2ld")) (setq no-date (fscanf-string pipe "%c")) (setq size (fscanf-fixnum pipe "%d%*c")) (setq sender (fscanf-string pipe "%17[\001-\177]%*c")) (setq subject (fscanf-string pipe "%[^\n]\n")) ) self ;return self if succesful NIL ;return NIL if hit EOF ) ) ) (send Mail_Message_Class :answer :display_string '() '( (format nil "~A ~A ~A/~A~A ~A ~A ~A" num anno month date no-date size sender subject) )) (send Mail_Message_Class :answer :default_action '() '((find-file (format nil "~A/~A/~A" MAILPATH folder num)))) ;; ;; i'm too lazy to add a getenv() interface to WINTERP... this'll do for now. ;; (setq MAILPATH (let* ((pipe (popen "/bin/echo $HOME" "r")) (home (read-line pipe)) ) (pclose pipe) (strcat home "/Mail")) ;this is the default directory ;for MH... this assumes you haven't ;put the MH directory elsewhere ;via a ~/.mh_profile entry. ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; This returns a list of Mail_Message_Class instances corresponding ;; to the mail messages scanned from over range . ;; (defun mh-scan (foldername msgs) (do* ((fp (popen (strcat "scan " "+" foldername " " msgs " -noclear -noheader -reverse -width 80" " -format '" FOLDER_SCAN_FORMAT "'") :direction :input)) (msg (send (send Mail_Message_Class :new) :read-msg-info fp foldername) (send (send Mail_Message_Class :new) :read-msg-info fp foldername)) (result NIL) ) ((null msg) ;:read-msg-info returns NIL on EOF (pclose fp) (cdr result) ;last msg was EOF, remove it ) (setq result (cons msg result)) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (setq top_w (send TOP_LEVEL_SHELL_WIDGET_CLASS :new ; :XMN_GEOMETRY "500x700+1+1" :XMN_TITLE "Mail Browser" :XMN_ICON_NAME "Mail Browser" )) (setq paned_w (send XM_PANED_WINDOW_WIDGET_CLASS :new :managed top_w )) (setq objs-list (mh-scan "inbox" "last:30")) (setq list_w (send List_Browser :new objs-list :managed :scrolled "browser" paned_w :xmn_visible_item_count 20 )) (setq label_w (send XM_LABEL_WIDGET_CLASS :new :managed "label" paned_w :xmn_label_string "None" )) ;; ;; set constraint resources on label widget so that paned window ;; doesn't give it resize sashes. ;; (let (height) (send label_w :get_values :xmn_height 'height) (send label_w :set_values :xmn_maximum height :xmn_minimum height ) ) (setq textedit_w (send XM_TEXT_WIDGET_CLASS :new :managed :scrolled "view" paned_w :XMN_EDIT_MODE :MULTI_LINE_EDIT :XMN_HEIGHT 400 :XMN_EDITABLE nil ;don't allow user to change text. )) (send top_w :realize) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun find-file (file) (let* (;; loc vars (fp (open file :direction :input) ) inspos text_line ) (if (null fp) (error "Can't open file." file)) (send label_w :set_values :xmn_label_string file) (send textedit_w :set_string "") ;clear out old text (send paned_w :update_display) ;incase reading file takes long time (send textedit_w :disable_redisplay NIL) ;don't show changes till done (send textedit_w :replace 0 0 (read-line fp)) (loop (if (null (setq text_line (read-line fp))) (return)) (setq inspos (send textedit_w :get_insertion_position)) (send textedit_w :replace inspos inspos (strcat "\n" text_line)) ) (send textedit_w :enable_redisplay) ;now show changes... (close fp) ) ) ------------------------------------------------------------------------------- Niels Mayer -- hplabs!mayer -- mayer@hplabs.hp.com Human-Computer Interaction Department Hewlett-Packard Laboratories Palo Alto, CA. *