summaryrefslogtreecommitdiff
path: root/Apps/Scigraph/scigraph/mouse.lisp
blob: a8ca6220bf3209b2a18d336dffbab05e82bbad04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
;;; -*- Syntax: Common-lisp; Package: graph -*-
#|
Copyright (c) 1987-1993 by BBN Systems and Technologies,
A Division of Bolt, Beranek and Newman Inc.
All rights reserved.

Permission to use, copy, modify and distribute this software and its
documentation is hereby granted without fee, provided that the above
copyright notice of BBN Systems and Technologies, this paragraph and the
one following appear in all copies and in supporting documentation, and
that the name Bolt Beranek and Newman Inc. not be used in advertising or
publicity pertaining to distribution of the software without specific,
written prior permission. Any distribution of this software or derivative
works must comply with all applicable United States export control laws.

BBN makes no representation about the suitability of this software for any
purposes.  It is provided "AS IS", without express or implied warranties
including (but not limited to) all implied warranties of merchantability
and fitness for a particular purpose, and notwithstanding any other
provision contained herein.  In no event shall BBN be liable for any
special, indirect or consequential damages whatsoever resulting from loss
of use, data or profits, whether in an action of contract, negligence or
other tortuous action, arising out of or in connection with the use or
performance of this software, even if BBN Systems and Technologies is
advised of the possiblity of such damages.
|#

(in-package :graph)

;;;
;;; Mouse stuff
;;;

(defun uv-under-mouse (stream)
  "The UV position of the mouse."
  (multiple-value-bind (x y) (stream-pointer-position* stream)
    (screen-to-uv stream x y)))

(defmacro button-case (button &key left middle right)
  "Implementation-specific way to dispatch based on the button pushed."
  `(cond
    ,@(when left
        `(((event-matches-gesture-name-p ,button :select)
           ,left)))
    ,@(when middle
        `(((event-matches-gesture-name-p ,button :describe)
           ,middle)))
    ,@(when right
        `(((event-matches-gesture-name-p ,button :menu)
           ,right)))))

(defmethod post-mouse-documentation (stream string)
  (locally (declare (ignore stream))
    (clim-extensions:frame-display-pointer-documentation-string
     *application-frame* string)))

