From: Frank Buss
Subject: CLIM Gadgets
Date: 
Message-ID: <clcctp$pmu$1@newsreader2.netcologne.de>
I tried to implement a very simple drawing program in CLIM, without 
saving or object/presentation mapping, just like this Java applet:

http://www.frank-buss.de/java/freihand/index.html

I didn't found much documentation (searching for "Java JFrame" gives 
65,000 result, but searching for "define-application-frame" only 232 at 
Google). I tried to built my own gadget, expecting that the handle-* 
methods are called, but they are not called. What is wrong with the code 
below and is it possible to write such a simple application as short as 
in Java with CLIM?

(require "clim")
(in-package :clim-user)

(defclass lines-pane (basic-gadget basic-pane)
  ((width :type integer
	  :initarg :width
	  :initform 300)
   (height :type integer
	   :initarg :height
	   :initform 300))
  (:default-initargs :activation-gestures nil))

(defparameter *x0* 0)
(defparameter *y0* 0)

(defparameter *x1* 100)
(defparameter *y1* 100)

(defmethod handle-repaint :before ((pane lines-pane) region)
  (declare (ignore region))
  (draw-line* pane *x0* *y0 *x1* *y1*))

(defmethod compose-space ((pane lines-pane) &key width height)
  (declare (ignore width height))
  (make-space-requirement :width 300
                          :height 300))
 
(defmethod handle-event ((pane lines-pane)
                         (event pointer-button-press-event))
  (setf *x0* (pointer-event-x event))
  (setf *y0* (pointer-event-y event)))
 
(defmethod handle-event ((pane lines-pane)
                         (event pointer-button-release-event))
  (setf *x1* (pointer-event-x event))
  (setf *y1* (pointer-event-y event)))

