#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xmu/Drawing.h>
#include <X11/Xaw/ScrollbarP.h>
#include <X11/Xaw/XawInit.h>
#include "Private.h"
#define NoButton -1
#define PICKLENGTH(widget, x, y) \
(((widget)->scrollbar.orientation == XtorientHorizontal) ? (x) : (y))
static void XawScrollbarClassInitialize(void);
static void XawScrollbarDestroy(Widget);
static void XawScrollbarInitialize(Widget, Widget, ArgList, Cardinal*_args);
static void XawScrollbarRealize(Widget, Mask*, XSetWindowAttributes*);
static void XawScrollbarRedisplay(Widget, XEvent*, Region);
static void XawScrollbarResize(Widget);
static Boolean XawScrollbarSetValues(Widget, Widget, Widget,
ArgList, Cardinal*);
static Boolean CompareEvents(XEvent*, XEvent*);
static void CreateGC(Widget);
static float FloatInRange(float, float, float);
static float FractionLoc(ScrollbarWidget, int, int);
static void ExtractPosition(XEvent*, Position*, Position*);
static int InRange(int, int, int);
static void FillArea(ScrollbarWidget, int, int, int);
static Bool LookAhead(Widget, XEvent*);
static void PaintThumb(ScrollbarWidget);
static Bool PeekNotifyEvent(Display*, XEvent*, char*);
static void SetDimensions(ScrollbarWidget);
static void EndScroll(Widget, XEvent*, String*, Cardinal*);
static void MoveThumb(Widget, XEvent*, String*, Cardinal*);
static void NotifyScroll(Widget, XEvent*, String*, Cardinal*);
static void NotifyThumb(Widget, XEvent*, String*, Cardinal*);
static void StartScroll(Widget, XEvent*, String*, Cardinal*);
static char defaultTranslations[] =
"<Btn1Down>:" "StartScroll(Forward)\n"
"<Btn2Down>:" "StartScroll(Continuous) MoveThumb() NotifyThumb()\n"
"<Btn3Down>:" "StartScroll(Backward)\n"
"<Btn2Motion>:" "MoveThumb() NotifyThumb()\n"
"<BtnUp>:" "NotifyScroll(Proportional) EndScroll()\n";
static float floatZero = 0.0;
#define Offset(field) XtOffsetOf(ScrollbarRec, field)
static XtResource resources[] = {
{
XtNlength,
XtCLength,
XtRDimension,
sizeof(Dimension),
Offset(scrollbar.length),
XtRImmediate,
(XtPointer)1
},
{
XtNthickness,
XtCThickness,
XtRDimension,
sizeof(Dimension),
Offset(scrollbar.thickness),
XtRImmediate,
(XtPointer)14
},
{
XtNorientation,
XtCOrientation,
XtROrientation,
sizeof(XtOrientation),
Offset(scrollbar.orientation),
XtRImmediate,
(XtPointer)XtorientVertical
},
{
XtNscrollProc,
XtCCallback,
XtRCallback,
sizeof(XtPointer),
Offset(scrollbar.scrollProc),
XtRCallback,
NULL
},
{
XtNthumbProc,
XtCCallback,
XtRCallback,
sizeof(XtPointer),
Offset(scrollbar.thumbProc),
XtRCallback,
NULL
},
{
XtNjumpProc,
XtCCallback,
XtRCallback,
sizeof(XtPointer),
Offset(scrollbar.jumpProc),
XtRCallback,
NULL
},
{
XtNthumb,
XtCThumb,
XtRBitmap,
sizeof(Pixmap),
Offset(scrollbar.thumb),
XtRImmediate,
(XtPointer)XtUnspecifiedPixmap
},
{
XtNforeground,
XtCForeground,
XtRPixel,
sizeof(Pixel),
Offset(scrollbar.foreground),
XtRString,
XtDefaultForeground
},
{
XtNshown,
XtCShown,
XtRFloat,
sizeof(float),
Offset(scrollbar.shown),
XtRFloat,
(XtPointer)&floatZero
},
{
XtNtopOfThumb,
XtCTopOfThumb,
XtRFloat,
sizeof(float),
Offset(scrollbar.top),
XtRFloat,
(XtPointer)&floatZero
},
{
XtNscrollVCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.verCursor),
XtRString,
"sb_v_double_arrow"
},
{
XtNscrollHCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.horCursor),
XtRString,
"sb_h_double_arrow"
},
{
XtNscrollUCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.upCursor),
XtRString,
"sb_up_arrow"
},
{
XtNscrollDCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.downCursor),
XtRString,
"sb_down_arrow"
},
{
XtNscrollLCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.leftCursor),
XtRString,
"sb_left_arrow"
},
{
XtNscrollRCursor,
XtCCursor,
XtRCursor,
sizeof(Cursor),
Offset(scrollbar.rightCursor),
XtRString,
"sb_right_arrow"
},
{
XtNminimumThumb,
XtCMinimumThumb,
XtRDimension,
sizeof(Dimension),
Offset(scrollbar.min_thumb),
XtRImmediate,
(XtPointer)7
},
};
#undef Offset
static XtActionsRec actions[] = {
{"StartScroll", StartScroll},
{"MoveThumb", MoveThumb},
{"NotifyThumb", NotifyThumb},
{"NotifyScroll", NotifyScroll},
{"EndScroll", EndScroll},
};
#define Superclass (&simpleClassRec)
ScrollbarClassRec scrollbarClassRec = {
{
(WidgetClass)&simpleClassRec,
"Scrollbar",
sizeof(ScrollbarRec),
XawScrollbarClassInitialize,
NULL,
False,
XawScrollbarInitialize,
NULL,
XawScrollbarRealize,
actions,
XtNumber(actions),
resources,
XtNumber(resources),
NULLQUARK,
True,
True,
True,
False,
XawScrollbarDestroy,
XawScrollbarResize,
XawScrollbarRedisplay,
XawScrollbarSetValues,
NULL,
XtInheritSetValuesAlmost,
NULL,
NULL,
XtVersion,
NULL,
defaultTranslations,
XtInheritQueryGeometry,
XtInheritDisplayAccelerator,
NULL,
},
{
XtInheritChangeSensitive,
},
{
NULL,
},
};
WidgetClass scrollbarWidgetClass = (WidgetClass)&scrollbarClassRec;
static void
XawScrollbarClassInitialize(void)
{
XawInitializeWidgetSet();
XtAddConverter(XtRString, XtROrientation, XmuCvtStringToOrientation,
NULL, 0);
XtSetTypeConverter(XtROrientation, XtRString, XmuCvtOrientationToString,
NULL, 0, XtCacheNone, NULL);
}
static int
InRange(int num, int small, int big)
{
return ((num < small) ? small : ((num > big) ? big : num));
}
static float
FloatInRange(float num, float small, float big)
{
return ((num < small) ? small : ((num > big) ? big : num));
}
static float
FractionLoc(ScrollbarWidget w, int x, int y)
{
float result;
result = PICKLENGTH(w, x / (float)XtWidth(w), y / (float)XtHeight(w));
return (FloatInRange(result, 0.0, 1.0));
}
static void
FillArea(ScrollbarWidget w, int top, int bottom, int thumb)
{
Dimension length;
top = XawMax(1, top);
if (w->scrollbar.orientation == XtorientHorizontal)
bottom = XawMin(bottom, XtWidth(w) - 1);
else
bottom = XawMin(bottom, XtHeight(w) - 1);
if (bottom <= top)
return;
length = bottom - top;
switch(thumb) {
case 1:
if (w->scrollbar.orientation == XtorientHorizontal)
XFillRectangle(XtDisplay(w), XtWindow(w), w->scrollbar.gc,
top, 1, length, XtHeight(w) - 2);
else
XFillRectangle(XtDisplay(w), XtWindow(w), w->scrollbar.gc,
1, top, XtWidth(w) - 2, length);
break;
case 0:
if (w->scrollbar.orientation == XtorientHorizontal)
XClearArea(XtDisplay(w), XtWindow(w),
top, 1, length, XtHeight(w) - 2, False);
else
XClearArea(XtDisplay(w), XtWindow(w),
1, top, XtWidth(w) - 2, length, False);
break;
}
}
static void
PaintThumb(ScrollbarWidget w)
{
Position oldtop, oldbot, newtop, newbot;
oldtop = w->scrollbar.topLoc;
oldbot = oldtop + w->scrollbar.shownLength;
newtop = w->scrollbar.length * w->scrollbar.top;
newbot = newtop + (int)(w->scrollbar.length * w->scrollbar.shown);
if (newbot < newtop + (int)w->scrollbar.min_thumb)
newbot = newtop + w->scrollbar.min_thumb;
w->scrollbar.topLoc = newtop;
w->scrollbar.shownLength = newbot - newtop;
if (XtIsRealized((Widget)w)) {
if (newtop < oldtop)
FillArea(w, newtop, XawMin(newbot, oldtop), 1);
if (newtop > oldtop)
FillArea(w, oldtop, XawMin(newtop, oldbot), 0);
if (newbot < oldbot)
FillArea(w, XawMax(newbot, oldtop), oldbot, 0);
if (newbot > oldbot)
FillArea(w, XawMax(newtop, oldbot), newbot, 1);
}
}
static void
SetDimensions(ScrollbarWidget w)
{
if (w->scrollbar.orientation == XtorientVertical) {
w->scrollbar.length = XtHeight(w);
w->scrollbar.thickness = XtWidth(w);
}
else {
w->scrollbar.length = XtWidth(w);
w->scrollbar.thickness = XtHeight(w);
}
}
static void
XawScrollbarDestroy(Widget w)
{
ScrollbarWidget sbw = (ScrollbarWidget)w;
XtReleaseGC(w, sbw->scrollbar.gc);
}
static void
CreateGC(Widget w)
{
ScrollbarWidget sbw = (ScrollbarWidget)w;
XGCValues gcValues;
XtGCMask mask;
unsigned int depth = 1;
if (sbw->scrollbar.thumb == XtUnspecifiedPixmap)
sbw->scrollbar.thumb = XmuCreateStippledPixmap(XtScreen(w),
(Pixel)1, (Pixel)0,
depth);
else if (sbw->scrollbar.thumb != None) {
Window root;
int x, y;
unsigned int width, height, bw;
XGetGeometry(XtDisplay(w), sbw->scrollbar.thumb, &root, &x, &y,
&width, &height, &bw, &depth);
}
gcValues.foreground = sbw->scrollbar.foreground;
gcValues.background = sbw->core.background_pixel;
mask = GCForeground | GCBackground;
if (sbw->scrollbar.thumb != None) {
if (depth == 1) {
gcValues.fill_style = FillOpaqueStippled;
gcValues.stipple = sbw->scrollbar.thumb;
mask |= GCFillStyle | GCStipple;
}
else {
gcValues.fill_style = FillTiled;
gcValues.tile = sbw->scrollbar.thumb;
mask |= GCFillStyle | GCTile;
}
}
sbw->scrollbar.gc = XtGetGC(w, mask, &gcValues);
}
static void
XawScrollbarInitialize(Widget request, Widget cnew,
ArgList args, Cardinal *num_args)
{
ScrollbarWidget w = (ScrollbarWidget)cnew;
CreateGC(cnew);
if (XtWidth(w) == 0)
XtWidth(w) = w->scrollbar.orientation == XtorientVertical ?
w->scrollbar.thickness : w->scrollbar.length;
if (XtHeight(w) == 0)
XtHeight(w) = w->scrollbar.orientation == XtorientHorizontal ?
w->scrollbar.thickness : w->scrollbar.length;
SetDimensions(w);
w->scrollbar.direction = 0;
w->scrollbar.topLoc = 0;
w->scrollbar.shownLength = w->scrollbar.min_thumb;
}
static void
XawScrollbarRealize(Widget gw, Mask *valueMask,
XSetWindowAttributes *attributes)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
w->scrollbar.inactiveCursor = w->scrollbar.orientation == XtorientVertical ?
w->scrollbar.verCursor : w->scrollbar.horCursor;
XtVaSetValues(gw, XtNcursor, w->scrollbar.inactiveCursor, NULL);
(*scrollbarWidgetClass->core_class.superclass->core_class.realize)
(gw, valueMask, attributes);
}
static Boolean
XawScrollbarSetValues(Widget current, Widget request, Widget desired,
ArgList args, Cardinal *num_args)
{
ScrollbarWidget w = (ScrollbarWidget)current;
ScrollbarWidget dw = (ScrollbarWidget)desired;
Boolean redraw = False;
if (dw->scrollbar.top < 0.0 || dw->scrollbar.top > 1.0)
dw->scrollbar.top = w->scrollbar.top;
if (dw->scrollbar.shown < 0.0 || dw->scrollbar.shown > 1.0)
dw->scrollbar.shown = w->scrollbar.shown;
if (XtIsRealized (desired)) {
if (w->scrollbar.foreground != dw->scrollbar.foreground ||
w->core.background_pixel != dw->core.background_pixel ||
w->scrollbar.thumb != dw->scrollbar.thumb) {
XtReleaseGC((Widget)dw, w->scrollbar.gc);
CreateGC((Widget)dw);
redraw = True;
}
if (w->scrollbar.top != dw->scrollbar.top ||
w->scrollbar.shown != dw->scrollbar.shown)
redraw = True;
}
return (redraw);
}
static void
XawScrollbarResize(Widget gw)
{
SetDimensions((ScrollbarWidget)gw);
XawScrollbarRedisplay(gw, NULL, NULL);
}
static void
XawScrollbarRedisplay(Widget gw, XEvent *event, Region region)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
int x, y;
unsigned int width, height;
if (Superclass->core_class.expose)
(*Superclass->core_class.expose)(gw, event, region);
if (w->scrollbar.orientation == XtorientHorizontal) {
x = w->scrollbar.topLoc;
y = 1;
width = w->scrollbar.shownLength;
height = XtHeight(w) - 2;
}
else {
x = 1;
y = w->scrollbar.topLoc;
width = XtWidth(w) - 2;
height = w->scrollbar.shownLength;
}
if (region == NULL ||
XRectInRegion(region, x, y, width, height) != RectangleOut) {
w->scrollbar.topLoc = -(w->scrollbar.length + 1);
PaintThumb(w);
}
}
static void
StartScroll(Widget gw, XEvent *event, String *params, Cardinal *num_params)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
Cursor cursor;
char direction;
if (w->scrollbar.direction != 0)
return;
if (*num_params > 0)
direction = *params[0];
else
direction = 'C';
w->scrollbar.direction = direction;
switch(direction) {
case 'B':
case 'b':
cursor = w->scrollbar.orientation == XtorientVertical ?
w->scrollbar.downCursor : w->scrollbar.rightCursor;
break;
case 'F':
case 'f':
cursor = w->scrollbar.orientation == XtorientVertical ?
w->scrollbar.upCursor : w->scrollbar.leftCursor;
break;
case 'C':
case 'c':
cursor = w->scrollbar.orientation == XtorientVertical ?
w->scrollbar.rightCursor : w->scrollbar.upCursor;
break;
default:
return;
}
XtVaSetValues(gw, XtNcursor, cursor, NULL);
XFlush(XtDisplay(w));
}
static Boolean
CompareEvents(XEvent *oldEvent, XEvent *newEvent)
{
#define Check(field) if (newEvent->field != oldEvent->field) return (False)
Check(xany.display);
Check(xany.type);
Check(xany.window);
switch(newEvent->type) {
case MotionNotify:
Check(xmotion.state);
break;
case ButtonPress:
case ButtonRelease:
Check(xbutton.state);
Check(xbutton.button);
break;
case KeyPress:
case KeyRelease:
Check(xkey.state);
Check(xkey.keycode);
break;
case EnterNotify:
case LeaveNotify:
Check(xcrossing.mode);
Check(xcrossing.detail);
Check(xcrossing.state);
break;
}
#undef Check
return (True);
}
struct EventData {
XEvent *oldEvent;
int count;
};
static Bool
PeekNotifyEvent(Display *dpy, XEvent *event, char *args)
{
struct EventData *eventData = (struct EventData*)args;
return (++eventData->count == QLength(dpy)
|| CompareEvents(event, eventData->oldEvent));
}
static Bool
LookAhead(Widget w, XEvent *event)
{
XEvent newEvent;
struct EventData eventData;
if (QLength(XtDisplay(w)) == 0)
return (False);
eventData.count = 0;
eventData.oldEvent = event;
XPeekIfEvent(XtDisplay(w), &newEvent, PeekNotifyEvent, (char*)&eventData);
if (CompareEvents(event, &newEvent))
return (True);
return (False);
}
static void
ExtractPosition(XEvent *event, Position *x, Position *y)
{
switch(event->type) {
case MotionNotify:
*x = event->xmotion.x;
*y = event->xmotion.y;
break;
case ButtonPress:
case ButtonRelease:
*x = event->xbutton.x;
*y = event->xbutton.y;
break;
case KeyPress:
case KeyRelease:
*x = event->xkey.x;
*y = event->xkey.y;
break;
case EnterNotify:
case LeaveNotify:
*x = event->xcrossing.x;
*y = event->xcrossing.y;
break;
default:
*x = 0;
*y = 0;
break;
}
}
static void
NotifyScroll(Widget gw, XEvent *event, String *params, Cardinal *num_params)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
long call_data = 0;
char style;
Position x, y;
if (w->scrollbar.direction == 0)
return;
if (LookAhead(gw, event))
return;
if (*num_params > 0)
style = *params[0];
else
style = 'P';
switch(style) {
case 'P':
case 'p':
ExtractPosition(event, &x, &y);
call_data = InRange(PICKLENGTH(w, x, y), 0, (int)w->scrollbar.length);
break;
case 'F':
case 'f':
call_data = w->scrollbar.length;
break;
}
switch(w->scrollbar.direction) {
case 'B':
case 'b':
call_data = -call_data;
case 'F':
case 'f':
XtCallCallbacks(gw, XtNscrollProc, (XtPointer)call_data);
break;
case 'C':
case 'c':
break;
}
}
static void
EndScroll(Widget gw, XEvent *event, String *params, Cardinal *num_params)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
XtVaSetValues(gw, XtNcursor, w->scrollbar.inactiveCursor, NULL);
XFlush(XtDisplay(w));
w->scrollbar.direction = 0;
}
static void
MoveThumb(Widget gw, XEvent *event, String *params, Cardinal *num_params)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
Position x, y;
if (w->scrollbar.direction == 0)
return;
if (LookAhead(gw, event))
return;
if (!event->xmotion.same_screen)
return;
ExtractPosition(event, &x, &y);
w->scrollbar.top = FractionLoc(w, x, y);
}
static void
NotifyThumb(Widget gw, XEvent *event, String *params, Cardinal *num_params)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
union {
XtPointer xtp;
float xtf;
} xtpf;
if (w->scrollbar.direction == 0)
return;
if (LookAhead(gw, event))
return;
xtpf.xtf = w->scrollbar.top;
XtCallCallbacks(gw, XtNthumbProc, xtpf.xtp);
XtCallCallbacks(gw, XtNjumpProc, (XtPointer)&w->scrollbar.top);
PaintThumb(w);
}
void
XawScrollbarSetThumb(Widget gw,
#if NeedWidePrototypes
double top, double shown
#else
float top, float shown
#endif
)
{
ScrollbarWidget w = (ScrollbarWidget)gw;
if (w->scrollbar.direction == 'c')
return;
w->scrollbar.top = top > 1.0 ? 1.0 : top >= 0.0 ? top : w->scrollbar.top;
w->scrollbar.shown = shown > 1.0 ? 1.0 : shown >= 0.0 ?
shown : w->scrollbar.shown;
PaintThumb(w);
}