1/* $Id$
2 * Copyright (c) 2003, Joe English
3 *
4 * ttk::scrollbar widget.
5 */
6
7#include <tk.h>
8
9#include "ttkTheme.h"
10#include "ttkWidget.h"
11
12/*------------------------------------------------------------------------
13 * +++ Scrollbar widget record.
14 */
15typedef struct
16{
17    Tcl_Obj	*commandObj;
18
19    int 	orient;
20    Tcl_Obj	*orientObj;
21
22    double	first;			/* top fraction */
23    double	last;			/* bottom fraction */
24
25    Ttk_Box	troughBox;		/* trough parcel */
26    int 	minSize;		/* minimum size of thumb */
27} ScrollbarPart;
28
29typedef struct
30{
31    WidgetCore core;
32    ScrollbarPart scrollbar;
33} Scrollbar;
34
35static Tk_OptionSpec ScrollbarOptionSpecs[] =
36{
37    {TK_OPTION_STRING, "-command", "command", "Command", "",
38	Tk_Offset(Scrollbar,scrollbar.commandObj), -1, 0,0,0},
39
40    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical",
41	Tk_Offset(Scrollbar,scrollbar.orientObj),
42	Tk_Offset(Scrollbar,scrollbar.orient),
43	0,(ClientData)ttkOrientStrings,STYLE_CHANGED },
44
45    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
46};
47
48/*------------------------------------------------------------------------
49 * +++ Widget hooks.
50 */
51
52static void
53ScrollbarInitialize(Tcl_Interp *interp, void *recordPtr)
54{
55    Scrollbar *sb = recordPtr;
56    sb->scrollbar.first = 0.0;
57    sb->scrollbar.last = 1.0;
58
59    TtkTrackElementState(&sb->core);
60}
61
62static Ttk_Layout ScrollbarGetLayout(
63    Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
64{
65    Scrollbar *sb = recordPtr;
66    return TtkWidgetGetOrientedLayout(
67	interp, theme, recordPtr, sb->scrollbar.orientObj);
68}
69
70/*
71 * ScrollbarDoLayout --
72 * 	Layout hook.  Adjusts the position of the scrollbar thumb.
73 *
74 * Side effects:
75 * 	Sets sb->troughBox and sb->minSize.
76 */
77static void ScrollbarDoLayout(void *recordPtr)
78{
79    Scrollbar *sb = recordPtr;
80    WidgetCore *corePtr = &sb->core;
81    Ttk_Element thumb;
82    Ttk_Box thumbBox;
83    int thumbWidth, thumbHeight;
84    double first, last, size;
85    int minSize;
86
87    /*
88     * Use generic layout manager to compute initial layout:
89     */
90    Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
91
92    /*
93     * Locate thumb element, extract parcel and requested minimum size:
94     */
95    thumb = Ttk_FindElement(corePtr->layout, "thumb");
96    if (!thumb)	/* Something has gone wrong -- bail */
97	return;
98
99    sb->scrollbar.troughBox = thumbBox = Ttk_ElementParcel(thumb);
100    Ttk_LayoutNodeReqSize(
101	corePtr->layout, thumb, &thumbWidth,&thumbHeight);
102
103    /*
104     * Adjust thumb element parcel:
105     */
106    first = sb->scrollbar.first;
107    last  = sb->scrollbar.last;
108
109    if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) {
110	minSize = thumbHeight;
111	size = thumbBox.height - minSize;
112	thumbBox.y += (int)(size * first);
113	thumbBox.height = (int)(size * last) + minSize - (int)(size * first);
114    } else {
115	minSize = thumbWidth;
116	size = thumbBox.width - minSize;
117	thumbBox.x += (int)(size * first);
118	thumbBox.width = (int)(size * last) + minSize - (int)(size * first);
119    }
120    sb->scrollbar.minSize = minSize;
121    Ttk_PlaceElement(corePtr->layout, thumb, thumbBox);
122}
123
124/*------------------------------------------------------------------------
125 * +++ Widget commands.
126 */
127
128/* $sb set $first $last --
129 * 	Set the position of the scrollbar.
130 */
131static int
132ScrollbarSetCommand(
133    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
134{
135    Scrollbar *scrollbar = recordPtr;
136    Tcl_Obj *firstObj, *lastObj;
137    double first, last;
138
139    if (objc != 4) {
140	Tcl_WrongNumArgs(interp, 2, objv, "first last");
141	return TCL_ERROR;
142    }
143
144    firstObj = objv[2];
145    lastObj = objv[3];
146    if (Tcl_GetDoubleFromObj(interp, firstObj, &first) != TCL_OK
147	|| Tcl_GetDoubleFromObj(interp, lastObj, &last) != TCL_OK)
148	return TCL_ERROR;
149
150    /* Range-checks:
151     */
152    if (first < 0.0) {
153	first = 0.0;
154    } else if (first > 1.0) {
155	first = 1.0;
156    }
157
158    if (last < first) {
159	last = first;
160    } else if (last > 1.0) {
161	last = 1.0;
162    }
163
164    /* ASSERT: 0.0 <= first <= last <= 1.0 */
165
166    scrollbar->scrollbar.first = first;
167    scrollbar->scrollbar.last = last;
168    if (first <= 0.0 && last >= 1.0) {
169	scrollbar->core.state |= TTK_STATE_DISABLED;
170    } else {
171	scrollbar->core.state &= ~TTK_STATE_DISABLED;
172    }
173
174    TtkRedisplayWidget(&scrollbar->core);
175
176    return TCL_OK;
177}
178
179/* $sb get --
180 * 	Returns the last thing passed to 'set'.
181 */
182static int
183ScrollbarGetCommand(
184    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
185{
186    Scrollbar *scrollbar = recordPtr;
187    Tcl_Obj *result[2];
188
189    if (objc != 2) {
190	Tcl_WrongNumArgs(interp, 2, objv, "");
191	return TCL_ERROR;
192    }
193
194    result[0] = Tcl_NewDoubleObj(scrollbar->scrollbar.first);
195    result[1] = Tcl_NewDoubleObj(scrollbar->scrollbar.last);
196    Tcl_SetObjResult(interp, Tcl_NewListObj(2, result));
197
198    return TCL_OK;
199}
200
201/* $sb delta $dx $dy --
202 * 	Returns the percentage change corresponding to a mouse movement
203 * 	of $dx, $dy.
204 */
205static int
206ScrollbarDeltaCommand(
207    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
208{
209    Scrollbar *sb = recordPtr;
210    double dx, dy;
211    double delta = 0.0;
212
213    if (objc != 4) {
214	Tcl_WrongNumArgs(interp, 2, objv, "dx dy");
215	return TCL_ERROR;
216    }
217
218    if (Tcl_GetDoubleFromObj(interp, objv[2], &dx) != TCL_OK
219	|| Tcl_GetDoubleFromObj(interp, objv[3], &dy) != TCL_OK)
220    {
221	return TCL_ERROR;
222    }
223
224    delta = 0.0;
225    if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) {
226	int size = sb->scrollbar.troughBox.height - sb->scrollbar.minSize;
227	if (size > 0) {
228	    delta = (double)dy / (double)size;
229	}
230    } else {
231	int size = sb->scrollbar.troughBox.width - sb->scrollbar.minSize;
232	if (size > 0) {
233	    delta = (double)dx / (double)size;
234	}
235    }
236
237    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(delta));
238    return TCL_OK;
239}
240
241/* $sb fraction $x $y --
242 * 	Returns a real number between 0 and 1 indicating  where  the
243 * 	point given by x and y lies in the trough area of the scrollbar.
244 */
245static int
246ScrollbarFractionCommand(
247    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
248{
249    Scrollbar *sb = recordPtr;
250    Ttk_Box b = sb->scrollbar.troughBox;
251    int minSize = sb->scrollbar.minSize;
252    double x, y;
253    double fraction = 0.0;
254
255    if (objc != 4) {
256	Tcl_WrongNumArgs(interp, 2, objv, "x y");
257	return TCL_ERROR;
258    }
259
260    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK
261	|| Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK)
262    {
263	return TCL_ERROR;
264    }
265
266    fraction = 0.0;
267    if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) {
268	if (b.height > minSize) {
269	    fraction = (double)(y - b.y) / (double)(b.height - minSize);
270	}
271    } else {
272	if (b.width > minSize) {
273	    fraction = (double)(x - b.x) / (double)(b.width - minSize);
274	}
275    }
276
277    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(fraction));
278    return TCL_OK;
279}
280
281static const Ttk_Ensemble ScrollbarCommands[] = {
282    { "configure",	TtkWidgetConfigureCommand,0 },
283    { "cget",		TtkWidgetCgetCommand,0 },
284    { "delta",    	ScrollbarDeltaCommand,0 },
285    { "fraction",    	ScrollbarFractionCommand,0 },
286    { "get",    	ScrollbarGetCommand,0 },
287    { "identify",	TtkWidgetIdentifyCommand,0 },
288    { "instate",	TtkWidgetInstateCommand,0 },
289    { "set",  		ScrollbarSetCommand,0 },
290    { "state",  	TtkWidgetStateCommand,0 },
291    { 0,0,0 }
292};
293
294/*------------------------------------------------------------------------
295 * +++ Widget specification.
296 */
297static WidgetSpec ScrollbarWidgetSpec =
298{
299    "TScrollbar",		/* className */
300    sizeof(Scrollbar),		/* recordSize */
301    ScrollbarOptionSpecs,	/* optionSpecs */
302    ScrollbarCommands,		/* subcommands */
303    ScrollbarInitialize,	/* initializeProc */
304    TtkNullCleanup,		/* cleanupProc */
305    TtkCoreConfigure,		/* configureProc */
306    TtkNullPostConfigure,	/* postConfigureProc */
307    ScrollbarGetLayout,		/* getLayoutProc */
308    TtkWidgetSize, 		/* sizeProc */
309    ScrollbarDoLayout,		/* layoutProc */
310    TtkWidgetDisplay		/* displayProc */
311};
312
313TTK_BEGIN_LAYOUT(VerticalScrollbarLayout)
314    TTK_GROUP("Vertical.Scrollbar.trough", TTK_FILL_Y,
315	TTK_NODE("Vertical.Scrollbar.uparrow", TTK_PACK_TOP)
316	TTK_NODE("Vertical.Scrollbar.downarrow", TTK_PACK_BOTTOM)
317	TTK_NODE(
318	    "Vertical.Scrollbar.thumb", TTK_PACK_TOP|TTK_EXPAND|TTK_FILL_BOTH))
319TTK_END_LAYOUT
320
321TTK_BEGIN_LAYOUT(HorizontalScrollbarLayout)
322    TTK_GROUP("Horizontal.Scrollbar.trough", TTK_FILL_X,
323	TTK_NODE("Horizontal.Scrollbar.leftarrow", TTK_PACK_LEFT)
324	TTK_NODE("Horizontal.Scrollbar.rightarrow", TTK_PACK_RIGHT)
325	TTK_NODE(
326	"Horizontal.Scrollbar.thumb", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_BOTH))
327TTK_END_LAYOUT
328
329/*------------------------------------------------------------------------
330 * +++ Initialization.
331 */
332
333MODULE_SCOPE
334void TtkScrollbar_Init(Tcl_Interp *interp)
335{
336    Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
337
338    Ttk_RegisterLayout(theme,"Vertical.TScrollbar",VerticalScrollbarLayout);
339    Ttk_RegisterLayout(theme,"Horizontal.TScrollbar",HorizontalScrollbarLayout);
340
341    RegisterWidget(interp, "ttk::scrollbar", &ScrollbarWidgetSpec);
342}
343
344/*EOF*/
345