1 /* MwSlider.c -- Slider widget
2 *
3 * Author: Edward A. Falk
4 * falk@falconer.vip.best.com
5 *
6 * Date: 2 Nov 1999
7 *
8 * Copyright 1999 by Edward A. Falk. This widget may be used freely in any
9 * software. Source code is freely distributable provided that my name
10 * is not removed from it.
11 *
12 * The X Consortium, and any party obtaining a copy of these files
13 * from the X Consortium, directly or indirectly, is granted, free of
14 * charge, a full and unrestricted irrevocable, world-wide, paid up,
15 * royalty-free, nonexclusive right and license to deal in this
16 * software and documentation files (the "Software"), including
17 * without limitation the rights to use, copy, modify, merge, publish,
18 * distribute, sublicense, and sell copies of the Software, and to
19 * permit persons who receive copies from any such party to do so.
20 *
21 *
22 *
23 * This widget presents the user with a graphical slider which the
24 * user may manipulate to set a numeric value.
25 *
26 * $Log: Slider.c,v $
27 * Revision 1.7 2000/01/19 01:46:09 falk
28 * Fixed resources: input scale is short, not int.
29 *
30 * Revision 1.6 2000/01/18 20:39:24 falk
31 * _SliderThumb() utility added. Other code simplified accordingly.
32 *
33 * Revision 1.5 2000/01/17 20:54:21 falk
34 * default now horizontal. PreferredSize now a class method. DrawThumb
35 * now centers properly.
36 *
37 * Revision 1.4 2000/01/14 19:04:39 falk
38 * fixed polarity of scrollwheel mouse interactions
39 *
40 * Revision 1.3 2000/01/11 19:39:36 falk
41 * Added foreground resource. Added keyboard focus.
42 *
43 * Revision 1.2 2000/01/11 18:26:28 falk
44 * Recomputes slider position after resize. Added "newvalue" argument
45 * to XawSliderSetValue
46 *
47 * Revision 1.1 1999/11/04 17:22:10 falk
48 * Initial revision
49 *
50 *
51 */
52
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <ctype.h>
56
57 #include <X11/Xlib.h>
58 #include <X11/IntrinsicP.h>
59 #include <X11/StringDefs.h>
60 #include <X11/Xmu/Drawing.h>
61 #include <X11/Xmu/Misc.h>
62 #include <X11/Xmu/CharSet.h>
63
64 #include "MwSliderP.h"
65 #include "MwGcs.h"
66
67 #define DEF_WID 100
68 #define DEF_HGT 10
69 #define THUMB_WID 20 /* default thumb width */
70 #define THUMB_HGT 10 /* default thumb height */
71
72
73 /****************************************************************
74 *
75 * MwSlider Resources
76 *
77 ****************************************************************/
78
79 #define offset(field) XtOffsetOf(MwSliderRec, slider.field)
80 static XtResource resources[] = {
81 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
82 offset(font), XtRString, XtDefaultFont},
83 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
84 offset(foreground), XtRString, XtDefaultForeground},
85 {XtNminimum, XtCMinimum, XtRInt, sizeof(int),
86 offset(minimum), XtRImmediate, (XtPointer)0},
87 {XtNmaximum, XtCMaximum, XtRInt, sizeof(int),
88 offset(maximum), XtRImmediate, (XtPointer)100},
89 {XtNvalue, XtCValue, XtRInt, sizeof(int),
90 offset(value), XtRImmediate, (XtPointer)0},
91 {XtNstep, XtCStep, XtRInt, sizeof(int),
92 offset(step), XtRImmediate, (XtPointer)-1},
93 {XtNstep2, XtCStep2, XtRInt, sizeof(int),
94 offset(step2), XtRImmediate, (XtPointer)1},
95 {XtNorientation, XtCOrientation, XtROrientation, sizeof(XtOrientation),
96 offset(orientation), XtRImmediate, (XtPointer) XtorientHorizontal},
97 {XtNshowValue, XtCShowValue, XtRBoolean, sizeof(Boolean),
98 offset(showValue), XtRImmediate, (XtPointer) False},
99 {XtNdecimalPoints, XtCDecimalPoints, XtRShort, sizeof(short),
100 offset(decimalPoints), XtRImmediate, (XtPointer)0},
101 {XtNinputScale, XtCInputScale, XtRShort, sizeof(short),
102 offset(inputScale), XtRImmediate, (XtPointer)100},
103 {XtNautoScale, XtCAutoScale, XtRBoolean, sizeof(Boolean),
104 offset(autoScale), XtRImmediate, (XtPointer) True},
105 {XtNthumbLength, XtCThumbLength, XtRShort, sizeof(short),
106 offset(thumbLength), XtRImmediate, (XtPointer) THUMB_WID},
107 {XtNdragCallback, XtCDragCallback, XtRCallback, sizeof(XtPointer),
108 offset(dragCallback), XtRCallback, NULL},
109 {XtNvalueChangedCallback, XtCValueChangedCallback,
110 XtRCallback, sizeof(XtPointer),
111 offset(valueChangedCallback), XtRCallback, NULL},
112
113 {XtNbeNiceToColormap, XtCBeNiceToColormap, XtRBoolean, sizeof(Boolean),
114 offset(be_nice_to_cmap), XtRImmediate, (XtPointer) False},
115 {XtNshadowWidth, XtCShadowWidth, XtRShort, sizeof(short),
116 offset(shadowWidth), XtRImmediate, (XtPointer) 1},
117 {XtNtopShadowContrast, XtCTopShadowContrast, XtRShort, sizeof(short),
118 offset(top_contrast), XtRImmediate, (XtPointer) 20},
119 {XtNbottomShadowContrast, XtCBottomShadowContrast, XtRShort, sizeof(short),
120 offset(bot_contrast), XtRImmediate, (XtPointer) 40},
121 {XtNarmShadowContrast, XtCArmShadowContrast, XtRShort, sizeof(short),
122 offset(arm_contrast), XtRImmediate, (XtPointer) 20},
123 {XtNinsensitiveContrast, XtCInsensitiveContrast, XtRShort, sizeof(short),
124 offset(insensitive_contrast), XtRImmediate, (XtPointer) 33},
125 };
126 #undef offset
127
128
129
130
131
132
133 /* FORWARD REFERENCES: */
134
135 #ifdef NeedFunctionPrototypes
136
137 /* member functions */
138
139 static void SliderClassInit() ;
140 static void SliderClassPartInit(WidgetClass wc) ;
141 static void SliderInit( Widget req, Widget new, ArgList, Cardinal *nargs) ;
142 static void SliderRealize(Widget, Mask *, XSetWindowAttributes *) ;
143 static void SliderDestroy( Widget w) ;
144 static void SliderResize( Widget w) ;
145 static void SliderExpose( Widget w, XEvent *event, Region region) ;
146 static Boolean SliderSetValues(Widget, Widget, Widget, ArgList, Cardinal *) ;
147 static XtGeometryResult
148 SliderQueryGeometry( Widget, XtWidgetGeometry *, XtWidgetGeometry *) ;
149 static Boolean SliderAcceptFocus(Widget, Time *) ;
150 static void SliderDrawBorder(MwSliderWidget) ;
151 static void SliderDrawBackground(MwSliderWidget, int, int, int, int) ;
152 static void SliderDrawThumb(MwSliderWidget) ;
153 static void SliderMoveThumb(MwSliderWidget sw, int newpos) ;
154 static void SliderFocusProc(MwSliderWidget sw) ;
155
156 /* action procs */
157
158 static void StartAdjust(Widget, XEvent *, String *, Cardinal *) ;
159 static void HandleThumb(Widget, XEvent *, String *, Cardinal *) ;
160 static void EndAdjust(Widget, XEvent *, String *, Cardinal *) ;
161 static void Adjust(Widget, XEvent *, String *, Cardinal *) ;
162 static void Notify(Widget, XEvent *, String *, Cardinal *) ;
163 static void Focus(Widget, XEvent *, String *, Cardinal *) ;
164
165
166 /* internal privates */
167
168 static void ChangeSliderValue(MwSliderWidget sw, int value) ;
169
170 static void SliderPreferredSize( MwSliderWidget,
171 Dimension *wid, Dimension *hgt,
172 Dimension *min_wid, Dimension *min_hgt) ;
173
174 static void SliderAllocGCs(MwSliderWidget) ;
175 static void SliderFreeGCs(MwSliderWidget) ;
176
177 #else
178
179 static void SliderClassInit() ;
180 static void SliderClassPartInit() ;
181 static void SliderInit() ;
182 static void SliderRealize() ;
183 static void SliderDestroy() ;
184 static void SliderResize() ;
185 static void SliderExpose() ;
186 static Boolean SliderSetValues() ;
187 static XtGeometryResult SliderQueryGeometry() ;
188 static Boolean SliderAcceptFocus() ;
189 static void SliderDrawBorder() ;
190 static void SliderDrawBackground() ;
191 static void SliderDrawThumb() ;
192 static void SliderMoveThumb() ;
193 static void SliderFocusProc() ;
194 static void StartAdjust() ;
195 static void HandleThumb() ;
196 static void EndAdjust() ;
197 static void Adjust() ;
198 static void Notify() ;
199 static void Focus() ;
200 static void ChangeSliderValue() ;
201 static void SliderPreferredSize() ;
202 static void SliderAllocGCs() ;
203 static void SliderFreeGCs() ;
204
205 #endif
206
207
208 static char defaultTranslations[] = "\
209 <FocusIn>: Focus(1) \n\
210 <FocusOut>: Focus(0) \n\
211 <Btn1Down>: StartAdjust() \n\
212 <Btn1Motion>: HandleThumb() \n\
213 <Btn1Up>: EndAdjust() Notify() \n\
214 <Btn4Down>: Adjust(step2) Notify() \n\
215 <Btn5Down>: Adjust(-step2) Notify() \n\
216 <Key>Left: Adjust(-step2) Notify() \n\
217 <Key>KP_Left: Adjust(-step2) Notify() \n\
218 <Key>Right: Adjust(step2) Notify() \n\
219 <Key>KP_Right: Adjust(step2) Notify() \n\
220 <Key>Down: Adjust(-step2) Notify() \n\
221 <Key>KP_Down: Adjust(-step2) Notify() \n\
222 <Key>Up: Adjust(step2) Notify() \n\
223 <Key>KP_Up: Adjust(step2) Notify() \n\
224 <Key>Page_Up: Adjust(step) Notify() \n\
225 <Key>KP_Page_Up: Adjust(step) Notify() \n\
226 <Key>Page_Down: Adjust(-step) Notify() \n\
227 <Key>KP_Page_Down: Adjust(-step) Notify() \n\
228 <Key>Home: Adjust(home) Notify() \n\
229 <Key>KP_Home: Adjust(home) Notify() \n\
230 <Key>End: Adjust(end) Notify() \n\
231 <Key>KP_End: Adjust(end) Notify() \n\
232 " ;
233
234
235 static XtActionsRec actionsList[] =
236 {
237 {"StartAdjust", StartAdjust},
238 {"HandleThumb", HandleThumb},
239 {"EndAdjust", EndAdjust},
240 {"Adjust", Adjust},
241 {"Notify", Notify},
242 {"Focus", Focus},
243 } ;
244
245
246
247 /****************************************************************
248 *
249 * Full class record constant
250 *
251 ****************************************************************/
252
253 #define SuperClass (&coreClassRec)
254
255 MwSliderClassRec mwSliderClassRec = {
256 {
257 /* core_class fields */
258 /* superclass */ (WidgetClass) SuperClass,
259 /* class_name */ "MwSlider",
260 /* widget_size */ sizeof(MwSliderRec),
261 /* class_initialize */ SliderClassInit,
262 /* class_part_init */ SliderClassPartInit,
263 /* class_inited */ FALSE,
264 /* initialize */ SliderInit,
265 /* initialize_hook */ NULL,
266 /* realize */ SliderRealize,
267 /* actions */ actionsList,
268 /* num_actions */ XtNumber(actionsList),
269 /* resources */ resources,
270 /* num_resources */ XtNumber(resources),
271 /* xrm_class */ NULLQUARK,
272 /* compress_motion */ TRUE,
273 /* compress_exposure */ XtExposeCompressMaximal|
274 XtExposeGraphicsExpose|
275 XtExposeGraphicsExposeMerged|
276 XtExposeNoRegion,
277 /* compress_enterleave*/ TRUE,
278 /* visible_interest */ FALSE,
279 /* destroy */ SliderDestroy,
280 /* resize */ SliderResize,
281 /* expose */ SliderExpose,
282 /* set_values */ SliderSetValues,
283 /* set_values_hook */ NULL,
284 /* set_values_almost */ XtInheritSetValuesAlmost,
285 /* get_values_hook */ NULL,
286 /* accept_focus */ SliderAcceptFocus,
287 /* version */ XtVersion,
288 /* callback_private */ NULL,
289 /* tm_table */ defaultTranslations,
290 /* query_geometry */ SliderQueryGeometry,
291 /* display_accelerator*/ XtInheritDisplayAccelerator,
292 /* extension */ NULL
293 },
294 {
295 /* Slider class fields */
296 /* draw_border */ SliderDrawBorder,
297 /* draw_background */ SliderDrawBackground,
298 /* draw_thumb */ SliderDrawThumb,
299 /* move_thumb */ SliderMoveThumb,
300 /* focus_proc */ SliderFocusProc,
301 /* preferred_size */ SliderPreferredSize,
302 /* extension */ NULL,
303 }
304 };
305
306 WidgetClass mwSliderWidgetClass = (WidgetClass)&mwSliderClassRec;
307
308
309
310 #ifdef DEBUG
311 #ifdef __STDC__
312 #define assert(e) \
313 if(!(e)) fprintf(stderr,"yak! %s at %s:%d\n",#e,__FILE__,__LINE__)
314 #else
315 #define assert(e) \
316 if(!(e)) fprintf(stderr,"yak! e at %s:%d\n",__FILE__,__LINE__)
317 #endif
318 #else
319 #define assert(e)
320 #endif
321
322
323
324
325 /****************************************************************
326 *
327 * Member Procedures
328 *
329 ****************************************************************/
330
331 static void
SliderClassInit(void)332 SliderClassInit(void)
333 {
334 /* register converters */
335
336 XtAddConverter( XtRString, XtROrientation, XmuCvtStringToOrientation,
337 NULL, 0) ;
338 }
339
340
341 static void
SliderClassPartInit(WidgetClass wc)342 SliderClassPartInit(WidgetClass wc)
343 {
344 MwSliderWidgetClass sc = (MwSliderWidgetClass)wc ;
345 MwSliderWidgetClass super = (MwSliderWidgetClass)wc->core_class.superclass;
346
347 if( sc->slider_class.draw_border == XtInheritDrawBorder )
348 sc->slider_class.draw_border = super->slider_class.draw_border ;
349
350 if( sc->slider_class.draw_background == XtInheritDrawBackground )
351 sc->slider_class.draw_background = super->slider_class.draw_background ;
352
353 if( sc->slider_class.draw_thumb == XtInheritDrawThumb )
354 sc->slider_class.draw_thumb = super->slider_class.draw_thumb ;
355
356 if( sc->slider_class.move_thumb == XtInheritMoveThumb )
357 sc->slider_class.move_thumb = super->slider_class.move_thumb ;
358
359 if( sc->slider_class.focus_proc == XtInheritFocusProc )
360 sc->slider_class.focus_proc = super->slider_class.focus_proc ;
361
362 if( sc->slider_class.preferred_size == XtInheritPreferredSize )
363 sc->slider_class.preferred_size = super->slider_class.preferred_size ;
364 }
365
366
367
368 /* Init a newly created slider widget. */
369
370
371 static void
SliderInit(Widget request,Widget new,ArgList args,Cardinal * num_args)372 SliderInit(Widget request, Widget new, ArgList args, Cardinal *num_args)
373 {
374 MwSliderWidget sw = (MwSliderWidget)new;
375 int minimum = sw->slider.minimum ;
376 int maximum = sw->slider.maximum ;
377
378 /* defer GC allocation, etc., until Realize() time. */
379 sw->slider.foregroundGC =
380 sw->slider.backgroundGC =
381 sw->slider.greyGC =
382 sw->slider.topGC =
383 sw->slider.botGC = None ;
384 sw->slider.grey50 = None ;
385 if( maximum != minimum )
386 sw->slider.thumbpos =
387 sw->slider.length*(sw->slider.value-minimum)/(maximum-minimum) ;
388 else
389 sw->slider.thumbpos = 0 ;
390
391 if( sw->slider.step < 0 )
392 sw->slider.step = (sw->slider.maximum - sw->slider.minimum)/10 ;
393
394 sw->slider.adjusting = False ;
395
396
397 /* if size not explicitly set, set it to our preferred size now. */
398
399 if( request->core.width == 0 || request->core.height == 0 )
400 {
401 Dimension w,h, dummy ;
402 SliderClass(sw)->slider_class.preferred_size(sw, &w,&h, &dummy,&dummy);
403 if( request->core.width == 0 ) new->core.width = w ;
404 if( request->core.height == 0 ) new->core.height = h ;
405 XtClass(new)->core_class.resize(new) ;
406 }
407
408 sw->slider.needs_layout = True ;
409 }
410
411
412
413
414
415 /* Called when slider widget first realized. Create the window
416 * and allocate the GCs
417 */
418
419 static void
SliderRealize(Widget w,Mask * valueMask,XSetWindowAttributes * attributes)420 SliderRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
421 {
422 MwSliderWidget sw = (MwSliderWidget) w;
423
424 /* chain upwards */
425 SuperClass->core_class.realize(w, valueMask, attributes);
426
427 SliderAllocGCs(sw) ;
428 }
429
430
431
432 static void
SliderDestroy(Widget w)433 SliderDestroy(Widget w)
434 {
435 SliderFreeGCs((MwSliderWidget)w) ;
436 }
437
438
439 /* Parent has resized us. This will require that the slider be
440 * laid out again.
441 */
442
443 static void
SliderResize(Widget w)444 SliderResize(Widget w)
445 {
446 MwSliderWidget sw = (MwSliderWidget) w;
447 int length ;
448 int s = sw->slider.shadowWidth ;
449 int minimum, maximum ;
450
451 /* compute working length of slider */
452
453 sw->slider.start = sw->slider.shadowWidth ;
454
455 if( sw->slider.orientation == XtorientHorizontal ) {
456 length = sw->core.width ;
457 sw->slider.thumbWidth = sw->core.height - s*2 ;
458 }
459 else {
460 length = sw->core.height ;
461 sw->slider.thumbWidth = sw->core.width - s*2 ;
462 }
463
464 length -= + sw->slider.thumbLength + s*2 ;
465 if( length < 0 ) length = 0 ;
466 sw->slider.length = length ;
467
468 /* TODO: shrink thumb if it will make things fit? */
469
470 /* recompute thumb position */
471 minimum = sw->slider.minimum ;
472 maximum = sw->slider.maximum ;
473 if( maximum != minimum )
474 sw->slider.thumbpos =
475 length*(sw->slider.value-minimum)/(maximum-minimum) ;
476 else
477 sw->slider.thumbpos = 0 ;
478
479 if( sw->slider.autoScale )
480 sw->slider.inputScale = length < 100 ? length : 100 ;
481
482 sw->slider.needs_layout = False ;
483 }
484
485
486
487 /* Redraw entire Slider widget */
488
489 static void
SliderExpose(Widget w,XEvent * event,Region region)490 SliderExpose(Widget w, XEvent *event, Region region)
491 {
492 MwSliderWidget sw = (MwSliderWidget) w;
493
494 if( sw->slider.needs_layout )
495 XtClass(w)->core_class.resize(w) ;
496
497 SliderClass(sw)->slider_class.draw_border(sw) ;
498 SliderClass(sw)->slider_class.draw_background(sw,
499 0,0, w->core.width, w->core.height) ;
500 SliderClass(sw)->slider_class.draw_thumb(sw) ;
501 }
502
503
504 /* Called when any Slider widget resources are changed. */
505
506 static Boolean
SliderSetValues(Widget old,Widget request,Widget new,ArgList args,Cardinal * num_args)507 SliderSetValues(Widget old, Widget request, Widget new,
508 ArgList args, Cardinal *num_args)
509 {
510 MwSliderWidget oldsw = (MwSliderWidget) old ;
511 MwSliderWidget sw = (MwSliderWidget) new ;
512 Boolean needRedraw = False ;
513
514 if( sw->slider.value != oldsw->slider.value ||
515 sw->slider.minimum != oldsw->slider.minimum ||
516 sw->slider.maximum != oldsw->slider.maximum )
517 {
518 int minimum = sw->slider.minimum ;
519 int maximum = sw->slider.maximum ;
520
521 if( maximum != minimum )
522 sw->slider.thumbpos =
523 sw->slider.length*(sw->slider.value-minimum)/(maximum-minimum) ;
524 else
525 sw->slider.thumbpos = 0 ;
526
527 needRedraw = True ;
528 }
529
530 if( sw->slider.orientation != oldsw->slider.orientation ||
531 sw->slider.thumbLength != oldsw->slider.thumbLength ||
532 sw->slider.shadowWidth != oldsw->slider.shadowWidth ||
533 sw->slider.font != oldsw->slider.font )
534 {
535 /* do some resizing stuff? */
536 needRedraw = True ;
537 sw->slider.needs_layout = True ;
538 }
539
540 /* if any color changes, need to recompute GCs and redraw */
541
542 if( sw->core.background_pixel != oldsw->core.background_pixel ||
543 sw->core.background_pixmap != oldsw->core.background_pixmap ||
544 sw->slider.top_contrast != oldsw->slider.top_contrast ||
545 sw->slider.bot_contrast != oldsw->slider.bot_contrast ||
546 sw->slider.arm_contrast != oldsw->slider.arm_contrast ||
547 sw->slider.insensitive_contrast !=
548 oldsw->slider.insensitive_contrast ||
549 sw->slider.be_nice_to_cmap != oldsw->slider.be_nice_to_cmap )
550 {
551 /* brute-force method, recompute everything. It would be
552 * more efficient to only recompute the GC's that are
553 * actually affected.
554 */
555 SliderFreeGCs(sw) ;
556 SliderAllocGCs(sw) ;
557 needRedraw = True ;
558 }
559
560 if( sw->core.sensitive != oldsw->core.sensitive )
561 needRedraw = True ;
562
563 return needRedraw ;
564 }
565
566
567
568 static Boolean
SliderAcceptFocus(Widget w,Time * tm)569 SliderAcceptFocus( Widget w, Time *tm )
570 {
571 if( !w->core.being_destroyed && XtIsRealized(w) &&
572 XtIsSensitive(w) && w->core.visible && XtIsManaged(w) )
573 {
574 Widget p ;
575 for(p = XtParent(w); !XtIsShell(p); p = XtParent(p)) ;
576 XtSetKeyboardFocus(p, w) ;
577 return True ;
578 }
579 else
580 return False ;
581 }
582
583
584
585
586
587 /*
588 * Return preferred size. Accept changes to our preferred size,
589 * reject changes that are too small, return "almost" for sizes that
590 * are too big.
591 *
592 * These return values are highly subjective. For example, many
593 * widgets (e.g. scrolled windows or text windows) would be perfectly
594 * happy with any size greater than the minimum.
595 */
596
597 static XtGeometryResult
SliderQueryGeometry(Widget w,XtWidgetGeometry * intended,XtWidgetGeometry * preferred)598 SliderQueryGeometry(Widget w,
599 XtWidgetGeometry *intended, XtWidgetGeometry *preferred)
600 {
601 register MwSliderWidget sw = (MwSliderWidget)w ;
602 XtGeometryMask mode = intended->request_mode ;
603 int iw = intended->width ;
604 int ih = intended->height ;
605 Dimension min_wid, min_hgt ;
606
607 /* provide preferred size to caller, don't care about position,
608 * border_width, etc.
609 */
610 preferred->request_mode = CWWidth | CWHeight ;
611 SliderClass(sw)->slider_class.preferred_size (sw,
612 &preferred->width, &preferred->height, &min_wid, &min_hgt) ;
613
614 /* reject if caller proposing no change */
615 /* TODO: is this really worth it? */
616 if( (!(mode & CWX) || intended->x == w->core.x) &&
617 (!(mode & CWY) || intended->y == w->core.y) &&
618 (!(mode & CWBorderWidth) ||
619 intended->border_width == w->core.border_width) &&
620 (!(mode & CWWidth) || iw == w->core.width) &&
621 (!(mode & CWHeight) || ih == w->core.height) )
622 return XtGeometryNo ;
623
624 /* reject if too small */
625 if( ((mode & CWWidth) && iw < min_wid ) ||
626 ((mode & CWHeight) && ih < min_hgt ) )
627 return XtGeometryNo ;
628
629 /* Since we don't mind being too big, accept anything else. */
630 return XtGeometryYes ;
631 }
632
633
634
635
636
637 /****************************************************************
638 *
639 * Action Procedures
640 *
641 ****************************************************************/
642
643 /*
644 * Rules for converting between pointer motion and slider motion:
645 *
646 * Definitions:
647 * spos = slider position
648 * ds = change in slider position
649 * length = domain of slider motion
650 * range = maximum-minimum
651 * dx = pointer motion
652 * dv = change in value
653 *
654 *
655 * Relationship between slider position and slider value are described
656 * by this equation:
657 *
658 * spos/length = (value-minimum)/(maximum-minimum)
659 *
660 * convert value => slider position:
661 *
662 * spos = length*(value-minimum)/(maximum-minimum)
663 *
664 * convert slider position => value:
665 *
666 * value = (maximum-minimum)*spos/length + minimum
667 *
668 *
669 *
670 * Changes in pointer position are related to changes in slider
671 * position:
672 *
673 * ds = dx*inputScale/100
674 *
675 *
676 * It's better to take pointer motion and map it directly to value
677 * changes, especially with small sliders.
678 *
679 * dv = ds*range/length
680 * dv = dx*inputScale/100*range/length
681 *
682 * There are three special cases that are worth optimizing:
683 *
684 * inputScale = 100:
685 *
686 * dv = dx*range/length
687 *
688 * inputScale = length:
689 * length = 0:
690 *
691 * dv = dx*range/100
692 *
693 */
694
695
696
697
698
699 static void
StartAdjust(Widget w,XEvent * ev,String * args,Cardinal * num_args)700 StartAdjust(Widget w, XEvent *ev, String *args, Cardinal *num_args)
701 {
702 MwSliderWidget sw = (MwSliderWidget)w ;
703 int s = sw->slider.shadowWidth ;
704 int pt ;
705
706 /* find out if this was a click on the thumb, or the
707 * background.
708 */
709 if( sw->slider.orientation == XtorientHorizontal )
710 pt = s + ev->xbutton.x ;
711 else
712 pt = sw->core.height - s - ev->xbutton.y ;
713
714 if( pt < sw->slider.thumbpos + 2*s )
715 ChangeSliderValue(sw, sw->slider.value - sw->slider.step) ;
716 else if( pt > sw->slider.thumbpos + 2*s + sw->slider.thumbLength )
717 ChangeSliderValue(sw, sw->slider.value + sw->slider.step) ;
718 else {
719 sw->slider.oldvalue = sw->slider.value ;
720 sw->slider.adjustPt = pt ;
721 sw->slider.adjusting = True ;
722 }
723 }
724
725
726 static void
HandleThumb(Widget w,XEvent * ev,String * args,Cardinal * num_args)727 HandleThumb(Widget w, XEvent *ev, String *args, Cardinal *num_args)
728 {
729 MwSliderWidget sw = (MwSliderWidget)w ;
730 int s = sw->slider.shadowWidth ;
731 int pt, dp, dv ;
732 int length = sw->slider.length ;
733 int range = sw->slider.maximum - sw->slider.minimum ;
734
735 if( !sw->slider.adjusting ) return ;
736
737 if( length <= 0 ) length = 1 ;
738
739 if( sw->slider.orientation == XtorientHorizontal )
740 pt = s + ev->xbutton.x ;
741 else
742 pt = sw->core.height - s - ev->xbutton.y ;
743
744 dp = pt - sw->slider.adjustPt ;
745
746 /* special cases */
747 if( sw->slider.inputScale == 100 )
748 dv = dp*range/length ;
749 else if( sw->slider.inputScale == length )
750 dv = dp*range/100 ;
751 else
752 dv = dp*sw->slider.inputScale*range/length/100 ;
753
754 ChangeSliderValue(sw, sw->slider.oldvalue + dv) ;
755 XtCallCallbackList(w,
756 sw->slider.dragCallback, (XtPointer)sw->slider.value) ;
757 }
758
759
760 static void
EndAdjust(Widget w,XEvent * ev,String * args,Cardinal * num_args)761 EndAdjust(Widget w, XEvent *ev, String *args, Cardinal *num_args)
762 {
763 MwSliderWidget sw = (MwSliderWidget)w ;
764
765 if( !sw->slider.adjusting ) return ;
766
767 sw->slider.adjusting = False ;
768 }
769
770
771 static void
Adjust(Widget w,XEvent * ev,String * args,Cardinal * num_args)772 Adjust(Widget w, XEvent *ev, String *args, Cardinal *num_args)
773 {
774 MwSliderWidget sw = (MwSliderWidget)w ;
775 int newvalue = sw->slider.value ;
776
777 if( *num_args == 0 ) return ; /* TODO: warning? */
778
779 if( *num_args >= 1 )
780 {
781 Bool neg = False ;
782 int n = 0 ;
783 String str = *args ;
784 if( *str == '-' ) { neg = True ; ++str ; }
785 if( isdigit(*str) )
786 n = atoi(str) ;
787 else if( XmuCompareISOLatin1(str, "step2") == 0 )
788 n = sw->slider.step2 ;
789 else if( XmuCompareISOLatin1(str, "step") == 0 )
790 n = sw->slider.step ;
791 else if( XmuCompareISOLatin1(str, "home") == 0 )
792 newvalue = sw->slider.minimum ;
793 else if( XmuCompareISOLatin1(str, "end") == 0 )
794 newvalue = sw->slider.maximum ;
795
796 if( neg ) n = -n ;
797 newvalue += n ;
798
799 ChangeSliderValue(sw, newvalue) ;
800 }
801 }
802
803 static void
Notify(Widget w,XEvent * ev,String * args,Cardinal * num_args)804 Notify(Widget w, XEvent *ev, String *args, Cardinal *num_args)
805 {
806 MwSliderWidget sw = (MwSliderWidget)w ;
807 XtCallCallbackList(w,
808 sw->slider.valueChangedCallback, (XtPointer)sw->slider.value) ;
809 }
810
811
812 static void
Focus(Widget w,XEvent * ev,String * args,Cardinal * num_args)813 Focus(Widget w, XEvent *ev, String *args, Cardinal *num_args)
814 {
815 MwSliderWidget sw = (MwSliderWidget)w ;
816
817 if( *num_args == 0 ) return ; /* TODO: warning? */
818
819 sw->slider.has_focus = atoi(*args) ;
820
821 SliderClass(sw)->slider_class.focus_proc(sw) ;
822 }
823
824
825
826
827
828
829 /****************************************************************
830 *
831 * Public Procedures
832 *
833 ****************************************************************/
834
835
836 /* Set the top fc, optionally call all callbacks. */
837 void
XawSliderSetValue(Widget w,int newvalue,Bool callCallbacks)838 XawSliderSetValue(Widget w, int newvalue, Bool callCallbacks)
839 {
840 MwSliderWidget sw = (MwSliderWidget) w;
841 if( !XtIsSubclass(w, mwSliderWidgetClass) ) return ;
842 ChangeSliderValue(sw, newvalue) ;
843 if( callCallbacks)
844 XtCallCallbackList(w,
845 sw->slider.valueChangedCallback, (XtPointer)sw->slider.value) ;
846 }
847
848
849
850
851 /****************************************************************
852 *
853 * Private Procedures
854 *
855 ****************************************************************/
856
857
858 /*
859 * Overview:
860 *
861 * draw_border() draws the "static" part of the widget, that is, any
862 * part that will never be obscured by the thumb. In the base class,
863 * this means the shadow border. This function should also draw the
864 * keyboard focus feedback, possibly by calling focus_proc().
865 *
866 * draw_background() draws whatever part of the slider is under the
867 * thumb. It is used during expose() and to repair damage made by
868 * moving the thumb. It accepts as arguments a region to be
869 * redrawn. This may describe a region just vacated by the thumb,
870 * or it may describe the entire widget. This function may also need
871 * to draw the keyboard focus feedback, if such feedback can be damaged
872 * by thumb motion. This function will not be called with width or
873 * height equal to zero.
874 *
875 * draw_thumb() draws the thumb at the current location.
876 *
877 * move_thumb() moves the thumb to a new location. In the base class,
878 * this function uses XCopyArea() to move the thumb, and draw_background()
879 * to repair the damage. XCopyArea() may not be suitable if the thumb
880 * is non-rectangular.
881 *
882 * focus_proc() is called in response to changes in keyboard focus;
883 * it draws the visual indication that the slider has gained
884 * or has lost keyboard focus. This proc examines the has_focus
885 * variable.
886 *
887 * preferred_size() returns the slider's preferred size, and the
888 * minimum acceptable size.
889 *
890 * Variables:
891 *
892 * start the first pixel the thumb may ever occupy
893 *
894 * length the working length of the slider -- i.e. the total
895 * adjusting range of the thumb. The first pixel
896 * occupied by the thumb may range from start to
897 * start+length.
898 *
899 * thumbpos current location of the thumb; ranges from 0 to length
900 *
901 * thumbLength length of the thumb. The last pixel occupied by the
902 * thumb may range from (start + thumbLength - 1) to
903 * (start + length + thumbLength -1)
904 *
905 * thumbWidth width of the thumb; used by move_thumb() to determine
906 * the size of the region to be copied. Note that for
907 * horizontal sliders, this is the vertical dimension.
908 *
909 */
910
911
912
913 static void
SliderDrawBorder(MwSliderWidget sw)914 SliderDrawBorder(MwSliderWidget sw)
915 {
916 Draw3dBox((Widget)sw, 0,0, sw->core.width, sw->core.height,
917 sw->slider.shadowWidth, sw->slider.botGC, sw->slider.topGC) ;
918 }
919
920
921 static void
SliderDrawBackground(MwSliderWidget sw,int x,int y,int wid,int hgt)922 SliderDrawBackground(MwSliderWidget sw, int x, int y, int wid, int hgt)
923 {
924 Widget w = (Widget)sw ;
925 int s = sw->slider.shadowWidth ;
926 int x2 = x+wid ;
927 int y2 = y+hgt ;
928 int x3 = s ;
929 int y3 = s ;
930 int x4 = sw->core.width - s ;
931 int y4 = sw->core.height - s ;
932
933 /* easy; just clip the requested region to within the shadow
934 * border and fill.
935 */
936
937 if( x < x3 ) x = x3 ;
938 if( y < y3 ) y = y3 ;
939 if( x2 > x4 ) x2 = x4 ;
940 if( y2 > y4 ) y2 = y4 ;
941
942 XFillRectangle(XtDisplay(w), XtWindow(w), sw->slider.greyGC,
943 x,y, x2-x, y2-y) ;
944 }
945
946
947 /* Utility: return the bounds of the thumb */
948
949 void
_SliderThumb(MwSliderWidget sw,int * x,int * y,int * wid,int * hgt)950 _SliderThumb(MwSliderWidget sw, int *x, int *y, int *wid, int *hgt)
951 {
952 int st = sw->slider.start + sw->slider.thumbpos ;
953 int tl = sw->slider.thumbLength ;
954 int tw = sw->slider.thumbWidth ;
955
956 if( sw->slider.thumbpos < 0 )
957 sw->slider.thumbpos = 0 ;
958 else if( sw->slider.thumbpos > sw->slider.length )
959 sw->slider.thumbpos = sw->slider.length ;
960
961 /* assume thumb centered in slider */
962
963 if( sw->slider.orientation == XtorientHorizontal ) {
964 *wid = tl ;
965 *hgt = tw ;
966 *x = st ;
967 *y = (sw->core.height - tw)/2 ;
968 }
969 else {
970 *wid = tw ;
971 *hgt = tl ;
972 *x = (sw->core.width - tw)/2 ;
973 *y = sw->core.height - st - tl ;
974 }
975 }
976
977
978 static void
SliderDrawThumb(MwSliderWidget sw)979 SliderDrawThumb(MwSliderWidget sw)
980 {
981 Display *dpy = XtDisplay((Widget)sw) ;
982 Window win = XtWindow((Widget)sw) ;
983 int s = sw->slider.shadowWidth ;
984 int x,y, wid,hgt ;
985 GC botgc = sw->slider.botGC ;
986 GC topgc = sw->slider.topGC ;
987
988 _SliderThumb(sw, &x, &y, &wid, &hgt) ;
989
990 XClearArea(dpy,win, x,y, wid,hgt, False) ;
991
992 if( sw->slider.orientation == XtorientHorizontal ) {
993 XDrawLine(dpy,win, botgc, x+wid/2-1, y, x+wid/2-1, y+hgt-1) ;
994 XDrawLine(dpy,win, topgc, x+wid/2, y, x+wid/2, y+hgt-1) ;
995 }
996 else {
997 XDrawLine(dpy,win, botgc, x, y+hgt/2-1, x+wid-1, y+hgt/2-1) ;
998 XDrawLine(dpy,win, topgc, x, y+hgt/2, x+wid-1, y+hgt/2) ;
999 }
1000 Draw3dBox((Widget)sw, x,y, wid, hgt, s, topgc, botgc) ;
1001 }
1002
1003
1004 static void
SliderMoveThumb(MwSliderWidget sw,int newpos)1005 SliderMoveThumb(MwSliderWidget sw, int newpos)
1006 {
1007 Display *dpy = XtDisplay((Widget)sw) ;
1008 Window win = XtWindow((Widget)sw) ;
1009 int sx,sy, dx,dy, wid,hgt ;
1010 int oldpos = sw->slider.thumbpos ;
1011 GC gc = sw->slider.backgroundGC ;
1012
1013 /* assume the thumb is centered in the slider */
1014
1015 if( newpos < 0 )
1016 newpos = 0 ;
1017 else if( newpos > sw->slider.length )
1018 newpos = sw->slider.length ;
1019
1020 if( newpos == oldpos )
1021 return ;
1022
1023 _SliderThumb(sw, &sx, &sy, &wid, &hgt) ;
1024
1025 dx = sx ;
1026 dy = sy ;
1027 if( sw->slider.orientation == XtorientHorizontal )
1028 dx += newpos - oldpos ;
1029 else
1030 dy -= newpos - oldpos ;
1031 XCopyArea(dpy,win,win, gc, sx,sy, wid,hgt, dx,dy) ;
1032
1033 if( sw->slider.orientation == XtorientHorizontal ) {
1034 /* fill in left or right? */
1035 if( newpos > oldpos ) { /* right */
1036 dx = sx ;
1037 if( newpos - oldpos < wid ) wid = newpos - oldpos ;
1038 }
1039 else { /* left */
1040 dx = sx + wid ;
1041 if( oldpos - newpos < wid ) wid = oldpos - newpos ;
1042 dx -= wid ;
1043 }
1044 }
1045 else {
1046 /* fill in top or bottom? */
1047 if( newpos > oldpos ) { /* bottom */
1048 dy = sy + hgt ;
1049 if( newpos - oldpos < hgt ) hgt = newpos - oldpos ;
1050 dy -= hgt ;
1051 }
1052 else { /* top */
1053 dy = sy ;
1054 if( oldpos - newpos < hgt ) hgt = oldpos - newpos ;
1055 }
1056 }
1057 SliderClass(sw)->slider_class.draw_background(sw, dx,dy, wid, hgt) ;
1058
1059 sw->slider.thumbpos = newpos ;
1060 }
1061
1062
1063 /* In the base class, we indicate focus by a solid border that
1064 * will at least partially overlay the 3-d border. Undraw the
1065 * solid border by simply redrawing the 3-d border.
1066 */
1067
1068 static void
SliderFocusProc(MwSliderWidget sw)1069 SliderFocusProc(MwSliderWidget sw)
1070 {
1071 if( sw->slider.has_focus )
1072 XDrawRectangle(XtDisplay((Widget)sw), XtWindow((Widget)sw),
1073 sw->slider.foregroundGC,
1074 0,0, sw->core.width-1, sw->core.height-1) ;
1075 else
1076 SliderDrawBorder(sw) ;
1077 }
1078
1079
1080 static void
ChangeSliderValue(MwSliderWidget sw,int newvalue)1081 ChangeSliderValue(MwSliderWidget sw, int newvalue)
1082 {
1083 int newpos ;
1084 int minimum = sw->slider.minimum ;
1085 int maximum = sw->slider.maximum ;
1086
1087 if( newvalue < minimum )
1088 newvalue = minimum ;
1089 else if( newvalue > maximum )
1090 newvalue = maximum ;
1091
1092 if( maximum != minimum )
1093 newpos = sw->slider.length*(newvalue-minimum)/(maximum-minimum) ;
1094 else
1095 newpos = 0 ;
1096 SliderClass(sw)->slider_class.move_thumb(sw, newpos) ;
1097 sw->slider.value = newvalue ;
1098 }
1099
1100
1101
1102 static void
SliderAllocGCs(MwSliderWidget sw)1103 SliderAllocGCs(MwSliderWidget sw)
1104 {
1105 Widget w = (Widget) sw;
1106
1107 if( !XtIsRealized(w) )
1108 return ;
1109
1110 sw->slider.backgroundGC = AllocBackgroundGC((Widget)sw, None) ;
1111 sw->slider.foregroundGC = AllocFgGC(w,
1112 sw->slider.foreground, sw->slider.font->fid) ;
1113 sw->slider.topGC = AllocTopShadowGC(w,
1114 sw->slider.top_contrast, sw->slider.be_nice_to_cmap) ;
1115 sw->slider.botGC = AllocBotShadowGC(w,
1116 sw->slider.bot_contrast, sw->slider.be_nice_to_cmap) ;
1117 sw->slider.greyGC = AllocArmGC(w,
1118 sw->slider.arm_contrast, sw->slider.be_nice_to_cmap) ;
1119 }
1120
1121
1122 static void
SliderFreeGCs(MwSliderWidget sw)1123 SliderFreeGCs(MwSliderWidget sw)
1124 {
1125 Widget w = (Widget) sw;
1126
1127 if( !XtIsRealized(w) )
1128 return ;
1129
1130 XtReleaseGC(w, sw->slider.backgroundGC) ;
1131 XtReleaseGC(w, sw->slider.foregroundGC) ;
1132 XtReleaseGC(w, sw->slider.topGC) ;
1133 XtReleaseGC(w, sw->slider.botGC) ;
1134 XtReleaseGC(w, sw->slider.greyGC) ;
1135 XmuReleaseStippledPixmap(XtScreen(w), sw->slider.grey50) ;
1136 }
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147 /****************************************************************
1148 *
1149 * Geometry Utilities
1150 *
1151 ****************************************************************/
1152
1153
1154
1155
1156 /*
1157 * Find preferred size. Depends on widget contents.
1158 */
1159
1160
1161 static void
SliderPreferredSize(MwSliderWidget sw,Dimension * reply_width,Dimension * reply_height,Dimension * min_wid,Dimension * min_hgt)1162 SliderPreferredSize(MwSliderWidget sw,
1163 Dimension *reply_width, Dimension *reply_height,
1164 Dimension *min_wid, Dimension *min_hgt)
1165 {
1166 int s = sw->slider.shadowWidth ;
1167
1168 if( sw->slider.orientation == XtorientHorizontal ) {
1169 *reply_width = DEF_WID ;
1170 *reply_height = THUMB_HGT + s*4 ;
1171 *min_wid = sw->slider.thumbLength + s*2 + 1 ;
1172 *min_hgt = s*2 + 3 ;
1173 }
1174 else {
1175 *reply_width = THUMB_HGT + s*4 ;
1176 *reply_height = DEF_WID ;
1177 *min_wid = s*2 + 3 ;
1178 *min_hgt = sw->slider.thumbLength + s*2 + 1 ;
1179 }
1180 }
1181