Panner.c revision 7a84e134
1/*
2 * $Xorg: Panner.c,v 1.4 2001/02/09 02:03:45 xorgcvs Exp $
3 *
4Copyright 1989, 1994, 1998  The Open Group
5
6Permission to use, copy, modify, distribute, and sell this software and its
7documentation for any purpose is hereby granted without fee, provided that
8the above copyright notice appear in all copies and that both that
9copyright notice and this permission notice appear in supporting
10documentation.
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
19AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22Except as contained in this notice, the name of The Open Group shall not be
23used in advertising or otherwise to promote the sale, use or other dealings
24in this Software without prior written authorization from The Open Group.
25 *
26 * Author:  Jim Fulton, MIT X Consortium
27 */
28
29/* $XFree86: xc/lib/Xaw/Panner.c,v 3.8 2001/07/25 15:04:49 dawes Exp $ */
30
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34#include <ctype.h>
35#include <math.h>
36#include <X11/IntrinsicP.h>
37#include <X11/StringDefs.h>
38#include <X11/Xos.h>
39#include <X11/Xmu/CharSet.h>
40#include <X11/Xmu/Drawing.h>
41#include <X11/Xmu/Misc.h>
42#include <X11/Xaw/PannerP.h>
43#include <X11/Xaw/XawInit.h>
44#include "Private.h"
45
46#if defined(ISC) && __STDC__ && !defined(ISC30)
47extern double atof(char *);
48#else
49#include <stdlib.h>			/* for atof() */
50#endif
51
52/*
53 * Class Methods
54 */
55static void XawPannerDestroy(Widget);
56static void XawPannerInitialize(Widget, Widget, ArgList, Cardinal*);
57static XtGeometryResult XawPannerQueryGeometry(Widget, XtWidgetGeometry*,
58					       XtWidgetGeometry*);
59static void XawPannerRealize(Widget, XtValueMask*, XSetWindowAttributes*);
60static void XawPannerRedisplay(Widget, XEvent*, Region);
61static void XawPannerResize(Widget);
62static Boolean XawPannerSetValues(Widget, Widget, Widget, ArgList, Cardinal*);
63static void XawPannerSetValuesAlmost(Widget, Widget, XtWidgetGeometry*,
64				     XtWidgetGeometry*);
65
66/*
67 * Prototypes
68 */
69static void check_knob(PannerWidget, Bool);
70static void get_default_size(PannerWidget, Dimension*, Dimension*);
71static Bool get_event_xy(PannerWidget, XEvent*, int*, int*);
72static void move_shadow(PannerWidget);
73static int parse_page_string(char*, int, int, Bool*);
74static void rescale(PannerWidget);
75static void reset_shadow_gc(PannerWidget);
76static void reset_slider_gc(PannerWidget);
77static void reset_xor_gc(PannerWidget);
78static void scale_knob(PannerWidget, Bool, Bool);
79
80/*
81 * Actions
82 */
83static void ActionAbort(Widget, XEvent*, String*, Cardinal*);
84static void ActionMove(Widget, XEvent*, String*, Cardinal*);
85static void ActionNotify(Widget, XEvent*, String*, Cardinal*);
86static void ActionPage(Widget, XEvent*, String*, Cardinal*);
87static void ActionSet(Widget, XEvent*, String*, Cardinal*);
88static void ActionStart(Widget, XEvent*, String*, Cardinal*);
89static void ActionStop(Widget, XEvent*, String*, Cardinal*);
90
91/*
92 * From Xmu/Distinct.c
93 */
94Bool XmuDistinguishablePixels(Display*, Colormap, unsigned long*, int);
95
96/*
97 * Initialization
98 */
99static char defaultTranslations[] =
100"<Btn1Down>:"		"start()\n"
101"<Btn1Motion>:"		"move()\n"
102"<Btn1Up>:"		"notify() stop()\n"
103"<Btn2Down>:"		"abort()\n"
104":<Key>KP_Enter:"	"set(rubberband,toggle)\n"
105"<Key>space:"		"page(+1p,+1p)\n"
106"<Key>Delete:"		"page(-1p,-1p)\n"
107":<Key>KP_Delete:"	"page(-1p,-1p)\n"
108"<Key>BackSpace:"	"page(-1p,-1p)\n"
109"<Key>Left:"		"page(-.5p,+0)\n"
110":<Key>KP_Left:"	"page(-.5p,+0)\n"
111"<Key>Right:"		"page(+.5p,+0)\n"
112":<Key>KP_Right:"	"page(+.5p,+0)\n"
113"<Key>Up:"		"page(+0,-.5p)\n"
114":<Key>KP_Up:"		"page(+0,-.5p)\n"
115"<Key>Down:"		"page(+0,+.5p)\n"
116":<Key>KP_Down:"	"page(+0,+.5p)\n"
117"<Key>Home:"		"page(0,0)\n"
118":<Key>KP_Home:"	"page(0,0)\n"
119;
120
121static XtActionsRec actions[] = {
122  {"start",	ActionStart},		/* start tmp graphics */
123  {"stop",	ActionStop},		/* stop tmp graphics */
124  {"abort",	ActionAbort},		/* punt */
125  {"move",	ActionMove},		/* move tmp graphics on Motion event */
126  {"page",	ActionPage},		/* page around usually from keyboard */
127  {"notify",	ActionNotify},		/* callback new position */
128  {"set",	ActionSet},		/* set various parameters */
129};
130
131#define offset(field)	XtOffsetOf(PannerRec, panner.field)
132static XtResource resources[] = {
133    {
134      XtNallowOff,
135      XtCAllowOff,
136      XtRBoolean,
137      sizeof(Boolean),
138      offset(allow_off),
139      XtRImmediate,
140      (XtPointer)False
141    },
142    {
143      XtNresize,
144      XtCResize,
145      XtRBoolean,
146      sizeof(Boolean),
147      offset(resize_to_pref),
148      XtRImmediate,
149      (XtPointer)True
150    },
151    {
152      XtNreportCallback,
153      XtCReportCallback,
154      XtRCallback,
155      sizeof(XtPointer),
156      offset(report_callbacks),
157      XtRCallback,
158      NULL
159    },
160    {
161      XtNdefaultScale,
162      XtCDefaultScale,
163      XtRDimension,
164      sizeof(Dimension),
165      offset(default_scale),
166      XtRImmediate,
167      (XtPointer)PANNER_DEFAULT_SCALE
168    },
169    {
170      XtNrubberBand,
171      XtCRubberBand,
172      XtRBoolean,
173      sizeof(Boolean),
174      offset(rubber_band),
175      XtRImmediate,
176      (XtPointer)False
177    },
178    {
179      XtNforeground,
180      XtCForeground,
181      XtRPixel,
182      sizeof(Pixel),
183      offset(foreground),
184      XtRString,
185      (XtPointer)XtDefaultBackground
186    },
187    {
188      XtNinternalSpace,
189      XtCInternalSpace,
190      XtRDimension,
191      sizeof(Dimension),
192      offset(internal_border),
193      XtRImmediate,
194      (XtPointer)4
195    },
196    {
197      XtNlineWidth,
198      XtCLineWidth,
199      XtRDimension,
200      sizeof(Dimension),
201      offset(line_width),
202      XtRImmediate,
203      (XtPointer)0
204    },
205    {
206      XtNcanvasWidth,
207      XtCCanvasWidth,
208      XtRDimension,
209      sizeof(Dimension),
210      offset(canvas_width),
211      XtRImmediate,
212      (XtPointer)0
213    },
214    {
215      XtNcanvasHeight,
216      XtCCanvasHeight,
217      XtRDimension,
218      sizeof(Dimension),
219      offset(canvas_height),
220      XtRImmediate,
221      (XtPointer)0
222    },
223    {
224      XtNsliderX,
225      XtCSliderX,
226      XtRPosition,
227      sizeof(Position),
228      offset(slider_x),
229      XtRImmediate,
230      (XtPointer)0
231    },
232    {
233      XtNsliderY,
234      XtCSliderY,
235      XtRPosition,
236      sizeof(Position),
237      offset(slider_y),
238      XtRImmediate,
239      (XtPointer)0
240    },
241    {
242      XtNsliderWidth,
243      XtCSliderWidth,
244      XtRDimension,
245      sizeof(Dimension),
246      offset(slider_width),
247      XtRImmediate,
248      (XtPointer)0
249    },
250    {
251      XtNsliderHeight,
252      XtCSliderHeight,
253      XtRDimension,
254      sizeof(Dimension),
255      offset(slider_height),
256      XtRImmediate,
257      (XtPointer)0
258    },
259    {
260      XtNshadowColor,
261      XtCShadowColor,
262      XtRPixel,
263      sizeof(Pixel),
264      offset(shadow_color),
265      XtRString,
266      (XtPointer)XtDefaultForeground
267    },
268    {
269      XtNshadowThickness,
270      XtCShadowThickness,
271      XtRDimension,
272      sizeof(Dimension),
273      offset(shadow_thickness),
274      XtRImmediate,
275      (XtPointer)2
276    },
277    {
278      XtNbackgroundStipple,
279      XtCBackgroundStipple,
280      XtRString,
281      sizeof(String),
282      offset(stipple_name),
283      XtRImmediate,
284      NULL
285    },
286};
287#undef offset
288
289#define Superclass	(&simpleClassRec)
290PannerClassRec pannerClassRec = {
291  /* core */
292  {
293    (WidgetClass)Superclass,		/* superclass */
294    "Panner",				/* class_name */
295    sizeof(PannerRec),			/* widget_size */
296    XawInitializeWidgetSet,		/* class_initialize */
297    NULL,				/* class_part_initialize */
298    False,				/* class_inited */
299    XawPannerInitialize,		/* initialize */
300    NULL,				/* initialize_hook */
301    XawPannerRealize,			/* realize */
302    actions,				/* actions */
303    XtNumber(actions),			/* num_actions */
304    resources,				/* resources */
305    XtNumber(resources),		/* num_resources */
306    NULLQUARK,				/* xrm_class */
307    True,				/* compress_motion */
308    True,				/* compress_exposure */
309    True,				/* compress_enterleave */
310    False,				/* visible_interest */
311    XawPannerDestroy,			/* destroy */
312    XawPannerResize,			/* resize */
313    XawPannerRedisplay,			/* expose */
314    XawPannerSetValues,			/* set_values */
315    NULL,				/* set_values_hook */
316    XawPannerSetValuesAlmost,		/* set_values_almost */
317    NULL,				/* get_values_hook */
318    NULL,				/* accept_focus */
319    XtVersion,				/* version */
320    NULL,				/* callback_private */
321    defaultTranslations,		/* tm_table */
322    XawPannerQueryGeometry,		/* query_geometry */
323    XtInheritDisplayAccelerator,	/* display_accelerator */
324    NULL,				/* extension */
325  },
326  /* simple */
327  {
328    XtInheritChangeSensitive,		/* change_sensitive */
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 = pw->panner.internal_border << 1;
427    Position maxx = (Position)XtWidth(pw) - pad -
428		    (Position)pw->panner.knob_width;
429    Position maxy = (Position)XtHeight(pw) - pad -
430		    (Position)pw->panner.knob_height;
431    Position *x = knob ? &pw->panner.knob_x : &pw->panner.tmp.x;
432    Position *y = knob ? &pw->panner.knob_y : &pw->panner.tmp.y;
433
434    /*
435     * note that positions are already normalized (i.e. internal_border
436     * has been subtracted out)
437     */
438    if (*x < 0)
439	*x = 0;
440    if (*x > maxx)
441	*x = maxx;
442
443    if (*y < 0)
444	*y = 0;
445    if (*y > maxy)
446	*y = maxy;
447
448    if (knob) {
449	pw->panner.slider_x = (Position)((double)pw->panner.knob_x
450					/ pw->panner.haspect + 0.5);
451	pw->panner.slider_y = (Position)((double)pw->panner.knob_y
452					/ pw->panner.vaspect + 0.5);
453	pw->panner.last_x = pw->panner.last_y = PANNER_OUTOFRANGE;
454    }
455}
456
457static void
458move_shadow(PannerWidget pw)
459{
460    if (pw->panner.shadow_thickness > 0) {
461	int lw = pw->panner.shadow_thickness + (pw->panner.line_width << 1);
462	int pad = pw->panner.internal_border;
463
464	if (pw->panner.knob_height > lw && pw->panner.knob_width > lw) {
465	    XRectangle *r = pw->panner.shadow_rects;
466
467	    r->x = pw->panner.knob_x + pad + pw->panner.knob_width;
468	    r->y = pw->panner.knob_y + pad + lw;
469	    r->width = pw->panner.shadow_thickness;
470	    r->height = pw->panner.knob_height - lw;
471	    r++;
472	    r->x = pw->panner.knob_x + pad + lw;
473	    r->y = pw->panner.knob_y + pad + pw->panner.knob_height;
474	    r->width = pw->panner.knob_width - lw + pw->panner.shadow_thickness;
475	    r->height = pw->panner.shadow_thickness;
476	    pw->panner.shadow_valid = True;
477	    return;
478	}
479    }
480    pw->panner.shadow_valid = False;
481}
482
483static void
484scale_knob(PannerWidget pw, Bool location, Bool size)
485{
486    if (location) {
487	pw->panner.knob_x = (Position)PANNER_HSCALE(pw, pw->panner.slider_x);
488	pw->panner.knob_y = (Position)PANNER_VSCALE(pw, pw->panner.slider_y);
489    }
490    if (size) {
491	Dimension width, height;
492
493	if (pw->panner.slider_width < 1)
494	    pw->panner.slider_width = pw->panner.canvas_width;
495	if (pw->panner.slider_height < 1)
496	    pw->panner.slider_height = pw->panner.canvas_height;
497	width = Min(pw->panner.slider_width, pw->panner.canvas_width);
498	height = Min(pw->panner.slider_height, pw->panner.canvas_height);
499
500	pw->panner.knob_width = (Dimension)PANNER_HSCALE(pw, width);
501	pw->panner.knob_height = (Dimension)PANNER_VSCALE(pw, height);
502    }
503    if (!pw->panner.allow_off)
504	check_knob(pw, True);
505    move_shadow(pw);
506}
507
508static void
509rescale(PannerWidget pw)
510{
511    int hpad = pw->panner.internal_border << 1;
512    int vpad = hpad;
513
514    if (pw->panner.canvas_width < 1)
515	pw->panner.canvas_width = XtWidth(pw);
516    if (pw->panner.canvas_height < 1)
517	pw->panner.canvas_height = XtHeight(pw);
518
519    if (XtWidth(pw) <= hpad)
520	hpad = 0;
521    if (XtHeight(pw) <= vpad)
522	vpad = 0;
523
524    pw->panner.haspect = ((double)XtWidth(pw) - hpad + .5)
525			 / (double)pw->panner.canvas_width;
526    pw->panner.vaspect = ((double)XtHeight(pw) - vpad + .5)
527			 / (double)pw->panner.canvas_height;
528    scale_knob(pw, True, True);
529}
530
531static void
532get_default_size(PannerWidget pw, Dimension *wp, Dimension *hp)
533{
534    Dimension pad = pw->panner.internal_border << 1;
535
536    *wp = PANNER_DSCALE(pw, pw->panner.canvas_width) + pad;
537    *hp = PANNER_DSCALE(pw, pw->panner.canvas_height) + pad;
538}
539
540static Bool
541get_event_xy(PannerWidget pw, XEvent *event, int *x, int *y)
542{
543    int pad = pw->panner.internal_border;
544
545    switch (event->type) {
546	case ButtonPress:
547	case ButtonRelease:
548	    *x = event->xbutton.x - pad;
549	    *y = event->xbutton.y - pad;
550	    return (True);
551	case KeyPress:
552	case KeyRelease:
553	    *x = event->xkey.x - pad;
554	    *y = event->xkey.y - pad;
555	    return (True);
556	case EnterNotify:
557	case LeaveNotify:
558	    *x = event->xcrossing.x - pad;
559	    *y = event->xcrossing.y - pad;
560	    return (True);
561	case MotionNotify:
562	    *x = event->xmotion.x - pad;
563	    *y = event->xmotion.y - pad;
564	    return (True);
565    }
566
567    return (False);
568}
569
570static int
571parse_page_string(char *s, int pagesize, int canvassize, Bool *relative)
572{
573    char *cp;
574    double val = 1.0;
575    Bool rel = False;
576
577    /*
578     * syntax:    spaces [+-] number spaces [pc\0] spaces
579     */
580    for (; isascii(*s) && isspace(*s); s++)	/* skip white space */
581	;
582
583    if (*s == '+' || *s == '-')	{		/* deal with signs */
584	rel = True;
585	if (*s == '-')
586	    val = -1.0;
587	s++;
588    }
589    if (!*s) {				/* if null then return nothing */
590	*relative = True;
591	return (0);
592    }
593
594					/* skip over numbers */
595    for (cp = s; isascii(*s) && (isdigit(*s) || *s == '.'); s++)
596	;
597    val *= atof(cp);
598
599					/* skip blanks */
600    for (; isascii(*s) && isspace(*s); s++)
601	;
602
603    if (*s) {				/* if units */
604	switch (s[0]) {
605	    case 'p':
606	    case 'P':
607		val *= (double)pagesize;
608		break;
609	    case 'c':
610	    case 'C':
611		val *= (double)canvassize;
612		break;
613	}
614    }
615    *relative = rel;
616
617    return ((int)val);
618}
619
620#define DRAW_TMP(pw) \
621{ \
622    XDrawRectangle(XtDisplay(pw), XtWindow(pw),				\
623		   pw->panner.xor_gc,					\
624		   pw->panner.tmp.x + pw->panner.internal_border,	\
625		   pw->panner.tmp.y + pw->panner.internal_border,	\
626		   pw->panner.knob_width - 1,				\
627		   pw->panner.knob_height - 1);				\
628    pw->panner.tmp.showing = !pw->panner.tmp.showing;			\
629}
630
631#define UNDRAW_TMP(pw) \
632{ \
633    if (pw->panner.tmp.showing)			\
634      DRAW_TMP(pw);				\
635}
636
637#define BACKGROUND_STIPPLE(pw) \
638XmuLocatePixmapFile(pw->core.screen, pw->panner.stipple_name,		\
639		    pw->panner.shadow_color, pw->core.background_pixel,	\
640		    pw->core.depth, NULL, 0, NULL, NULL, NULL, NULL)
641
642#define PIXMAP_OKAY(pm) ((pm) != None && (pm) != XtUnspecifiedPixmap)
643
644/*ARGSUSED*/
645static void
646XawPannerInitialize(Widget greq, Widget gnew, ArgList args, Cardinal *num_args)
647{
648    PannerWidget req = (PannerWidget)greq, cnew = (PannerWidget)gnew;
649    Dimension defwidth, defheight;
650
651    if (req->panner.canvas_width < 1)
652	cnew->panner.canvas_width = 1;
653    if (req->panner.canvas_height < 1)
654	cnew->panner.canvas_height = 1;
655    if (req->panner.default_scale < 1)
656	cnew->panner.default_scale = PANNER_DEFAULT_SCALE;
657
658    get_default_size(req, &defwidth, &defheight);
659    if (XtWidth(req) < 1)
660	XtWidth(cnew) = defwidth;
661    if (XtHeight(req) < 1)
662	XtHeight(cnew) = defheight;
663
664    cnew->panner.shadow_gc = NULL;
665    reset_shadow_gc(cnew);		/* shadowColor */
666    cnew->panner.slider_gc = NULL;
667    reset_slider_gc(cnew);		/* foreground */
668    cnew->panner.xor_gc = NULL;
669    reset_xor_gc(cnew); 		/* foreground ^ background */
670
671    rescale(cnew);			/* does a position check */
672    cnew->panner.shadow_valid = False;
673    cnew->panner.tmp.doing = False;
674    cnew->panner.tmp.showing = False;
675  }
676
677static void
678XawPannerRealize(Widget gw, XtValueMask *valuemaskp,
679		 XSetWindowAttributes *attr)
680{
681    PannerWidget pw = (PannerWidget)gw;
682    Pixmap pm = XtUnspecifiedPixmap;
683    Bool gotpm = False;
684
685    if (pw->core.background_pixmap == XtUnspecifiedPixmap) {
686	if (pw->panner.stipple_name)
687	    pm = BACKGROUND_STIPPLE(pw);
688
689	if (PIXMAP_OKAY(pm)) {
690	    attr->background_pixmap = pm;
691	    *valuemaskp |= CWBackPixmap;
692	    *valuemaskp &= ~CWBackPixel;
693	    gotpm = True;
694	}
695    }
696    (*pannerWidgetClass->core_class.superclass->core_class.realize)
697	(gw, valuemaskp, attr);
698
699    if (gotpm)
700	XFreePixmap(XtDisplay(gw), pm);
701}
702
703static void
704XawPannerDestroy(Widget gw)
705{
706    PannerWidget pw = (PannerWidget)gw;
707
708    XtReleaseGC(gw, pw->panner.shadow_gc);
709    XtReleaseGC(gw, pw->panner.slider_gc);
710    XtReleaseGC(gw, pw->panner.xor_gc);
711}
712
713static void
714XawPannerResize(Widget gw)
715{
716    rescale((PannerWidget)gw);
717}
718
719static void
720XawPannerRedisplay(Widget gw, XEvent *event, Region region)
721{
722    PannerWidget pw = (PannerWidget)gw;
723    Display *dpy = XtDisplay(gw);
724    Window w = XtWindow(gw);
725    int pad = pw->panner.internal_border;
726    Dimension lw = pw->panner.line_width;
727    Dimension extra = pw->panner.shadow_thickness + (lw << 1);
728    int kx = pw->panner.knob_x + pad, ky = pw->panner.knob_y + pad;
729
730    if (Superclass->core_class.expose)
731	(Superclass->core_class.expose)(gw, event, region);
732
733    pw->panner.tmp.showing = False;
734    XClearArea(XtDisplay(pw), XtWindow(pw),
735	       (int)pw->panner.last_x - ((int)lw) + pad,
736	       (int)pw->panner.last_y - ((int)lw) + pad,
737	       pw->panner.knob_width + extra,
738	       pw->panner.knob_height + extra,
739	       False);
740    pw->panner.last_x = pw->panner.knob_x;
741    pw->panner.last_y = pw->panner.knob_y;
742
743    XFillRectangle(dpy, w, pw->panner.slider_gc, kx, ky,
744		   pw->panner.knob_width - 1, pw->panner.knob_height - 1);
745
746    if (lw)
747	XDrawRectangle(dpy, w, pw->panner.shadow_gc, kx, ky,
748		       pw->panner.knob_width - 1,  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, Widget gnew,
760		   ArgList args, Cardinal *num_args)
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 (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, Cardinal *num_params)
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 = x - pw->panner.knob_x;
885    pw->panner.tmp.dy = 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, Cardinal *num_params)
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 = x - pw->panner.tmp.dx;
901	pw->panner.tmp.y = 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 = x - pw->panner.tmp.dx;
946    pw->panner.tmp.y = 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 = x;
994	pw->panner.tmp.y = 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, String *params, Cardinal *num_params)
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		     (Position)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		     (Position)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, 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