(defmacro with-mouse-documentation ((window string) &body body)
  `(unwind-protect
       (progn (post-mouse-documentation ,window (or ,string " ")) ,@body)
     (post-mouse-documentation ,window " ")))

(defmacro with-pointer-cursor ((sheet cursor) &body body)
  ;; XXX McCLIM will get pointer-cursors soon... -- moore
  ;; CLH FIXME! Does mcclim have pointer-cursors now?!?
  #+(or (not clim-2) mcclim)
  `(progn ,@body)
  #+(and clim-2 (not mcclim))
  `(let ((.old. (sheet-pointer-cursor ,sheet)))
     (unwind-protect
	 (progn (setf (sheet-pointer-cursor ,sheet) ,cursor)
		,@body)
       (setf (sheet-pointer-cursor ,sheet) .old.))))

;;; DRAG-ICON is used to do most all of the mouse tracking.  It differs from
;;; dragging-output in that the latter simply does output once and drags
;;; the output record around the screen.  This function explicitly erases and
;;; redraws, which is useful if the shape of the output depends upon its location
;;; (e.g. sliders).
(defun drag-icon (stream draw-it erase-it move-it 
		  &optional documentation (cursor :move))
  "Mouse tracker for dragging graphic objects."
  ;; Erase the object before calling this function.
  ;; Dont forget to redraw the object after this function returns.
  ;; This requirement gives the caller the freedom to use an "abbreviated"
  ;; drawing for the inner loop, which may be necessary to create the
  ;; illusion of animation.
  (unless (extended-input-stream-p stream)
	  (error "Cannot track the mouse on this stream (~S)" stream))
  (with-pointer-cursor (stream cursor)
    (let (last-x last-y
	  (movements 0)
	  ;; If we have had some movement and then the mouse is released, we
	  ;; probably want to quit the loop.  We don't count the first few because the
	  ;; user might still be releasing the button that got him here.
	  (down-threshold 0)
	  (up-threshold 0))
      ;; Sometimes we get rationals.
      ;; (declare (fixnum last-x last-y movements))
      (unless documentation
	(setq documentation "Click/Release mouse to set new position"))
      (multiple-value-setq (last-x last-y) (stream-pointer-position* stream))
      (unless (and last-x last-y) (beep) (setq last-x 0 last-y 0))
      (flet ((update-position (x y)
	       ;; "pixel" positions are often ratios and floats in clim
	       (post-mouse-documentation stream documentation)
	       (let ((dx (- x last-x))
		     (dy (- y last-y)))
		 ;;(declare (fixnum dx dy) (fixnum x y))
		 (when (or (not (zerop dx)) (not (zerop dy)))
		   ;;(draw-circle last-x last-y 5 :filled t :alu %flip :stream stream)
		   ;;(draw-circle x y 5 :filled t :alu %flip :stream stream)
		   (incf movements)
		   (funcall erase-it stream)
		   (funcall move-it dx dy)
		   (setq last-x x last-y y)
		   (funcall draw-it stream)
		   ;; In X-windows, you need to force any buffered output.
		   (force-output stream)
		   )))
	     (button-clicked (button release-p)
	       ;; Seem to get spurious left-click button releases shortly
	       ;; after entering the tracker (13 movements).  Maybe leftover
	       ;; from the presentation menu that got us here...
	       (when (if release-p
			 (> movements up-threshold)
		       (> movements down-threshold))
		 (return-from drag-icon (values button last-x last-y)))))
        (with-output-recording-disabled (stream)
	  (unwind-protect
	      (progn
		(funcall draw-it stream)
		(force-output stream)
		(with-mouse-documentation (stream documentation)
		  (tracking-pointer (stream)
					 (:pointer-motion
					  (x y)
					  (update-position (values (truncate x)) (values (truncate y))))
					 (:pointer-button-press 
					  (event x y) 
					  (update-position (values (truncate x)) (values (truncate y)))
					  (button-clicked event nil))
					 (:pointer-button-release 
					  (event x y) 
					  (update-position (values (truncate x)) (values (truncate y)))
					  (button-clicked event t)))))
	    ;; CLIM leaves the button event resulting from :button-press in the input
	    ;; buffer, so take it out now.
	    (funcall erase-it stream)
	    (force-output stream)))))))

#|
(defun test-tracking (&optional (stream *standard-output*))
  (let ((string "Test Tracking") x y)
    (multiple-value-setq (x y) (stream-pointer-position* stream))
    (drag-icon stream
	       #'(lambda (stream) (draw-string string x y :stream stream :alu %flip))
	       #'(lambda (stream) (draw-string string x y :stream stream :alu %flip))
	       #'(lambda (dx dy)
		   (incf x dx)
		   (incf y dy)
		   (setq string (format nil "~S ~S" x y))))))
|#

(defun device-mouse-point (stream
			   &optional
			   (documentation "Mouse-Left: Select Point; Mouse-Middle: Cancel"))
  "Returns u,v and gesture chosen by the mouse."
  ;; It is good to draw a 'cursor' even though the true mouse cursor is drawn as
  ;; well, so that the user can see by looking at the screen what the hell is
  ;; going on.
  (let ((fudge 10) button)
    (multiple-value-bind (x y) (stream-pointer-position* stream)
      (flet ((x-marks-the-spot (str)
	       (draw-line x (- y fudge) x (+ y fudge) :stream str :alu %flip)
	       (draw-line (- x fudge) y (+ x fudge) y :stream str :alu %flip)
	       (draw-circle x y fudge :stream stream :alu %flip)))
	(setq button (drag-icon stream
				#'x-marks-the-spot
				#'x-marks-the-spot
				#'(lambda (dx dy) (incf x dx) (incf y dy))
				documentation))
	(multiple-value-setq (x y) (screen-to-uv stream x y))
	(button-case button
		     :left (values x y button)
		     :right (values x y button))))))

(defun shift-p (window)
  "Determine whether the shift key is depressed."
  (logtest +shift-key+
           (port-modifier-state (port window))))

(defun mouse-input-rectangle (stream)
  "Return edges of rectangle in stream coordinates."
  ;;(declare (values left top right bottom button))
  (multiple-value-bind (left top)
      (device-mouse-point stream)
    (when left
      (multiple-value-setq (left top) (uv-to-screen stream left top))
      (multiple-value-bind (right bottom) (stream-pointer-position* stream)
	(let (button)
	  (flet ((drawit (str)
		   (shift-p str)
		   (draw-rectangle left right top bottom :stream str :alu %flip)))
	    (setq button (drag-icon stream
				    #'drawit
				    #'drawit 
				    #'(lambda (dx dy)
					(when (shift-p stream)
					  (incf left dx)
					  (incf top dy))
					(incf right dx)
					(incf bottom dy))
				    "Mouse-Left: Done; Mouse-Middle: Cancel; Shift: Drag"))
	    (if (< right left) (psetq left right right left))
	    (if (< top bottom) (psetq top bottom bottom top))
	    (button-case
	      button
	      :left (values left top right bottom button)
	      :right (values left top right bottom button))))))))

(defun device-specify-rectangle (stream)
  "Ask user to specify a rectangle on STREAM with the mouse.
   Returns LEFT TOP RIGHT BOTTOM in UV coordinates."
  ;;(declare (values left top right bottom))
  (multiple-value-bind (left top right bottom button)
      (mouse-input-rectangle stream)
    (when left
      (multiple-value-setq (left top) (screen-to-uv stream left top))
      (multiple-value-setq (right bottom) (screen-to-uv stream right bottom))
      (values (min left right) (max top bottom)
	      (max left right) (min top bottom)
	      button))))

(defun map-polygon-edges (function corners)
  (let* ((this (car (last corners)))
	 (next (pop corners))
	 (x1 (pop this))
	 (y1 (pop this))
	 (x2 (pop next))
	 (y2 (pop next)))
    (loop
      (if (not x2) (return))
      (funcall function x1 y1 x2 y2)
      (setq next (pop corners))
      (setq x1 x2 y1 y2)
      (setq x2 (pop next) y2 (pop next)))))

(defun draw-screen-polygon (corners stream alu)
  (map-polygon-edges
    #'(lambda (x1 y1 x2 y2)
	(draw-line x1 y1 x2 y2 :stream stream :alu alu))
    corners))

(defun select-screen-polygon (stream &optional (cursor :position))
  "Select a sequence of points in screen coordinates.  Finish by clicking on first point."
  (with-output-recording-disabled (stream)
    (multiple-value-bind (lastx lasty) (device-mouse-point stream)
      (when lastx
	(multiple-value-setq (lastx lasty) (uv-to-screen stream lastx lasty))
	(when lastx
	  (sleep .4)			; wait for button release.
	  (let* ((first (list lastx lasty))
		 (first-highlighted nil)
		 (points (list first))
		 (rad 5)
		 (documentation
		  "Mouse-Left: Select Point; Mouse-Middle: Cancel; Mouse-Right: Finish")
		 (x lastx)
		 (y lasty))
	    (unwind-protect
		(block tracking
		  (with-pointer-cursor (stream cursor)
		    (labels ((distance (x1 y1 x2 y2)
			       (sqrt (+ (expt (- x2 x1) 2) (expt (- y2 y1) 2))))
			     (near-first (x0 y0)
			       (< (distance x0 y0 (car first) (cadr first)) rad))
			     (highlight-first ()
			       (setq first-highlighted (not first-highlighted))
			       (draw-circle (car first) (cadr first) rad :filled nil
					    :alu %flip :stream stream))
			     (rubberband (x0 y0)
			       (draw-line lastx lasty x0 y0 :stream stream :alu %flip))
			     (update-position (x0 y0)
			       (post-mouse-documentation stream documentation)
			       (Rubberband x y)
			       (setq x x0 y y0)
			       (rubberband x y)
			       (if first-highlighted
				   (if (near-first x0 y0) nil (highlight-first))
				 (if (near-first x0 y0) (highlight-first) nil))
			       (force-output stream))
			     (all-done ()
			       (update-position (car first) (cadr first))
			       (if first-highlighted (highlight-first))
			       (force-output stream)
			       (return-from tracking points))
			     (button-clicked (button)
			       (button-case
				button
				:middle
				(progn
				  ;; cancel
				  (update-position (car first) (cadr first))
				  (push (list (truncate x) (truncate y)) points)
				  (if first-highlighted (highlight-first))
				  (return-from tracking nil))
				:right (all-done)
				:left
				(cond ((near-first x y) (all-done))
				      (t 
				       ;; select another point
				       (push (list (truncate x) (truncate y)) points)
				       (setq lastx x lasty y))))))
		      (with-mouse-documentation (stream documentation)
			(tracking-pointer
			 (stream)
			 (:pointer-motion (x y) (update-position x y))
			 (:pointer-button-press (event x y)
						(update-position x y)
						(button-clicked event)))
			points))))
	      ;; Erase results when done:
	      (draw-screen-polygon points stream %flip))))))))