Files
cpython-source-deps/generic/ttk/ttkProgress.c
2021-03-30 00:54:10 +01:00

556 lines
15 KiB
C

/*
* Copyright (c) Joe English, Pat Thoyts, Michael Kirkham
*
* ttk::progressbar widget.
*/
#include "tkInt.h"
#include "ttkTheme.h"
#include "ttkWidget.h"
/*------------------------------------------------------------------------
* +++ Widget record:
*/
#define DEF_PROGRESSBAR_LENGTH "100"
enum {
TTK_PROGRESSBAR_DETERMINATE, TTK_PROGRESSBAR_INDETERMINATE
};
static const char *const ProgressbarModeStrings[] = {
"determinate", "indeterminate", NULL
};
typedef struct {
Tcl_Obj *orientObj;
Tcl_Obj *lengthObj;
Tcl_Obj *modeObj;
Tcl_Obj *variableObj;
Tcl_Obj *maximumObj;
Tcl_Obj *valueObj;
Tcl_Obj *phaseObj;
int mode;
Ttk_TraceHandle *variableTrace; /* Trace handle for -variable option */
int period; /* Animation period */
int maxPhase; /* Max animation phase */
Tcl_TimerToken timer; /* Animation timer */
} ProgressbarPart;
typedef struct {
WidgetCore core;
ProgressbarPart progress;
} Progressbar;
static Tk_OptionSpec ProgressbarOptionSpecs[] =
{
{TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient",
"horizontal", Tk_Offset(Progressbar,progress.orientObj), -1,
0, (void *)ttkOrientStrings, STYLE_CHANGED },
{TK_OPTION_PIXELS, "-length", "length", "Length",
DEF_PROGRESSBAR_LENGTH, Tk_Offset(Progressbar,progress.lengthObj), -1,
0, 0, GEOMETRY_CHANGED },
{TK_OPTION_STRING_TABLE, "-mode", "mode", "ProgressMode", "determinate",
Tk_Offset(Progressbar,progress.modeObj),
Tk_Offset(Progressbar,progress.mode),
0, (void *)ProgressbarModeStrings, 0 },
{TK_OPTION_DOUBLE, "-maximum", "maximum", "Maximum",
"100", Tk_Offset(Progressbar,progress.maximumObj), -1,
0, 0, 0 },
{TK_OPTION_STRING, "-variable", "variable", "Variable",
NULL, Tk_Offset(Progressbar,progress.variableObj), -1,
TK_OPTION_NULL_OK, 0, 0 },
{TK_OPTION_DOUBLE, "-value", "value", "Value",
"0.0", Tk_Offset(Progressbar,progress.valueObj), -1,
0, 0, 0 },
{TK_OPTION_INT, "-phase", "phase", "Phase",
"0", Tk_Offset(Progressbar,progress.phaseObj), -1,
0, 0, 0 },
WIDGET_TAKEFOCUS_FALSE,
WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
};
/*------------------------------------------------------------------------
* +++ Animation procedures:
*/
/* AnimationEnabled --
* Returns 1 if animation should be active, 0 otherwise.
*/
static int AnimationEnabled(Progressbar *pb)
{
double maximum = 100, value = 0;
Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
return pb->progress.period > 0
&& value > 0.0
&& ( value < maximum
|| pb->progress.mode == TTK_PROGRESSBAR_INDETERMINATE);
}
/* AnimateProgressProc --
* Timer callback for progress bar animation.
* Increments the -phase option, redisplays the widget,
* and reschedules itself if animation still enabled.
*/
static void AnimateProgressProc(ClientData clientData)
{
Progressbar *pb = (Progressbar *)clientData;
pb->progress.timer = 0;
if (AnimationEnabled(pb)) {
int phase = 0;
Tcl_GetIntFromObj(NULL, pb->progress.phaseObj, &phase);
/*
* Update -phase:
*/
++phase;
if (phase > pb->progress.maxPhase) {
phase = 0;
}
Tcl_DecrRefCount(pb->progress.phaseObj);
pb->progress.phaseObj = Tcl_NewIntObj(phase);
Tcl_IncrRefCount(pb->progress.phaseObj);
/*
* Reschedule:
*/
pb->progress.timer = Tcl_CreateTimerHandler(
pb->progress.period, AnimateProgressProc, clientData);
TtkRedisplayWidget(&pb->core);
}
}
/* CheckAnimation --
* If animation is enabled and not scheduled, schedule it.
* If animation is disabled but scheduled, cancel it.
*/
static void CheckAnimation(Progressbar *pb)
{
if (AnimationEnabled(pb)) {
if (pb->progress.timer == 0) {
pb->progress.timer = Tcl_CreateTimerHandler(
pb->progress.period, AnimateProgressProc, pb);
}
} else {
if (pb->progress.timer != 0) {
Tcl_DeleteTimerHandler(pb->progress.timer);
pb->progress.timer = 0;
}
}
}
/*------------------------------------------------------------------------
* +++ Trace hook for progressbar -variable option:
*/
static void VariableChanged(void *recordPtr, const char *value)
{
Progressbar *pb = (Progressbar *)recordPtr;
Tcl_Obj *newValue;
double scratch;
if (WidgetDestroyed(&pb->core)) {
return;
}
if (!value) {
/* Linked variable is unset -- disable widget */
TtkWidgetChangeState(&pb->core, TTK_STATE_DISABLED, 0);
return;
}
TtkWidgetChangeState(&pb->core, 0, TTK_STATE_DISABLED);
newValue = Tcl_NewStringObj(value, -1);
Tcl_IncrRefCount(newValue);
if (Tcl_GetDoubleFromObj(NULL, newValue, &scratch) != TCL_OK) {
TtkWidgetChangeState(&pb->core, TTK_STATE_INVALID, 0);
return;
}
TtkWidgetChangeState(&pb->core, 0, TTK_STATE_INVALID);
Tcl_DecrRefCount(pb->progress.valueObj);
pb->progress.valueObj = newValue;
CheckAnimation(pb);
TtkRedisplayWidget(&pb->core);
}
/*------------------------------------------------------------------------
* +++ Widget class methods:
*/
static void ProgressbarInitialize(Tcl_Interp *dummy, void *recordPtr)
{
Progressbar *pb = (Progressbar *)recordPtr;
(void)dummy;
pb->progress.variableTrace = 0;
pb->progress.timer = 0;
}
static void ProgressbarCleanup(void *recordPtr)
{
Progressbar *pb = (Progressbar *)recordPtr;
if (pb->progress.variableTrace)
Ttk_UntraceVariable(pb->progress.variableTrace);
if (pb->progress.timer)
Tcl_DeleteTimerHandler(pb->progress.timer);
}
/*
* Configure hook:
*
* @@@ TODO: deal with [$pb configure -value ... -variable ...]
*/
static int ProgressbarConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
{
Progressbar *pb = (Progressbar *)recordPtr;
Tcl_Obj *varName = pb->progress.variableObj;
Ttk_TraceHandle *vt = 0;
if (varName != NULL && *Tcl_GetString(varName) != '\0') {
vt = Ttk_TraceVariable(interp, varName, VariableChanged, recordPtr);
if (!vt) return TCL_ERROR;
}
if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
if (vt) Ttk_UntraceVariable(vt);
return TCL_ERROR;
}
if (pb->progress.variableTrace) {
Ttk_UntraceVariable(pb->progress.variableTrace);
}
pb->progress.variableTrace = vt;
return TCL_OK;
}
/*
* Post-configuration hook:
*/
static int ProgressbarPostConfigure(
Tcl_Interp *dummy, void *recordPtr, int mask)
{
Progressbar *pb = (Progressbar *)recordPtr;
int status = TCL_OK;
(void)dummy;
(void)mask;
if (pb->progress.variableTrace) {
status = Ttk_FireTrace(pb->progress.variableTrace);
if (WidgetDestroyed(&pb->core)) {
return TCL_ERROR;
}
if (status != TCL_OK) {
/* Unset -variable: */
Ttk_UntraceVariable(pb->progress.variableTrace);
Tcl_DecrRefCount(pb->progress.variableObj);
pb->progress.variableTrace = 0;
pb->progress.variableObj = NULL;
return TCL_ERROR;
}
}
CheckAnimation(pb);
return status;
}
/*
* Size hook:
* Compute base layout size, overrid
*/
static int ProgressbarSize(void *recordPtr, int *widthPtr, int *heightPtr)
{
Progressbar *pb = (Progressbar *)recordPtr;
int length = 100;
int orient = TTK_ORIENT_HORIZONTAL;
TtkWidgetSize(recordPtr, widthPtr, heightPtr);
/* Override requested width (height) based on -length and -orient
*/
Tk_GetPixelsFromObj(NULL, pb->core.tkwin, pb->progress.lengthObj, &length);
Ttk_GetOrientFromObj(NULL, pb->progress.orientObj, &orient);
if (orient == TTK_ORIENT_HORIZONTAL) {
*widthPtr = length;
} else {
*heightPtr = length;
}
return 1;
}
/*
* Layout hook:
* Adjust size and position of pbar element, if present.
*/
static void ProgressbarDeterminateLayout(
Progressbar *pb,
Ttk_Element pbar,
Ttk_Box parcel,
double fraction,
Ttk_Orient orient)
{
if (fraction < 0.0) fraction = 0.0;
if (fraction > 1.0) fraction = 1.0;
if (orient == TTK_ORIENT_HORIZONTAL) {
parcel.width = (int)(parcel.width * fraction);
} else {
int newHeight = (int)(parcel.height * fraction);
parcel.y += (parcel.height - newHeight);
parcel.height = newHeight;
}
Ttk_PlaceElement(pb->core.layout, pbar, parcel);
}
static void ProgressbarIndeterminateLayout(
Progressbar *pb,
Ttk_Element pbar,
Ttk_Box parcel,
double fraction,
Ttk_Orient orient)
{
Ttk_Box pbarBox = Ttk_ElementParcel(pbar);
fraction = fmod(fabs(fraction), 2.0);
if (fraction > 1.0) {
fraction = 2.0 - fraction;
}
if (orient == TTK_ORIENT_HORIZONTAL) {
pbarBox.x = parcel.x + (int)(fraction * (parcel.width-pbarBox.width));
} else {
pbarBox.y = parcel.y + (int)(fraction * (parcel.height-pbarBox.height));
}
Ttk_PlaceElement(pb->core.layout, pbar, pbarBox);
}
static void ProgressbarDoLayout(void *recordPtr)
{
Progressbar *pb = (Progressbar *)recordPtr;
WidgetCore *corePtr = &pb->core;
Ttk_Element pbar = Ttk_FindElement(corePtr->layout, "pbar");
double value = 0.0, maximum = 100.0;
int orient = TTK_ORIENT_HORIZONTAL;
Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
/* Adjust the bar size:
*/
Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
Ttk_GetOrientFromObj(NULL, pb->progress.orientObj, &orient);
if (pbar) {
double fraction = value / maximum;
Ttk_Box parcel = Ttk_ClientRegion(corePtr->layout, "trough");
if (pb->progress.mode == TTK_PROGRESSBAR_DETERMINATE) {
ProgressbarDeterminateLayout(
pb, pbar, parcel, fraction, orient);
} else {
ProgressbarIndeterminateLayout(
pb, pbar, parcel, fraction, orient);
}
}
}
static Ttk_Layout ProgressbarGetLayout(
Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
{
Progressbar *pb = (Progressbar *)recordPtr;
Ttk_Layout layout = TtkWidgetGetOrientedLayout(
interp, theme, recordPtr, pb->progress.orientObj);
/*
* Check if the style supports animation:
*/
pb->progress.period = 0;
pb->progress.maxPhase = 0;
if (layout) {
Tcl_Obj *periodObj = Ttk_QueryOption(layout,"-period", 0);
Tcl_Obj *maxPhaseObj = Ttk_QueryOption(layout,"-maxphase", 0);
if (periodObj)
Tcl_GetIntFromObj(NULL, periodObj, &pb->progress.period);
if (maxPhaseObj)
Tcl_GetIntFromObj(NULL, maxPhaseObj, &pb->progress.maxPhase);
}
return layout;
}
/*------------------------------------------------------------------------
* +++ Widget commands:
*/
/* $sb step ?amount?
*/
static int ProgressbarStepCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
Progressbar *pb = (Progressbar *)recordPtr;
double value = 0.0, stepAmount = 1.0;
Tcl_Obj *newValueObj;
if (objc == 3) {
if (Tcl_GetDoubleFromObj(interp, objv[2], &stepAmount) != TCL_OK) {
return TCL_ERROR;
}
} else if (objc != 2) {
Tcl_WrongNumArgs(interp, 2,objv, "?stepAmount?");
return TCL_ERROR;
}
(void)Tcl_GetDoubleFromObj(NULL, pb->progress.valueObj, &value);
value += stepAmount;
/* In determinate mode, wrap around if value exceeds maximum:
*/
if (pb->progress.mode == TTK_PROGRESSBAR_DETERMINATE) {
double maximum = 100.0;
(void)Tcl_GetDoubleFromObj(NULL, pb->progress.maximumObj, &maximum);
value = fmod(value, maximum);
}
newValueObj = Tcl_NewDoubleObj(value);
Tcl_IncrRefCount(newValueObj);
TtkRedisplayWidget(&pb->core);
/* Update value by setting the linked -variable, if there is one:
*/
if (pb->progress.variableTrace) {
int result = Tcl_ObjSetVar2(
interp, pb->progress.variableObj, 0, newValueObj,
TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)
? TCL_OK : TCL_ERROR;
Tcl_DecrRefCount(newValueObj);
return result;
}
/* Otherwise, change the -value directly:
*/
Tcl_DecrRefCount(pb->progress.valueObj);
pb->progress.valueObj = newValueObj;
CheckAnimation(pb);
return TCL_OK;
}
/* $sb start|stop ?args? --
* Change [$sb $cmd ...] to [ttk::progressbar::$cmd ...]
* and pass to interpreter.
*/
static int ProgressbarStartStopCommand(
Tcl_Interp *interp, const char *cmdName, int objc, Tcl_Obj *const objv[])
{
Tcl_Obj *cmd = Tcl_NewListObj(objc, objv);
Tcl_Obj *prefix[2];
int status;
/* ASSERT: objc >= 2 */
prefix[0] = Tcl_NewStringObj(cmdName, -1);
prefix[1] = objv[0];
Tcl_ListObjReplace(interp, cmd, 0,2, 2,prefix);
Tcl_IncrRefCount(cmd);
status = Tcl_EvalObjEx(interp, cmd, 0);
Tcl_DecrRefCount(cmd);
return status;
}
static int ProgressbarStartCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
(void)recordPtr;
return ProgressbarStartStopCommand(
interp, "::ttk::progressbar::start", objc, objv);
}
static int ProgressbarStopCommand(
void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
(void)recordPtr;
return ProgressbarStartStopCommand(
interp, "::ttk::progressbar::stop", objc, objv);
}
static const Ttk_Ensemble ProgressbarCommands[] = {
{ "configure", TtkWidgetConfigureCommand,0 },
{ "cget", TtkWidgetCgetCommand,0 },
{ "identify", TtkWidgetIdentifyCommand,0 },
{ "instate", TtkWidgetInstateCommand,0 },
{ "start", ProgressbarStartCommand,0 },
{ "state", TtkWidgetStateCommand,0 },
{ "step", ProgressbarStepCommand,0 },
{ "stop", ProgressbarStopCommand,0 },
{ 0,0,0 }
};
/*
* Widget specification:
*/
static WidgetSpec ProgressbarWidgetSpec =
{
"TProgressbar", /* className */
sizeof(Progressbar), /* recordSize */
ProgressbarOptionSpecs, /* optionSpecs */
ProgressbarCommands, /* subcommands */
ProgressbarInitialize, /* initializeProc */
ProgressbarCleanup, /* cleanupProc */
ProgressbarConfigure, /* configureProc */
ProgressbarPostConfigure, /* postConfigureProc */
ProgressbarGetLayout, /* getLayoutProc */
ProgressbarSize, /* sizeProc */
ProgressbarDoLayout, /* layoutProc */
TtkWidgetDisplay /* displayProc */
};
/*
* Layouts:
*/
TTK_BEGIN_LAYOUT(VerticalProgressbarLayout)
TTK_GROUP("Vertical.Progressbar.trough", TTK_FILL_BOTH,
TTK_NODE("Vertical.Progressbar.pbar", TTK_PACK_BOTTOM|TTK_FILL_X))
TTK_END_LAYOUT
TTK_BEGIN_LAYOUT(HorizontalProgressbarLayout)
TTK_GROUP("Horizontal.Progressbar.trough", TTK_FILL_BOTH,
TTK_NODE("Horizontal.Progressbar.pbar", TTK_PACK_LEFT|TTK_FILL_Y))
TTK_END_LAYOUT
/*
* Initialization:
*/
MODULE_SCOPE
void TtkProgressbar_Init(Tcl_Interp *interp)
{
Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp);
Ttk_RegisterLayout(themePtr,
"Vertical.TProgressbar", VerticalProgressbarLayout);
Ttk_RegisterLayout(themePtr,
"Horizontal.TProgressbar", HorizontalProgressbarLayout);
RegisterWidget(interp, "ttk::progressbar", &ProgressbarWidgetSpec);
}
/*EOF*/