1/* $Id$
2 *
3 * Copyright (c) Joe English, Pat Thoyts, Michael Kirkham
4 *
5 * ttk::progressbar widget.
6 */
7
8#include <math.h>
9#include <tk.h>
10
11#include "ttkTheme.h"
12#include "ttkWidget.h"
13
14/*------------------------------------------------------------------------
15 * +++ Widget record:
16 */
17
18#define DEF_PROGRESSBAR_LENGTH "100"
19enum {
20    TTK_PROGRESSBAR_DETERMINATE, TTK_PROGRESSBAR_INDETERMINATE
21};
22static const char *const ProgressbarModeStrings[] = {
23    "determinate", "indeterminate", NULL
24};
25
26typedef struct {
27    Tcl_Obj 	*orientObj;
28    Tcl_Obj 	*lengthObj;
29    Tcl_Obj 	*modeObj;
30    Tcl_Obj 	*variableObj;
31    Tcl_Obj 	*maximumObj;
32    Tcl_Obj 	*valueObj;
33    Tcl_Obj 	*phaseObj;
34
35    int 	mode;
36    Ttk_TraceHandle *variableTrace;	/* Trace handle for -variable option */
37    int 	period;			/* Animation period */
38    int 	maxPhase;		/* Max animation phase */
39    Tcl_TimerToken timer;		/* Animation timer */
40
41} ProgressbarPart;
42
43typedef struct {
44    WidgetCore 		core;
45    ProgressbarPart	progress;
46} Progressbar;
47
48static Tk_OptionSpec ProgressbarOptionSpecs[] =
49{
50    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient",
51	"horizontal", Tk_Offset(Progressbar,progress.orientObj), -1,
52	0, (ClientData)ttkOrientStrings, STYLE_CHANGED },
53    {TK_OPTION_PIXELS, "-length", "length", "Length",
54        DEF_PROGRESSBAR_LENGTH, Tk_Offset(Progressbar,progress.lengthObj), -1,
55	0, 0, GEOMETRY_CHANGED },
56    {TK_OPTION_STRING_TABLE, "-mode", "mode", "ProgressMode", "determinate",
57	Tk_Offset(Progressbar,progress.modeObj),
58	Tk_Offset(Progressbar,progress.mode),
59	0, (ClientData)ProgressbarModeStrings, 0 },
60    {TK_OPTION_DOUBLE, "-maximum", "maximum", "Maximum",
61	"100", Tk_Offset(Progressbar,progress.maximumObj), -1,
62	0, 0, 0 },
63    {TK_OPTION_STRING, "-variable", "variable", "Variable",
64	NULL, Tk_Offset(Progressbar,progress.variableObj), -1,
65	TK_OPTION_NULL_OK, 0, 0 },
66    {TK_OPTION_DOUBLE, "-value", "value", "Value",
67	"0.0", Tk_Offset(Progressbar,progress.valueObj), -1,
68	0, 0, 0 },
69    {TK_OPTION_INT, "-phase", "phase", "Phase",
70	"0", Tk_Offset(Progressbar,progress.phaseObj), -1,
71	0, 0, 0 },
72    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
73};
74
75/*------------------------------------------------------------------------
76 * +++ Animation procedures:
77 */
78
79/* AnimationEnabled --
80 * 	Returns 1 if animation should be active, 0 otherwise.
81 */
82static int AnimationEnabled(Progressbar *pb)
83{
84    double maximum = 100, value = 0;
85
86    Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
87    Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
88
89    return pb->progress.period > 0
90	&& value > 0.0
91	&& (   value < maximum
92	    || pb->progress.mode == TTK_PROGRESSBAR_INDETERMINATE);
93}
94
95/* AnimateProgressProc --
96 * 	Timer callback for progress bar animation.
97 * 	Increments the -phase option, redisplays the widget,
98 * 	and reschedules itself if animation still enabled.
99 */
100static void AnimateProgressProc(ClientData clientData)
101{
102    Progressbar *pb = clientData;
103
104    pb->progress.timer = 0;
105
106    if (AnimationEnabled(pb)) {
107	int phase = 0;
108	Tcl_GetIntFromObj(NULL, pb->progress.phaseObj, &phase);
109
110	/*
111	 * Update -phase:
112	 */
113	++phase;
114	if (pb->progress.maxPhase)
115	    phase %= pb->progress.maxPhase;
116	Tcl_DecrRefCount(pb->progress.phaseObj);
117	pb->progress.phaseObj = Tcl_NewIntObj(phase);
118	Tcl_IncrRefCount(pb->progress.phaseObj);
119
120	/*
121	 * Reschedule:
122	 */
123	pb->progress.timer = Tcl_CreateTimerHandler(
124	    pb->progress.period, AnimateProgressProc, clientData);
125
126	TtkRedisplayWidget(&pb->core);
127    }
128}
129
130/* CheckAnimation --
131 * 	If animation is enabled and not scheduled, schedule it.
132 * 	If animation is disabled but scheduled, cancel it.
133 */
134static void CheckAnimation(Progressbar *pb)
135{
136    if (AnimationEnabled(pb)) {
137	if (pb->progress.timer == 0) {
138	    pb->progress.timer = Tcl_CreateTimerHandler(
139		pb->progress.period, AnimateProgressProc, (ClientData)pb);
140	}
141    } else {
142	if (pb->progress.timer != 0) {
143	    Tcl_DeleteTimerHandler(pb->progress.timer);
144	    pb->progress.timer = 0;
145	}
146    }
147}
148
149/*------------------------------------------------------------------------
150 * +++ Trace hook for progressbar -variable option:
151 */
152
153static void VariableChanged(void *recordPtr, const char *value)
154{
155    Progressbar *pb = recordPtr;
156    Tcl_Obj *newValue;
157    double scratch;
158
159    if (WidgetDestroyed(&pb->core)) {
160	return;
161    }
162
163    if (!value) {
164	/* Linked variable is unset -- disable widget */
165	TtkWidgetChangeState(&pb->core, TTK_STATE_DISABLED, 0);
166	return;
167    }
168    TtkWidgetChangeState(&pb->core, 0, TTK_STATE_DISABLED);
169
170    newValue = Tcl_NewStringObj(value, -1);
171    Tcl_IncrRefCount(newValue);
172    if (Tcl_GetDoubleFromObj(NULL, newValue, &scratch) != TCL_OK) {
173	TtkWidgetChangeState(&pb->core, TTK_STATE_INVALID, 0);
174	return;
175    }
176    TtkWidgetChangeState(&pb->core, 0, TTK_STATE_INVALID);
177    Tcl_DecrRefCount(pb->progress.valueObj);
178    pb->progress.valueObj = newValue;
179
180    CheckAnimation(pb);
181    TtkRedisplayWidget(&pb->core);
182}
183
184/*------------------------------------------------------------------------
185 * +++ Widget class methods:
186 */
187
188static void ProgressbarInitialize(Tcl_Interp *interp, void *recordPtr)
189{
190    Progressbar *pb = recordPtr;
191    pb->progress.variableTrace = 0;
192    pb->progress.timer = 0;
193}
194
195static void ProgressbarCleanup(void *recordPtr)
196{
197    Progressbar *pb = recordPtr;
198    if (pb->progress.variableTrace)
199	Ttk_UntraceVariable(pb->progress.variableTrace);
200    if (pb->progress.timer)
201	Tcl_DeleteTimerHandler(pb->progress.timer);
202}
203
204/*
205 * Configure hook:
206 *
207 * @@@ TODO: deal with [$pb configure -value ... -variable ...]
208 */
209static int ProgressbarConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
210{
211    Progressbar *pb = recordPtr;
212    Tcl_Obj *varName = pb->progress.variableObj;
213    Ttk_TraceHandle *vt = 0;
214
215    if (varName != NULL && *Tcl_GetString(varName) != '\0') {
216	vt = Ttk_TraceVariable(interp, varName, VariableChanged, recordPtr);
217	if (!vt) return TCL_ERROR;
218    }
219
220    if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
221	if (vt) Ttk_UntraceVariable(vt);
222	return TCL_ERROR;
223    }
224
225    if (pb->progress.variableTrace) {
226	Ttk_UntraceVariable(pb->progress.variableTrace);
227    }
228    pb->progress.variableTrace = vt;
229
230    return TCL_OK;
231}
232
233/*
234 * Post-configuration hook:
235 */
236static int ProgressbarPostConfigure(
237    Tcl_Interp *interp, void *recordPtr, int mask)
238{
239    Progressbar *pb = recordPtr;
240    int status = TCL_OK;
241
242    if (pb->progress.variableTrace) {
243	status = Ttk_FireTrace(pb->progress.variableTrace);
244	if (WidgetDestroyed(&pb->core)) {
245	    return TCL_ERROR;
246	}
247	if (status != TCL_OK) {
248	    /* Unset -variable: */
249	    Ttk_UntraceVariable(pb->progress.variableTrace);
250	    Tcl_DecrRefCount(pb->progress.variableObj);
251	    pb->progress.variableTrace = 0;
252	    pb->progress.variableObj = NULL;
253	    return TCL_ERROR;
254	}
255    }
256
257    CheckAnimation(pb);
258
259    return status;
260}
261
262/*
263 * Size hook:
264 * 	Compute base layout size, overrid
265 */
266static int ProgressbarSize(void *recordPtr, int *widthPtr, int *heightPtr)
267{
268    Progressbar *pb = recordPtr;
269    int length = 100, orient = TTK_ORIENT_HORIZONTAL;
270
271    TtkWidgetSize(recordPtr, widthPtr, heightPtr);
272
273    /* Override requested width (height) based on -length and -orient
274     */
275    Tk_GetPixelsFromObj(NULL, pb->core.tkwin, pb->progress.lengthObj, &length);
276    Ttk_GetOrientFromObj(NULL, pb->progress.orientObj, &orient);
277
278    if (orient == TTK_ORIENT_HORIZONTAL) {
279	*widthPtr = length;
280    } else {
281	*heightPtr = length;
282    }
283
284    return 1;
285}
286
287/*
288 * Layout hook:
289 * 	Adjust size and position of pbar element, if present.
290 */
291
292static void ProgressbarDeterminateLayout(
293    Progressbar *pb,
294    Ttk_Element pbar,
295    Ttk_Box parcel,
296    double fraction,
297    Ttk_Orient orient)
298{
299    if (fraction < 0.0) fraction = 0.0;
300    if (fraction > 1.0) fraction = 1.0;
301
302    if (orient == TTK_ORIENT_HORIZONTAL) {
303	parcel.width = (int)(parcel.width * fraction);
304    } else {
305	int newHeight = (int)(parcel.height * fraction);
306	parcel.y += (parcel.height - newHeight);
307	parcel.height = newHeight;
308    }
309    Ttk_PlaceElement(pb->core.layout, pbar, parcel);
310}
311
312static void ProgressbarIndeterminateLayout(
313    Progressbar *pb,
314    Ttk_Element pbar,
315    Ttk_Box parcel,
316    double fraction,
317    Ttk_Orient orient)
318{
319    Ttk_Box pbarBox = Ttk_ElementParcel(pbar);
320
321    fraction = fmod(fabs(fraction), 2.0);
322    if (fraction > 1.0) {
323	fraction = 2.0 - fraction;
324    }
325
326    if (orient == TTK_ORIENT_HORIZONTAL) {
327	pbarBox.x = parcel.x + (int)(fraction * (parcel.width-pbarBox.width));
328    } else {
329	pbarBox.y = parcel.y + (int)(fraction * (parcel.height-pbarBox.height));
330    }
331    Ttk_PlaceElement(pb->core.layout, pbar, pbarBox);
332}
333
334static void ProgressbarDoLayout(void *recordPtr)
335{
336    Progressbar *pb = recordPtr;
337    WidgetCore *corePtr = &pb->core;
338    Ttk_Element pbar = Ttk_FindElement(corePtr->layout, "pbar");
339    double value = 0.0, maximum = 100.0;
340    int orient = TTK_ORIENT_HORIZONTAL;
341
342    Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
343
344    /* Adjust the bar size:
345     */
346
347    Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
348    Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
349    Ttk_GetOrientFromObj(NULL, pb->progress.orientObj, &orient);
350
351    if (pbar) {
352	double fraction = value / maximum;
353	Ttk_Box parcel = Ttk_ClientRegion(corePtr->layout, "trough");
354
355	if (pb->progress.mode == TTK_PROGRESSBAR_DETERMINATE) {
356	    ProgressbarDeterminateLayout(
357		pb, pbar, parcel, fraction, orient);
358	} else {
359	    ProgressbarIndeterminateLayout(
360		pb, pbar, parcel, fraction, orient);
361	}
362    }
363}
364
365static Ttk_Layout ProgressbarGetLayout(
366    Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
367{
368    Progressbar *pb = recordPtr;
369    Ttk_Layout layout = TtkWidgetGetOrientedLayout(
370	interp, theme, recordPtr, pb->progress.orientObj);
371
372    /*
373     * Check if the style supports animation:
374     */
375    pb->progress.period = 0;
376    pb->progress.maxPhase = 0;
377    if (layout) {
378	Tcl_Obj *periodObj = Ttk_QueryOption(layout,"-period", 0);
379	Tcl_Obj *maxPhaseObj = Ttk_QueryOption(layout,"-maxphase", 0);
380	if (periodObj)
381	    Tcl_GetIntFromObj(NULL, periodObj, &pb->progress.period);
382	if (maxPhaseObj)
383	    Tcl_GetIntFromObj(NULL, maxPhaseObj, &pb->progress.maxPhase);
384    }
385
386    return layout;
387}
388
389/*------------------------------------------------------------------------
390 * +++ Widget commands:
391 */
392
393/* $sb step ?amount?
394 */
395static int ProgressbarStepCommand(
396    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
397{
398    Progressbar *pb = recordPtr;
399    double value = 0.0, stepAmount = 1.0;
400    Tcl_Obj *newValueObj;
401
402    if (objc == 3) {
403	if (Tcl_GetDoubleFromObj(interp, objv[2], &stepAmount) != TCL_OK) {
404	    return TCL_ERROR;
405	}
406    } else if (objc != 2) {
407	Tcl_WrongNumArgs(interp, 2,objv, "?stepAmount?");
408	return TCL_ERROR;
409    }
410
411    (void)Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
412    value += stepAmount;
413
414    /* In determinate mode, wrap around if value exceeds maximum:
415     */
416    if (pb->progress.mode == TTK_PROGRESSBAR_DETERMINATE) {
417	double maximum = 100.0;
418	(void)Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
419	value = fmod(value, maximum);
420    }
421
422    newValueObj = Tcl_NewDoubleObj(value);
423
424    TtkRedisplayWidget(&pb->core);
425
426    /* Update value by setting the linked -variable, if there is one:
427     */
428    if (pb->progress.variableTrace) {
429	return Tcl_ObjSetVar2(
430		    interp, pb->progress.variableObj, 0, newValueObj,
431		    TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)
432	    ? TCL_OK : TCL_ERROR;
433    }
434
435    /* Otherwise, change the -value directly:
436     */
437    Tcl_IncrRefCount(newValueObj);
438    Tcl_DecrRefCount(pb->progress.valueObj);
439    pb->progress.valueObj = newValueObj;
440    CheckAnimation(pb);
441
442    return TCL_OK;
443}
444
445/* $sb start|stop ?args? --
446 * Change [$sb $cmd ...] to [ttk::progressbar::$cmd ...]
447 * and pass to interpreter.
448 */
449static int ProgressbarStartStopCommand(
450    Tcl_Interp *interp, const char *cmdName, int objc, Tcl_Obj *const objv[])
451{
452    Tcl_Obj *cmd = Tcl_NewListObj(objc, objv);
453    Tcl_Obj *prefix[2];
454    int status;
455
456    /* ASSERT: objc >= 2 */
457
458    prefix[0] = Tcl_NewStringObj(cmdName, -1);
459    prefix[1] = objv[0];
460    Tcl_ListObjReplace(interp, cmd, 0,2, 2,prefix);
461
462    Tcl_IncrRefCount(cmd);
463    status = Tcl_EvalObjEx(interp, cmd, 0);
464    Tcl_DecrRefCount(cmd);
465
466    return status;
467}
468
469static int ProgressbarStartCommand(
470    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
471{
472    return ProgressbarStartStopCommand(
473	interp, "::ttk::progressbar::start", objc, objv);
474}
475
476static int ProgressbarStopCommand(
477    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
478{
479    return ProgressbarStartStopCommand(
480	interp, "::ttk::progressbar::stop", objc, objv);
481}
482
483static const Ttk_Ensemble ProgressbarCommands[] = {
484    { "configure",	TtkWidgetConfigureCommand,0 },
485    { "cget",		TtkWidgetCgetCommand,0 },
486    { "identify",	TtkWidgetIdentifyCommand,0 },
487    { "instate",	TtkWidgetInstateCommand,0 },
488    { "start", 		ProgressbarStartCommand,0 },
489    { "state",  	TtkWidgetStateCommand,0 },
490    { "step", 		ProgressbarStepCommand,0 },
491    { "stop", 		ProgressbarStopCommand,0 },
492    { 0,0,0 }
493};
494
495/*
496 * Widget specification:
497 */
498static WidgetSpec ProgressbarWidgetSpec =
499{
500    "TProgressbar",		/* className */
501    sizeof(Progressbar),	/* recordSize */
502    ProgressbarOptionSpecs,	/* optionSpecs */
503    ProgressbarCommands,	/* subcommands */
504    ProgressbarInitialize,	/* initializeProc */
505    ProgressbarCleanup,		/* cleanupProc */
506    ProgressbarConfigure,	/* configureProc */
507    ProgressbarPostConfigure,	/* postConfigureProc */
508    ProgressbarGetLayout,	/* getLayoutProc */
509    ProgressbarSize, 		/* sizeProc */
510    ProgressbarDoLayout,	/* layoutProc */
511    TtkWidgetDisplay		/* displayProc */
512};
513
514/*
515 * Layouts:
516 */
517TTK_BEGIN_LAYOUT(VerticalProgressbarLayout)
518    TTK_GROUP("Vertical.Progressbar.trough", TTK_FILL_BOTH,
519	TTK_NODE("Vertical.Progressbar.pbar", TTK_PACK_BOTTOM|TTK_FILL_X))
520TTK_END_LAYOUT
521
522TTK_BEGIN_LAYOUT(HorizontalProgressbarLayout)
523    TTK_GROUP("Horizontal.Progressbar.trough", TTK_FILL_BOTH,
524	TTK_NODE("Horizontal.Progressbar.pbar", TTK_PACK_LEFT|TTK_FILL_Y))
525TTK_END_LAYOUT
526
527/*
528 * Initialization:
529 */
530
531MODULE_SCOPE
532void TtkProgressbar_Init(Tcl_Interp *interp)
533{
534    Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp);
535
536    Ttk_RegisterLayout(themePtr,
537	"Vertical.TProgressbar", VerticalProgressbarLayout);
538    Ttk_RegisterLayout(themePtr,
539	"Horizontal.TProgressbar", HorizontalProgressbarLayout);
540
541    RegisterWidget(interp, "ttk::progressbar", &ProgressbarWidgetSpec);
542}
543
544/*EOF*/
545