[comp.windows.x] Motif from LISP

gillett@AI.MIT.EDU (Walter E. Gillett) (02/21/90)

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.  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?  How hard
is it to use the C-based Motif toolkit from LISP via foreign function calls
(from Franz Allegro CL or Lucid CL)?

mayer@hplabsz.HPL.HP.COM (Niels Mayer) (02/21/90)

   ... 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 <items_list> <args-for-the-list-widget>)
;; <items_list> is a list of BROWSER_OBJECTs as described above.
;; <args-for-the-list-widget> -- 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 <items_list>
	  (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: <recipient>"
       "%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 <foldername> over range <msgs>.
;;
(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.
				   *

berlage@gmdzi.UUCP (Thomas Berlage) (02/21/90)

We have developed an interface solution between Motif and Common Lisp. Our
first attempt was to use the foreign function interface (on Lucid, but
Franz Allegro would have been roughly the same). This is possible, but a
lot of tedious work to map all calls and data structures. The reason we
abandoned this solution is that we didn't get it to work with
multiprocessing inside the Lisp, i.e. only one Lisp-process may use
foreign function calls to the interface.

Our solution uses a separate Motif process, which has the additional
advantage of better interactive performance (and easier porting). It is
even possible to use a Lisp machine with an X terminal as its display,
going through the Motif process on a remote UNIX server. Our interface
currently runs on Franz Allegro, Lucid, and Symbolics. The Motif part
should run on every workstation where Motif is installed (we use Suns).

The interface is already used in different research projects here at
GMD, as well as in the BABYLON expert system product development.

Before you start sending cash :-) : We are currently investigating
several ways how to distribute this code, because we are unable to
provide direct support. If you are interested in this solution,
send email to the following address:

          baecker@gmdzi.uucp (Andreas Baecker)

We will then notify you immediately when the distribution path is
determined. The amount of responses will help us in the decision, too.

---
Thomas Berlage (berlage@gmdzi.uucp), GMD
(German National Research Center for Computer Science)
P.O. Box 1240, D-5205 St.Augustin

gillett@AI.MIT.EDU (Walter E. Gillett) (02/23/90)

>We have developed an interface solution between Motif and Common Lisp...
>Our solution uses a separate Motif process...

Thanks for the info.  Could you give more detail on how your LISP<->Motif
solution works?  What exactly does the Motif process do (is it written in
C?)  What data is exchanged between the Motif and LISP processes?  What IPC
mechanism is used for communication?

gillett@AI.MIT.EDU (Walter E. Gillett) (02/23/90)

Return-Path: <Brad.Myers@a.gp.cs.cmu.edu>
Date: Thursday, 22 February 1990 09:30:41 EST
From: Brad.Myers@a.gp.cs.cmu.edu
To: "Walter E. Gillett" <gillett@ai.mit.edu>
Subject: Re: Garnet
In-Reply-To: <9002212335.AA06198@wheat-chex>

Garnet is a comprehensive user interface development environment for CommonLisp
and X/11 we are developing at CMU.  It is free, and there are already some
copies floating around MIT.  Like the other toolkits, Garnet is also policy-free,
and we do *not* have any Motif widgets created yet,
although Garnet makes it easy to create widgets, so it would not be hard
to create an interface that LOOKS like Motif, but the widget set would not
be called by applications like Motif's.  It would be nice if someone created
a Motif widget set for Garnet, but nobody has volunteered.  If you are interested
in doing this, we would like to help support the effort, but we do not have the
resources to do it.

The MIT people who have Garnet are:

Steve Strassmann
MIT Media Lab
20 Ames St. 
Cambridge, MA 02139
617-253-0664
straz@media-lab.media.mit.edu

Kum-Yew Lai
Research Scientist
Center for Coordination Science
1 Amherst Street, E40-141
Cambridge, MA 02139
Tel: 617/253-8410
E-mail: KY@XV.MIT.EDU

Duane Boning
boning@caf.mit.edu
Phone:  617-253-0450

The formal announcement of garnet is below.  Please let me know if you have further
questions.

Brad A. Myers
School of Computer Science
Carnegie Mellon University
Pittsburgh, PA  15213-3890
(412) 268-5150
bam@a.gp.cs.cmu.edu

------------------------------------------------------------------------

                 Announcing the Availability of Garnet:
     Generating an Amalgam of Realtime, Novel Editors and Toolkits

The Garnet research project in the School of Computer Science at Carnegie
Mellon University is happy to announce the release of our toolkit for
general use.  The Garnet Toolkit helps to implement highly-interactive,
graphical, direct manipulation programs for X/11 in CommonLisp.  These
programs typically have a number of graphical objects (up to about 2500) on
the screen that can be manipulated by the mouse and keyboard.  Typical
applications of the Garnet toolkit include: drawing programs such as
Macintosh MacDraw, iconic file manipulation programs such as the Macintosh
Finder, box and arrow diagram editors such as graphs and PERT charts,
graphical programming languages, board game user interfaces, simulation and
process monitoring programs, user interface construction tools, some forms
of CAD/CAM programs, etc.  The Garnet Toolkit does not help with text
editing (except for small labels or property-sheet fields).

Important features of the toolkit include:
* Coverage of the entire user interface, including the contents of the
  applications' windows.

* Look-and-feel independent, while still providing a high-level of support.
  A set of "widgets" is provided for those who do not wish to define a
  look-and-feel.

* An object-oriented architecture using a prototype-instance model.

* Constraints integrated with the object system, so that any slot (instance
  variable) of any object can be declared as a "formula" which will be
  re-evaluated whenever there is a change in any other objects it references.

* Automatic graphic object updating.  Graphical objects are retained and
  remember their position on the screen.  Whenever any property changes,
  they erase and redraw themselves, along with any other damaged objects.

* Separation of input handling from graphics programming, through the use
  of "interactor" objects, which encapsulate interactive behaviors.

* Hiding all of X/11.  The programmer using the Garnet Toolkit never makes
  Xlib (CLX) calls or receives Xlib events.

In the future, high level tools including a sophisticated Interface Builder,
called Lapidary, will be released.  Garnet is implemented on top of the CLX
interface to X/11, and will work in various environments including IBM RTs using
CMU CommonLisp and Suns using Lucid CommonLisp.  There is very little
implementation-specific code, so porting to other platforms should be simple.
Garnet does NOT use CLOS or any existing X toolkit (such as Xtk or Motif).  The
toolkit comes with debugging tools, complete reference manuals, and a tutorial.

Garnet is being developed under a grant from DARPA.  Papers about Garnet have
appeared at OOPSLA, SIGCHI, and UIST.

Garnet source and binaries are available for free, but you need to have a
license from CMU.  Send requests (including a physical mail address) for
additional information or a license to:

	Brad A. Myers
	School of Computer Science
	Carnegie Mellon University
	Pittsburgh, PA  15213-3890
	(412) 268-5150
	brad.myers@cs.cmu.edu