1 /* Copyright (C) 1993 Nathan Sidwell */
2 /* RCS $Id: Drag.c,v 4.14 1995/12/21 15:55:04 nathan Exp $ */
3 /* the drag widget is an override shell to be popped up
4  * and follow the pointer until released.
5  * A pixmap is displayed in the widget.
6  * This should probably just be a composite widget whose child
7  * does the painting. Ho hum
8  */
9 /*{{{  includes*/
10 #include "ansiknr.h"
11 #include <X11/X.h>
12 #include <X11/Xlib.h>
13 #include <X11/IntrinsicP.h>
14 #include <X11/StringDefs.h>
15 #include <X11/Xaw/SimpleP.h>
16 #include <X11/ShellP.h>
17 #include "Drag.h"
18 /*}}}*/
19 /*{{{  structs*/
20 /*{{{  typedef struct _DragClass*/
21 typedef struct _DragClass
22 {
23   int     ansi_compliance; /* not used */
24 } DragClassPart;
25 /*}}}*/
26 /*{{{  typedef struct _DragClassRec*/
27 typedef struct _DragClassRec
28 {
29   CoreClassPart   core_class;
30   CompositeClassPart   composite_class;
31   ShellClassPart shell_class;
32   OverrideShellClassPart override_shell_class;
33   DragClassPart   drag_class;
34 } DragClassRec;
35 /*}}}*/
36 /*{{{  typedef struct _DragPart*/
37 typedef struct
38 {
39   /* resources */
40   Pixmap    pixmap;           /* the icon to display */
41   XtCallbackList callbacks;   /* callbacks to notify */
42   WidgetList choices;         /* choices of selection */
43   Cardinal  num_choices;      /* number of choices */
44   Cursor    cursor;           /* cursor to use */
45 
46   /* private state */
47   unsigned  width;          /* pixmap width */
48   unsigned  height;         /* pixmap height */
49   GC        gc;             /* GC to draw */
50   Position  x;              /* corner of pixmap */
51   Position  y;              /* corner of pixmap */
52   Position  offset_x;       /* x offset */
53   Position  offset_y;       /* y offset */
54   Widget    widget;         /* invoker */
55 } DragPart;
56 /*}}}*/
57 /*{{{  typedef struct _DragRec*/
58 typedef struct _DragRec
59 {
60   CorePart    core;
61   CompositePart  composite;
62   ShellPart  shell;
63   OverrideShellPart  override;
64   DragPart    drag;
65 } DragRec;
66 /*}}}*/
67 /*}}}*/
68 /*{{{  resources*/
69 static XtResource resources[] =
70 {
71   {XtNpixmap,   XtCPixmap, XtRBitmap, sizeof(Pixmap),
72       XtOffsetOf(DragRec, drag.pixmap), XtRImmediate, (XtPointer)None},
73   {XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
74       XtOffsetOf(DragRec, drag.callbacks), XtRCallback, (XtPointer)NULL},
75   {MredNwidgetChoices, MredCWidgetList, XtRWidgetList, sizeof(WidgetList),
76       XtOffsetOf(DragRec, drag.choices), XtRImmediate, (XtPointer)0},
77   {MredNnumWidgetChoices, XtCIndex, XtRCardinal, sizeof(Cardinal),
78       XtOffsetOf(DragRec, drag.num_choices), XtRImmediate, (XtPointer)0},
79   {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
80       XtOffsetOf(DragRec, drag.cursor), XtRString, (XtPointer)"left_ptr"},
81 };
82 /*}}}*/
83 /*{{{  prototypes*/
84 static VOIDFUNC Drag PROTOARG((Widget, XEvent *, String *, Cardinal *));
85 static VOIDFUNC Notify PROTOARG((Widget, XEvent *, String *, Cardinal *));
86 
87 static VOIDFUNC ClassPartInitialize PROTOARG((WidgetClass));
88 static VOIDFUNC Destroy PROTOARG((Widget));
89 static VOIDFUNC Initialize PROTOARG((Widget, Widget, ArgList, Cardinal *));
90 static XtGeometryResult QueryGeometry
91     PROTOARG((Widget, XtWidgetGeometry *, XtWidgetGeometry *));
92 static VOIDFUNC Redisplay PROTOARG((Widget, XEvent *, Region));
93 static VOIDFUNC Resize PROTOARG((Widget));
94 static Boolean SetValues
95     PROTOARG((Widget, Widget, Widget, ArgList, Cardinal *));
96 
97 static VOIDFUNC GetGC PROTOARG((DragWidget));
98 static VOIDFUNC GetPixmapSize PROTOARG((DragWidget));
99 static unsigned GetPointerPosition
100     PROTOARG((XEvent *, Position *, Position *));
101 /*}}}*/
102 /*{{{  translations*/
103 static char translations[] = "\
104 <BtnUp>:notify()\n\
105 <BtnMotion>:drag()\n\
106 ";
107 /*}}}*/
108 /*{{{  actions*/
109 static XtActionsRec actions[] =
110 {
111   {"drag", Drag},
112   {"notify", Notify},
113 };
114 /*}}}*/
115 /*{{{  static CompositeClassExtensionRec compositeExtension =*/
116 static CompositeClassExtensionRec compositeExtension =
117 {
118   NULL,
119   NULLQUARK,
120   XtCompositeExtensionVersion,
121   sizeof(CompositeClassExtensionRec),
122   False
123 };
124 /*}}}*/
125 #define SuperClass (WidgetClass)&overrideShellClassRec
126 /*{{{  DragClassRec dragClassRec =*/
127 DragClassRec dragClassRec =
128 {
129   /*{{{  core class part*/
130   {
131     SuperClass,                        /* superclass */
132     "Drag",                           /* class_name */
133     sizeof(DragRec),                  /* size */
134     NULL,                             /* class_initialize */
135     ClassPartInitialize,              /* class_part_initialize */
136     False,                            /* class_inited */
137     Initialize,                       /* initialize */
138     NULL,                             /* initialize_hook */
139     XtInheritRealize,                 /* realize */
140     actions,                          /* actions */
141     XtNumber(actions),                /* num_actions */
142     resources,                        /* resources */
143     XtNumber(resources),              /* num_resources */
144     NULLQUARK,                        /* xrm_class */
145     True,                             /* compress_motion */
146     XtExposeCompressMultiple,         /* compress_exposure */
147     True,                             /* compress_enterleave */
148     False,                            /* visible_interest */
149     Destroy,                          /* destroy */
150     Resize,                           /* resize */
151     Redisplay,                        /* expose */
152     SetValues,                        /* set_values */
153     NULL,                             /* set_values_hook */
154     XtInheritSetValuesAlmost,         /* set_values_almost */
155     NULL,                             /* get_values_hook */
156     NULL,                             /* accept_focus */
157     XtVersion,                        /* version */
158     NULL,                             /* callback_private */
159     translations,                     /* default_translations */
160     QueryGeometry,                    /* query_geometry */
161     XtInheritDisplayAccelerator,      /* display_accelerator */
162     NULL,                             /* extension */
163   },
164   /*}}}*/
165   /*{{{  composite class part*/
166   {
167     XtInheritGeometryManager,
168     XtInheritChangeManaged,
169     XtInheritInsertChild,
170     XtInheritDeleteChild,
171     NULL
172   },
173   /*}}}*/
174   /*{{{  shell class part*/
175   {
176     NULL
177   },
178   /*}}}*/
179   /*{{{  override shell class part*/
180   {
181     NULL
182   },
183   /*}}}*/
184   /*{{{  drag class part*/
185   {
186     0,        /* dummy */
187   },
188   /*}}}*/
189 };
190 /*}}}*/
191 WidgetClass dragWidgetClass = (WidgetClass)&dragClassRec;
192 /* actions */
193 /*{{{  void Drag(widget, event, params, num_params)*/
194 static VOIDFUNC Drag
195 FUNCARG((widget, event, params, num_params),
196 	Widget    widget
197 ARGSEP  XEvent    *event
198 ARGSEP  String    *params
199 ARGSEP  Cardinal  *num_params
200 )
201 /* Follows the widget to the current pointer position
202  * If invoked by non pointer event, nothing happens
203  */
204 {
205   static Arg args[] = {{XtNx, 0}, {XtNy, 0}};
206   DragWidget dw;
207   Position  x, y;
208 
209   dw = (DragWidget)widget;
210   if(GetPointerPosition(event, &x, &y))
211     {
212       args[0].value = x - dw->drag.offset_x - dw->core.border_width;
213       args[1].value = y - dw->drag.offset_y - dw->core.border_width;
214       XtSetValues((Widget)dw, args, XtNumber(args));
215     }
216   return;
217 }
218 /*}}}*/
219 /*{{{  void Notify(widget, event, params, num_params)*/
220 static VOIDFUNC Notify
221 FUNCARG((widget, event, params, num_params),
222 	Widget    widget
223 ARGSEP  XEvent    *event
224 ARGSEP  String    *params
225 ARGSEP  Cardinal  *num_params
226 )
227 /* Ungrabs the pointer and pops down the widget
228  * notifies the call back that we've selected a widget
229  * Normally called on button up
230  * searches the list of selectable widgets for the one underneath
231  * the center of the widget.
232  * if no widget is found then the callback is not called.
233  */
234 {
235   DragWidget dw;
236   Position  x, y;
237 
238   XtUngrabPointer(widget, event->xbutton.time);
239   XtPopdown(widget);
240   dw = (DragWidget)widget;
241   if(GetPointerPosition(event, &x, &y))
242     {
243       int       count;
244       Widget    *wptr;
245 
246       x += dw->core.width / 2 - dw->drag.offset_x;
247       y += dw->core.height / 2 - dw->drag.offset_y;
248       for(wptr = dw->drag.choices, count = dw->drag.num_choices;
249 	  count--; wptr++)
250 	{
251 	  Position  rx, ry;
252 
253 	  XtTranslateCoords(*wptr, 0, 0, &rx, &ry);
254 	  if(XtIsRealized(*wptr) && rx <= x && ry <= y &&
255 	      rx + (int)((CorePart *)*wptr)->width > x &&
256 	      ry + (int)((CorePart *)*wptr)->height > y)
257 	    {
258 	      DragCallback data;
259 
260 	      data.selected = *wptr;
261 	      data.invoker = dw->drag.widget;
262 	      data.offset_x = x - rx;
263 	      data.offset_y = y - ry;
264 	      XtCallCallbackList((Widget)dw, dw->drag.callbacks,
265 		  (XtPointer)&data);
266 	      break;
267 	    }
268 	}
269     }
270   return;
271 }
272 /*}}}*/
273 /* class methods */
274 /*{{{  void ClassPartInitialize(widgetclass)*/
275 static VOIDFUNC ClassPartInitialize
276 FUNCARG((widgetclass),
277 	WidgetClass widgetclass
278 )
279 /* just add the composite class extension
280  */
281 {
282   DragWidgetClass dwclass;
283 
284   dwclass = (DragWidgetClass)widgetclass;
285   compositeExtension.next_extension =
286       dwclass->composite_class.extension;
287   dwclass->composite_class.extension = (XtPointer)&compositeExtension;
288   return;
289 }
290 /*}}}*/
291 /* methods */
292 /*{{{  void Destroy(widget)*/
293 static VOIDFUNC Destroy
294 FUNCARG((widget),
295 	Widget    widget
296 )
297 /* free the gc we use for drawing
298  */
299 {
300   DragWidget dw;
301 
302   dw = (DragWidget)widget;
303   XtReleaseGC((Widget)dw, dw->drag.gc);
304   return;
305 }
306 /*}}}*/
307 /*{{{  void Initialize(treq, tnew, args, num_args)*/
308 static VOIDFUNC Initialize
309 FUNCARG((treq, tnew, args, num_args),
310 	Widget    treq
311 ARGSEP  Widget    tnew
312 ARGSEP  ArgList   args
313 ARGSEP  Cardinal  *num_args
314 )
315 /* initialize widget instance,
316  * allocate gc and set default size
317  */
318 {
319   DragWidget ndw;
320 
321   ndw = (DragWidget)tnew;
322   GetPixmapSize(ndw);
323   if(!ndw->core.width)
324     ndw->core.width = ndw->drag.width ? ndw->drag.width : 16;
325   if(!ndw->core.height)
326     ndw->core.height = ndw->drag.height ? ndw->drag.height : 16;
327   GetGC(ndw);
328   return;
329 }
330 /*}}}*/
331 /*{{{  XtGeometryResult QueryGeometry(widget, proposed, answer)*/
332 static XtGeometryResult QueryGeometry
333 FUNCARG((widget, proposed, answer),
334 	Widget    widget
335 ARGSEP  XtWidgetGeometry *proposed
336 ARGSEP  XtWidgetGeometry *answer
337 )
338 /* validate geometry request from parent
339  * try to set to size of pixmap
340  */
341 {
342   DragWidget dw;
343 
344   dw = (DragWidget)widget;
345   answer->request_mode = CWWidth | CWHeight;
346   answer->height = dw->drag.width ? dw->drag.width : 16;
347   answer->height = dw->drag.height ? dw->drag.height : 16;
348   if((proposed->request_mode & (CWWidth | CWHeight)) == (CWWidth | CWHeight) &&
349       proposed->width == answer->width && proposed->height == answer->height)
350     return XtGeometryYes;
351   else if(answer->width == dw->core.width && answer->height == dw->core.height)
352     return XtGeometryNo;
353   else
354     return XtGeometryAlmost;
355 }
356 /*}}}*/
357 /*{{{  void Redisplay(widget, event, region)*/
358 static VOIDFUNC Redisplay
359 FUNCARG((widget, event, region),
360 	Widget    widget
361 ARGSEP  XEvent    *event
362 ARGSEP  Region    region
363 )
364 /* repaint the widget on expose
365  */
366 {
367   DragWidget  dw;
368 
369   if(!XtIsRealized(widget))
370     return;
371   dw = (DragWidget)widget;
372   if(dw->drag.pixmap != None)
373     XCopyArea(XtDisplay(dw), dw->drag.pixmap, XtWindow(dw), dw->drag.gc, 0, 0,
374       dw->drag.width, dw->drag.height, dw->drag.x, dw->drag.y);
375   return;
376 }
377 /*}}}*/
378 /*{{{  void Resize(widget)*/
379 static VOIDFUNC Resize
380 FUNCARG((widget),
381 	Widget    widget
382 )
383 /* when resized, reset the pixmap corner so that its centered
384  */
385 {
386   DragWidget dw;
387 
388   dw = (DragWidget)widget;
389   dw->drag.x = (dw->core.width - dw->drag.width) / 2;
390   dw->drag.y = (dw->core.height - dw->drag.height) / 2;
391   return;
392 }
393 /*}}}*/
394 /*{{{  Boolean SetValues(cw, rw, nw, args, num_args)*/
395 static Boolean SetValues
396 FUNCARG((cw, rw, nw, args, num_args),
397 	Widget    cw
398 ARGSEP  Widget    rw
399 ARGSEP  Widget    nw
400 ARGSEP  ArgList   args
401 ARGSEP  Cardinal  *num_args
402 )
403 /* recompute things if the pixmap changes
404  */
405 {
406   Boolean  redraw;
407   DragWidget cdw;
408   DragWidget ndw;
409 
410   redraw = False;
411   cdw = (DragWidget)cw;
412   ndw = (DragWidget)nw;
413   if(cdw->drag.pixmap != ndw->drag.pixmap)
414     {
415       redraw = True;
416       GetPixmapSize(ndw);
417       if(ndw->drag.width)
418 	{
419 	  ndw->core.width = ndw->drag.width;
420 	  ndw->drag.x = 0;
421 	}
422       if(ndw->drag.height)
423 	{
424 	  ndw->core.height = ndw->drag.height;
425 	  ndw->drag.y = 0;
426 	}
427     }
428   if(cdw->core.width != ndw->core.width ||
429       cdw->core.height != ndw->core.height)
430     redraw = False;
431   return redraw;
432 }
433 /*}}}*/
434 /* public routines */
435 /*{{{  void DragPopup(invoker, widget, pixmap, offset_x, offset_y, x, y, time)*/
436 extern VOIDFUNC DragPopup
437 FUNCARG((invoker, widget, pixmap, offset_x, offset_y, x, y, time),
438 	Widget    invoker   /* invoker widget */
439 ARGSEP  Widget    widget    /* drag widget */
440 ARGSEP  Pixmap    pixmap    /* pixmap to display */
441 ARGSEP  Position  offset_x  /* pointer offset on pixmap */
442 ARGSEP  Position  offset_y
443 ARGSEP  Position  x         /* root postion of pointer */
444 ARGSEP  Position  y
445 ARGSEP  Time      time      /* time of last event (needed for grab) */
446 )
447 /* called to invoke a drag selection
448  * set pixmap and offset values
449  * popup the widget
450  * grab the pointer
451  * If the pointer grab fails, the widget is popped down
452  */
453 {
454   /*{{{  set_args*/
455   static Arg set_args[] =
456   {
457     {XtNx},
458     {XtNy},
459     {XtNpixmap},
460   };
461   /*}}}*/
462   DragWidget dw;
463 
464   dw = (DragWidget)widget;
465   if(!XtIsSubclass((Widget)dw, (WidgetClass)&dragClassRec))
466     return;
467   dw->drag.offset_x = offset_x;
468   dw->drag.offset_y = offset_y;
469   dw->drag.widget = invoker;
470   set_args[0].value = x - dw->core.border_width - offset_x;
471   set_args[1].value = y - dw->core.border_width - offset_y;
472   set_args[2].value = pixmap;
473   XtSetValues((Widget)dw, set_args, pixmap == None ? 2 : 3);
474   XtPopupSpringLoaded((Widget)dw);
475   if(XtGrabPointer((Widget)dw, False, ButtonMotionMask | ButtonReleaseMask |
476       EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, None,
477       dw->drag.cursor, time) != GrabSuccess)
478     XtPopdown((Widget)dw);
479   return;
480 }
481 /*}}}*/
482 /* private routines */
483 /*{{{  void GetGC(dw)*/
484 static VOIDFUNC GetGC
485 FUNCARG((widget),
486 	DragWidget    widget
487 )
488 /* get a default gc to copy with
489  */
490 {
491   XGCValues     values;
492 
493   widget->drag.gc = XtGetGC((Widget)widget, 0, &values);
494   return;
495 }
496 /*}}}*/
497 /*{{{  void GetPixmapSize(dw)*/
498 static VOIDFUNC GetPixmapSize
499 FUNCARG((widget),
500 	DragWidget  widget
501 )
502 /* get the pixmap size
503  * Note this requires a server query
504  * if the pixmap is None, then set 0 size
505  */
506 {
507   if(widget->drag.pixmap != None)
508     {
509       Window  root;
510       int x, y;
511       unsigned border;
512       unsigned depth;
513       Status status;
514 
515       status = XGetGeometry(XtDisplay(widget), widget->drag.pixmap, &root,
516 	  &x, &y, &widget->drag.width, &widget->drag.height, &border, &depth);
517     }
518   else
519     widget->drag.width = widget->drag.height = 0;
520   return;
521 }
522 /*}}}*/
523 /*{{{  unsigned GetPointerPosition(dw, event, x, y)*/
524 static unsigned GetPointerPosition
525 FUNCARG((event, xp, yp),
526 	XEvent    *event  /* the event to query */
527 ARGSEP  Position  *xp     /* root pointer x return */
528 ARGSEP  Position  *yp     /* root pointer y return */
529 )
530 /* get the pointer position on the root window.
531  * returns 0 if not a pointer event
532  */
533 {
534   unsigned  got;
535   Position  x;
536   Position  y;
537 
538   /*{{{  get position from event*/
539   switch(event->type)
540   {
541     case ButtonPress:
542     case ButtonRelease:
543       x = event->xbutton.x_root;
544       y = event->xbutton.y_root;
545       got = 1;
546       break;
547     case MotionNotify:
548       x = event->xmotion.x_root;
549       y = event->xmotion.y_root;
550       got = 1;
551       break;
552     case EnterNotify:
553     case LeaveNotify:
554       x = event->xcrossing.x_root;
555       y = event->xcrossing.y_root;
556       got = 1;
557       break;
558     default:
559       x = y = 0;
560       got = 0;
561   }
562   /*}}}*/
563   if(got)
564     {
565       *xp = x;
566       *yp = y;
567     }
568   return got;
569 }
570 /*}}}*/
571