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