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