Panner.c revision 421c997b
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(char*, 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  },
327  /* panner */
328  {
329    NULL,				/* extension */
330  }
331};
332
333WidgetClass pannerWidgetClass = (WidgetClass) &pannerClassRec;
334
335
336/*
337 * Implementation
338 */
339static void
340reset_shadow_gc(PannerWidget pw)
341{
342    XtGCMask valuemask = GCForeground;
343    XGCValues values;
344    unsigned long   pixels[3];
345
346    if (pw->panner.shadow_gc)
347	XtReleaseGC((Widget)pw, pw->panner.shadow_gc);
348
349    pixels[0] = pw->panner.foreground;
350    pixels[1] = pw->core.background_pixel;
351    pixels[2] = pw->panner.shadow_color;
352
353    if (!pw->panner.stipple_name &&
354	!XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
355				  pixels, 3) &&
356	XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
357				 pixels, 2)) {
358	valuemask = GCTile | GCFillStyle;
359	values.fill_style = FillTiled;
360	values.tile = XmuCreateStippledPixmap(XtScreen((Widget)pw),
361					      pw->panner.foreground,
362					      pw->core.background_pixel,
363					      pw->core.depth);
364    }
365    else {
366	if (!pw->panner.line_width &&
367	    !XmuDistinguishablePixels(XtDisplay(pw), pw->core.colormap,
368				      pixels, 2))
369	    pw->panner.line_width = 1;
370	valuemask = GCForeground;
371	values.foreground = pw->panner.shadow_color;
372    }
373    if (pw->panner.line_width > 0) {
374	values.line_width = pw->panner.line_width;
375	valuemask |= GCLineWidth;
376    }
377
378    pw->panner.shadow_gc = XtGetGC((Widget)pw, valuemask, &values);
379}
380
381static void
382reset_slider_gc(PannerWidget pw)
383{
384    XtGCMask valuemask = GCForeground;
385    XGCValues values;
386
387    if (pw->panner.slider_gc)
388	XtReleaseGC((Widget)pw, pw->panner.slider_gc);
389
390    values.foreground = pw->panner.foreground;
391
392    pw->panner.slider_gc = XtGetGC((Widget)pw, valuemask, &values);
393}
394
395static void
396reset_xor_gc(PannerWidget pw)
397{
398    if (pw->panner.xor_gc)
399	XtReleaseGC((Widget)pw, pw->panner.xor_gc);
400
401    if (pw->panner.rubber_band) {
402	XtGCMask valuemask = (GCForeground | GCFunction);
403	XGCValues values;
404	Pixel tmp;
405
406	tmp = (pw->panner.foreground == pw->core.background_pixel ?
407	       pw->panner.shadow_color : pw->panner.foreground);
408	values.foreground = tmp ^ pw->core.background_pixel;
409	values.function = GXxor;
410	if (pw->panner.line_width > 0) {
411	    valuemask |= GCLineWidth;
412	    values.line_width = pw->panner.line_width;
413	}
414	pw->panner.xor_gc = XtGetGC((Widget)pw, valuemask, &values);
415    }
416    else
417	pw->panner.xor_gc = NULL;
418}
419
420static void
421check_knob(PannerWidget pw, Bool knob)
422{
423    Position pad = pw->panner.internal_border << 1;
424    Position maxx = (Position)XtWidth(pw) - pad -
425		    (Position)pw->panner.knob_width;
426    Position maxy = (Position)XtHeight(pw) - pad -
427		    (Position)pw->panner.knob_height;
428    Position *x = knob ? &pw->panner.knob_x : &pw->panner.tmp.x;
429    Position *y = knob ? &pw->panner.knob_y : &pw->panner.tmp.y;
430
431    /*
432     * note that positions are already normalized (i.e. internal_border
433     * has been subtracted out)
434     */
435    if (*x < 0)
436	*x = 0;
437    if (*x > maxx)
438	*x = maxx;
439
440    if (*y < 0)
441	*y = 0;
442    if (*y > maxy)
443	*y = maxy;
444
445    if (knob) {
446	pw->panner.slider_x = (Position)((double)pw->panner.knob_x
447					/ pw->panner.haspect + 0.5);
448	pw->panner.slider_y = (Position)((double)pw->panner.knob_y
449					/ pw->panner.vaspect + 0.5);
450	pw->panner.last_x = pw->panner.last_y = PANNER_OUTOFRANGE;
451    }
452}
453
454static void
455move_shadow(PannerWidget pw)
456{
457    if (pw->panner.shadow_thickness > 0) {
458	int lw = pw->panner.shadow_thickness + (pw->panner.line_width << 1);
459	int pad = pw->panner.internal_border;
460
461	if (pw->panner.knob_height > lw && pw->panner.knob_width > lw) {
462	    XRectangle *r = pw->panner.shadow_rects;
463
464	    r->x = pw->panner.knob_x + pad + pw->panner.knob_width;
465	    r->y = pw->panner.knob_y + pad + lw;
466	    r->width = pw->panner.shadow_thickness;
467	    r->height = pw->panner.knob_height - lw;
468	    r++;
469	    r->x = pw->panner.knob_x + pad + lw;
470	    r->y = pw->panner.knob_y + pad + pw->panner.knob_height;
471	    r->width = pw->panner.knob_width - lw + pw->panner.shadow_thickness;
472	    r->height = pw->panner.shadow_thickness;
473	    pw->panner.shadow_valid = True;
474	    return;
475	}
476    }
477    pw->panner.shadow_valid = False;
478}
479
480static void
481scale_knob(PannerWidget pw, Bool location, Bool size)
482{
483    if (location) {
484	pw->panner.knob_x = (Position)PANNER_HSCALE(pw, pw->panner.slider_x);
485	pw->panner.knob_y = (Position)PANNER_VSCALE(pw, pw->panner.slider_y);
486    }
487    if (size) {
488	Dimension width, height;
489
490	if (pw->panner.slider_width < 1)
491	    pw->panner.slider_width = pw->panner.canvas_width;
492	if (pw->panner.slider_height < 1)
493	    pw->panner.slider_height = pw->panner.canvas_height;
494	width = Min(pw->panner.slider_width, pw->panner.canvas_width);
495	height = Min(pw->panner.slider_height, pw->panner.canvas_height);
496
497	pw->panner.knob_width = (Dimension)PANNER_HSCALE(pw, width);
498	pw->panner.knob_height = (Dimension)PANNER_VSCALE(pw, height);
499    }
500    if (!pw->panner.allow_off)
501	check_knob(pw, True);
502    move_shadow(pw);
503}
504
505static void
506rescale(PannerWidget pw)
507{
508    int hpad = pw->panner.internal_border << 1;
509    int vpad = hpad;
510
511    if (pw->panner.canvas_width < 1)
512	pw->panner.canvas_width = XtWidth(pw);
513    if (pw->panner.canvas_height < 1)
514	pw->panner.canvas_height = XtHeight(pw);
515
516    if (XtWidth(pw) <= hpad)
517	hpad = 0;
518    if (XtHeight(pw) <= vpad)
519	vpad = 0;
520
521    pw->panner.haspect = ((double)XtWidth(pw) - hpad + .5)
522			 / (double)pw->panner.canvas_width;
523    pw->panner.vaspect = ((double)XtHeight(pw) - vpad + .5)
524			 / (double)pw->panner.canvas_height;
525    scale_knob(pw, True, True);
526}
527
528static void
529get_default_size(PannerWidget pw, Dimension *wp, Dimension *hp)
530{
531    Dimension pad = pw->panner.internal_border << 1;
532
533    *wp = PANNER_DSCALE(pw, pw->panner.canvas_width) + pad;
534    *hp = PANNER_DSCALE(pw, pw->panner.canvas_height) + pad;
535}
536
537static Bool
538get_event_xy(PannerWidget pw, XEvent *event, int *x, int *y)
539{
540    int pad = pw->panner.internal_border;
541
542    switch (event->type) {
543	case ButtonPress:
544	case ButtonRelease:
545	    *x = event->xbutton.x - pad;
546	    *y = event->xbutton.y - pad;
547	    return (True);
548	case KeyPress:
549	case KeyRelease:
550	    *x = event->xkey.x - pad;
551	    *y = event->xkey.y - pad;
552	    return (True);
553	case EnterNotify:
554	case LeaveNotify:
555	    *x = event->xcrossing.x - pad;
556	    *y = event->xcrossing.y - pad;
557	    return (True);
558	case MotionNotify:
559	    *x = event->xmotion.x - pad;
560	    *y = event->xmotion.y - pad;
561	    return (True);
562    }
563
564    return (False);
565}
566
567static int
568parse_page_string(char *s, int pagesize, int canvassize, Bool *relative)
569{
570    char *cp;
571    double val = 1.0;
572    Bool rel = False;
573
574    /*
575     * syntax:    spaces [+-] number spaces [pc\0] spaces
576     */
577    for (; isascii(*s) && isspace(*s); s++)	/* skip white space */
578	;
579
580    if (*s == '+' || *s == '-')	{		/* deal with signs */
581	rel = True;
582	if (*s == '-')
583	    val = -1.0;
584	s++;
585    }
586    if (!*s) {				/* if null then return nothing */
587	*relative = True;
588	return (0);
589    }
590
591					/* skip over numbers */
592    for (cp = s; isascii(*s) && (isdigit(*s) || *s == '.'); s++)
593	;
594    val *= atof(cp);
595
596					/* skip blanks */
597    for (; isascii(*s) && isspace(*s); s++)
598	;
599
600    if (*s) {				/* if units */
601	switch (s[0]) {
602	    case 'p':
603	    case 'P':
604		val *= (double)pagesize;
605		break;
606	    case 'c':
607	    case 'C':
608		val *= (double)canvassize;
609		break;
610	}
611    }
612    *relative = rel;
613
614    return ((int)val);
615}
616
617#define DRAW_TMP(pw) \
618{ \
619    XDrawRectangle(XtDisplay(pw), XtWindow(pw),				\
620		   pw->panner.xor_gc,					\
621		   pw->panner.tmp.x + pw->panner.internal_border,	\
622		   pw->panner.tmp.y + pw->panner.internal_border,	\
623		   pw->panner.knob_width - 1,				\
624		   pw->panner.knob_height - 1);				\
625    pw->panner.tmp.showing = !pw->panner.tmp.showing;			\
626}
627
628#define UNDRAW_TMP(pw) \
629{ \
630    if (pw->panner.tmp.showing)			\
631      DRAW_TMP(pw);				\
632}
633
634#define BACKGROUND_STIPPLE(pw) \
635XmuLocatePixmapFile(pw->core.screen, pw->panner.stipple_name,		\
636		    pw->panner.shadow_color, pw->core.background_pixel,	\
637		    pw->core.depth, NULL, 0, NULL, NULL, NULL, NULL)
638
639#define PIXMAP_OKAY(pm) ((pm) != None && (pm) != XtUnspecifiedPixmap)
640
641/*ARGSUSED*/
642static void
643XawPannerInitialize(Widget greq, Widget gnew, ArgList args, Cardinal *num_args)
644{
645    PannerWidget req = (PannerWidget)greq, cnew = (PannerWidget)gnew;
646    Dimension defwidth, defheight;
647
648    if (req->panner.canvas_width < 1)
649	cnew->panner.canvas_width = 1;
650    if (req->panner.canvas_height < 1)
651	cnew->panner.canvas_height = 1;
652    if (req->panner.default_scale < 1)
653	cnew->panner.default_scale = PANNER_DEFAULT_SCALE;
654
655    get_default_size(req, &defwidth, &defheight);
656    if (XtWidth(req) < 1)
657	XtWidth(cnew) = defwidth;
658    if (XtHeight(req) < 1)
659	XtHeight(cnew) = defheight;
660
661    cnew->panner.shadow_gc = NULL;
662    reset_shadow_gc(cnew);		/* shadowColor */
663    cnew->panner.slider_gc = NULL;
664    reset_slider_gc(cnew);		/* foreground */
665    cnew->panner.xor_gc = NULL;
666    reset_xor_gc(cnew);			/* foreground ^ background */
667
668    rescale(cnew);			/* does a position check */
669    cnew->panner.shadow_valid = False;
670    cnew->panner.tmp.doing = False;
671    cnew->panner.tmp.showing = False;
672  }
673
674static void
675XawPannerRealize(Widget gw, XtValueMask *valuemaskp,
676		 XSetWindowAttributes *attr)
677{
678    PannerWidget pw = (PannerWidget)gw;
679    Pixmap pm = XtUnspecifiedPixmap;
680    Bool gotpm = False;
681
682    if (pw->core.background_pixmap == XtUnspecifiedPixmap) {
683	if (pw->panner.stipple_name)
684	    pm = BACKGROUND_STIPPLE(pw);
685
686	if (PIXMAP_OKAY(pm)) {
687	    attr->background_pixmap = pm;
688	    *valuemaskp |= CWBackPixmap;
689	    *valuemaskp &= ~CWBackPixel;
690	    gotpm = True;
691	}
692    }
693    (*pannerWidgetClass->core_class.superclass->core_class.realize)
694	(gw, valuemaskp, attr);
695
696    if (gotpm)
697	XFreePixmap(XtDisplay(gw), pm);
698}
699
700static void
701XawPannerDestroy(Widget gw)
702{
703    PannerWidget pw = (PannerWidget)gw;
704
705    XtReleaseGC(gw, pw->panner.shadow_gc);
706    XtReleaseGC(gw, pw->panner.slider_gc);
707    XtReleaseGC(gw, pw->panner.xor_gc);
708}
709
710static void
711XawPannerResize(Widget gw)
712{
713    rescale((PannerWidget)gw);
714}
715
716static void
717XawPannerRedisplay(Widget gw, XEvent *event, Region region)
718{
719    PannerWidget pw = (PannerWidget)gw;
720    Display *dpy = XtDisplay(gw);
721    Window w = XtWindow(gw);
722    int pad = pw->panner.internal_border;
723    Dimension lw = pw->panner.line_width;
724    Dimension extra = pw->panner.shadow_thickness + (lw << 1);
725    int kx = pw->panner.knob_x + pad, ky = pw->panner.knob_y + pad;
726
727    if (Superclass->core_class.expose)
728	(Superclass->core_class.expose)(gw, event, region);
729
730    pw->panner.tmp.showing = False;
731    XClearArea(XtDisplay(pw), XtWindow(pw),
732	       (int)pw->panner.last_x - ((int)lw) + pad,
733	       (int)pw->panner.last_y - ((int)lw) + pad,
734	       pw->panner.knob_width + extra,
735	       pw->panner.knob_height + extra,
736	       False);
737    pw->panner.last_x = pw->panner.knob_x;
738    pw->panner.last_y = pw->panner.knob_y;
739
740    XFillRectangle(dpy, w, pw->panner.slider_gc, kx, ky,
741		   pw->panner.knob_width - 1, pw->panner.knob_height - 1);
742
743    if (lw)
744	XDrawRectangle(dpy, w, pw->panner.shadow_gc, kx, ky,
745		       pw->panner.knob_width - 1,  pw->panner.knob_height - 1);
746
747    if (pw->panner.shadow_valid)
748	XFillRectangles(dpy, w, pw->panner.shadow_gc, pw->panner.shadow_rects, 2);
749
750    if (pw->panner.tmp.doing && pw->panner.rubber_band)
751	DRAW_TMP(pw);
752}
753
754/*ARGSUSED*/
755static Boolean
756XawPannerSetValues(Widget gcur, Widget greq, Widget gnew,
757		   ArgList args, Cardinal *num_args)
758{
759    PannerWidget cur = (PannerWidget)gcur;
760    PannerWidget cnew = (PannerWidget)gnew;
761    Bool redisplay = False;
762
763    if (cur->panner.foreground != cnew->panner.foreground) {
764	reset_slider_gc(cnew);
765	if (cur->panner.foreground != cur->core.background_pixel)
766	    reset_xor_gc(cnew);
767	redisplay = True;
768    }
769    else if (cur->panner.line_width != cnew->panner.line_width ||
770	     cur->core.background_pixel != cnew->core.background_pixel) {
771	reset_xor_gc(cnew);
772	redisplay = True;
773    }
774    if (cur->panner.shadow_color != cnew->panner.shadow_color) {
775	reset_shadow_gc(cnew);
776	if (cur->panner.foreground == cur->core.background_pixel)
777	    reset_xor_gc(cnew);
778	redisplay = True;
779    }
780    if (cur->panner.shadow_thickness != cnew->panner.shadow_thickness) {
781	move_shadow(cnew);
782	redisplay = True;
783    }
784    if (cur->panner.rubber_band != cnew->panner.rubber_band) {
785	reset_xor_gc(cnew);
786	if (cnew->panner.tmp.doing)
787	    redisplay = True;
788    }
789
790    if ((cur->panner.stipple_name != cnew->panner.stipple_name
791	 || cur->panner.shadow_color != cnew->panner.shadow_color
792	 || cur->core.background_pixel != cnew->core.background_pixel)
793	&& XtIsRealized(gnew)) {
794	Pixmap pm = cnew->panner.stipple_name ?
795			BACKGROUND_STIPPLE(cnew) : XtUnspecifiedPixmap;
796
797	if (PIXMAP_OKAY(pm)) {
798	    XSetWindowBackgroundPixmap(XtDisplay(cnew), XtWindow(cnew), pm);
799	    XFreePixmap(XtDisplay(cnew), pm);
800	}
801	else
802	    XSetWindowBackground(XtDisplay(cnew), XtWindow(cnew),
803				 cnew->core.background_pixel);
804
805	redisplay = True;
806    }
807
808    if (cnew->panner.resize_to_pref &&
809	(cur->panner.canvas_width != cnew->panner.canvas_width
810	 || cur->panner.canvas_height != cnew->panner.canvas_height
811	 || cur->panner.resize_to_pref != cnew->panner.resize_to_pref)) {
812	get_default_size(cnew, &cnew->core.width, &cnew->core.height);
813	redisplay = True;
814    }
815    else if (cur->panner.canvas_width != cnew->panner.canvas_width
816	     || cur->panner.canvas_height != cnew->panner.canvas_height
817	     || cur->panner.internal_border != cnew->panner.internal_border) {
818	rescale(cnew);			/* does a scale_knob as well */
819	redisplay = True;
820    }
821    else {
822	Bool loc = cur->panner.slider_x != cnew->panner.slider_x ||
823		   cur->panner.slider_y != cnew->panner.slider_y;
824	Bool siz = cur->panner.slider_width != cnew->panner.slider_width ||
825		   cur->panner.slider_height != cnew->panner.slider_height;
826	if (loc || siz || (cur->panner.allow_off != cnew->panner.allow_off
827			   && cnew->panner.allow_off)) {
828	    scale_knob(cnew, loc, siz);
829	    redisplay = True;
830	}
831    }
832
833    return (redisplay);
834}
835
836static void
837XawPannerSetValuesAlmost(Widget gold, Widget gnew, XtWidgetGeometry *req,
838			 XtWidgetGeometry *reply)
839{
840    if (reply->request_mode == 0)	/* got turned down, so cope */
841	XawPannerResize(gnew);
842
843    (*pannerWidgetClass->core_class.superclass->core_class.set_values_almost)
844	(gold, gnew, req, reply);
845}
846
847static XtGeometryResult
848XawPannerQueryGeometry(Widget gw, XtWidgetGeometry *intended,
849		       XtWidgetGeometry *pref)
850{
851    PannerWidget pw = (PannerWidget)gw;
852
853    pref->request_mode = (CWWidth | CWHeight);
854    get_default_size(pw, &pref->width, &pref->height);
855
856    if (((intended->request_mode & (CWWidth | CWHeight)) == (CWWidth | CWHeight))
857	&& intended->width == pref->width && intended->height == pref->height)
858	return (XtGeometryYes);
859    else if (pref->width == XtWidth(pw) && pref->height == XtHeight(pw))
860	return (XtGeometryNo);
861
862    return (XtGeometryAlmost);
863}
864
865
866/*ARGSUSED*/
867static void
868ActionStart(Widget gw, XEvent *event, String *params, Cardinal *num_params)
869{
870    PannerWidget pw = (PannerWidget)gw;
871    int x, y;
872
873    if (!get_event_xy(pw, event, &x, &y)) {
874	XBell(XtDisplay(gw), 0);
875	return;
876    }
877
878    pw->panner.tmp.doing = True;
879    pw->panner.tmp.startx = pw->panner.knob_x;
880    pw->panner.tmp.starty = pw->panner.knob_y;
881    pw->panner.tmp.dx = x - pw->panner.knob_x;
882    pw->panner.tmp.dy = y - pw->panner.knob_y;
883    pw->panner.tmp.x = pw->panner.knob_x;
884    pw->panner.tmp.y = pw->panner.knob_y;
885    if (pw->panner.rubber_band)
886	DRAW_TMP(pw);
887}
888
889/*ARGSUSED*/
890static void
891ActionStop(Widget gw, XEvent *event, String *params, Cardinal *num_params)
892{
893    PannerWidget pw = (PannerWidget)gw;
894    int x, y;
895
896    if (get_event_xy(pw, event, &x, &y)) {
897	pw->panner.tmp.x = x - pw->panner.tmp.dx;
898	pw->panner.tmp.y = y - pw->panner.tmp.dy;
899	if (!pw->panner.allow_off)
900	    check_knob(pw, False);
901    }
902    if (pw->panner.rubber_band)
903	DRAW_TMP(pw);
904    pw->panner.tmp.doing = False;
905}
906
907static void
908ActionAbort(Widget gw, XEvent *event, String *params, Cardinal *num_params)
909{
910    PannerWidget pw = (PannerWidget)gw;
911
912    if (!pw->panner.tmp.doing)
913	return;
914
915    if (pw->panner.rubber_band)
916	UNDRAW_TMP(pw);
917
918    if (!pw->panner.rubber_band) {		/* restore old position */
919	pw->panner.tmp.x = pw->panner.tmp.startx;
920	pw->panner.tmp.y = pw->panner.tmp.starty;
921	ActionNotify(gw, event, params, num_params);
922    }
923    pw->panner.tmp.doing = False;
924}
925
926static void
927ActionMove(Widget gw, XEvent *event, String *params, Cardinal *num_params)
928{
929    PannerWidget pw = (PannerWidget)gw;
930    int x, y;
931
932    if (!pw->panner.tmp.doing)
933      return;
934
935    if (!get_event_xy(pw, event, &x, &y)) {
936	XBell(XtDisplay(gw), 0);	/* should do error message */
937	return;
938    }
939
940    if (pw->panner.rubber_band)
941	UNDRAW_TMP(pw);
942    pw->panner.tmp.x = x - pw->panner.tmp.dx;
943    pw->panner.tmp.y = y - pw->panner.tmp.dy;
944
945    if (!pw->panner.rubber_band)
946	ActionNotify(gw, event, params, num_params);
947    else {
948	if (!pw->panner.allow_off)
949	    check_knob(pw, False);
950	DRAW_TMP(pw);
951    }
952}
953
954
955static void
956ActionPage(Widget gw, XEvent *event, String *params, Cardinal *num_params)
957{
958    PannerWidget pw = (PannerWidget)gw;
959    Cardinal zero = 0;
960    Bool isin = pw->panner.tmp.doing;
961    int x, y;
962    Bool relx, rely;
963    int pad = pw->panner.internal_border << 1;
964
965    if (*num_params != 2) {
966      XBell(XtDisplay(gw), 0);
967	return;
968    }
969
970    x = parse_page_string(params[0], pw->panner.knob_width,
971			  (int)XtWidth(pw) - pad, &relx);
972    y = parse_page_string(params[1], pw->panner.knob_height,
973			  (int)XtHeight(pw) - pad, &rely);
974
975    if (relx)
976	x += pw->panner.knob_x;
977    if (rely)
978	y += pw->panner.knob_y;
979
980    if (isin) {				/* if in, then use move */
981	XEvent ev;
982
983	ev.xbutton.type = ButtonPress;
984	ev.xbutton.x = x;
985	ev.xbutton.y = y;
986	ActionMove(gw, &ev, NULL, &zero);
987    }
988    else {
989	pw->panner.tmp.doing = True;
990	pw->panner.tmp.x = x;
991	pw->panner.tmp.y = y;
992	ActionNotify(gw, event, NULL, &zero);
993	pw->panner.tmp.doing = False;
994    }
995}
996
997/*ARGSUSED*/
998static void
999ActionNotify(Widget gw, XEvent *event, String *params, Cardinal *num_params)
1000{
1001    PannerWidget pw = (PannerWidget)gw;
1002
1003    if (!pw->panner.tmp.doing)
1004	return;
1005
1006    if (!pw->panner.allow_off)
1007	check_knob(pw, False);
1008    pw->panner.knob_x = pw->panner.tmp.x;
1009    pw->panner.knob_y = pw->panner.tmp.y;
1010    move_shadow(pw);
1011
1012    pw->panner.slider_x = (Position)((double)pw->panner.knob_x
1013				   / pw->panner.haspect + 0.5);
1014    pw->panner.slider_y = (Position)((double) pw->panner.knob_y
1015				   / pw->panner.vaspect + 0.5);
1016    if (!pw->panner.allow_off) {
1017	Position tmp;
1018
1019	if (pw->panner.slider_x
1020	    > (tmp = (Position)pw->panner.canvas_width -
1021		     (Position)pw->panner.slider_width))
1022	    pw->panner.slider_x = tmp;
1023	if (pw->panner.slider_x < 0)
1024	    pw->panner.slider_x = 0;
1025	if (pw->panner.slider_y
1026	    > (tmp = (Position)pw->panner.canvas_height -
1027		     (Position)pw->panner.slider_height))
1028	    pw->panner.slider_y = tmp;
1029	if (pw->panner.slider_y < 0)
1030	    pw->panner.slider_y = 0;
1031    }
1032
1033    if (pw->panner.last_x != pw->panner.knob_x ||
1034	pw->panner.last_y != pw->panner.knob_y) {
1035	XawPannerReport rep;
1036
1037	XawPannerRedisplay(gw, NULL, NULL);
1038	rep.changed = XawPRSliderX | XawPRSliderY;
1039	rep.slider_x = pw->panner.slider_x;
1040	rep.slider_y = pw->panner.slider_y;
1041	rep.slider_width = pw->panner.slider_width;
1042	rep.slider_height = pw->panner.slider_height;
1043	rep.canvas_width = pw->panner.canvas_width;
1044	rep.canvas_height = pw->panner.canvas_height;
1045	XtCallCallbackList(gw, pw->panner.report_callbacks, (XtPointer)&rep);
1046    }
1047}
1048
1049/*ARGSUSED*/
1050static void
1051ActionSet(Widget gw, XEvent *event, String *params, Cardinal *num_params)
1052{
1053    PannerWidget pw = (PannerWidget)gw;
1054    Bool rb;
1055
1056    if (*num_params < 2 ||
1057	XmuCompareISOLatin1(params[0], "rubberband") != 0) {
1058	XBell(XtDisplay(gw), 0);
1059	return;
1060    }
1061
1062    if (XmuCompareISOLatin1(params[1], "on") == 0)
1063	rb = True;
1064    else if (XmuCompareISOLatin1(params[1], "off") == 0)
1065	rb = False;
1066    else if (XmuCompareISOLatin1(params[1], "toggle") == 0)
1067	rb = !pw->panner.rubber_band;
1068    else {
1069      XBell(XtDisplay(gw), 0);
1070	return;
1071    }
1072
1073    if (rb != pw->panner.rubber_band) {
1074	Arg args[1];
1075
1076	XtSetArg(args[0], XtNrubberBand, rb);
1077	XtSetValues(gw, args, 1);
1078    }
1079}
1080