Panner.c revision 5b16253f
1/*
2 *
3Copyright 1989, 1994, 1998  The Open Group
4
5Permission to use, copy, modify, distribute, and sell this software and its
6documentation for any purpose is hereby granted without fee, provided that
7the above copyright notice appear in all copies and that both that
8copyright notice and this permission notice appear in supporting
9documentation.
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
21Except as contained in this notice, the name of The Open Group shall not be
22used in advertising or otherwise to promote the sale, use or other dealings
23in this Software without prior written authorization from The Open Group.
24 *
25 * Author:  Jim Fulton, MIT X Consortium
26 */
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <ctype.h>
32#include <math.h>
33#include <X11/IntrinsicP.h>
34#include <X11/StringDefs.h>
35#include <X11/Xos.h>
36#include <X11/Xmu/CharSet.h>
37#include <X11/Xmu/Drawing.h>
38#include <X11/Xmu/Misc.h>
39#include <X11/Xaw/PannerP.h>
40#include <X11/Xaw/XawInit.h>
41#include "Private.h"
42
43#if defined(ISC) && __STDC__ && !defined(ISC30)
44extern double atof(char *);
45#else
46#include <stdlib.h>			/* for atof() */
47#endif
48
49/*
50 * Class Methods
51 */
52static void XawPannerDestroy(Widget);
53static void XawPannerInitialize(Widget, Widget, ArgList, Cardinal*);
54static XtGeometryResult XawPannerQueryGeometry(Widget, XtWidgetGeometry*,
55					       XtWidgetGeometry*);
56static void XawPannerRealize(Widget, XtValueMask*, XSetWindowAttributes*);
57static void XawPannerRedisplay(Widget, XEvent*, Region);
58static void XawPannerResize(Widget);
59static Boolean XawPannerSetValues(Widget, Widget, Widget, ArgList, Cardinal*);
60static void XawPannerSetValuesAlmost(Widget, Widget, XtWidgetGeometry*,
61				     XtWidgetGeometry*);
62
63/*
64 * Prototypes
65 */
66static void check_knob(PannerWidget, Bool);
67static void get_default_size(PannerWidget, Dimension*, Dimension*);
68static Bool get_event_xy(PannerWidget, XEvent*, int*, int*);
69static void move_shadow(PannerWidget);
70static int parse_page_string(String, int, int, Bool*);
71static void rescale(PannerWidget);
72static void reset_shadow_gc(PannerWidget);
73static void reset_slider_gc(PannerWidget);
74static void reset_xor_gc(PannerWidget);
75static void scale_knob(PannerWidget, Bool, Bool);
76
77/*
78 * Actions
79 */
80static void ActionAbort(Widget, XEvent*, String*, Cardinal*);
81static void ActionMove(Widget, XEvent*, String*, Cardinal*);
82static void ActionNotify(Widget, XEvent*, String*, Cardinal*);
83static void ActionPage(Widget, XEvent*, String*, Cardinal*);
84static void ActionSet(Widget, XEvent*, String*, Cardinal*);
85static void ActionStart(Widget, XEvent*, String*, Cardinal*);
86static void ActionStop(Widget, XEvent*, String*, Cardinal*);
87
88/*
89 * From Xmu/Distinct.c
90 */
91Bool XmuDistinguishablePixels(Display*, Colormap, unsigned long*, int);
92
93/*
94 * Initialization
95 */
96static char defaultTranslations[] =
97"<Btn1Down>:"		"start()\n"
98"<Btn1Motion>:"		"move()\n"
99"<Btn1Up>:"		"notify() stop()\n"
100"<Btn2Down>:"		"abort()\n"
101":<Key>KP_Enter:"	"set(rubberband,toggle)\n"
102"<Key>space:"		"page(+1p,+1p)\n"
103"<Key>Delete:"		"page(-1p,-1p)\n"
104":<Key>KP_Delete:"	"page(-1p,-1p)\n"
105"<Key>BackSpace:"	"page(-1p,-1p)\n"
106"<Key>Left:"		"page(-.5p,+0)\n"
107":<Key>KP_Left:"	"page(-.5p,+0)\n"
108"<Key>Right:"		"page(+.5p,+0)\n"
109":<Key>KP_Right:"	"page(+.5p,+0)\n"
110"<Key>Up:"		"page(+0,-.5p)\n"
111":<Key>KP_Up:"		"page(+0,-.5p)\n"
112"<Key>Down:"		"page(+0,+.5p)\n"
113":<Key>KP_Down:"	"page(+0,+.5p)\n"
114"<Key>Home:"		"page(0,0)\n"
115":<Key>KP_Home:"	"page(0,0)\n"
116;
117
118static XtActionsRec actions[] = {
119  {"start",	ActionStart},		/* start tmp graphics */
120  {"stop",	ActionStop},		/* stop tmp graphics */
121  {"abort",	ActionAbort},		/* punt */
122  {"move",	ActionMove},		/* move tmp graphics on Motion event */
123  {"page",	ActionPage},		/* page around usually from keyboard */
124  {"notify",	ActionNotify},		/* callback new position */
125  {"set",	ActionSet},		/* set various parameters */
126};
127
128#define offset(field)	XtOffsetOf(PannerRec, panner.field)
129static XtResource resources[] = {
130    {
131      XtNallowOff,
132      XtCAllowOff,
133      XtRBoolean,
134      sizeof(Boolean),
135      offset(allow_off),
136      XtRImmediate,
137      (XtPointer)False
138    },
139    {
140      XtNresize,
141      XtCResize,
142      XtRBoolean,
143      sizeof(Boolean),
144      offset(resize_to_pref),
145      XtRImmediate,
146      (XtPointer)True
147    },
148    {
149      XtNreportCallback,
150      XtCReportCallback,
151      XtRCallback,
152      sizeof(XtPointer),
153      offset(report_callbacks),
154      XtRCallback,
155      NULL
156    },
157    {
158      XtNdefaultScale,
159      XtCDefaultScale,
160      XtRDimension,
161      sizeof(Dimension),
162      offset(default_scale),
163      XtRImmediate,
164      (XtPointer)PANNER_DEFAULT_SCALE
165    },
166    {
167      XtNrubberBand,
168      XtCRubberBand,
169      XtRBoolean,
170      sizeof(Boolean),
171      offset(rubber_band),
172      XtRImmediate,
173      (XtPointer)False
174    },
175    {
176      XtNforeground,
177      XtCForeground,
178      XtRPixel,
179      sizeof(Pixel),
180      offset(foreground),
181      XtRString,
182      (XtPointer)XtDefaultBackground
183    },
184    {
185      XtNinternalSpace,
186      XtCInternalSpace,
187      XtRDimension,
188      sizeof(Dimension),
189      offset(internal_border),
190      XtRImmediate,
191      (XtPointer)4
192    },
193    {
194      XtNlineWidth,
195      XtCLineWidth,
196      XtRDimension,
197      sizeof(Dimension),
198      offset(line_width),
199      XtRImmediate,
200      (XtPointer)0
201    },
202    {
203      XtNcanvasWidth,
204      XtCCanvasWidth,
205      XtRDimension,
206      sizeof(Dimension),
207      offset(canvas_width),
208      XtRImmediate,
209      (XtPointer)0
210    },
211    {
212      XtNcanvasHeight,
213      XtCCanvasHeight,
214      XtRDimension,
215      sizeof(Dimension),
216      offset(canvas_height),
217      XtRImmediate,
218      (XtPointer)0
219    },
220    {
221      XtNsliderX,
222      XtCSliderX,
223      XtRPosition,
224      sizeof(Position),
225      offset(slider_x),
226      XtRImmediate,
227      (XtPointer)0
228    },
229    {
230      XtNsliderY,
231      XtCSliderY,
232      XtRPosition,
233      sizeof(Position),
234      offset(slider_y),
235      XtRImmediate,
236      (XtPointer)0
237    },
238    {
239      XtNsliderWidth,
240      XtCSliderWidth,
241      XtRDimension,
242      sizeof(Dimension),
243      offset(slider_width),
244      XtRImmediate,
245      (XtPointer)0
246    },
247    {
248      XtNsliderHeight,
249      XtCSliderHeight,
250      XtRDimension,
251      sizeof(Dimension),
252      offset(slider_height),
253      XtRImmediate,
254      (XtPointer)0
255    },
256    {
257      XtNshadowColor,
258      XtCShadowColor,
259      XtRPixel,
260      sizeof(Pixel),
261      offset(shadow_color),
262      XtRString,
263      (XtPointer)XtDefaultForeground
264    },
265    {
266      XtNshadowThickness,
267      XtCShadowThickness,
268      XtRDimension,
269      sizeof(Dimension),
270      offset(shadow_thickness),
271      XtRImmediate,
272      (XtPointer)2
273    },
274    {
275      XtNbackgroundStipple,
276      XtCBackgroundStipple,
277      XtRString,
278      sizeof(String),
279      offset(stipple_name),
280      XtRImmediate,
281      NULL
282    },
283};
284#undef offset
285
286#define Superclass	(&simpleClassRec)
287PannerClassRec pannerClassRec = {
288  /* core */
289  {
290    (WidgetClass)Superclass,		/* superclass */
291    "Panner",				/* class_name */
292    sizeof(PannerRec),			/* widget_size */
293    XawInitializeWidgetSet,		/* class_initialize */
294    NULL,				/* class_part_initialize */
295    False,				/* class_inited */
296    XawPannerInitialize,		/* initialize */
297    NULL,				/* initialize_hook */
298    XawPannerRealize,			/* realize */
299    actions,				/* actions */
300    XtNumber(actions),			/* num_actions */
301    resources,				/* resources */
302    XtNumber(resources),		/* num_resources */
303    NULLQUARK,				/* xrm_class */
304    True,				/* compress_motion */
305    True,				/* compress_exposure */
306    True,				/* compress_enterleave */
307    False,				/* visible_interest */
308    XawPannerDestroy,			/* destroy */
309    XawPannerResize,			/* resize */
310    XawPannerRedisplay,			/* expose */
311    XawPannerSetValues,			/* set_values */
312    NULL,				/* set_values_hook */
313    XawPannerSetValuesAlmost,		/* set_values_almost */
314    NULL,				/* get_values_hook */
315    NULL,				/* accept_focus */
316    XtVersion,				/* version */
317    NULL,				/* callback_private */
318    defaultTranslations,		/* tm_table */
319    XawPannerQueryGeometry,		/* query_geometry */
320    XtInheritDisplayAccelerator,	/* display_accelerator */
321    NULL,				/* extension */
322  },
323  /* simple */
324  {
325    XtInheritChangeSensitive,		/* change_sensitive */
326#ifndef OLDXAW
327    NULL,
328#endif
329  },
330  /* panner */
331  {
332    NULL,				/* extension */
333  }
334};
335
336WidgetClass pannerWidgetClass = (WidgetClass) &pannerClassRec;
337
338
339/*
340 * Implementation
341 */
342static void
343reset_shadow_gc(PannerWidget pw)
344{
345    XtGCMask valuemask = GCForeground;
346    XGCValues values;
347    unsigned long   pixels[3];
348
349    if (pw->panner.shadow_gc)
350	XtReleaseGC((Widget)pw, pw->panner.shadow_gc);
351
352    pixels[0] = pw->panner.foreground;
353    pixels[1] = pw->core.background_pixel;
354    pixels[2] = pw->panner.shadow_color;
355
356    if (!pw->panner.stipple_name &&
357	!XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
358				  pixels, 3) &&
359	XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
360				 pixels, 2)) {
361	valuemask = GCTile | GCFillStyle;
362	values.fill_style = FillTiled;
363	values.tile = XmuCreateStippledPixmap(XtScreen((Widget)pw),
364					      pw->panner.foreground,
365					      pw->core.background_pixel,
366					      pw->core.depth);
367    }
368    else {
369	if (!pw->panner.line_width &&
370	    !XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
371				      pixels, 2))
372	    pw->panner.line_width = 1;
373	valuemask = GCForeground;
374	values.foreground = pw->panner.shadow_color;
375    }
376    if (pw->panner.line_width > 0) {
377	values.line_width = pw->panner.line_width;
378	valuemask |= GCLineWidth;
379    }
380
381    pw->panner.shadow_gc = XtGetGC((Widget)pw, valuemask, &values);
382}
383
384static void
385reset_slider_gc(PannerWidget pw)
386{
387    XtGCMask valuemask = GCForeground;
388    XGCValues values;
389
390    if (pw->panner.slider_gc)
391	XtReleaseGC((Widget)pw, pw->panner.slider_gc);
392
393    values.foreground = pw->panner.foreground;
394
395    pw->panner.slider_gc = XtGetGC((Widget)pw, valuemask, &values);
396}
397
398static void
399reset_xor_gc(PannerWidget pw)
400{
401    if (pw->panner.xor_gc)
402	XtReleaseGC((Widget)pw, pw->panner.xor_gc);
403
404    if (pw->panner.rubber_band) {
405	XtGCMask valuemask = (GCForeground | GCFunction);
406	XGCValues values;
407	Pixel tmp;
408
409	tmp = (pw->panner.foreground == pw->core.background_pixel ?
410	       pw->panner.shadow_color : pw->panner.foreground);
411	values.foreground = tmp ^ pw->core.background_pixel;
412	values.function = GXxor;
413	if (pw->panner.line_width > 0) {
414	    valuemask |= GCLineWidth;
415	    values.line_width = pw->panner.line_width;
416	}
417	pw->panner.xor_gc = XtGetGC((Widget)pw, valuemask, &values);
418    }
419    else
420	pw->panner.xor_gc = NULL;
421}
422
423static void
424check_knob(PannerWidget pw, Bool knob)
425{
426    Position pad = (Position)(pw->panner.internal_border << 1);
427    Position maxx = (Position)(XtWidth(pw) - pad - pw->panner.knob_width);
428    Position maxy = (Position)(XtHeight(pw) - pad - pw->panner.knob_height);
429    Position *x = knob ? &pw->panner.knob_x : &pw->panner.tmp.x;
430    Position *y = knob ? &pw->panner.knob_y : &pw->panner.tmp.y;
431
432    /*
433     * note that positions are already normalized (i.e. internal_border
434     * has been subtracted out)
435     */
436    if (*x < 0)
437	*x = 0;
438    if (*x > maxx)
439	*x = maxx;
440
441    if (*y < 0)
442	*y = 0;
443    if (*y > maxy)
444	*y = maxy;
445
446    if (knob) {
447	pw->panner.slider_x = (Position)((double)pw->panner.knob_x
448					/ pw->panner.haspect + 0.5);
449	pw->panner.slider_y = (Position)((double)pw->panner.knob_y
450					/ pw->panner.vaspect + 0.5);
451	pw->panner.last_x = pw->panner.last_y = PANNER_OUTOFRANGE;
452    }
453}
454
455static void
456move_shadow(PannerWidget pw)
457{
458    if (pw->panner.shadow_thickness > 0) {
459	int lw = pw->panner.shadow_thickness + (pw->panner.line_width << 1);
460	int pad = pw->panner.internal_border;
461
462	if (pw->panner.knob_height > lw && pw->panner.knob_width > lw) {
463	    XRectangle *r = pw->panner.shadow_rects;
464
465	    r->x = (short)(pw->panner.knob_x + pad + pw->panner.knob_width);
466	    r->y = (short)(pw->panner.knob_y + pad + lw);
467	    r->width = (unsigned short)(pw->panner.shadow_thickness);
468	    r->height = (unsigned short)(pw->panner.knob_height - lw);
469	    r++;
470	    r->x = (short)(pw->panner.knob_x + pad + lw);
471	    r->y = (short)(pw->panner.knob_y + pad + pw->panner.knob_height);
472	    r->width = (unsigned short)(pw->panner.knob_width - lw + pw->panner.shadow_thickness);
473	    r->height = (unsigned short)(pw->panner.shadow_thickness);
474	    pw->panner.shadow_valid = True;
475	    return;
476	}
477    }
478    pw->panner.shadow_valid = False;
479}
480
481static void
482scale_knob(PannerWidget pw, Bool location, Bool size)
483{
484    if (location) {
485	pw->panner.knob_x = (Position)PANNER_HSCALE(pw, pw->panner.slider_x);
486	pw->panner.knob_y = (Position)PANNER_VSCALE(pw, pw->panner.slider_y);
487    }
488    if (size) {
489	Dimension width, height;
490
491	if (pw->panner.slider_width < 1)
492	    pw->panner.slider_width = pw->panner.canvas_width;
493	if (pw->panner.slider_height < 1)
494	    pw->panner.slider_height = pw->panner.canvas_height;
495	width = Min(pw->panner.slider_width, pw->panner.canvas_width);
496	height = Min(pw->panner.slider_height, pw->panner.canvas_height);
497
498	pw->panner.knob_width = (Dimension)PANNER_HSCALE(pw, width);
499	pw->panner.knob_height = (Dimension)PANNER_VSCALE(pw, height);
500    }
501    if (!pw->panner.allow_off)
502	check_knob(pw, True);
503    move_shadow(pw);
504}
505
506static void
507rescale(PannerWidget pw)
508{
509    int hpad = pw->panner.internal_border << 1;
510    int vpad = hpad;
511
512    if (pw->panner.canvas_width < 1)
513	pw->panner.canvas_width = XtWidth(pw);
514    if (pw->panner.canvas_height < 1)
515	pw->panner.canvas_height = XtHeight(pw);
516
517    if (XtWidth(pw) <= hpad)
518	hpad = 0;
519    if (XtHeight(pw) <= vpad)
520	vpad = 0;
521
522    pw->panner.haspect = ((double)XtWidth(pw) - hpad + .5)
523			 / (double)pw->panner.canvas_width;
524    pw->panner.vaspect = ((double)XtHeight(pw) - vpad + .5)
525			 / (double)pw->panner.canvas_height;
526    scale_knob(pw, True, True);
527}
528
529static void
530get_default_size(PannerWidget pw, Dimension *wp, Dimension *hp)
531{
532    Dimension pad = (Dimension)(pw->panner.internal_border << 1);
533
534    *wp = (Dimension)(PANNER_DSCALE(pw, pw->panner.canvas_width) + pad);
535    *hp = (Dimension)(PANNER_DSCALE(pw, pw->panner.canvas_height) + pad);
536}
537
538static Bool
539get_event_xy(PannerWidget pw, XEvent *event, int *x, int *y)
540{
541    int pad = pw->panner.internal_border;
542
543    switch (event->type) {
544	case ButtonPress:
545	case ButtonRelease:
546	    *x = event->xbutton.x - pad;
547	    *y = event->xbutton.y - pad;
548	    return (True);
549	case KeyPress:
550	case KeyRelease:
551	    *x = event->xkey.x - pad;
552	    *y = event->xkey.y - pad;
553	    return (True);
554	case EnterNotify:
555	case LeaveNotify:
556	    *x = event->xcrossing.x - pad;
557	    *y = event->xcrossing.y - pad;
558	    return (True);
559	case MotionNotify:
560	    *x = event->xmotion.x - pad;
561	    *y = event->xmotion.y - pad;
562	    return (True);
563    }
564
565    return (False);
566}
567
568static int
569parse_page_string(String s, int pagesize, int canvassize, Bool *relative)
570{
571    String cp;
572    double val = 1.0;
573    Bool rel = False;
574
575    /*
576     * syntax:    spaces [+-] number spaces [pc\0] spaces
577     */
578    for (; isascii((unsigned char)*s) && isspace((unsigned char)*s); s++)	/* skip white space */
579	;
580
581    if (*s == '+' || *s == '-')	{		/* deal with signs */
582	rel = True;
583	if (*s == '-')
584	    val = -1.0;
585	s++;
586    }
587    if (!*s) {				/* if null then return nothing */
588	*relative = True;
589	return (0);
590    }
591
592					/* skip over numbers */
593    for (cp = s; isascii((unsigned char)*s) && (isdigit((unsigned char)*s) || *s == '.'); s++)
594	;
595    val *= atof(cp);
596
597					/* skip blanks */
598    for (; isascii((unsigned char)*s) && isspace((unsigned char)*s); s++)
599	;
600
601    if (*s) {				/* if units */
602	switch (s[0]) {
603	    case 'p':
604	    case 'P':
605		val *= (double)pagesize;
606		break;
607	    case 'c':
608	    case 'C':
609		val *= (double)canvassize;
610		break;
611	}
612    }
613    *relative = rel;
614
615    return ((int)val);
616}
617
618#define DRAW_TMP(pw) \
619{ \
620    XDrawRectangle(XtDisplay(pw), XtWindow(pw),				\
621		   pw->panner.xor_gc,					\
622		   (pw->panner.tmp.x + pw->panner.internal_border),	\
623		   (pw->panner.tmp.y + pw->panner.internal_border),	\
624		   (unsigned)(pw->panner.knob_width - 1),		\
625		   (unsigned)(pw->panner.knob_height - 1));		\
626    pw->panner.tmp.showing = !pw->panner.tmp.showing;			\
627}
628
629#define UNDRAW_TMP(pw) \
630{ \
631    if (pw->panner.tmp.showing)			\
632      DRAW_TMP(pw);				\
633}
634
635#define BACKGROUND_STIPPLE(pw) \
636XmuLocatePixmapFile(pw->core.screen, pw->panner.stipple_name,		\
637		    pw->panner.shadow_color, pw->core.background_pixel,	\
638		    pw->core.depth, NULL, 0, NULL, NULL, NULL, NULL)
639
640#define PIXMAP_OKAY(pm) ((pm) != None && (pm) != XtUnspecifiedPixmap)
641
642/*ARGSUSED*/
643static void
644XawPannerInitialize(Widget greq, Widget gnew, ArgList args _X_UNUSED, Cardinal *num_args _X_UNUSED)
645{
646    PannerWidget req = (PannerWidget)greq, cnew = (PannerWidget)gnew;
647    Dimension defwidth, defheight;
648
649    if (req->panner.canvas_width < 1)
650	cnew->panner.canvas_width = 1;
651    if (req->panner.canvas_height < 1)
652	cnew->panner.canvas_height = 1;
653    if (req->panner.default_scale < 1)
654	cnew->panner.default_scale = PANNER_DEFAULT_SCALE;
655
656    get_default_size(req, &defwidth, &defheight);
657    if (XtWidth(req) < 1)
658	XtWidth(cnew) = defwidth;
659    if (XtHeight(req) < 1)
660	XtHeight(cnew) = defheight;
661
662    cnew->panner.shadow_gc = NULL;
663    reset_shadow_gc(cnew);		/* shadowColor */
664    cnew->panner.slider_gc = NULL;
665    reset_slider_gc(cnew);		/* foreground */
666    cnew->panner.xor_gc = NULL;
667    reset_xor_gc(cnew);			/* foreground ^ background */
668
669    rescale(cnew);			/* does a position check */
670    cnew->panner.shadow_valid = False;
671    cnew->panner.tmp.doing = False;
672    cnew->panner.tmp.showing = False;
673  }
674
675static void
676XawPannerRealize(Widget gw, XtValueMask *valuemaskp,
677		 XSetWindowAttributes *attr)
678{
679    PannerWidget pw = (PannerWidget)gw;
680    Pixmap pm = XtUnspecifiedPixmap;
681    Bool gotpm = False;
682
683    if (pw->core.background_pixmap == XtUnspecifiedPixmap) {
684	if (pw->panner.stipple_name)
685	    pm = BACKGROUND_STIPPLE(pw);
686
687	if (PIXMAP_OKAY(pm)) {
688	    attr->background_pixmap = pm;
689	    *valuemaskp |= CWBackPixmap;
690	    *valuemaskp &= (XtValueMask)(~CWBackPixel);
691	    gotpm = True;
692	}
693    }
694    (*pannerWidgetClass->core_class.superclass->core_class.realize)
695	(gw, valuemaskp, attr);
696
697    if (gotpm)
698	XFreePixmap(XtDisplay(gw), pm);
699}
700
701static void
702XawPannerDestroy(Widget gw)
703{
704    PannerWidget pw = (PannerWidget)gw;
705
706    XtReleaseGC(gw, pw->panner.shadow_gc);
707    XtReleaseGC(gw, pw->panner.slider_gc);
708    XtReleaseGC(gw, pw->panner.xor_gc);
709}
710
711static void
712XawPannerResize(Widget gw)
713{
714    rescale((PannerWidget)gw);
715}
716
717static void
718XawPannerRedisplay(Widget gw, XEvent *event, Region region)
719{
720    PannerWidget pw = (PannerWidget)gw;
721    Display *dpy = XtDisplay(gw);
722    Window w = XtWindow(gw);
723    int pad = pw->panner.internal_border;
724    Dimension lw = pw->panner.line_width;
725    Dimension extra = (Dimension)(pw->panner.shadow_thickness + (lw << 1));
726    int kx = pw->panner.knob_x + pad, ky = pw->panner.knob_y + pad;
727
728    if (Superclass->core_class.expose)
729	(Superclass->core_class.expose)(gw, event, region);
730
731    pw->panner.tmp.showing = False;
732    XClearArea(XtDisplay(pw), XtWindow(pw),
733	       (int)pw->panner.last_x - ((int)lw) + pad,
734	       (int)pw->panner.last_y - ((int)lw) + pad,
735	       (unsigned)(pw->panner.knob_width + extra),
736	       (unsigned)(pw->panner.knob_height + extra),
737	       False);
738    pw->panner.last_x = pw->panner.knob_x;
739    pw->panner.last_y = pw->panner.knob_y;
740
741    XFillRectangle(dpy, w, pw->panner.slider_gc, kx, ky,
742		   (unsigned)(pw->panner.knob_width - 1),
743		   (unsigned)(pw->panner.knob_height - 1));
744
745    if (lw)
746	XDrawRectangle(dpy, w, pw->panner.shadow_gc, kx, ky,
747		       (unsigned)(pw->panner.knob_width - 1),
748		       (unsigned)(pw->panner.knob_height - 1));
749
750    if (pw->panner.shadow_valid)
751	XFillRectangles(dpy, w, pw->panner.shadow_gc, pw->panner.shadow_rects, 2);
752
753    if (pw->panner.tmp.doing && pw->panner.rubber_band)
754	DRAW_TMP(pw);
755}
756
757/*ARGSUSED*/
758static Boolean
759XawPannerSetValues(Widget gcur, Widget greq _X_UNUSED, Widget gnew,
760		   ArgList args _X_UNUSED, Cardinal *num_args _X_UNUSED)
761{
762    PannerWidget cur = (PannerWidget)gcur;
763    PannerWidget cnew = (PannerWidget)gnew;
764    Bool redisplay = False;
765
766    if (cur->panner.foreground != cnew->panner.foreground) {
767	reset_slider_gc(cnew);
768	if (cur->panner.foreground != cur->core.background_pixel)
769	    reset_xor_gc(cnew);
770	redisplay = True;
771    }
772    else if (cur->panner.line_width != cnew->panner.line_width ||
773	     cur->core.background_pixel != cnew->core.background_pixel) {
774	reset_xor_gc(cnew);
775	redisplay = True;
776    }
777    if (cur->panner.shadow_color != cnew->panner.shadow_color) {
778	reset_shadow_gc(cnew);
779	if (cur->panner.foreground == cur->core.background_pixel)
780	    reset_xor_gc(cnew);
781	redisplay = True;
782    }
783    if (cur->panner.shadow_thickness != cnew->panner.shadow_thickness) {
784	move_shadow(cnew);
785	redisplay = True;
786    }
787    if (cur->panner.rubber_band != cnew->panner.rubber_band) {
788	reset_xor_gc(cnew);
789	if (cnew->panner.tmp.doing)
790	    redisplay = True;
791    }
792
793    if ((cur->panner.stipple_name != cnew->panner.stipple_name
794	 || cur->panner.shadow_color != cnew->panner.shadow_color
795	 || cur->core.background_pixel != cnew->core.background_pixel)
796	&& XtIsRealized(gnew)) {
797	Pixmap pm = cnew->panner.stipple_name ?
798			BACKGROUND_STIPPLE(cnew) : XtUnspecifiedPixmap;
799
800	if (PIXMAP_OKAY(pm)) {
801	    XSetWindowBackgroundPixmap(XtDisplay(cnew), XtWindow(cnew), pm);
802	    XFreePixmap(XtDisplay(cnew), pm);
803	}
804	else
805	    XSetWindowBackground(XtDisplay(cnew), XtWindow(cnew),
806				 cnew->core.background_pixel);
807
808	redisplay = True;
809    }
810
811    if (cnew->panner.resize_to_pref &&
812	(cur->panner.canvas_width != cnew->panner.canvas_width
813	 || cur->panner.canvas_height != cnew->panner.canvas_height
814	 || cur->panner.resize_to_pref != cnew->panner.resize_to_pref)) {
815	get_default_size(cnew, &cnew->core.width, &cnew->core.height);
816	redisplay = True;
817    }
818    else if (cur->panner.canvas_width != cnew->panner.canvas_width
819	     || cur->panner.canvas_height != cnew->panner.canvas_height
820	     || cur->panner.internal_border != cnew->panner.internal_border) {
821	rescale(cnew);			/* does a scale_knob as well */
822	redisplay = True;
823    }
824    else {
825	Bool loc = cur->panner.slider_x != cnew->panner.slider_x ||
826		   cur->panner.slider_y != cnew->panner.slider_y;
827	Bool siz = cur->panner.slider_width != cnew->panner.slider_width ||
828		   cur->panner.slider_height != cnew->panner.slider_height;
829	if (loc || siz || (cur->panner.allow_off != cnew->panner.allow_off
830			   && cnew->panner.allow_off)) {
831	    scale_knob(cnew, loc, siz);
832	    redisplay = True;
833	}
834    }
835
836    return (Boolean)(redisplay);
837}
838
839static void
840XawPannerSetValuesAlmost(Widget gold, Widget gnew, XtWidgetGeometry *req,
841			 XtWidgetGeometry *reply)
842{
843    if (reply->request_mode == 0)	/* got turned down, so cope */
844	XawPannerResize(gnew);
845
846    (*pannerWidgetClass->core_class.superclass->core_class.set_values_almost)
847	(gold, gnew, req, reply);
848}
849
850static XtGeometryResult
851XawPannerQueryGeometry(Widget gw, XtWidgetGeometry *intended,
852		       XtWidgetGeometry *pref)
853{
854    PannerWidget pw = (PannerWidget)gw;
855
856    pref->request_mode = (CWWidth | CWHeight);
857    get_default_size(pw, &pref->width, &pref->height);
858
859    if (((intended->request_mode & (CWWidth | CWHeight)) == (CWWidth | CWHeight))
860	&& intended->width == pref->width && intended->height == pref->height)
861	return (XtGeometryYes);
862    else if (pref->width == XtWidth(pw) && pref->height == XtHeight(pw))
863	return (XtGeometryNo);
864
865    return (XtGeometryAlmost);
866}
867
868
869/*ARGSUSED*/
870static void
871ActionStart(Widget gw, XEvent *event, String *params _X_UNUSED, Cardinal *num_params _X_UNUSED)
872{
873    PannerWidget pw = (PannerWidget)gw;
874    int x, y;
875
876    if (!get_event_xy(pw, event, &x, &y)) {
877	XBell(XtDisplay(gw), 0);
878	return;
879    }
880
881    pw->panner.tmp.doing = True;
882    pw->panner.tmp.startx = pw->panner.knob_x;
883    pw->panner.tmp.starty = pw->panner.knob_y;
884    pw->panner.tmp.dx = (Position)(x - pw->panner.knob_x);
885    pw->panner.tmp.dy = (Position)(y - pw->panner.knob_y);
886    pw->panner.tmp.x = pw->panner.knob_x;
887    pw->panner.tmp.y = pw->panner.knob_y;
888    if (pw->panner.rubber_band)
889	DRAW_TMP(pw);
890}
891
892/*ARGSUSED*/
893static void
894ActionStop(Widget gw, XEvent *event, String *params _X_UNUSED, Cardinal *num_params _X_UNUSED)
895{
896    PannerWidget pw = (PannerWidget)gw;
897    int x, y;
898
899    if (get_event_xy(pw, event, &x, &y)) {
900	pw->panner.tmp.x = (Position)(x - pw->panner.tmp.dx);
901	pw->panner.tmp.y = (Position)(y - pw->panner.tmp.dy);
902	if (!pw->panner.allow_off)
903	    check_knob(pw, False);
904    }
905    if (pw->panner.rubber_band)
906	DRAW_TMP(pw);
907    pw->panner.tmp.doing = False;
908}
909
910static void
911ActionAbort(Widget gw, XEvent *event, String *params, Cardinal *num_params)
912{
913    PannerWidget pw = (PannerWidget)gw;
914
915    if (!pw->panner.tmp.doing)
916	return;
917
918    if (pw->panner.rubber_band)
919	UNDRAW_TMP(pw);
920
921    if (!pw->panner.rubber_band) {		/* restore old position */
922	pw->panner.tmp.x = pw->panner.tmp.startx;
923	pw->panner.tmp.y = pw->panner.tmp.starty;
924	ActionNotify(gw, event, params, num_params);
925    }
926    pw->panner.tmp.doing = False;
927}
928
929static void
930ActionMove(Widget gw, XEvent *event, String *params, Cardinal *num_params)
931{
932    PannerWidget pw = (PannerWidget)gw;
933    int x, y;
934
935    if (!pw->panner.tmp.doing)
936      return;
937
938    if (!get_event_xy(pw, event, &x, &y)) {
939	XBell(XtDisplay(gw), 0);	/* should do error message */
940	return;
941    }
942
943    if (pw->panner.rubber_band)
944	UNDRAW_TMP(pw);
945    pw->panner.tmp.x = (Position)(x - pw->panner.tmp.dx);
946    pw->panner.tmp.y = (Position)(y - pw->panner.tmp.dy);
947
948    if (!pw->panner.rubber_band)
949	ActionNotify(gw, event, params, num_params);
950    else {
951	if (!pw->panner.allow_off)
952	    check_knob(pw, False);
953	DRAW_TMP(pw);
954    }
955}
956
957
958static void
959ActionPage(Widget gw, XEvent *event, String *params, Cardinal *num_params)
960{
961    PannerWidget pw = (PannerWidget)gw;
962    Cardinal zero = 0;
963    Bool isin = pw->panner.tmp.doing;
964    int x, y;
965    Bool relx, rely;
966    int pad = pw->panner.internal_border << 1;
967
968    if (*num_params != 2) {
969      XBell(XtDisplay(gw), 0);
970	return;
971    }
972
973    x = parse_page_string(params[0], pw->panner.knob_width,
974			  (int)XtWidth(pw) - pad, &relx);
975    y = parse_page_string(params[1], pw->panner.knob_height,
976			  (int)XtHeight(pw) - pad, &rely);
977
978    if (relx)
979	x += pw->panner.knob_x;
980    if (rely)
981	y += pw->panner.knob_y;
982
983    if (isin) {				/* if in, then use move */
984	XEvent ev;
985
986	ev.xbutton.type = ButtonPress;
987	ev.xbutton.x = x;
988	ev.xbutton.y = y;
989	ActionMove(gw, &ev, NULL, &zero);
990    }
991    else {
992	pw->panner.tmp.doing = True;
993	pw->panner.tmp.x = (Position)x;
994	pw->panner.tmp.y = (Position)y;
995	ActionNotify(gw, event, NULL, &zero);
996	pw->panner.tmp.doing = False;
997    }
998}
999
1000/*ARGSUSED*/
1001static void
1002ActionNotify(Widget gw, XEvent *event _X_UNUSED, String *params _X_UNUSED, Cardinal *num_params _X_UNUSED)
1003{
1004    PannerWidget pw = (PannerWidget)gw;
1005
1006    if (!pw->panner.tmp.doing)
1007	return;
1008
1009    if (!pw->panner.allow_off)
1010	check_knob(pw, False);
1011    pw->panner.knob_x = pw->panner.tmp.x;
1012    pw->panner.knob_y = pw->panner.tmp.y;
1013    move_shadow(pw);
1014
1015    pw->panner.slider_x = (Position)((double)pw->panner.knob_x
1016				   / pw->panner.haspect + 0.5);
1017    pw->panner.slider_y = (Position)((double) pw->panner.knob_y
1018				   / pw->panner.vaspect + 0.5);
1019    if (!pw->panner.allow_off) {
1020	Position tmp;
1021
1022	if (pw->panner.slider_x
1023	    > (tmp = (Position)(pw->panner.canvas_width -
1024				pw->panner.slider_width)))
1025	    pw->panner.slider_x = tmp;
1026	if (pw->panner.slider_x < 0)
1027	    pw->panner.slider_x = 0;
1028	if (pw->panner.slider_y
1029	    > (tmp = (Position)(pw->panner.canvas_height -
1030				pw->panner.slider_height)))
1031	    pw->panner.slider_y = tmp;
1032	if (pw->panner.slider_y < 0)
1033	    pw->panner.slider_y = 0;
1034    }
1035
1036    if (pw->panner.last_x != pw->panner.knob_x ||
1037	pw->panner.last_y != pw->panner.knob_y) {
1038	XawPannerReport rep;
1039
1040	XawPannerRedisplay(gw, NULL, NULL);
1041	rep.changed = XawPRSliderX | XawPRSliderY;
1042	rep.slider_x = pw->panner.slider_x;
1043	rep.slider_y = pw->panner.slider_y;
1044	rep.slider_width = pw->panner.slider_width;
1045	rep.slider_height = pw->panner.slider_height;
1046	rep.canvas_width = pw->panner.canvas_width;
1047	rep.canvas_height = pw->panner.canvas_height;
1048	XtCallCallbackList(gw, pw->panner.report_callbacks, (XtPointer)&rep);
1049    }
1050}
1051
1052/*ARGSUSED*/
1053static void
1054ActionSet(Widget gw, XEvent *event _X_UNUSED, String *params, Cardinal *num_params)
1055{
1056    PannerWidget pw = (PannerWidget)gw;
1057    Bool rb;
1058
1059    if (*num_params < 2 ||
1060	XmuCompareISOLatin1(params[0], "rubberband") != 0) {
1061	XBell(XtDisplay(gw), 0);
1062	return;
1063    }
1064
1065    if (XmuCompareISOLatin1(params[1], "on") == 0)
1066	rb = True;
1067    else if (XmuCompareISOLatin1(params[1], "off") == 0)
1068	rb = False;
1069    else if (XmuCompareISOLatin1(params[1], "toggle") == 0)
1070	rb = !pw->panner.rubber_band;
1071    else {
1072      XBell(XtDisplay(gw), 0);
1073	return;
1074    }
1075
1076    if (rb != pw->panner.rubber_band) {
1077	Arg args[1];
1078
1079	XtSetArg(args[0], XtNrubberBand, rb);
1080	XtSetValues(gw, args, 1);
1081    }
1082}
1083