CLIM mail archive


apparent omission of a layout pane class

   Date: Tue, 06 Oct 92 13:20:52 -0400
   From: Jeff Morrill <jmorrill@BBN.COM>

   Motif (and probably other GUIs) have two main ways to combine several
   panes into a frame.  (In Motif lingo, it's called combining several
   widgets into a composite widget.)

   1.  The RowColumn Widget.  CLIM is good at this.

   2.  The BulletinBoard Widget.  Children are not tiled into rows
       and columns, but rather placed at set positions within
       the coordinate system of the parent.  (They aren't repositionable.)

   The latter seems to have been omitted from CLIM.  It would be
   particularly useful when you have something like a text-editor
   widget or a scrolling-list widget as a part of a larger display.

It's only omitted in the sense that there isn't a high-level, built-in
language for laying out panes in bboard fashion (as there is for
row-column fashion).  This is mostly because there is no need for one,
as there are no constraints to specify.

   I just got through a painful clim 1 exercise of putting a scrolling
   application pane in the middle of a frame, like this:

    |                |
    |   ---------    |
    |   |       |    |
    |   |       |    |
    |   |       |    |
    |   ---------    |
    |                |

   I had to do this by making nine panes, eight of which do not draw their
   borders, but all of which have a nontrivial display function.
   I needed to draw a rectangle around the ninth
   pane, and this involved drawing the pieces of the rectangle using
   each of the eight surrounding display functions.

Eek!  I admire your fortitude, but am saddened that CLIM didn't lead
you to think of a better way.  I've always felt that one of the
failings of the CLIM 1 documentation is its weakness in describing
high-level capabilities in terms of the lower-level mechanisms out of
which they are built, and I guess that this proves it.  For example,
the :layout stuff just does a bunch of constraint computations and
then tells each pane where to go.  If you want a different layout,
just write a program to tell your panes where to go (see below).

   I would have rather had just two application panes, the outer of which
   was essentially an application pane with its own display function
   (and a single coordinate system) and with a "hole" cut out of it's
   middle for the inner pane.  Drawing in the "hole" would be allowed
   but would just get clipped.

   I'm not generally one to want more CLIM features, but BulletinBoard
   widgets seem almost indispensible in in a tutorial Motif book I have
   been studying.

Here is my brief cut at a bboard pane scheme.  This application frame
has one main pane.  There are commands to create new panes, move a
pane, delete a pane, and line up all the existing panes.  For now the
interaction style is verb-noun, line-oriented.  You invoke the
command, then click on the pane or the bboard area or whatever.  I
leave it as an excercise to the reader to spiff this up with
presentation translators that automatically invoke the commands when
panes or blank spaces are clicked on.

(in-package :clim-user)

;;; Simple application with one main "bulletin board" pane, in
;;; which inferior panes are created.

(define-application-frame bboard-test ()
  ((inferiors :initform nil :accessor bboard-inferiors))
  (:panes ((bboard :application :scroll-bars nil)
	   (interactor :interactor)))
  (:layout ((main (:column 1
			   (bboard :rest)
			   (interactor 1/5))))))

;;; Utility to read a button press event, ignoring other input.
(defun get-button-press (stream)
      (setq event (read-gesture :stream stream))
    (if (typep event 'pointer-button-press-event)
	(return (values (event-window event) 
			(pointer-event-x event)
			(pointer-event-y event)))
	(beep stream))))

;;; Of course, in real life you would write a blank-area translator
;;; that called this command with the x and y coordinates of the
;;; click that invoked it and create the window there.
(define-bboard-test-command (com-create-pane :name t)
  (let* ((frame *application-frame*)
	 (bboard (get-frame-pane frame 'bboard)))
    ;; Get an x/y location to create the inferior window.
    (multiple-value-bind (win x y) (get-button-press bboard)
      (declare (ignore win))
      ;; Make the pane there.
      (let ((pane (open-window-stream :parent bboard :left x :top y 
				      :width 100 :height 100
				      :input-buffer (stream-input-buffer bboard))))
	(push pane (bboard-inferiors frame)) ; remember it
	(window-expose pane)		; see it
	(force-output bboard)))))

;;; Read the comment on com-create-pane.
;;; Click on a pane, then on a new place for it.
;;; Has at least one bug: if you click on an inferior (rather than
;;; the bboard pane itself) for the new position the pane will
;;; be moved to the wrong place.
(define-bboard-test-command (com-move-pane :name t)
  (let* ((frame *application-frame*)
	 (bboard (get-frame-pane frame 'bboard)))
    ;; Click on a pane.
    (let ((pane (get-button-press bboard)))
      ;; Make sure it is one of the known inferiors
      (if (find pane (bboard-inferiors frame))
	  ;; Get a new place to put it.
	  (multiple-value-bind (win x y) (get-button-press bboard)
	    (declare (ignore win))
	    ;; Move it.  I can't believe that this is unexported.
	    ;; Can use (setf (bounding-rectangle-min-x pane) x)
	    ;; and repeat for y...
	    (clim::bounding-rectangle-set-position* pane x y))
	  (beep bboard)))))

(define-bboard-test-command (com-delete-pane :name t)
  (let* ((frame *application-frame*)
	 (bboard (get-frame-pane frame 'bboard)))
    ;; Click on a pane.
    (let ((pane (get-button-press bboard)))
      ;; Make sure it is one of the known inferiors
      (if (find pane (bboard-inferiors frame))
	    (setf (bboard-inferiors frame)
		  (delete pane (bboard-inferiors frame)))
	    (setf (window-visibility pane) nil)
	    (force-output bboard))

;;; This command lays out all the panes in a diagonal
(define-bboard-test-command (com-align-panes :name t)
  (let* ((frame *application-frame*)
	 (bboard (get-frame-pane frame 'bboard))
	 (x 0) (y 0) (increment 20))
    ;; Traverse the panes from oldest to newest
    (dolist (pane (reverse (bboard-inferiors frame)))
      ;; Move each to new location
      (clim::bounding-rectangle-set-position* pane x y)
      (incf x increment) (incf y increment))
    (force-output bboard)))

;;; Create the frame and run it.  Try the "Create Pane" and "Move Pane"
;;; commands.
(defun test-bboard (root)
  (let ((frame (make-application-frame 'bboard-test
				       :parent root
				       :width 500 :height 500)))
    (run-frame-top-level frame)


Follow-Ups: References:

Main Index | Thread Index