XEmacs 21.2.32 "Kastor & Polydeukes".
[chise/xemacs-chise.git.1] / lwlib / xlwscrollbar.c
1 /* Implements a lightweight scrollbar widget.
2    Copyright (C) 1992, 1993, 1994 Lucid, Inc.
3    Copyright (C) 1997 Sun Microsystems, Inc.
4
5 This file is part of the Lucid Widget Library.
6
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with XEmacs; see the file COPYING.  If not, write to
19 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.  */
21
22 /* Created by Douglas Keller <dkeller@vnet.ibm.com> */
23 /* Lots of hacking by Martin Buchholz */
24
25 /*
26  * Athena-style scrollbar button bindings added on Sun Dec 24 22:03:57 1995
27  * by Jonathan Stigelman <Stig@hackvan.com>...   Ho ho ho!
28  *
29  * To use them, put this resource in your .Xdefaults
30  *
31  * Emacs*XlwScrollBar.translations: #override \n\
32  *   <Btn1Down>:     PageDownOrRight()    \n\
33  *   <Btn3Down>:     PageUpOrLeft()
34  *
35  */
36
37 /*
38  * Resources Supported:
39  *     XmNforeground
40  *     XmNbackground
41  *     XmNtopShadowColor
42  *     XmNtopShadowPixmap
43  *     XmNbottomShadowColor
44  *     XmNbottomShadowPixmap
45  *     XmNtroughColor
46  *     XmNshadowThickness
47  *     XmNshowArrows
48  *     XmNorientation
49  *     XmNborderWidth
50  *
51  *     XmNminimum
52  *     XmNmaximum
53  *     XmNvalue
54  *     XmNincrement
55  *     XmNpageIncrement
56  *
57  *     XmNvalueChangedCallback
58  *     XmNincrementCallback
59  *     XmNdecrementCallback
60  *     XmNpageIncrementCallback
61  *     XmNpageDecrementCallback
62  *     XmNtoTopCallback
63  *     XmNtoBottomCallback
64  *     XmNdragCallback
65  *
66  *     XmNsliderStyle    - values can be: "plain" or "dimple"
67  *     XmNarrowPosition  - values can be: "opposite" or "same"
68  *
69  */
70
71 #include <config.h>
72 #include <stdio.h>
73 #include <stdlib.h>
74 #include <limits.h>
75
76 #include <X11/IntrinsicP.h>
77 #include <X11/StringDefs.h>
78 #include <X11/bitmaps/gray>
79
80 #include "xlwscrollbarP.h"
81 #include "xlwscrollbar.h"
82
83 #ifdef USE_DEBUG_MALLOC
84 #include <dmalloc.h>
85 #endif
86
87 #define DBUG(x)
88
89 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
90                   ? ((unsigned long) (x)) : ((unsigned long) (y)))
91
92 #define VERT(w) ((w)->sb.orientation == XmVERTICAL)
93
94 #define SS_MIN 8
95
96 typedef enum
97 {
98   BUTTON_NONE,
99   BUTTON_SLIDER,
100   BUTTON_UP_ARROW,
101   BUTTON_DOWN_ARROW,
102   BUTTON_TROUGH_ABOVE,
103   BUTTON_TROUGH_BELOW
104 } button_where;
105
106 typedef enum
107 {
108   SLIDER_PLAIN,
109   SLIDER_DIMPLE
110 } SliderStyle;
111
112 /*-------------------------- Resources ----------------------------------*/
113 #define offset(field) XtOffset(XlwScrollBarWidget, field)
114
115 static XtResource resources[] = {
116     { XmNforeground, XmCForeground, XtRPixel, sizeof(Pixel),
117       offset(sb.foreground), XtRImmediate, (XtPointer) XtDefaultForeground },
118
119     { XmNtopShadowColor, XmCTopShadowColor, XtRPixel,
120       sizeof(Pixel), offset(sb.topShadowColor), XtRImmediate, (XtPointer) ~0 },
121     { XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel,
122       sizeof(Pixel), offset(sb.bottomShadowColor), XtRImmediate,
123       (XtPointer)~0 },
124
125     { XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap,
126       sizeof (Pixmap), offset(sb.topShadowPixmap), XtRImmediate,
127       (XtPointer)None},
128     { XmNbottomShadowPixmap, XmCBottomShadowPixmap,
129       XtRPixmap, sizeof (Pixmap), offset(sb.bottomShadowPixmap),
130       XtRImmediate, (XtPointer)None},
131
132     { XmNtroughColor, XmCTroughColor, XtRPixel, sizeof(Pixel),
133       offset(sb.troughColor), XtRImmediate, (XtPointer)~0 },
134
135     { XmNshadowThickness, XmCShadowThickness, XtRInt,
136       sizeof(int), offset(sb.shadowThickness), XtRImmediate, (XtPointer)2 },
137
138     { XmNborderWidth, XmCBorderWidth, XtRDimension,
139       sizeof(Dimension), offset(core.border_width), XtRImmediate,
140       (XtPointer)0 },
141
142     { XmNshowArrows, XmCShowArrows, XtRBoolean,
143       sizeof(Boolean), offset(sb.showArrows), XtRImmediate, (XtPointer)True },
144
145     { XmNinitialDelay, XmCInitialDelay, XtRInt, sizeof(int),
146       offset(sb.initialDelay), XtRImmediate, (XtPointer) 250 },
147     { XmNrepeatDelay, XmCRepeatDelay, XtRInt, sizeof(int),
148       offset(sb.repeatDelay), XtRImmediate, (XtPointer) 50 },
149
150     { XmNorientation, XmCOrientation, XtROrientation,
151       sizeof(unsigned char), offset(sb.orientation), XtRImmediate,
152       (XtPointer) XmVERTICAL },
153
154     { XmNminimum, XmCMinimum, XtRInt, sizeof(int),
155       offset(sb.minimum), XtRImmediate, (XtPointer) 0},
156     { XmNmaximum, XmCMaximum, XtRInt, sizeof(int),
157       offset(sb.maximum), XtRImmediate, (XtPointer) 100},
158     { XmNvalue, XmCValue, XtRInt, sizeof(int),
159       offset(sb.value), XtRImmediate, (XtPointer) 0},
160     { XmNsliderSize, XmCSliderSize, XtRInt, sizeof(int),
161       offset(sb.sliderSize), XtRImmediate, (XtPointer) 10},
162     { XmNincrement, XmCIncrement, XtRInt, sizeof(int),
163       offset(sb.increment), XtRImmediate, (XtPointer) 1},
164     { XmNpageIncrement, XmCPageIncrement, XtRInt, sizeof(int),
165       offset(sb.pageIncrement), XtRImmediate, (XtPointer) 10},
166
167     { XmNvalueChangedCallback, XmCValueChangedCallback,
168       XtRCallback, sizeof(XtPointer), offset(sb.valueChangedCBL),
169       XtRCallback, NULL},
170     { XmNincrementCallback, XmCIncrementCallback,
171       XtRCallback, sizeof(XtPointer), offset(sb.incrementCBL),
172       XtRCallback, NULL},
173     { XmNdecrementCallback, XmCDecrementCallback,
174       XtRCallback, sizeof(XtPointer), offset(sb.decrementCBL),
175       XtRCallback, NULL},
176     { XmNpageIncrementCallback, XmCPageIncrementCallback,
177       XtRCallback, sizeof(XtPointer), offset(sb.pageIncrementCBL),
178       XtRCallback, NULL},
179     { XmNpageDecrementCallback, XmCPageDecrementCallback,
180       XtRCallback, sizeof(XtPointer), offset(sb.pageDecrementCBL),
181       XtRCallback, NULL},
182     { XmNtoTopCallback, XmCToTopCallback, XtRCallback,
183       sizeof(XtPointer), offset(sb.toTopCBL), XtRCallback, NULL},
184     { XmNtoBottomCallback, XmCToBottomCallback, XtRCallback,
185       sizeof(XtPointer), offset(sb.toBottomCBL), XtRCallback, NULL},
186     { XmNdragCallback, XmCDragCallback, XtRCallback,
187       sizeof(XtPointer), offset(sb.dragCBL), XtRCallback, NULL},
188
189       /* "knob" is obsolete; use "slider" instead. */
190     { XmNsliderStyle, XmCSliderStyle, XtRString, sizeof(char *),
191       offset(sb.sliderStyle), XtRImmediate, NULL},
192     { XmNknobStyle, XmCKnobStyle, XtRString, sizeof(char *),
193        offset(sb.knobStyle), XtRImmediate, NULL},
194
195     { XmNarrowPosition, XmCArrowPosition, XtRString, sizeof(char *),
196       offset(sb.arrowPosition), XtRImmediate, NULL},
197 };
198
199 /*-------------------------- Prototypes ---------------------------------*/
200
201 /* Actions */
202 typedef void Action(Widget w, XEvent *event, String *parms, Cardinal *num_parms);
203 static Action Select, PageUpOrLeft, PageDownOrRight, Drag, Release, Jump, Abort;
204
205 /* Methods */
206 static void Initialize(Widget treq, Widget tnew, ArgList args, Cardinal *num_args);
207 static Boolean SetValues(Widget current, Widget request, Widget nw, ArgList args, Cardinal *num_args);
208 static void Destroy(Widget widget);
209 static void Redisplay(Widget widget, XEvent *event, Region region);
210 static void Resize(Widget widget);
211 static void Realize(Widget widget, XtValueMask *valuemask, XSetWindowAttributes *attr);
212
213 /* Private */
214
215 /*-------------------------- Actions Table ------------------------------*/
216 static XtActionsRec actions[] =
217 {
218   {"Select",            Select},
219   {"PageDownOrRight",   PageDownOrRight},
220   {"PageUpOrLeft",      PageUpOrLeft},
221   {"Drag",              Drag},
222   {"Release",           Release},
223   {"Jump",              Jump},
224   {"Abort",             Abort},
225 };
226
227 /*--------------------- Default Translation Table -----------------------*/
228 static char default_translations[] =
229   "<Btn1Down>:    Select()\n"
230   "<Btn1Motion>:  Drag()\n"
231   "<Btn1Up>:      Release()\n"
232   "<Btn2Down>:    Jump()\n"
233   "<Btn2Motion>:  Drag()\n"
234   "<Btn2Up>:      Release()\n"
235   "<Key>Delete:   Abort()"
236 ;
237
238 /*------------------- Class record initialization -----------------------*/
239 XlwScrollBarClassRec xlwScrollBarClassRec = {
240     /* core_class fields */
241     {
242     /* superclass          */ (WidgetClass) &coreClassRec,
243     /* class_name          */ "XlwScrollBar",
244     /* widget_size         */ sizeof(XlwScrollBarRec),
245     /* class_initialize    */ NULL,
246     /* class_part_init     */ NULL,
247     /* class_inited        */ False,
248     /* initialize          */ Initialize,
249     /* initialize_hook     */ NULL,
250     /* realize             */ Realize,
251     /* actions             */ actions,
252     /* num_actions         */ XtNumber(actions),
253     /* resources           */ resources,
254     /* num_resources       */ XtNumber(resources),
255     /* xrm_class           */ NULLQUARK,
256     /* compress_motion     */ True,
257     /* compress_exposure   */ XtExposeCompressMultiple,
258     /* compress_enterleave */ True,
259     /* visible_interest    */ False,
260     /* destroy             */ Destroy,
261     /* resize              */ Resize,
262     /* expose              */ Redisplay,
263     /* set_values          */ SetValues,
264     /* set_values_hook     */ NULL,
265     /* set_values_almost   */ XtInheritSetValuesAlmost,
266     /* get_values_hook     */ NULL,
267     /* accept_focus        */ NULL,
268     /* version             */ XtVersionDontCheck,
269     /* callback_private    */ NULL,
270     /* tm_table            */ default_translations,
271     /* query_geometry      */ NULL,
272     },
273     /* scrollbar_class fields */
274     {
275         /* dummy_field         */ 0,
276     },
277 };
278
279 WidgetClass xlwScrollBarWidgetClass = (WidgetClass) &xlwScrollBarClassRec;
280
281 /*-------------------------- Debug Functions ----------------------------*/
282
283 #ifdef SHOW_CLEAR
284 static void
285 myXClearArea(Display *dpy, Drawable d, int x, int y, int w, int h,
286              Boolean exp, XlwScrollBarWidget widget)
287 {
288   XFillRectangle (dpy, d, widget->sb.topShadowGC, x, y, w, h);
289   XSync (dpy, False);
290   sleep (2);
291   XClearArea (dpy, d, x, y, w, h, exp);
292 }
293
294 #define XClearArea(dpy,win,x,y,width,height,exp) myXClearArea(dpy,win,x,y,width,height,exp,w)
295 #endif
296
297 #ifdef CHECK_VALUES
298 static void
299 check(XlwScrollBarWidget w)
300 {
301   int height = widget_h (w);
302   if (w->sb.showArrows)
303     height -= (2 * arrow_h (w));
304
305   if ((w->sb.above + w->sb.ss + w->sb.below > height) ||
306       (w->sb.value < w->sb.minimum) ||
307       (w->sb.value > w->sb.maximum - w->sb.sliderSize))
308     {
309       printf("above=%d ss=%d below=%d height=%d\n",
310              w->sb.above, w->sb.ss, w->sb.below, height);
311       printf("value=%d min=%d max=%d ss=%d max-ss=%d\n",
312              w->sb.value, w->sb.minimum, w->sb.maximum,
313              w->sb.sliderSize, w->sb.maximum - w->sb.sliderSize);
314       abort();
315     }
316 }
317
318 #  define CHECK(w) check(w)
319 #else
320 #  define CHECK(w)
321 #endif
322
323 /*-------------------------- Static functions ---------------------------*/
324
325 static void
326 call_callbacks (XlwScrollBarWidget w, int reason,
327                 int value, int pixel, XEvent *event)
328 {
329   XlwScrollBarCallbackStruct cbs;
330   Boolean called_anything;
331
332   cbs.reason = reason;
333   cbs.event  = event;
334   cbs.value  = value;
335   cbs.pixel  = pixel;
336
337   called_anything = False;
338
339   switch (reason)
340     {
341     case XmCR_VALUE_CHANGED:
342       XtCallCallbackList ((Widget) w, w->sb.valueChangedCBL, &cbs);
343       called_anything = True;
344       break;
345     case XmCR_INCREMENT:
346       if (w->sb.incrementCBL)
347         {
348           XtCallCallbackList ((Widget) w, w->sb.incrementCBL, &cbs);
349           called_anything = True;
350         }
351       break;
352     case XmCR_DECREMENT:
353       if (w->sb.decrementCBL)
354         {
355           XtCallCallbackList ((Widget) w, w->sb.decrementCBL, &cbs);
356           called_anything = True;
357         }
358       break;
359     case XmCR_PAGE_INCREMENT:
360       if (w->sb.incrementCBL)
361         {
362           XtCallCallbackList ((Widget) w, w->sb.pageIncrementCBL, &cbs);
363           called_anything = True;
364         }
365       break;
366     case XmCR_PAGE_DECREMENT:
367       if (w->sb.decrementCBL)
368         {
369           XtCallCallbackList ((Widget) w, w->sb.pageDecrementCBL, &cbs);
370           called_anything = True;
371         }
372       break;
373     case XmCR_TO_TOP:
374       if (w->sb.toTopCBL)
375         {
376           XtCallCallbackList ((Widget) w, w->sb.toTopCBL, &cbs);
377           called_anything = True;
378         }
379       break;
380     case XmCR_TO_BOTTOM:
381       if (w->sb.toBottomCBL)
382         {
383           XtCallCallbackList ((Widget) w, w->sb.toBottomCBL, &cbs);
384           called_anything = True;
385         }
386       break;
387     case XmCR_DRAG:
388       if (w->sb.dragCBL)
389         {
390           XtCallCallbackList ((Widget) w, w->sb.dragCBL, &cbs);
391         }
392       called_anything = True; /* Special Case */
393       break;
394     }
395
396   if (!called_anything)
397     {
398       cbs.reason = XmCR_VALUE_CHANGED;
399       XtCallCallbackList ((Widget) w, w->sb.valueChangedCBL, &cbs);
400     }
401 }
402
403 /* Widget sizes minus the shadow and highlight area */
404
405 static int
406 widget_x (XlwScrollBarWidget w)
407 {
408   return w->sb.shadowThickness;
409 }
410
411 static int
412 widget_y (XlwScrollBarWidget w)
413 {
414   return w->sb.shadowThickness;
415 }
416
417 static int
418 widget_w (XlwScrollBarWidget w)
419 {
420   int x = w->sb.shadowThickness;
421   int width = (VERT (w) ? w->core.width : w->core.height) - (2 * x);
422   return width > 1 ? width : 1;
423 }
424
425 static int
426 widget_h (XlwScrollBarWidget w)
427 {
428   int y = w->sb.shadowThickness;
429   int height = (VERT (w) ? w->core.height : w->core.width) - (2 * y);
430
431   return height > 1 ? height : 1;
432 }
433
434 static int
435 arrow_h (XlwScrollBarWidget w)
436 {
437   int width = widget_w (w);
438   int minimum_size = ((widget_h (w) - SS_MIN) / 2) - 1;
439   return minimum_size < width ? minimum_size : width;
440 }
441
442 static int
443 event_x (XlwScrollBarWidget w, XEvent *event)
444 {
445   return VERT (w) ? event->xbutton.x : event->xbutton.y;
446 }
447
448 static int
449 event_y (XlwScrollBarWidget w, XEvent *event)
450 {
451   return VERT (w) ? event->xbutton.y : event->xbutton.x;
452 }
453
454 /* Safe addition and subtraction */
455 static void
456 increment_value (XlwScrollBarWidget w, int diff)
457 {
458   w->sb.value = w->sb.maximum - diff < w->sb.value ?
459     w->sb.maximum :
460     w->sb.value + diff;
461 }
462
463 static void
464 decrement_value (XlwScrollBarWidget w, int diff)
465 {
466   w->sb.value = w->sb.minimum + diff > w->sb.value ?
467     w->sb.minimum :
468     w->sb.value - diff;
469 }
470
471 static SliderStyle
472 slider_style (XlwScrollBarWidget w)
473 {
474   return (w->sb.sliderStyle ? w->sb.sliderStyle[0] == 'd' :
475           w->sb.knobStyle   ? w->sb.knobStyle[0]   == 'd' :
476           0) ?
477     SLIDER_DIMPLE :
478     SLIDER_PLAIN;
479 }
480
481 static Boolean
482 arrow_same_end (XlwScrollBarWidget w)
483 {
484   return w->sb.arrowPosition && w->sb.arrowPosition[0] == 's' ? True : False;
485 }
486
487 /*-------------------------- GC and Pixel allocation --------------------*/
488 #ifndef XmUNSPECIFIED_PIXMAP
489 #define XmUNSPECIFIED_PIXMAP 2
490 #endif
491
492 static GC
493 get_gc (XlwScrollBarWidget w, Pixel fg, Pixel bg, Pixmap pm)
494 {
495   XGCValues values;
496   XtGCMask mask;
497
498   if (pm == w->sb.grayPixmap)
499     {
500       /* If we're using the gray pixmap, guarantee white on black ...
501        * otherwise, we could end up with something odd like grey on white
502        * when we're on a color display that ran out of color cells
503        */
504
505       fg = WhitePixelOfScreen (DefaultScreenOfDisplay (XtDisplay (w)));
506       bg = BlackPixelOfScreen (DefaultScreenOfDisplay (XtDisplay (w)));
507     }
508
509   values.foreground = fg;
510   values.background = bg;
511   values.fill_style = FillOpaqueStippled;
512   values.stipple    = pm;
513 /*  mask = GCForeground | GCBackground |
514     (pm == None ? 0 : GCStipple | GCFillStyle); gtb */
515   if (pm != None && pm != 0 && pm != XmUNSPECIFIED_PIXMAP)
516      values.stipple = pm;
517   else
518      values.stipple = None;
519   mask = GCForeground | GCBackground |
520    (values.stipple == None ? 0 : GCStipple | GCFillStyle);
521
522   return XtGetGC((Widget) w, mask, &values);
523 }
524
525 /* Replacement for XAllocColor() that tries to return the nearest
526    available color if the colormap is full.  From FSF Emacs. */
527
528 static int
529 allocate_nearest_color (Display *display, Colormap screen_colormap,
530                         XColor *color_def)
531 {
532   int status = XAllocColor (display, screen_colormap, color_def);
533   if (status)
534     return status;
535
536     {
537       /* If we got to this point, the colormap is full, so we're
538          going to try to get the next closest color.
539          The algorithm used is a least-squares matching, which is
540          what X uses for closest color matching with StaticColor visuals.  */
541
542       int nearest, x;
543       unsigned long nearest_delta = ULONG_MAX;
544
545       int no_cells = XDisplayCells (display, XDefaultScreen (display));
546       /* Don't use alloca here because lwlib doesn't have the
547          necessary configuration information that src does. */
548       XColor *cells = (XColor *) malloc (sizeof (XColor) * no_cells);
549
550       for (x = 0; x < no_cells; x++)
551         cells[x].pixel = x;
552
553       XQueryColors (display, screen_colormap, cells, no_cells);
554
555       for (nearest = 0, x = 0; x < no_cells; x++)
556         {
557           long dred   = (color_def->red   >> 8) - (cells[x].red   >> 8);
558           long dgreen = (color_def->green >> 8) - (cells[x].green >> 8);
559           long dblue  = (color_def->blue  >> 8) - (cells[x].blue  >> 8);
560           unsigned long delta = dred * dred + dgreen * dgreen + dblue * dblue;
561
562           if (delta < nearest_delta)
563             {
564               nearest = x;
565               nearest_delta = delta;
566             }
567         }
568       color_def->red   = cells[nearest].red;
569       color_def->green = cells[nearest].green;
570       color_def->blue  = cells[nearest].blue;
571       free (cells);
572       return XAllocColor (display, screen_colormap, color_def);
573     }
574 }
575
576 static void
577 make_shadow_pixels (XlwScrollBarWidget w)
578 {
579   Display *dpy = XtDisplay((Widget) w);
580   Colormap cmap = w->core.colormap;
581   XColor topc, botc;
582   int top_frobbed, bottom_frobbed;
583   Pixel bg, fg;
584
585   top_frobbed = bottom_frobbed = 0;
586
587   bg = w->core.background_pixel;
588   fg = w->sb.foreground;
589
590   if (w->sb.topShadowColor    == (Pixel)~0) w->sb.topShadowColor    = bg;
591   if (w->sb.bottomShadowColor == (Pixel)~0) w->sb.bottomShadowColor = fg;
592
593   if (w->sb.topShadowColor == bg || w->sb.topShadowColor == fg)
594     {
595       topc.pixel = bg;
596       XQueryColor (dpy, cmap, &topc);
597       /* don't overflow/wrap! */
598       topc.red   = MINL(65535, topc.red   * 1.2);
599       topc.green = MINL(65535, topc.green * 1.2);
600       topc.blue  = MINL(65535, topc.blue  * 1.2);
601       if (allocate_nearest_color (dpy, cmap, &topc))
602         {
603           if (topc.pixel == bg)
604             {
605               XFreeColors (dpy, cmap, &topc.pixel, 1, 0);
606               topc.red   = MINL(65535, topc.red   + 0x8000);
607               topc.green = MINL(65535, topc.green + 0x8000);
608               topc.blue  = MINL(65535, topc.blue  + 0x8000);
609               if (allocate_nearest_color (dpy, cmap, &topc))
610                 {
611                   w->sb.topShadowColor = topc.pixel;
612                 }
613             }
614           else
615             {
616               w->sb.topShadowColor = topc.pixel;
617             }
618
619           top_frobbed = 1;
620         }
621     }
622
623   if (w->sb.bottomShadowColor == fg || w->sb.bottomShadowColor == bg)
624     {
625       botc.pixel = bg;
626       XQueryColor (dpy, cmap, &botc);
627       botc.red   = (botc.red   * 3) / 5;
628       botc.green = (botc.green * 3) / 5;
629       botc.blue  = (botc.blue  * 3) / 5;
630       if (allocate_nearest_color (dpy, cmap, &botc))
631         {
632           if (botc.pixel == bg)
633             {
634               XFreeColors (dpy, cmap, &botc.pixel, 1, 0);
635               botc.red   = MINL(65535, botc.red   + 0x4000);
636               botc.green = MINL(65535, botc.green + 0x4000);
637               botc.blue  = MINL(65535, botc.blue  + 0x4000);
638               if (allocate_nearest_color (dpy, cmap, &botc))
639                 {
640                   w->sb.bottomShadowColor = botc.pixel;
641                 }
642             }
643           else
644             {
645               w->sb.bottomShadowColor = botc.pixel;
646             }
647           bottom_frobbed = 1;
648         }
649     }
650
651   if (top_frobbed && bottom_frobbed)
652     {
653       int top_avg = ((topc.red / 3) + (topc.green / 3) + (topc.blue / 3));
654       int bot_avg = ((botc.red / 3) + (botc.green / 3) + (botc.blue / 3));
655       if (bot_avg > top_avg)
656         {
657           Pixel tmp = w->sb.topShadowColor;
658           w->sb.topShadowColor = w->sb.bottomShadowColor;
659           w->sb.bottomShadowColor = tmp;
660         }
661       else if (topc.pixel == botc.pixel)
662         {
663           if (botc.pixel == bg)
664             w->sb.topShadowColor = bg;
665           else
666             w->sb.bottomShadowColor = fg;
667         }
668     }
669
670   if (w->sb.topShadowColor    == w->core.background_pixel ||
671       w->sb.bottomShadowColor == w->core.background_pixel)
672     {
673       /* Assume we're in mono. This code should be okay even if we're
674        * really in color but just short on color cells -- We want the
675        * following behavior, which has been empirically determined to
676        * work well for all fg/bg combinations in mono: If the trough
677        * and slider are BOTH black, then use a white top shadow and a
678        * grey bottom shadow, otherwise use a grey top shadow and a
679        * black bottom shadow.
680        */
681
682       Pixel white = WhitePixelOfScreen (DefaultScreenOfDisplay (XtDisplay (w)));
683       Pixel black = BlackPixelOfScreen (DefaultScreenOfDisplay (XtDisplay (w)));
684
685       /* Note: core.background_pixel is the color of the slider ... */
686
687       if (w->core.background_pixel == black &&
688           w->sb.troughColor == black)
689         {
690           w->sb.topShadowColor = white;
691           w->sb.bottomShadowPixmap = w->sb.grayPixmap;
692         } else {
693           w->sb.topShadowPixmap = w->sb.grayPixmap;
694           w->sb.bottomShadowColor = black;
695         }
696     }
697 }
698
699 static void
700 make_trough_pixel (XlwScrollBarWidget w)
701 {
702   Display *dpy = XtDisplay((Widget) w);
703   Colormap cmap = w->core.colormap;
704   XColor troughC;
705
706   if (w->sb.troughColor == (Pixel)~0) w->sb.troughColor = w->core.background_pixel;
707
708   if (w->sb.troughColor == w->core.background_pixel)
709     {
710       troughC.pixel = w->core.background_pixel;
711       XQueryColor (dpy, cmap, &troughC);
712       troughC.red   = (troughC.red   * 4) / 5;
713       troughC.green = (troughC.green * 4) / 5;
714       troughC.blue  = (troughC.blue  * 4) / 5;
715       if (allocate_nearest_color (dpy, cmap, &troughC))
716         w->sb.troughColor = troughC.pixel;
717     }
718 }
719
720 /*-------------------------- Draw 3D Border -----------------------------*/
721 static void
722 draw_shadows (Display *dpy, Drawable d, GC shine_gc, GC shadow_gc,
723               int x, int y, int width, int height, int shadowT)
724 {
725   XSegment shine[10], shadow[10];
726   int i;
727
728   if (shadowT > (width  / 2)) shadowT = (width  / 2);
729   if (shadowT > (height / 2)) shadowT = (height / 2);
730   if (shadowT <= 0) return;
731
732   for (i = 0; i < shadowT; i++)
733     {
734       /*  Top segments  */
735       shine[i].x1 = x;
736       shine[i].y2 = shine[i].y1 = y + i;
737       shine[i].x2 = x + width - i - 1;
738       /*  Left segments  */
739       shine[i + shadowT].x2 = shine[i + shadowT].x1 = x + i;
740       shine[i + shadowT].y1 = y + shadowT;
741       shine[i + shadowT].y2 = y + height - i - 1;
742
743       /*  Bottom segments  */
744       shadow[i].x1 = x + i;
745       shadow[i].y2 = shadow[i].y1 = y + height - i - 1;
746       shadow[i].x2 = x + width - 1 ;
747       /*  Right segments  */
748       shadow[i + shadowT].x2 = shadow[i + shadowT].x1 = x + width - i - 1;
749       shadow[i + shadowT].y1 = y + i + 1;
750       shadow[i + shadowT].y2 = y + height - 1 ;
751     }
752
753   XDrawSegments (dpy, d, shine_gc,  shine,  shadowT * 2);
754   XDrawSegments (dpy, d, shadow_gc, shadow, shadowT * 2);
755 }
756
757 /*------------------ Draw 3D Arrows: left, up, down, right --------------*/
758 static int
759 make_vert_seg (XSegment *seg, int x1, int y1, int x2, int y2, int shadowT)
760 {
761   int i;
762
763   for (i=0; i<shadowT; i++, seg++)
764     {
765       seg->x1 = x1;
766       seg->y1 = y1++;
767       seg->x2 = x2;
768       seg->y2 = y2++;
769     }
770   return shadowT;
771 }
772
773 static int
774 make_hor_seg (XSegment *seg, int x1, int y1, int x2, int y2, int shadowT)
775 {
776   int i;
777
778   for (i=0; i<shadowT; i++, seg++)
779     {
780       seg->x1 = x1++;
781       seg->y1 = y1;
782       seg->x2 = x2++;
783       seg->y2 = y2;
784     }
785   return shadowT;
786 }
787
788 static void
789 draw_arrow_up (Display *dpy, Drawable win, GC bgGC, GC shineGC, GC shadowGC,
790                int x, int y, int width, int height, int shadowT)
791 {
792   XSegment shine[10], shadow[10];
793   XPoint triangle[3];
794   int mid;
795
796   mid = width / 2;
797
798   if (shadowT > (width  / 2)) shadowT = (width  / 2);
799   if (shadowT > (height / 2)) shadowT = (height / 2);
800   if (shadowT < 0) shadowT = 0;
801
802   /*  /  */
803   make_vert_seg (shine,
804                  x, y + height - shadowT - 1,
805                  x + mid, y, shadowT);
806   /*  _\  */
807   make_vert_seg (shadow,
808                  x, y + height - shadowT - 1,
809                  x + width - 1, y + height - shadowT - 1, shadowT);
810   make_vert_seg (shadow + shadowT,
811                  x + mid, y,
812                  x + width - 1, y + height - shadowT - 1, shadowT);
813
814   triangle[0].x = x;
815   triangle[0].y = y + height - 1;
816   triangle[1].x = x + mid;
817   triangle[1].y = y;
818   triangle[2].x = x + width - 1;
819   triangle[2].y = y + height - 1;
820
821   XFillPolygon (dpy, win, bgGC, triangle, 3, Convex, ArcChord);
822
823   XDrawSegments (dpy, win, shadowGC, shadow, shadowT * 2);
824   XDrawSegments (dpy, win, shineGC,  shine,  shadowT);
825 }
826
827 static void
828 draw_arrow_left (Display *dpy, Drawable win, GC bgGC, GC shineGC, GC shadowGC,
829                  int x, int y, int width, int height, int shadowT)
830 {
831   XSegment shine[10], shadow[10];
832   XPoint triangle[3];
833
834   int mid = width / 2;
835
836   if (shadowT > (width  / 2)) shadowT = (width  / 2);
837   if (shadowT > (height / 2)) shadowT = (height / 2);
838   if (shadowT < 0) shadowT = 0;
839
840   /*  /  */
841   make_hor_seg (shine,
842                 x, y + mid,
843                 x + width - shadowT - 1, y, shadowT);
844   /*  \|  */
845   make_hor_seg (shadow,
846                 x, y + mid,
847                 x + width - shadowT - 1, y + height - 1, shadowT);
848   make_hor_seg (shadow + shadowT,
849                 x + width - shadowT - 1, y,
850                 x + width - shadowT - 1, y + height - 1, shadowT);
851
852   triangle[0].x = x + width - 1;
853   triangle[0].y = y + height - 1;
854   triangle[1].x = x;
855   triangle[1].y = y + mid;
856   triangle[2].x = x + width - 1;
857   triangle[2].y = y;
858
859   XFillPolygon (dpy, win, bgGC, triangle, 3, Convex, ArcChord);
860
861   XDrawSegments (dpy, win, shadowGC, shadow, shadowT * 2);
862   XDrawSegments (dpy, win, shineGC,  shine,  shadowT);
863 }
864
865 static void
866 draw_arrow_down (Display *dpy, Drawable win, GC bgGC, GC shineGC, GC shadowGC,
867                  int x, int y, int width, int height, int shadowT)
868 {
869   XSegment shine[10], shadow[10];
870   XPoint triangle[3];
871   int mid;
872
873   mid = width / 2;
874
875   if (shadowT > (width  / 2)) shadowT = (width  / 2);
876   if (shadowT > (height / 2)) shadowT = (height / 2);
877   if (shadowT < 0) shadowT = 0;
878
879   /*  \-  */
880   make_vert_seg (shine,
881                  x, y,
882                  x + mid, y + height - shadowT - 1, shadowT);
883   make_vert_seg (shine + shadowT,
884                  x, y,
885                  x + width - 1, y, shadowT);
886   /*  /  */
887   make_vert_seg (shadow,
888                  x + width - 1, y,
889                  x + mid, y + height - shadowT - 1, shadowT);
890
891   triangle[0].x = x;
892   triangle[0].y = y;
893   triangle[1].x = x + mid;
894   triangle[1].y = y + height - 1;
895   triangle[2].x = x + width - 1;
896   triangle[2].y = y;
897
898   XFillPolygon (dpy, win, bgGC, triangle, 3, Convex, ArcChord);
899
900   XDrawSegments (dpy, win, shadowGC, shadow, shadowT);
901   XDrawSegments (dpy, win, shineGC,  shine,  shadowT * 2);
902 }
903
904 static void
905 draw_arrow_right (Display *dpy, Drawable win, GC bgGC, GC shineGC, GC shadowGC,
906                   int x, int y, int width, int height, int shadowT)
907 {
908   XSegment shine[10], shadow[10];
909   XPoint triangle[3];
910   int mid;
911
912   mid = width / 2;
913
914   if (shadowT > (width  / 2)) shadowT = (width  / 2);
915   if (shadowT > (height / 2)) shadowT = (height / 2);
916   if (shadowT < 0) shadowT = 0;
917
918   /*  |\  */
919   make_hor_seg (shine,
920                 x, y,
921                 x + width - shadowT - 1, y + mid, shadowT);
922   make_hor_seg (shine + shadowT,
923                 x, y,
924                 x, y + height - 1, shadowT);
925   /*  /  */
926   make_hor_seg (shadow,
927                 x, y + height - 1,
928                 x + width - shadowT - 1, y + mid, shadowT);
929
930   triangle[0].x = x + 1;
931   triangle[0].y = y + height - 1;
932   triangle[1].x = x + width  - 1;
933   triangle[1].y = y + mid;
934   triangle[2].x = x + 1;
935   triangle[2].y = y;
936
937   XFillPolygon (dpy, win, bgGC, triangle, 3, Convex, ArcChord);
938
939   XDrawSegments (dpy, win, shadowGC, shadow, shadowT);
940   XDrawSegments (dpy, win, shineGC,  shine,  shadowT * 2);
941 }
942
943 static void
944 draw_dimple (Display *dpy, Drawable win, GC shine, GC shadow,
945              int x, int y, int width, int height)
946 {
947   XDrawArc (dpy, win, shine,  x, y, width, height, 46*64, 180*64);
948   XDrawArc (dpy, win, shadow, x, y, width, height, 45*64, -179*64);
949 }
950
951 /*------- Scrollbar values -> pixels, pixels -> scrollbar values --------*/
952
953 static void
954 seg_pixel_sizes (XlwScrollBarWidget w, int *above_return,
955                  int *ss_return, int *below_return)
956 {
957   float total, height, fuz;
958   int value, above, ss, below;
959
960   height = widget_h (w);
961   if (w->sb.showArrows) height -= (2 * arrow_h (w));
962
963   value = w->sb.value - w->sb.minimum;
964
965   total = w->sb.maximum - w->sb.minimum;
966   fuz   = total / 2;
967
968   ss    = (int) ((height * w->sb.sliderSize + fuz) / total);
969   above = (int) ((height * value + fuz) / total);
970   below = (int) ((height) - (ss + above));
971
972   /* Don't let slider get smaller than SS_MIN */
973   if (ss < SS_MIN)
974     {
975       /* add a percent amount for integer rounding */
976       float tmp = (((float) (SS_MIN - ss) * (float) value) / total) + 0.5;
977
978       above -= (int) tmp;
979       ss = SS_MIN;
980       below = (int) ((height) - (ss + above));
981
982       if (above < 0)
983         {
984           above = 0;
985           below = (int) (height - ss);
986         }
987       if (below < 0)
988         {
989           above = (int) (height - ss);
990           below = 0;
991         }
992       if (ss > height)
993         {
994           above = 0;
995           ss    = (int) height;
996           below = 0;
997         }
998     }
999
1000   *above_return = above;
1001   *ss_return    = ss;
1002   *below_return = below;
1003
1004   CHECK (w);
1005 }
1006
1007 static void
1008 verify_values (XlwScrollBarWidget w)
1009 {
1010   int total = w->sb.maximum - w->sb.minimum;
1011
1012   if (w->sb.sliderSize > total)
1013       w->sb.sliderSize = total;
1014
1015   if (w->sb.pageIncrement > total)
1016       w->sb.pageIncrement = total;
1017
1018   if (w->sb.increment > total)
1019       w->sb.increment = total;
1020
1021   if (w->sb.value < w->sb.minimum)
1022       w->sb.value = w->sb.minimum;
1023
1024   if (w->sb.value > w->sb.maximum)
1025       w->sb.value = w->sb.maximum;
1026
1027   if (w->sb.sliderSize > w->sb.maximum - w->sb.value)
1028       w->sb.sliderSize = w->sb.maximum - w->sb.value;
1029 }
1030
1031 static int
1032 value_from_pixel (XlwScrollBarWidget w, int above)
1033 {
1034   float total, height, fuz;
1035   int value, ss;
1036
1037   height = widget_h (w);
1038   if (w->sb.showArrows)
1039     height -= (2 * arrow_h (w));
1040
1041   total = w->sb.maximum - w->sb.minimum;
1042   fuz = height / 2;
1043
1044   ss = (int) ((height * w->sb.sliderSize + (total / 2)) / total);
1045
1046   if (ss < SS_MIN)
1047     {
1048       /* add a percent amount for integer rounding */
1049       above += (int) ((((SS_MIN - ss) * above) + fuz) / height);
1050     }
1051
1052   {
1053     /* Prevent SIGFPE's that would occur if we don't truncate the value. */
1054     float floatval = w->sb.minimum + ((float)(above * total + fuz) / height);
1055     if (floatval >= (float) INT_MAX)
1056       value = INT_MAX;
1057     else if (floatval <= (float) INT_MIN)
1058       value = INT_MIN;
1059     else
1060       value = (int) floatval;
1061   }
1062
1063   return value;
1064 }
1065
1066
1067 static void
1068 redraw_dimple (XlwScrollBarWidget w, Display *dpy, Window win,
1069                int x, int y, int width, int height)
1070 {
1071   if (SLIDER_DIMPLE == slider_style (w))
1072     {
1073       int size;
1074       int slider_p = (w->sb.armed == ARM_SLIDER);
1075       GC shine  = slider_p ? w->sb.bottomShadowGC : w->sb.topShadowGC;
1076       GC shadow = slider_p ? w->sb.topShadowGC    : w->sb.bottomShadowGC;
1077       int shadowT = w->sb.shadowThickness;
1078
1079       x += shadowT;
1080       y += shadowT;
1081       width  -= 2*shadowT;
1082       height -= 2*shadowT;
1083
1084       size = (width < height ? width : height) * 3 / 4;
1085
1086       if (size%2 != (width < height ? width : height)%2) size--;
1087
1088       DBUG (fprintf (stderr, "%d %d\n",
1089                      x + (width / 2) - (size / 2) - 2*shadowT,
1090                      width - size - shadowT));
1091
1092       draw_dimple (dpy, win, shine, shadow,
1093                    x + (width  / 2) - (size / 2),
1094                    y + (height / 2) - (size / 2),
1095                    size, size);
1096     }
1097 }
1098
1099 static void
1100 draw_slider (XlwScrollBarWidget w, int above, int ss, int below)
1101 {
1102   Display *dpy = XtDisplay ((Widget) w);
1103   Window   win = XtWindow  ((Widget) w);
1104
1105   int x       = widget_x (w);
1106   int y       = widget_y (w);
1107   int width   = widget_w (w);
1108   int height  = widget_h (w);
1109   int shadowT = w->sb.shadowThickness;
1110   int vert_p  = VERT (w);
1111
1112   if (shadowT > (width  / 2)) shadowT = (width  / 2);
1113   if (shadowT > (height / 2)) shadowT = (height / 2);
1114   if (shadowT < 0)            shadowT = 0;
1115
1116   if (w->sb.showArrows && !arrow_same_end (w))
1117     y += arrow_h (w);
1118
1119   /* trough above slider */
1120   if (above > 0)
1121     {
1122       if (vert_p)
1123         XClearArea (dpy, win, x, y, width, above, False);
1124       else
1125         XClearArea (dpy, win, y, x, above, width, False);
1126     }
1127
1128   /* slider */
1129   if (vert_p)
1130     {
1131       draw_shadows (dpy, win, w->sb.topShadowGC, w->sb.bottomShadowGC,
1132                     x, y + above, width, ss, shadowT);
1133       XFillRectangle (dpy, win, w->sb.backgroundGC,
1134                       x+shadowT, y + above + shadowT,
1135                       width-2*shadowT, ss-2*shadowT);
1136       redraw_dimple (w, dpy, win, x, y + above, width, ss);
1137     }
1138   else
1139     {
1140       draw_shadows (dpy, win, w->sb.topShadowGC, w->sb.bottomShadowGC,
1141                     y + above, x, ss, width, shadowT);
1142       XFillRectangle (dpy, win, w->sb.backgroundGC,
1143                       y + above + shadowT, x+shadowT,
1144                       ss-2*shadowT, width-2*shadowT);
1145       redraw_dimple (w, dpy, win, y + above, x, ss, width);
1146     }
1147
1148   /* trough below slider */
1149   if (below > 0)
1150     {
1151       if (vert_p)
1152         XClearArea (dpy, win, x, y + above + ss, width, below, False);
1153       else
1154         XClearArea (dpy, win, y + above + ss, x, below, width, False);
1155     }
1156
1157   CHECK (w);
1158 }
1159
1160 static void
1161 redraw_up_arrow (XlwScrollBarWidget w, Boolean armed, Boolean clear_behind)
1162 {
1163   Display *dpy = XtDisplay ((Widget) w);
1164   Window   win = XtWindow  ((Widget) w);
1165
1166   int x       = widget_x (w);
1167   int y       = widget_y (w);
1168   int width   = widget_w (w);
1169   int height  = widget_h (w);
1170   int shadowT = w->sb.shadowThickness;
1171   int arrow_height = arrow_h (w);
1172
1173   GC bg     = w->sb.backgroundGC;
1174   GC shine  = armed ? w->sb.bottomShadowGC : w->sb.topShadowGC;
1175   GC shadow = armed ? w->sb.topShadowGC    : w->sb.bottomShadowGC;
1176
1177   if (VERT (w))
1178     {
1179       if (arrow_same_end (w))
1180         y += height - 2 * arrow_height;
1181       if (clear_behind)
1182         XClearArea (dpy, win, x, y, width, arrow_height + 1, False);
1183       draw_arrow_up (dpy, win, bg, shine, shadow,
1184                      x + (width - arrow_height)/2, y,
1185                      arrow_height, arrow_height, shadowT);
1186     }
1187   else
1188     {
1189       if (arrow_same_end (w))
1190         y += height - 2 * arrow_height;
1191       if (clear_behind)
1192         XClearArea (dpy, win, y, x, arrow_height + 1, height, False);
1193       draw_arrow_left (dpy, win, bg, shine, shadow,
1194                        y, x + (width - arrow_height)/2,
1195                        arrow_height, arrow_height, shadowT);
1196     }
1197 }
1198
1199 static void
1200 redraw_down_arrow (XlwScrollBarWidget w, Boolean armed, Boolean clear_behind)
1201 {
1202   Display *dpy = XtDisplay ((Widget) w);
1203   Window   win = XtWindow  ((Widget) w);
1204
1205   int x       = widget_x (w);
1206   int y       = widget_y (w);
1207   int width   = widget_w (w);
1208   int height  = widget_h (w);
1209   int shadowT = w->sb.shadowThickness;
1210   int arrow_height = arrow_h (w);
1211
1212   GC bg     = w->sb.backgroundGC;
1213   GC shine  = armed ? w->sb.bottomShadowGC : w->sb.topShadowGC;
1214   GC shadow = armed ? w->sb.topShadowGC    : w->sb.bottomShadowGC;
1215
1216   if (VERT (w))
1217     {
1218       if (clear_behind)
1219         XClearArea (dpy, win, x, y + height - arrow_height, width,
1220                     arrow_height + 1, False);
1221       draw_arrow_down (dpy, win, bg, shine, shadow,
1222                        x + (width - arrow_height)/2,
1223                        y + height - arrow_height + 1,
1224                        arrow_height, arrow_height, shadowT);
1225     }
1226   else
1227     {
1228       if (clear_behind)
1229         XClearArea (dpy, win, y + height - arrow_height, x,
1230                     arrow_height + 1, height, False);
1231       draw_arrow_right (dpy, win, bg, shine, shadow,
1232                         y + height - arrow_height + 1,
1233                         x + (width - arrow_height)/2,
1234                         arrow_height, arrow_height, shadowT);
1235     }
1236 }
1237
1238 static void
1239 redraw_everything (XlwScrollBarWidget w, Region region, Boolean behind_arrows)
1240 {
1241   Display *dpy = XtDisplay ((Widget) w);
1242   Window   win = XtWindow  ((Widget) w);
1243
1244   if (w->sb.showArrows)
1245     {
1246       if (region == NULL)
1247         {
1248           redraw_up_arrow   (w, False, behind_arrows);
1249           redraw_down_arrow (w, False, behind_arrows);
1250         }
1251       else
1252         {
1253           int x        = widget_x (w);
1254           int y        = widget_y (w);
1255           int width    = widget_w (w);
1256           int height   = widget_h (w);
1257           int arrow_height = arrow_h (w);
1258           int ax = x, ay = y;
1259
1260           if (arrow_same_end (w))
1261             {
1262               if (VERT (w))
1263                 ay = y + height - arrow_height - arrow_height;
1264               else
1265                 ax = x + height - arrow_height - arrow_height;
1266             }
1267           if (XRectInRegion (region, ax, ay, width, width))
1268             redraw_up_arrow (w, False, behind_arrows);
1269
1270           if (VERT (w))
1271             ay = y + height - arrow_height;
1272           else
1273             ax = x + height - arrow_height;
1274           if (XRectInRegion (region, ax, ay, width, width))
1275             redraw_down_arrow (w, False, behind_arrows);
1276         }
1277     }
1278
1279   draw_shadows (dpy, win, w->sb.bottomShadowGC, w->sb.topShadowGC, 0, 0,
1280                 w->core.width, w->core.height, w->sb.shadowThickness);
1281
1282   draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1283 }
1284
1285 /*-------------------------- Method Functions ---------------------------*/
1286
1287 static void
1288 Initialize (Widget treq, Widget tnew, ArgList args, Cardinal *num_args)
1289 {
1290   XlwScrollBarWidget request = (XlwScrollBarWidget) treq;
1291   XlwScrollBarWidget w = (XlwScrollBarWidget) tnew;
1292   Display *dpy = XtDisplay ((Widget) w);
1293   Window win = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
1294
1295   if (request->core.width  == 0) w->core.width  += (VERT (w) ? 12 : 25);
1296   if (request->core.height == 0) w->core.height += (VERT (w) ? 25 : 12);
1297
1298   verify_values (w);
1299
1300   w->sb.lastY = 0;
1301   w->sb.above = 0;
1302   w->sb.ss    = 0;
1303   w->sb.below = 0;
1304   w->sb.armed = ARM_NONE;
1305   w->sb.forced_scroll = FORCED_SCROLL_NONE;
1306
1307   if (w->sb.shadowThickness > 5) w->sb.shadowThickness = 5;
1308
1309   w->sb.grayPixmap =
1310     XCreatePixmapFromBitmapData (dpy, win, (char *) gray_bits, gray_width,
1311                                  gray_height, 1, 0, 1);
1312
1313   make_trough_pixel (w);
1314
1315   make_shadow_pixels (w);
1316
1317   w->sb.backgroundGC =
1318     get_gc (w, w->core.background_pixel, w->core.background_pixel, None);
1319   w->sb.topShadowGC =
1320     get_gc (w, w->sb.topShadowColor, w->core.background_pixel,
1321             w->sb.topShadowPixmap);
1322   w->sb.bottomShadowGC =
1323     get_gc (w, w->sb.bottomShadowColor, w->core.background_pixel,
1324             w->sb.bottomShadowPixmap);
1325
1326   w->sb.fullRedrawNext = True;
1327
1328   w->sb.timerActive = False;
1329 }
1330
1331 static void
1332 Destroy (Widget widget)
1333 {
1334   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1335   Display *dpy = XtDisplay ((Widget) w);
1336
1337   XtReleaseGC (widget, w->sb.bottomShadowGC);
1338   XtReleaseGC (widget, w->sb.topShadowGC);
1339   XtReleaseGC (widget, w->sb.backgroundGC);
1340
1341   XFreePixmap (dpy, w->sb.grayPixmap);
1342
1343   if (w->sb.timerActive)
1344     {
1345       XtRemoveTimeOut (w->sb.timerId);
1346       w->sb.timerActive = False; /* Should be a no-op, but you never know */
1347     }
1348 }
1349
1350 static void
1351 Realize (Widget widget, XtValueMask *valuemask, XSetWindowAttributes *attr)
1352 {
1353   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1354   Display *dpy = XtDisplay ((Widget) w);
1355   Window win;
1356   XSetWindowAttributes win_attr;
1357
1358   (*coreClassRec.core_class.realize)(widget, valuemask, attr);
1359
1360   win = XtWindow ((Widget) w);
1361
1362   seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1363
1364   XSetWindowBackground (dpy, win, w->sb.troughColor);
1365
1366   /* Change bit gravity so widget is not cleared on resize */
1367   win_attr.bit_gravity = NorthWestGravity;
1368   XChangeWindowAttributes (dpy, win, CWBitGravity , &win_attr);
1369
1370 }
1371
1372 static void
1373 Resize (Widget widget)
1374 {
1375   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1376   Display *dpy = XtDisplay ((Widget) w);
1377   Window win   = XtWindow  ((Widget) w);
1378
1379   if (XtIsRealized (widget))
1380     {
1381       DBUG (fprintf (stderr, "Resize = %08lx\n", w));
1382
1383       seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1384
1385       /* redraw_everything (w, NULL, True); */
1386
1387       w->sb.fullRedrawNext = True;
1388       /* Force expose event */
1389       XClearArea (dpy, win, widget_x (w), widget_y (w), 1, 1, True);
1390     }
1391
1392   if (w->sb.timerActive)
1393     {
1394       XtRemoveTimeOut (w->sb.timerId);
1395       w->sb.timerActive = False;
1396     }
1397 }
1398
1399 static void
1400 Redisplay (Widget widget, XEvent *event, Region region)
1401 {
1402   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1403
1404   DBUG (fprintf (stderr, "Redisplay = %08lx\n", w));
1405
1406   if (XtIsRealized (widget))
1407     {
1408       if (w->sb.fullRedrawNext)
1409         redraw_everything (w, NULL, True);
1410       else
1411         redraw_everything (w, region, False);
1412       w->sb.fullRedrawNext = False;
1413     }
1414 }
1415
1416 static Boolean
1417 SetValues (Widget current, Widget request, Widget neww,
1418            ArgList args, Cardinal *num_args)
1419 {
1420   XlwScrollBarWidget cur = (XlwScrollBarWidget) current;
1421   XlwScrollBarWidget w = (XlwScrollBarWidget) neww;
1422   Boolean do_redisplay = False;
1423
1424   if (cur->sb.troughColor != w->sb.troughColor)
1425     {
1426       if (XtIsRealized ((Widget) w))
1427         {
1428           XSetWindowBackground (XtDisplay((Widget) w), XtWindow ((Widget) w),
1429                                 w->sb.troughColor);
1430           do_redisplay = True;
1431         }
1432     }
1433
1434   if (cur->core.background_pixel != w->core.background_pixel)
1435     {
1436       XtReleaseGC ((Widget)cur, cur->sb.backgroundGC);
1437       w->sb.backgroundGC =
1438         get_gc (w, w->core.background_pixel, w->core.background_pixel, None);
1439       do_redisplay = True;
1440     }
1441
1442   if (cur->sb.topShadowColor != w->sb.topShadowColor ||
1443       cur->sb.topShadowPixmap != w->sb.topShadowPixmap)
1444     {
1445       XtReleaseGC ((Widget)cur, cur->sb.topShadowGC);
1446       w->sb.topShadowGC =
1447         get_gc (w, w->sb.topShadowColor, w->core.background_pixel,
1448                 w->sb.topShadowPixmap);
1449       do_redisplay = True;
1450     }
1451
1452   if (cur->sb.bottomShadowColor != w->sb.bottomShadowColor ||
1453       cur->sb.bottomShadowPixmap != w->sb.bottomShadowPixmap)
1454     {
1455       XtReleaseGC ((Widget)cur, cur->sb.bottomShadowGC);
1456       w->sb.bottomShadowGC =
1457         get_gc (w, w->sb.bottomShadowColor, w->core.background_pixel,
1458                 w->sb.bottomShadowPixmap);
1459       do_redisplay = True;
1460     }
1461
1462   if (cur->sb.orientation != w->sb.orientation)
1463     do_redisplay = True;
1464
1465
1466   if (cur->sb.minimum       != w->sb.minimum       ||
1467       cur->sb.maximum       != w->sb.maximum       ||
1468       cur->sb.sliderSize    != w->sb.sliderSize    ||
1469       cur->sb.value         != w->sb.value         ||
1470       cur->sb.pageIncrement != w->sb.pageIncrement ||
1471       cur->sb.increment     != w->sb.increment)
1472     {
1473       verify_values (w);
1474       if (XtIsRealized ((Widget) w))
1475         {
1476           seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1477           draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1478         }
1479     }
1480
1481   if (w->sb.shadowThickness > 5) w->sb.shadowThickness = 5;
1482
1483   return do_redisplay;
1484 }
1485
1486 void
1487 XlwScrollBarGetValues (Widget widget, int *value, int *sliderSize,
1488                        int *increment, int *pageIncrement)
1489 {
1490   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1491
1492   if (w && XtClass ((Widget) w) == xlwScrollBarWidgetClass)
1493     {
1494       if (value)         *value         = w->sb.value;
1495       if (sliderSize)    *sliderSize    = w->sb.sliderSize;
1496       if (increment)     *increment     = w->sb.increment;
1497       if (pageIncrement) *pageIncrement = w->sb.pageIncrement;
1498     }
1499 }
1500
1501 void
1502 XlwScrollBarSetValues (Widget widget, int value, int sliderSize,
1503                        int increment, int pageIncrement, Boolean notify)
1504 {
1505   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1506
1507   if (w && XtClass ((Widget) w) == xlwScrollBarWidgetClass &&
1508       (w->sb.value         != value         ||
1509        w->sb.sliderSize    != sliderSize    ||
1510        w->sb.increment     != increment     ||
1511        w->sb.pageIncrement != pageIncrement))
1512     {
1513       int last_value = w->sb.value;
1514
1515       w->sb.value         = value;
1516       w->sb.sliderSize    = sliderSize;
1517       w->sb.increment     = increment;
1518       w->sb.pageIncrement = pageIncrement;
1519
1520       verify_values (w);
1521
1522       if (XtIsRealized (widget))
1523         {
1524           seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1525           draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1526
1527           if (w->sb.value != last_value && notify)
1528             call_callbacks (w, XmCR_VALUE_CHANGED, w->sb.value, 0, NULL);
1529         }
1530     }
1531 }
1532
1533 /*-------------------------- Action Functions ---------------------------*/
1534
1535 static void
1536 timer (XtPointer data, XtIntervalId *id)
1537 {
1538   XlwScrollBarWidget w = (XlwScrollBarWidget) data;
1539   w->sb.timerActive = False;
1540
1541   if (w->sb.armed != ARM_NONE)
1542     {
1543       int last_value = w->sb.value;
1544       int reason;
1545
1546       switch (w->sb.armed)
1547         {
1548         case ARM_PAGEUP:
1549           decrement_value (w, w->sb.pageIncrement);
1550           reason = XmCR_PAGE_DECREMENT;
1551           break;
1552         case ARM_PAGEDOWN:
1553           increment_value (w, w->sb.pageIncrement);
1554           reason = XmCR_PAGE_INCREMENT;
1555           break;
1556         case ARM_UP:
1557           decrement_value (w, w->sb.increment);
1558           reason = XmCR_DECREMENT;
1559           break;
1560         case ARM_DOWN:
1561           increment_value (w, w->sb.increment);
1562           reason = XmCR_INCREMENT;
1563           break;
1564         default:
1565           reason = XmCR_NONE;
1566         }
1567
1568       verify_values (w);
1569
1570       if (last_value != w->sb.value)
1571         {
1572           seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1573           draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1574
1575           call_callbacks (w, reason, w->sb.value, 0, NULL);
1576
1577           w->sb.timerId =
1578             XtAppAddTimeOut (XtWidgetToApplicationContext ((Widget) w),
1579                              (unsigned long) w->sb.repeatDelay,
1580                              timer,  (XtPointer) w);
1581           w->sb.timerActive = True;
1582         }
1583     }
1584 }
1585
1586 static button_where
1587 what_button (XlwScrollBarWidget w, int mouse_x, int mouse_y)
1588 {
1589   int width   = widget_w (w);
1590   int height  = widget_h (w);
1591   int arrow_height = arrow_h (w);
1592
1593   mouse_x -= widget_x (w);
1594   mouse_y -= widget_y (w);
1595
1596   if (mouse_x < 0 || mouse_x >= width ||
1597       mouse_y < 0 || mouse_y >= height)
1598     return BUTTON_NONE;
1599
1600   if (w->sb.showArrows)
1601     {
1602       if (mouse_y >= (height -= arrow_height))
1603         return BUTTON_DOWN_ARROW;
1604
1605       if (arrow_same_end (w))
1606         {
1607           if (mouse_y >= (height -= arrow_height))
1608             return BUTTON_UP_ARROW;
1609         }
1610       else
1611         if ( (mouse_y -= arrow_height) < 0)
1612           return BUTTON_UP_ARROW;
1613     }
1614
1615   if ( (mouse_y -= w->sb.above) < 0)
1616     return BUTTON_TROUGH_ABOVE;
1617
1618   if ( (mouse_y -= w->sb.ss) < 0)
1619     return BUTTON_SLIDER;
1620
1621   return BUTTON_TROUGH_BELOW;
1622 }
1623
1624 static void
1625 Select (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1626 {
1627   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1628   button_where sb_button;
1629
1630   int mouse_x = event_x (w, event);
1631   int mouse_y = event_y (w, event);
1632
1633   int last_value = w->sb.savedValue = w->sb.value;
1634   int reason     = XmCR_NONE;
1635
1636   XtGrabKeyboard ((Widget) w, False, GrabModeAsync, GrabModeAsync,
1637                   event->xbutton.time);
1638
1639   sb_button = what_button (w, mouse_x, mouse_y);
1640
1641   if (w->sb.forced_scroll != FORCED_SCROLL_NONE)
1642     {
1643       switch (sb_button)
1644         {
1645         case BUTTON_TROUGH_ABOVE:
1646         case BUTTON_TROUGH_BELOW:
1647         case BUTTON_SLIDER:
1648           sb_button= BUTTON_NONE; /* cause next switch to fall through */
1649           if (w->sb.forced_scroll == FORCED_SCROLL_UPLEFT)
1650             {
1651               decrement_value (w, w->sb.pageIncrement);
1652               w->sb.armed = ARM_PAGEUP;
1653               reason      = XmCR_PAGE_DECREMENT;
1654               break;
1655             }
1656           else if (w->sb.forced_scroll == FORCED_SCROLL_DOWNRIGHT)
1657             {
1658               increment_value (w, w->sb.pageIncrement);
1659               w->sb.armed = ARM_PAGEDOWN;
1660               reason      = XmCR_PAGE_INCREMENT;
1661               break;
1662             }
1663           abort();
1664         default:
1665           ; /* Do nothing */
1666         }
1667     }
1668
1669   switch (sb_button)
1670     {
1671     case BUTTON_TROUGH_ABOVE:
1672       decrement_value (w, w->sb.pageIncrement);
1673       w->sb.armed = ARM_PAGEUP;
1674       reason      = XmCR_PAGE_DECREMENT;
1675       break;
1676     case BUTTON_TROUGH_BELOW:
1677       increment_value (w, w->sb.pageIncrement);
1678       w->sb.armed = ARM_PAGEDOWN;
1679       reason      = XmCR_PAGE_INCREMENT;
1680       break;
1681     case BUTTON_SLIDER:
1682       w->sb.lastY = mouse_y;
1683       w->sb.armed = ARM_SLIDER;
1684       draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1685       break;
1686     case BUTTON_UP_ARROW:
1687       if (event->xbutton.state & ControlMask)
1688         {
1689           w->sb.value = w->sb.minimum;
1690           reason      = XmCR_TO_TOP;
1691         }
1692       else
1693         {
1694           decrement_value (w, w->sb.increment);
1695           reason      = XmCR_DECREMENT;
1696         }
1697       w->sb.armed = ARM_UP;
1698       redraw_up_arrow (w, True, False);
1699       break;
1700     case BUTTON_DOWN_ARROW:
1701       if (event->xbutton.state & ControlMask)
1702         {
1703           w->sb.value = w->sb.maximum;
1704           reason      = XmCR_TO_BOTTOM;
1705         }
1706       else
1707         {
1708           increment_value (w, w->sb.increment);
1709           reason      = XmCR_INCREMENT;
1710         }
1711       w->sb.armed = ARM_DOWN;
1712       redraw_down_arrow (w, True, False);
1713       break;
1714     case BUTTON_NONE:
1715       ; /* Do nothing */
1716     }
1717
1718   verify_values (w);
1719
1720   if (last_value != w->sb.value)
1721     {
1722       seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1723       draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1724
1725       call_callbacks (w, reason, w->sb.value, mouse_y, event);
1726
1727       if (w->sb.timerActive)
1728         XtRemoveTimeOut (w->sb.timerId);
1729
1730       w->sb.timerId =
1731         XtAppAddTimeOut (XtWidgetToApplicationContext ((Widget) w),
1732                          (unsigned long) w->sb.initialDelay,
1733                          timer,  (XtPointer) w);
1734       w->sb.timerActive = True;
1735     }
1736
1737   CHECK (w);
1738 }
1739
1740 static void
1741 PageDownOrRight (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1742 {
1743   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1744   w->sb.forced_scroll = FORCED_SCROLL_DOWNRIGHT;
1745   Select (widget, event, parms, num_parms);
1746   w->sb.forced_scroll = FORCED_SCROLL_NONE;
1747 }
1748
1749 static void
1750 PageUpOrLeft (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1751 {
1752   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1753   w->sb.forced_scroll = FORCED_SCROLL_UPLEFT;
1754   Select (widget, event, parms, num_parms);
1755   w->sb.forced_scroll = FORCED_SCROLL_NONE;
1756 }
1757
1758 static void
1759 Drag (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1760 {
1761   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1762
1763   if (w->sb.armed == ARM_SLIDER)
1764     {
1765       int mouse_y = event_y (w, event);
1766       int diff    = mouse_y - w->sb.lastY;
1767
1768       if (diff < -(w->sb.above)) /* up */
1769         {
1770           mouse_y -= (diff + w->sb.above);
1771           diff = -(w->sb.above);
1772         }
1773       else if (diff > w->sb.below) /* down */
1774         {
1775           mouse_y -= (diff - w->sb.below);
1776           diff = w->sb.below;
1777         }
1778
1779       if (diff)
1780         {
1781           w->sb.above += diff;
1782           w->sb.below -= diff;
1783
1784           draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1785
1786           w->sb.lastY = mouse_y;
1787
1788           w->sb.value = value_from_pixel (w, w->sb.above);
1789           verify_values (w);
1790           CHECK (w);
1791
1792           call_callbacks (w, XmCR_DRAG, w->sb.value, event_y (w, event), event);
1793         }
1794     }
1795   CHECK (w);
1796 }
1797
1798 static void
1799 Release (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1800 {
1801   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1802
1803   switch (w->sb.armed)
1804     {
1805     case ARM_SLIDER:
1806       call_callbacks (w, XmCR_VALUE_CHANGED, w->sb.value, event_y (w, event), event);
1807       w->sb.armed = ARM_NONE;
1808       draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1809       break;
1810     case ARM_UP:
1811       redraw_up_arrow (w, False, False);
1812       break;
1813     case ARM_DOWN:
1814       redraw_down_arrow (w, False, False);
1815       break;
1816     default:
1817       ; /* Do nothing */
1818     }
1819
1820   XtUngrabKeyboard ((Widget) w, event->xbutton.time);
1821
1822   w->sb.armed = ARM_NONE;
1823 }
1824
1825 static void
1826 Jump (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1827 {
1828   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1829   int last_value;
1830
1831   int mouse_x = event_x (w, event);
1832   int mouse_y = event_y (w, event);
1833
1834   int scroll_region_y = widget_y (w);
1835   int scroll_region_h = widget_h (w);
1836
1837   if (w->sb.showArrows)
1838     {
1839       int arrow_height = arrow_h (w);
1840       scroll_region_h -= 2 * arrow_height;
1841       if (!arrow_same_end (w))
1842         scroll_region_y += arrow_height;
1843     }
1844
1845   XtGrabKeyboard ((Widget) w, False, GrabModeAsync, GrabModeAsync,
1846                   event->xbutton.time);
1847
1848   switch (what_button (w, mouse_x, mouse_y))
1849     {
1850     case BUTTON_TROUGH_ABOVE:
1851     case BUTTON_TROUGH_BELOW:
1852     case BUTTON_SLIDER:
1853       w->sb.savedValue = w->sb.value;
1854
1855       last_value = w->sb.value;
1856
1857       w->sb.above = mouse_y - (w->sb.ss / 2) - scroll_region_y;
1858       if (w->sb.above < 0)
1859         w->sb.above = 0;
1860       else if (w->sb.above + w->sb.ss > scroll_region_h)
1861         w->sb.above = scroll_region_h - w->sb.ss;
1862
1863       w->sb.below = scroll_region_h - w->sb.ss - w->sb.above;
1864
1865       w->sb.armed = ARM_SLIDER;
1866       draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1867
1868       w->sb.value = value_from_pixel (w, w->sb.above);
1869       verify_values (w);
1870       CHECK (w);
1871
1872       w->sb.lastY = mouse_y;
1873
1874       if (w->sb.value != last_value)
1875         call_callbacks (w, XmCR_DRAG, w->sb.value, mouse_y, event);
1876
1877       break;
1878     default:
1879       ; /* Do nothing */
1880     }
1881   CHECK (w);
1882 }
1883
1884 static void
1885 Abort (Widget widget, XEvent *event, String *parms, Cardinal *num_parms)
1886 {
1887   XlwScrollBarWidget w = (XlwScrollBarWidget) widget;
1888
1889   if (w->sb.armed != ARM_NONE)
1890     {
1891       if (w->sb.value != w->sb.savedValue)
1892         {
1893           w->sb.value = w->sb.savedValue;
1894
1895           seg_pixel_sizes (w, &w->sb.above, &w->sb.ss, &w->sb.below);
1896           draw_slider (w, w->sb.above, w->sb.ss, w->sb.below);
1897
1898           call_callbacks (w, XmCR_VALUE_CHANGED, w->sb.value,
1899                           event_y (w, event), event);
1900         }
1901
1902       switch (w->sb.armed)
1903         {
1904         case ARM_UP:   redraw_up_arrow   (w, False, False); break;
1905         case ARM_DOWN: redraw_down_arrow (w, False, False); break;
1906         default: ; /* Do nothing */
1907         }
1908
1909       w->sb.armed = ARM_NONE;
1910
1911       XtUngrabKeyboard ((Widget) w, event->xbutton.time);
1912     }
1913 }