🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.windows.x
Article #37096

Re: Motif from LISP

#37096
From: Niels Mayer
Date: Wed, 21 Feb 1990 02:39
445 lines
18212 bytes
   ... 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.
				   *
Technical Details
Message-ID: <4851@hplabsz.HPL.HP.COM>
Path: arc1-ca.pugleaf.net!utzoo!utgpu!jarvis.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!ucbvax!hplabs!hplabsz!mayer
References: <9002201746.AA02540@rice-chex>

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!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