(define-application-frame painter-frame ()
  ()
  (:pane
   (clim:make-pane 'lines-pane))
  (:geometry :width 400 :height 400))

(defun show-painter ()
  (let ((frame (make-application-frame 'painter-frame)))
    (run-frame-top-level frame)))


;; call it with (clim-user::show-painter)


-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de

From: Paolo Amoroso
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <ff4743a5.0410230404.2e898719@posting.google.com>
Frank Buss <··@frank-buss.de> wrote in message news:<············@newsreader2.netcologne.de>...

> I tried to implement a very simple drawing program in CLIM, without 
> saving or object/presentation mapping, just like this Java applet:
(...)
> I didn't found much documentation (searching for "Java JFrame" gives 
> 65,000 result, but searching for "define-application-frame" only 232 at 
> Google). I tried to built my own gadget, expecting that the handle-* 

That's why I created my blog, which frequently covers CLIM:

  http://www.paoloamoroso.it/log

It is aggregated by:

  http://planet.lisp.org


Paolo
From: Rainer Joswig
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <joswig-5C56CC.05081323102004@news-50.dca.giganews.com>
In article <············@newsreader2.netcologne.de>,
 Frank Buss <··@frank-buss.de> wrote:

> I tried to implement a very simple drawing program in CLIM, without 
> saving or object/presentation mapping, just like this Java applet:
> 
> http://www.frank-buss.de/java/freihand/index.html
> 
> I didn't found much documentation (searching for "Java JFrame" gives 
> 65,000 result, but searching for "define-application-frame" only 232 at 
> Google). I tried to built my own gadget, expecting that the handle-* 
> methods are called, but they are not called. What is wrong with the code 
> below and is it possible to write such a simple application as short as 
> in Java with CLIM?
> 
> (require "clim")
> (in-package :clim-user)
> 
> (defclass lines-pane (basic-gadget basic-pane)
>   ((width :type integer
> 	  :initarg :width
> 	  :initform 300)
>    (height :type integer
> 	   :initarg :height
> 	   :initform 300))
>   (:default-initargs :activation-gestures nil))
> 
> (defparameter *x0* 0)
> (defparameter *y0* 0)
> 
> (defparameter *x1* 100)
> (defparameter *y1* 100)

Global state. Bad.

> 
> (defmethod handle-repaint :before ((pane lines-pane) region)
>   (declare (ignore region))
>   (draw-line* pane *x0* *y0 *x1* *y1*))
> 
> (defmethod compose-space ((pane lines-pane) &key width height)
>   (declare (ignore width height))
>   (make-space-requirement :width 300
>                           :height 300))
>  
> (defmethod handle-event ((pane lines-pane)
>                          (event pointer-button-press-event))
>   (setf *x0* (pointer-event-x event))
>   (setf *y0* (pointer-event-y event)))
>  
> (defmethod handle-event ((pane lines-pane)
>                          (event pointer-button-release-event))
>   (setf *x1* (pointer-event-x event))
>   (setf *y1* (pointer-event-y event)))
> 
> (define-application-frame painter-frame ()
>   ()
>   (:pane
>    (clim:make-pane 'lines-pane))
>   (:geometry :width 400 :height 400))
> 
> (defun show-painter ()
>   (let ((frame (make-application-frame 'painter-frame)))
>     (run-frame-top-level frame)))
> 
> 
> ;; call it with (clim-user::show-painter)



How about this one:

(in-package "CLIM-USER")

(define-application-frame test1 () ()
  (:panes
   (main :application)))

(define-test1-command (com-test :menu "Test" :name t) ()
  (let ((x1 0)
        (y1 0)
        (x2 0)
        (y2 0)
        (mouse-button-press nil)
        (stream (get-frame-pane *application-frame* 'main)))
    (tracking-pointer (stream)
      (:pointer-button-press (event x y )
       (setf x1 x y1 y x2 x y2 y)
       (draw-line* stream x1 y1 x2 y2
                   :ink +flipping-ink+)
       (setf mouse-button-press t))
      (:pointer-motion (window x y)
       (when Mouse-button-press
         (draw-line* stream x2 y2 x y
                     :ink +flipping-ink+)
         (setf x2 x y2 y)))
      (:pointer-button-release (event x y)
       (when (eq mouse-button-press t)
         (return
          (list x1 y1 x2 y2)))))))

(define-test1-command (com-exit :menu "Exit" :name t) ()
  (frame-exit *application-frame*))

(defun test-me ()
  (run-frame-top-level
   (make-application-frame 'test1)))
From: Frank Buss
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <cldcka$f5d$1@newsreader2.netcologne.de>
Rainer Joswig <······@lisp.de> wrote:

> How about this one:
> 
> (in-package "CLIM-USER")
> 
> (define-application-frame test1 () ()
>   (:panes
>    (main :application)))
> 
> (define-test1-command (com-test :menu "Test" :name t) ()

thanks, this works, but looks like it saves all lines, because when I draw 
some lines and then scale the window, it is getting slower the more lines 
are drawn. After searching the Web, the CLIM User Guide from Franz, the 
CLIM Spec, LispWorks with apropos and class-inspector and browsing the 
McClim implementation for some time (it is really difficult to find any 
compact and useful information about CLIM), I've found another solution, 
which works and doesn't save the lines, like in my Java applet (see below), 
but I have some questions:

- why doesn't basic-pane work, but silica::leaf-pane?

- silica::leaf-pane is not documented in the CLIM spec 
(http://clim.mikemac.com/spec/toc.html), what do I need to write a portable 
gadget?

- why are the lines not cached, like in your example? In both examples 
draw-line draws to a pane.


(require "clim")
(in-package :clim-user)

;; call it with 
;; (clim-user::show-painter)

(defclass lines-pane (basic-gadget silica::leaf-pane)
  ((x :initform 0) (y :initform 0) (mouse-pressed :initform nil)))

(defmethod compose-space ((pane lines-pane) &key width height)
  (declare (ignore width height))
  (make-space-requirement :max-width 100000
                          :max-height 100000))
 
(defmethod handle-event ((pane lines-pane)
                         (event pointer-button-press-event))
  (with-slots (x y mouse-pressed) pane
    (setf x (pointer-event-x event))
    (setf y (pointer-event-y event))
    (setf mouse-pressed t)))
 
(defmethod handle-event ((pane lines-pane)
                         (event pointer-motion-event))
  (with-slots (x y mouse-pressed) pane
    (when mouse-pressed
      (let ((x2 (pointer-event-x event))
            (y2 (pointer-event-y event)))
        (draw-line* pane x y x2 y2)
        (setf x x2)
        (setf y y2)))))

(defmethod handle-event ((pane lines-pane)
                         (event pointer-button-release-event))
  (setf (slot-value pane 'mouse-pressed) nil))

(define-application-frame painter-frame ()
  ()
  (:pane
   (clim:make-pane 'lines-pane))
  (:geometry :width 400 :height 400))

(defun show-painter ()
  (let ((frame (make-application-frame 'painter-frame)))
    (run-frame-top-level frame)))

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Rainer Joswig
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <joswig-1D23C5.13425423102004@news-50.dca.giganews.com>
In article <············@newsreader2.netcologne.de>,
 Frank Buss <··@frank-buss.de> wrote:

> Rainer Joswig <······@lisp.de> wrote:
> 
> > How about this one:
> > 
> > (in-package "CLIM-USER")
> > 
> > (define-application-frame test1 () ()
> >   (:panes
> >    (main :application)))
> > 
> > (define-test1-command (com-test :menu "Test" :name t) ()
> 
> thanks, this works, but looks like it saves all lines, because when I draw 
> some lines and then scale the window, it is getting slower the more lines 
> are drawn.

Then you need to control Output Recording. Certain
streams are recording the output. You can control this
in various ways.

For example you can use the macro WITH-OUTPUT-RECORDING-OPTIONS
around your drawing functions. See below.

(in-package "CLIM-USER")

(define-application-frame test1a () ()
  (:panes
   (main :application)))

(define-test1a-command (com-test :menu "Test" :name t) ()
  (let ((x1 0)
        (y1 0)
        (x2 0)
        (y2 0)
        (mouse-button-press nil)
        (stream (get-frame-pane *application-frame* 'main)))
    (with-output-recording-options (stream :record nil)
      (tracking-pointer (stream)
        (:pointer-button-press (event x y )
         (setf x1 x y1 y x2 x y2 y)
         (draw-line* stream x1 y1 x2 y2
                     :ink +flipping-ink+)
         (setf mouse-button-press t))
        (:pointer-motion (window x y)
         (when Mouse-button-press
           (draw-line* stream x2 y2 x y
                       :ink +flipping-ink+)
           (setf x2 x y2 y)))
        (:pointer-button-release (event x y)
         (when (eq mouse-button-press t)
           (return
            (list x1 y1 x2 y2))))))))

(define-test1a-command (com-exit :menu "Exit" :name t) ()
  (frame-exit *application-frame*))

(defun test-me ()
  (run-frame-top-level
   (make-application-frame 'test1a)))
From: Frank Buss
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <cldotp$3el$2@newsreader2.netcologne.de>
Rainer Joswig <······@lisp.de> wrote:

> For example you can use the macro WITH-OUTPUT-RECORDING-OPTIONS
> around your drawing functions. See below.

but this doesn't work the other way around. If I write this:

(defmethod handle-event ((pane lines-pane)
                         (event pointer-motion-event))
  (with-slots (x y mouse-pressed) pane
    (when mouse-pressed
      (let ((x2 (pointer-event-x event))
            (y2 (pointer-event-y event)))
        (with-output-recording-options (pane :record t)
          (draw-line* pane x y x2 y2))
        (setf x x2)
        (setf y y2)))))

it is not recorded. Which streams records the output and which doesn't?

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Arthur Lemmens
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <opsgcd3kt5k6vmsw@news.xs4all.nl>
Frank Buss wrote:

> Which streams records the output and which doesn't?

Rainer's examples use application-panes, which are clim-stream-panes.
All clim-stream-panes support the output recording protocol.

Your examples use basic-panes, which do not need to support the output
recording protocol.

Arthur
From: Frank Buss
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <cldoh3$3el$1@newsreader2.netcologne.de>
Frank Buss <··@frank-buss.de> wrote:

> - why doesn't basic-pane work, but silica::leaf-pane?

looks like a basic-pane is not enabled by default. This works:

(defclass lines-pane (basic-gadget basic-pane)
  ((x :initform 0) (y :initform 0) (mouse-pressed :initform nil)))

(defmethod initialize-instance :after ((pane lines-pane) &key &allow-other-keys) 
  (setf (sheet-enabled-p pane) t))
 
But I wonder if this is the standard way to write and use custom
gadgets. 

BTW: is it possible to get the source of CLIM2 and the other packages
used in LispWorks? It is not the same as with Java, because the JDK
doesn't cost anything, but even Microsoft provides the source of some of
their libraries, like the runtime-library for Visual C++. 

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Rainer Joswig
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <joswig-CE1D32.16531423102004@news-50.dca.giganews.com>
In article <············@newsreader2.netcologne.de>,
 Frank Buss <··@frank-buss.de> wrote:

> Frank Buss <··@frank-buss.de> wrote:
> 
> > - why doesn't basic-pane work, but silica::leaf-pane?
> 
> looks like a basic-pane is not enabled by default. This works:
> 
> (defclass lines-pane (basic-gadget basic-pane)
>   ((x :initform 0) (y :initform 0) (mouse-pressed :initform nil)))
> 
> (defmethod initialize-instance :after ((pane lines-pane) &key &allow-other-keys) 
>   (setf (sheet-enabled-p pane) t))
>  
> But I wonder if this is the standard way to write and use custom
> gadgets. 

No.

You don't want a gadget. You only need some pane. Gadgets are special
panes that model common toolkit components. In CLIM also it is
meant that these gadgets can be realized by the gadgets
of that underlying backend (Motif, Aqua, ...). Plus these
gadgets can be used in stream panes. So you could have
a table of button gadgets in a usual stream pane.

For your purpose a simple pane would be sufficient.

> BTW: is it possible to get the source of CLIM2 and the other packages
> used in LispWorks?

Not that I know.

> It is not the same as with Java, because the JDK
> doesn't cost anything, but even Microsoft provides the source of some of
> their libraries, like the runtime-library for Visual C++.

I agree that it would be preferable to have the source code.
From: Frank Buss
Subject: Re: CLIM Gadgets
Date: 
Message-ID: <cldtu9$dde$1@newsreader2.netcologne.de>
Rainer Joswig <······@lisp.de> wrote:

> For your purpose a simple pane would be sufficient.

you are right, this works, too:

(defclass lines-pane (basic-pane)
  ((x :initform 0) (y :initform 0) (mouse-pressed :initform nil)))

but I still need the "initialize-instance :after" method and the "compose-
space" method. Of course, I can define my own default pane, which 
implements this, if I need it for other tasks, but perhaps there is already 
such a default class in CLIM. 

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de