diff options
Diffstat (limited to 'generic/ttk')
38 files changed, 24891 insertions, 0 deletions
diff --git a/generic/ttk/ttk.decls b/generic/ttk/ttk.decls new file mode 100644 index 0000000..8b2b50b --- /dev/null +++ b/generic/ttk/ttk.decls @@ -0,0 +1,150 @@ +library ttk +interface ttk +epoch 0 +scspec TTKAPI + +declare 0 current { + Ttk_Theme Ttk_GetTheme(Tcl_Interp *interp, const char *name); +} +declare 1 current { + Ttk_Theme Ttk_GetDefaultTheme(Tcl_Interp *interp); +} +declare 2 current { + Ttk_Theme Ttk_GetCurrentTheme(Tcl_Interp *interp); +} +declare 3 current { + Ttk_Theme Ttk_CreateTheme( + Tcl_Interp *interp, const char *name, Ttk_Theme parent); +} +declare 4 current { + void Ttk_RegisterCleanup( + Tcl_Interp *interp, void *deleteData, Ttk_CleanupProc *cleanupProc); +} + +declare 5 current { + int Ttk_RegisterElementSpec( + Ttk_Theme theme, + const char *elementName, + Ttk_ElementSpec *elementSpec, + void *clientData); +} + +declare 6 current { + Ttk_ElementClass *Ttk_RegisterElement( + Tcl_Interp *interp, + Ttk_Theme theme, + const char *elementName, + Ttk_ElementSpec *elementSpec, + void *clientData); +} + +declare 7 current { + int Ttk_RegisterElementFactory( + Tcl_Interp *interp, + const char *name, + Ttk_ElementFactory factoryProc, + void *clientData); +} + +declare 8 current { + void Ttk_RegisterLayout( + Ttk_Theme theme, const char *className, Ttk_LayoutSpec layoutSpec); +} + +# +# State maps. +# +declare 10 current { + int Ttk_GetStateSpecFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_StateSpec *spec_rtn); +} +declare 11 current { + Tcl_Obj *Ttk_NewStateSpecObj( + unsigned int onbits, unsigned int offbits); +} +declare 12 current { + Ttk_StateMap Ttk_GetStateMapFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr); +} +declare 13 current { + Tcl_Obj *Ttk_StateMapLookup( + Tcl_Interp *interp, Ttk_StateMap map, Ttk_State state); +} +declare 14 current { + int Ttk_StateTableLookup( + Ttk_StateTable map[], Ttk_State state); +} + + +# +# Low-level geometry utilities. +# +declare 20 current { + int Ttk_GetPaddingFromObj( + Tcl_Interp *interp, + Tk_Window tkwin, + Tcl_Obj *objPtr, + Ttk_Padding *pad_rtn); +} +declare 21 current { + int Ttk_GetBorderFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + Ttk_Padding *pad_rtn); +} +declare 22 current { + int Ttk_GetStickyFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Sticky *sticky_rtn); +} +declare 23 current { + Ttk_Padding Ttk_MakePadding( + short l, short t, short r, short b); +} +declare 24 current { + Ttk_Padding Ttk_UniformPadding( + short borderWidth); +} +declare 25 current { + Ttk_Padding Ttk_AddPadding(Ttk_Padding pad1, Ttk_Padding pad2); +} +declare 26 current { + Ttk_Padding Ttk_RelievePadding( + Ttk_Padding padding, int relief, int n); +} +declare 27 current { + Ttk_Box Ttk_MakeBox(int x, int y, int width, int height); +} +declare 28 current { + int Ttk_BoxContains(Ttk_Box box, int x, int y); +} +declare 29 current { + Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int w, int h, Ttk_Side side); +} +declare 30 current { + Ttk_Box Ttk_StickBox(Ttk_Box parcel, int w, int h, Ttk_Sticky sticky); +} +declare 31 current { + Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int w, int h, Tk_Anchor anchor); +} +declare 32 current { + Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p); +} +declare 33 current { + Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p); +} +declare 34 current { + Ttk_Box Ttk_PlaceBox( + Ttk_Box *cavity, int w, int h, Ttk_Side side, Ttk_Sticky sticky); +} +declare 35 current { + Tcl_Obj *Ttk_NewBoxObj(Ttk_Box box); +} + +# +# Utilities. +# +declare 40 current { + int Ttk_GetOrientFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *orient); +} + + diff --git a/generic/ttk/ttkBlink.c b/generic/ttk/ttkBlink.c new file mode 100644 index 0000000..7e46fac --- /dev/null +++ b/generic/ttk/ttkBlink.c @@ -0,0 +1,166 @@ +/* + * Copyright 2004, Joe English. + * + * Usage: + * TtkBlinkCursor(corePtr), usually called in a widget's Init hook, + * arranges to periodically toggle the corePtr->flags CURSOR_ON bit + * on and off (and schedule a redisplay) whenever the widget has focus. + * + * Note: Widgets may have additional logic to decide whether + * to display the cursor or not (e.g., readonly or disabled states); + * TtkBlinkCursor() does not account for this. + * + * TODO: + * Add script-level access to configure application-wide blink rate. + */ + +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEF_CURSOR_ON_TIME 600 /* milliseconds */ +#define DEF_CURSOR_OFF_TIME 300 /* milliseconds */ + +/* Interp-specific data for tracking cursors: + */ +typedef struct +{ + WidgetCore *owner; /* Widget that currently has cursor */ + Tcl_TimerToken timer; /* Blink timer */ + int onTime; /* #milliseconds to blink cursor on */ + int offTime; /* #milliseconds to blink cursor off */ +} CursorManager; + +/* CursorManagerDeleteProc -- + * InterpDeleteProc for cursor manager. + */ +static void CursorManagerDeleteProc(ClientData clientData, Tcl_Interp *interp) +{ + CursorManager *cm = (CursorManager*)clientData; + if (cm->timer) { + Tcl_DeleteTimerHandler(cm->timer); + } + ckfree(clientData); +} + +/* GetCursorManager -- + * Look up and create if necessary the interp's cursor manager. + */ +static CursorManager *GetCursorManager(Tcl_Interp *interp) +{ + static const char *cm_key = "ttk::CursorManager"; + CursorManager *cm = (CursorManager *) Tcl_GetAssocData(interp, cm_key,0); + + if (!cm) { + cm = (CursorManager*)ckalloc(sizeof(*cm)); + cm->timer = 0; + cm->owner = 0; + cm->onTime = DEF_CURSOR_ON_TIME; + cm->offTime = DEF_CURSOR_OFF_TIME; + Tcl_SetAssocData(interp,cm_key,CursorManagerDeleteProc,(ClientData)cm); + } + return cm; +} + +/* CursorBlinkProc -- + * Timer handler to blink the insert cursor on and off. + */ +static void +CursorBlinkProc(ClientData clientData) +{ + CursorManager *cm = (CursorManager*)clientData; + int blinkTime; + + if (cm->owner->flags & CURSOR_ON) { + cm->owner->flags &= ~CURSOR_ON; + blinkTime = cm->offTime; + } else { + cm->owner->flags |= CURSOR_ON; + blinkTime = cm->onTime; + } + cm->timer = Tcl_CreateTimerHandler(blinkTime, CursorBlinkProc, clientData); + TtkRedisplayWidget(cm->owner); +} + +/* LoseCursor -- + * Turn cursor off, disable blink timer. + */ +static void LoseCursor(CursorManager *cm, WidgetCore *corePtr) +{ + if (corePtr->flags & CURSOR_ON) { + corePtr->flags &= ~CURSOR_ON; + TtkRedisplayWidget(corePtr); + } + if (cm->owner == corePtr) { + cm->owner = NULL; + } + if (cm->timer) { + Tcl_DeleteTimerHandler(cm->timer); + cm->timer = 0; + } +} + +/* ClaimCursor -- + * Claim ownership of the insert cursor and blink on. + */ +static void ClaimCursor(CursorManager *cm, WidgetCore *corePtr) +{ + if (cm->owner == corePtr) + return; + if (cm->owner) + LoseCursor(cm, cm->owner); + + corePtr->flags |= CURSOR_ON; + TtkRedisplayWidget(corePtr); + + cm->owner = corePtr; + cm->timer = Tcl_CreateTimerHandler(cm->onTime, CursorBlinkProc, cm); +} + +/* + * CursorEventProc -- + * Event handler for FocusIn and FocusOut events; + * claim/lose ownership of the insert cursor when the widget + * acquires/loses keyboard focus. + */ + +#define CursorEventMask (FocusChangeMask|StructureNotifyMask) +#define RealFocusEvent(d) \ + (d == NotifyInferior || d == NotifyAncestor || d == NotifyNonlinear) + +static void +CursorEventProc(ClientData clientData, XEvent *eventPtr) +{ + WidgetCore *corePtr = (WidgetCore *)clientData; + CursorManager *cm = GetCursorManager(corePtr->interp); + + switch (eventPtr->type) { + case DestroyNotify: + if (cm->owner == corePtr) + LoseCursor(cm, corePtr); + Tk_DeleteEventHandler( + corePtr->tkwin, CursorEventMask, CursorEventProc, clientData); + break; + case FocusIn: + if (RealFocusEvent(eventPtr->xfocus.detail)) + ClaimCursor(cm, corePtr); + break; + case FocusOut: + if (RealFocusEvent(eventPtr->xfocus.detail)) + LoseCursor(cm, corePtr); + break; + } +} + +/* + * TtkBlinkCursor (main routine) -- + * Arrange to blink the cursor on and off whenever the + * widget has focus. + */ +void TtkBlinkCursor(WidgetCore *corePtr) +{ + Tk_CreateEventHandler( + corePtr->tkwin, CursorEventMask, CursorEventProc, corePtr); +} + +/*EOF*/ diff --git a/generic/ttk/ttkButton.c b/generic/ttk/ttkButton.c new file mode 100644 index 0000000..2954184 --- /dev/null +++ b/generic/ttk/ttkButton.c @@ -0,0 +1,853 @@ +/* + * Copyright (c) 2003, Joe English + * + * label, button, checkbutton, radiobutton, and menubutton widgets. + */ + +#include <string.h> +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +/* Bit fields for OptionSpec mask field: + */ +#define STATE_CHANGED (0x100) /* -state option changed */ +#define DEFAULTSTATE_CHANGED (0x200) /* -default option changed */ + +/*------------------------------------------------------------------------ + * +++ Base resources for labels, buttons, checkbuttons, etc: + */ +typedef struct +{ + /* + * Text element resources: + */ + Tcl_Obj *textObj; + Tcl_Obj *textVariableObj; + Tcl_Obj *underlineObj; + Tcl_Obj *widthObj; + + Ttk_TraceHandle *textVariableTrace; + Ttk_ImageSpec *imageSpec; + + /* + * Image element resources: + */ + Tcl_Obj *imageObj; + + /* + * Compound label/image resources: + */ + Tcl_Obj *compoundObj; + Tcl_Obj *paddingObj; + + /* + * Compatibility/legacy options: + */ + Tcl_Obj *stateObj; + +} BasePart; + +typedef struct +{ + WidgetCore core; + BasePart base; +} Base; + +static Tk_OptionSpec BaseOptionSpecs[] = +{ + {TK_OPTION_STRING, "-text", "text", "Text", "", + Tk_Offset(Base,base.textObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", "", + Tk_Offset(Base,base.textVariableObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-underline", "underline", "Underline", + "-1", Tk_Offset(Base,base.underlineObj), -1, + 0,0,0 }, + /* SB: OPTION_INT, see <<NOTE-NULLOPTIONS>> */ + {TK_OPTION_STRING, "-width", "width", "Width", + NULL, Tk_Offset(Base,base.widthObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + /* + * Image options + */ + {TK_OPTION_STRING, "-image", "image", "Image", NULL/*default*/, + Tk_Offset(Base,base.imageObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + /* + * Compound base/image options + */ + {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound", + "none", Tk_Offset(Base,base.compoundObj), -1, + 0,(ClientData)ttkCompoundStrings,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-padding", "padding", "Pad", + NULL, Tk_Offset(Base,base.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED}, + + /* + * Compatibility/legacy options + */ + {TK_OPTION_STRING, "-state", "state", "State", + "normal", Tk_Offset(Base,base.stateObj), -1, + 0,0,STATE_CHANGED }, + + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/* + * Variable trace procedure for -textvariable option: + */ +static void TextVariableChanged(void *clientData, const char *value) +{ + Base *basePtr = clientData; + Tcl_Obj *newText; + + if (WidgetDestroyed(&basePtr->core)) { + return; + } + + newText = value ? Tcl_NewStringObj(value, -1) : Tcl_NewStringObj("", 0); + + Tcl_IncrRefCount(newText); + Tcl_DecrRefCount(basePtr->base.textObj); + basePtr->base.textObj = newText; + + TtkResizeWidget(&basePtr->core); +} + +static void +BaseInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Base *basePtr = recordPtr; + basePtr->base.textVariableTrace = 0; + basePtr->base.imageSpec = NULL; +} + +static void +BaseCleanup(void *recordPtr) +{ + Base *basePtr = recordPtr; + if (basePtr->base.textVariableTrace) + Ttk_UntraceVariable(basePtr->base.textVariableTrace); + if (basePtr->base.imageSpec) + TtkFreeImageSpec(basePtr->base.imageSpec); +} + +static int BaseConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Base *basePtr = recordPtr; + Tcl_Obj *textVarName = basePtr->base.textVariableObj; + Ttk_TraceHandle *vt = 0; + Ttk_ImageSpec *imageSpec = NULL; + + if (textVarName != NULL && *Tcl_GetString(textVarName) != '\0') { + vt = Ttk_TraceVariable(interp,textVarName,TextVariableChanged,basePtr); + if (!vt) return TCL_ERROR; + } + + if (basePtr->base.imageObj) { + imageSpec = TtkGetImageSpec( + interp, basePtr->core.tkwin, basePtr->base.imageObj); + if (!imageSpec) { + goto error; + } + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { +error: + if (imageSpec) TtkFreeImageSpec(imageSpec); + if (vt) Ttk_UntraceVariable(vt); + return TCL_ERROR; + } + + if (basePtr->base.textVariableTrace) { + Ttk_UntraceVariable(basePtr->base.textVariableTrace); + } + basePtr->base.textVariableTrace = vt; + + if (basePtr->base.imageSpec) { + TtkFreeImageSpec(basePtr->base.imageSpec); + } + basePtr->base.imageSpec = imageSpec; + + if (mask & STATE_CHANGED) { + TtkCheckStateOption(&basePtr->core, basePtr->base.stateObj); + } + + return TCL_OK; +} + +static int +BasePostConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Base *basePtr = recordPtr; + int status = TCL_OK; + + if (basePtr->base.textVariableTrace) { + status = Ttk_FireTrace(basePtr->base.textVariableTrace); + } + + return status; +} + +/*------------------------------------------------------------------------ + * +++ Label widget. + * Just a base widget that adds a few appearance-related options + */ + +typedef struct +{ + Tcl_Obj *backgroundObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *fontObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *anchorObj; + Tcl_Obj *justifyObj; + Tcl_Obj *wrapLengthObj; +} LabelPart; + +typedef struct +{ + WidgetCore core; + BasePart base; + LabelPart label; +} Label; + +static Tk_OptionSpec LabelOptionSpecs[] = +{ + {TK_OPTION_BORDER, "-background", "frameColor", "FrameColor", + NULL, Tk_Offset(Label,label.backgroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor", + NULL, Tk_Offset(Label,label.foregroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_FONT, "-font", "font", "Font", + NULL, Tk_Offset(Label,label.fontObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + NULL, Tk_Offset(Label,label.borderWidthObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", + NULL, Tk_Offset(Label,label.reliefObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + NULL, Tk_Offset(Label,label.anchorObj), -1, + TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED}, + {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify", + NULL, Tk_Offset(Label, label.justifyObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_PIXELS, "-wraplength", "wrapLength", "WrapLength", + NULL, Tk_Offset(Label, label.wrapLengthObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED /*SB: SIZE_CHANGED*/ }, + + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(BaseOptionSpecs) +}; + +static const Ttk_Ensemble LabelCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec LabelWidgetSpec = +{ + "TLabel", /* className */ + sizeof(Label), /* recordSize */ + LabelOptionSpecs, /* optionSpecs */ + LabelCommands, /* subcommands */ + BaseInitialize, /* initializeProc */ + BaseCleanup, /* cleanupProc */ + BaseConfigure, /* configureProc */ + BasePostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(LabelLayout) + TTK_GROUP("Label.border", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Label.padding", TTK_FILL_BOTH|TTK_BORDER, + TTK_NODE("Label.label", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Button widget. + * Adds a new subcommand "invoke", and options "-command" and "-default" + */ + +typedef struct +{ + Tcl_Obj *commandObj; + Tcl_Obj *defaultStateObj; +} ButtonPart; + +typedef struct +{ + WidgetCore core; + BasePart base; + ButtonPart button; +} Button; + +/* + * Option specifications: + */ +static Tk_OptionSpec ButtonOptionSpecs[] = +{ + {TK_OPTION_STRING, "-command", "command", "Command", + "", Tk_Offset(Button, button.commandObj), -1, 0,0,0}, + {TK_OPTION_STRING_TABLE, "-default", "default", "Default", + "normal", Tk_Offset(Button, button.defaultStateObj), -1, + 0, (ClientData) ttkDefaultStrings, DEFAULTSTATE_CHANGED}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(BaseOptionSpecs) +}; + +static int ButtonConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Button *buttonPtr = recordPtr; + + if (BaseConfigure(interp, recordPtr, mask) != TCL_OK) { + return TCL_ERROR; + } + + /* Handle "-default" option: + */ + if (mask & DEFAULTSTATE_CHANGED) { + int defaultState = TTK_BUTTON_DEFAULT_DISABLED; + Ttk_GetButtonDefaultStateFromObj( + NULL, buttonPtr->button.defaultStateObj, &defaultState); + if (defaultState == TTK_BUTTON_DEFAULT_ACTIVE) { + TtkWidgetChangeState(&buttonPtr->core, TTK_STATE_ALTERNATE, 0); + } else { + TtkWidgetChangeState(&buttonPtr->core, 0, TTK_STATE_ALTERNATE); + } + } + return TCL_OK; +} + +/* $button invoke -- + * Evaluate the button's -command. + */ +static int +ButtonInvokeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Button *buttonPtr = recordPtr; + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "invoke"); + return TCL_ERROR; + } + if (buttonPtr->core.state & TTK_STATE_DISABLED) { + return TCL_OK; + } + return Tcl_EvalObjEx(interp, buttonPtr->button.commandObj, TCL_EVAL_GLOBAL); +} + +static const Ttk_Ensemble ButtonCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "invoke", ButtonInvokeCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec ButtonWidgetSpec = +{ + "TButton", /* className */ + sizeof(Button), /* recordSize */ + ButtonOptionSpecs, /* optionSpecs */ + ButtonCommands, /* subcommands */ + BaseInitialize, /* initializeProc */ + BaseCleanup, /* cleanupProc */ + ButtonConfigure, /* configureProc */ + BasePostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(ButtonLayout) + TTK_GROUP("Button.border", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Button.focus", TTK_FILL_BOTH, + TTK_GROUP("Button.padding", TTK_FILL_BOTH, + TTK_NODE("Button.label", TTK_FILL_BOTH)))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Checkbutton widget. + */ +typedef struct +{ + Tcl_Obj *variableObj; + Tcl_Obj *onValueObj; + Tcl_Obj *offValueObj; + Tcl_Obj *commandObj; + + Ttk_TraceHandle *variableTrace; + +} CheckbuttonPart; + +typedef struct +{ + WidgetCore core; + BasePart base; + CheckbuttonPart checkbutton; +} Checkbutton; + +/* + * Option specifications: + */ +static Tk_OptionSpec CheckbuttonOptionSpecs[] = +{ + {TK_OPTION_STRING, "-variable", "variable", "Variable", + "", Tk_Offset(Checkbutton, checkbutton.variableObj), -1, + TK_OPTION_DONT_SET_DEFAULT,0,0}, + {TK_OPTION_STRING, "-onvalue", "onValue", "OnValue", + "1", Tk_Offset(Checkbutton, checkbutton.onValueObj), -1, + 0,0,0}, + {TK_OPTION_STRING, "-offvalue", "offValue", "OffValue", + "0", Tk_Offset(Checkbutton, checkbutton.offValueObj), -1, + 0,0,0}, + {TK_OPTION_STRING, "-command", "command", "Command", + "", Tk_Offset(Checkbutton, checkbutton.commandObj), -1, + 0,0,0}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(BaseOptionSpecs) +}; + +/* + * Variable trace procedure for checkbutton -variable option + */ +static void CheckbuttonVariableChanged(void *clientData, const char *value) +{ + Checkbutton *checkPtr = clientData; + + if (WidgetDestroyed(&checkPtr->core)) { + return; + } + + if (!value) { + TtkWidgetChangeState(&checkPtr->core, TTK_STATE_ALTERNATE, 0); + return; + } + /* else */ + TtkWidgetChangeState(&checkPtr->core, 0, TTK_STATE_ALTERNATE); + if (!strcmp(value, Tcl_GetString(checkPtr->checkbutton.onValueObj))) { + TtkWidgetChangeState(&checkPtr->core, TTK_STATE_SELECTED, 0); + } else { + TtkWidgetChangeState(&checkPtr->core, 0, TTK_STATE_SELECTED); + } +} + +static void +CheckbuttonInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Checkbutton *checkPtr = recordPtr; + Tcl_Obj *variableObj; + + /* default -variable is the widget name: + */ + variableObj = Tcl_NewStringObj(Tk_PathName(checkPtr->core.tkwin), -1); + Tcl_IncrRefCount(variableObj); + checkPtr->checkbutton.variableObj = variableObj; + BaseInitialize(interp, recordPtr); +} + +static void +CheckbuttonCleanup(void *recordPtr) +{ + Checkbutton *checkPtr = recordPtr; + Ttk_UntraceVariable(checkPtr->checkbutton.variableTrace); + checkPtr->checkbutton.variableTrace = 0; + BaseCleanup(recordPtr); +} + +static int +CheckbuttonConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Checkbutton *checkPtr = recordPtr; + Ttk_TraceHandle *vt = Ttk_TraceVariable( + interp, checkPtr->checkbutton.variableObj, + CheckbuttonVariableChanged, checkPtr); + + if (!vt) { + return TCL_ERROR; + } + + if (BaseConfigure(interp, recordPtr, mask) != TCL_OK){ + Ttk_UntraceVariable(vt); + return TCL_ERROR; + } + + Ttk_UntraceVariable(checkPtr->checkbutton.variableTrace); + checkPtr->checkbutton.variableTrace = vt; + + return TCL_OK; +} + +static int +CheckbuttonPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Checkbutton *checkPtr = recordPtr; + int status = TCL_OK; + + if (checkPtr->checkbutton.variableTrace) + status = Ttk_FireTrace(checkPtr->checkbutton.variableTrace); + if (status == TCL_OK && !WidgetDestroyed(&checkPtr->core)) + status = BasePostConfigure(interp, recordPtr, mask); + return status; +} + +/* + * Checkbutton 'invoke' subcommand: + * Toggles the checkbutton state. + */ +static int +CheckbuttonInvokeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Checkbutton *checkPtr = recordPtr; + WidgetCore *corePtr = &checkPtr->core; + Tcl_Obj *newValue; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "invoke"); + return TCL_ERROR; + } + if (corePtr->state & TTK_STATE_DISABLED) + return TCL_OK; + + /* + * Toggle the selected state. + */ + if (corePtr->state & TTK_STATE_SELECTED) + newValue = checkPtr->checkbutton.offValueObj; + else + newValue = checkPtr->checkbutton.onValueObj; + + if (Tcl_ObjSetVar2(interp, + checkPtr->checkbutton.variableObj, NULL, newValue, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) + == NULL) + return TCL_ERROR; + + if (WidgetDestroyed(corePtr)) + return TCL_ERROR; + + return Tcl_EvalObjEx(interp, + checkPtr->checkbutton.commandObj, TCL_EVAL_GLOBAL); +} + +static const Ttk_Ensemble CheckbuttonCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "invoke", CheckbuttonInvokeCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + /* MISSING: select, deselect, toggle */ + { 0,0,0 } +}; + +static WidgetSpec CheckbuttonWidgetSpec = +{ + "TCheckbutton", /* className */ + sizeof(Checkbutton), /* recordSize */ + CheckbuttonOptionSpecs, /* optionSpecs */ + CheckbuttonCommands, /* subcommands */ + CheckbuttonInitialize, /* initializeProc */ + CheckbuttonCleanup, /* cleanupProc */ + CheckbuttonConfigure, /* configureProc */ + CheckbuttonPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(CheckbuttonLayout) + TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH, + TTK_NODE("Checkbutton.indicator", TTK_PACK_LEFT) + TTK_GROUP("Checkbutton.focus", TTK_PACK_LEFT | TTK_STICK_W, + TTK_NODE("Checkbutton.label", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Radiobutton widget. + */ + +typedef struct +{ + Tcl_Obj *variableObj; + Tcl_Obj *valueObj; + Tcl_Obj *commandObj; + + Ttk_TraceHandle *variableTrace; + +} RadiobuttonPart; + +typedef struct +{ + WidgetCore core; + BasePart base; + RadiobuttonPart radiobutton; +} Radiobutton; + +/* + * Option specifications: + */ +static Tk_OptionSpec RadiobuttonOptionSpecs[] = +{ + {TK_OPTION_STRING, "-variable", "variable", "Variable", + "::selectedButton", Tk_Offset(Radiobutton, radiobutton.variableObj),-1, + 0,0,0}, + {TK_OPTION_STRING, "-value", "Value", "Value", + "1", Tk_Offset(Radiobutton, radiobutton.valueObj), -1, + 0,0,0}, + {TK_OPTION_STRING, "-command", "command", "Command", + "", Tk_Offset(Radiobutton, radiobutton.commandObj), -1, + 0,0,0}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(BaseOptionSpecs) +}; + +/* + * Variable trace procedure for radiobuttons. + */ +static void +RadiobuttonVariableChanged(void *clientData, const char *value) +{ + Radiobutton *radioPtr = clientData; + + if (WidgetDestroyed(&radioPtr->core)) { + return; + } + + if (!value) { + TtkWidgetChangeState(&radioPtr->core, TTK_STATE_ALTERNATE, 0); + return; + } + /* else */ + TtkWidgetChangeState(&radioPtr->core, 0, TTK_STATE_ALTERNATE); + if (!strcmp(value, Tcl_GetString(radioPtr->radiobutton.valueObj))) { + TtkWidgetChangeState(&radioPtr->core, TTK_STATE_SELECTED, 0); + } else { + TtkWidgetChangeState(&radioPtr->core, 0, TTK_STATE_SELECTED); + } +} + +static void +RadiobuttonCleanup(void *recordPtr) +{ + Radiobutton *radioPtr = recordPtr; + Ttk_UntraceVariable(radioPtr->radiobutton.variableTrace); + radioPtr->radiobutton.variableTrace = 0; + BaseCleanup(recordPtr); +} + +static int +RadiobuttonConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Radiobutton *radioPtr = recordPtr; + Ttk_TraceHandle *vt = Ttk_TraceVariable( + interp, radioPtr->radiobutton.variableObj, + RadiobuttonVariableChanged, radioPtr); + + if (!vt) { + return TCL_ERROR; + } + + if (BaseConfigure(interp, recordPtr, mask) != TCL_OK) { + Ttk_UntraceVariable(vt); + return TCL_ERROR; + } + + Ttk_UntraceVariable(radioPtr->radiobutton.variableTrace); + radioPtr->radiobutton.variableTrace = vt; + + return TCL_OK; +} + +static int +RadiobuttonPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Radiobutton *radioPtr = recordPtr; + int status = TCL_OK; + + if (radioPtr->radiobutton.variableTrace) + status = Ttk_FireTrace(radioPtr->radiobutton.variableTrace); + if (status == TCL_OK && !WidgetDestroyed(&radioPtr->core)) + status = BasePostConfigure(interp, recordPtr, mask); + return status; +} + +/* + * Radiobutton 'invoke' subcommand: + * Sets the radiobutton -variable to the -value, evaluates the -command. + */ +static int +RadiobuttonInvokeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Radiobutton *radioPtr = recordPtr; + WidgetCore *corePtr = &radioPtr->core; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "invoke"); + return TCL_ERROR; + } + if (corePtr->state & TTK_STATE_DISABLED) + return TCL_OK; + + if (Tcl_ObjSetVar2(interp, + radioPtr->radiobutton.variableObj, NULL, + radioPtr->radiobutton.valueObj, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) + == NULL) + return TCL_ERROR; + + if (WidgetDestroyed(corePtr)) + return TCL_ERROR; + + return Tcl_EvalObjEx(interp, + radioPtr->radiobutton.commandObj, TCL_EVAL_GLOBAL); +} + +static const Ttk_Ensemble RadiobuttonCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "invoke", RadiobuttonInvokeCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + /* MISSING: select, deselect */ + { 0,0,0 } +}; + +static WidgetSpec RadiobuttonWidgetSpec = +{ + "TRadiobutton", /* className */ + sizeof(Radiobutton), /* recordSize */ + RadiobuttonOptionSpecs, /* optionSpecs */ + RadiobuttonCommands, /* subcommands */ + BaseInitialize, /* initializeProc */ + RadiobuttonCleanup, /* cleanupProc */ + RadiobuttonConfigure, /* configureProc */ + RadiobuttonPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(RadiobuttonLayout) + TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH, + TTK_NODE("Radiobutton.indicator", TTK_PACK_LEFT) + TTK_GROUP("Radiobutton.focus", TTK_PACK_LEFT, + TTK_NODE("Radiobutton.label", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Menubutton widget. + */ + +typedef struct +{ + Tcl_Obj *menuObj; + Tcl_Obj *directionObj; +} MenubuttonPart; + +typedef struct +{ + WidgetCore core; + BasePart base; + MenubuttonPart menubutton; +} Menubutton; + +/* + * Option specifications: + */ +static const char *const directionStrings[] = { + "above", "below", "left", "right", "flush", NULL +}; +static Tk_OptionSpec MenubuttonOptionSpecs[] = +{ + {TK_OPTION_STRING, "-menu", "menu", "Menu", + "", Tk_Offset(Menubutton, menubutton.menuObj), -1, 0,0,0}, + {TK_OPTION_STRING_TABLE, "-direction", "direction", "Direction", + "below", Tk_Offset(Menubutton, menubutton.directionObj), -1, + 0,(ClientData)directionStrings,GEOMETRY_CHANGED}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(BaseOptionSpecs) +}; + +static const Ttk_Ensemble MenubuttonCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec MenubuttonWidgetSpec = +{ + "TMenubutton", /* className */ + sizeof(Menubutton), /* recordSize */ + MenubuttonOptionSpecs, /* optionSpecs */ + MenubuttonCommands, /* subcommands */ + BaseInitialize, /* initializeProc */ + BaseCleanup, /* cleanupProc */ + BaseConfigure, /* configureProc */ + BasePostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(MenubuttonLayout) + TTK_GROUP("Menubutton.border", TTK_FILL_BOTH, + TTK_GROUP("Menubutton.focus", TTK_FILL_BOTH, + TTK_NODE("Menubutton.indicator", TTK_PACK_RIGHT) + TTK_GROUP("Menubutton.padding", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_X, + TTK_NODE("Menubutton.label", TTK_PACK_LEFT)))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkButton_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "TLabel", LabelLayout); + Ttk_RegisterLayout(theme, "TButton", ButtonLayout); + Ttk_RegisterLayout(theme, "TCheckbutton", CheckbuttonLayout); + Ttk_RegisterLayout(theme, "TRadiobutton", RadiobuttonLayout); + Ttk_RegisterLayout(theme, "TMenubutton", MenubuttonLayout); + + RegisterWidget(interp, "ttk::label", &LabelWidgetSpec); + RegisterWidget(interp, "ttk::button", &ButtonWidgetSpec); + RegisterWidget(interp, "ttk::checkbutton", &CheckbuttonWidgetSpec); + RegisterWidget(interp, "ttk::radiobutton", &RadiobuttonWidgetSpec); + RegisterWidget(interp, "ttk::menubutton", &MenubuttonWidgetSpec); +} diff --git a/generic/ttk/ttkCache.c b/generic/ttk/ttkCache.c new file mode 100644 index 0000000..e3aeaba --- /dev/null +++ b/generic/ttk/ttkCache.c @@ -0,0 +1,350 @@ +/* + * Theme engine resource cache. + * + * Copyright (c) 2004, Joe English + * + * The problem: + * + * Tk maintains reference counts for fonts, colors, and images, + * and deallocates them when the reference count goes to zero. + * With the theme engine, resources are allocated right before + * drawing an element and released immediately after. + * This causes a severe performance penalty, and on PseudoColor + * visuals it causes colormap cycling as colormap entries are + * released and reused. + * + * Solution: Acquire fonts, colors, and objects from a + * resource cache instead of directly from Tk; the cache + * holds a semipermanent reference to the resource to keep + * it from being deallocated. + * + * The plumbing and control flow here is quite contorted; + * it would be better to address this problem in the core instead. + * + * @@@ BUGS/TODO: Need distinct caches for each combination + * of display, visual, and colormap. + * + * @@@ Colormap flashing on PseudoColor visuals is still possible, + * but this will be a transient effect. + */ + +#include <stdio.h> /* for sprintf */ +#include <tk.h> +#include "ttkTheme.h" + +struct Ttk_ResourceCache_ { + Tcl_Interp *interp; /* Interpreter for error reporting */ + Tk_Window tkwin; /* Cache window. */ + Tcl_HashTable fontTable; /* Entries: Tcl_Obj* holding FontObjs */ + Tcl_HashTable colorTable; /* Entries: Tcl_Obj* holding ColorObjs */ + Tcl_HashTable borderTable; /* Entries: Tcl_Obj* holding BorderObjs */ + Tcl_HashTable imageTable; /* Entries: Tk_Images */ + + Tcl_HashTable namedColors; /* Entries: RGB values as Tcl_StringObjs */ +}; + +/* + * Ttk_CreateResourceCache -- + * Initialize a new resource cache. + */ +Ttk_ResourceCache Ttk_CreateResourceCache(Tcl_Interp *interp) +{ + Ttk_ResourceCache cache = (Ttk_ResourceCache)ckalloc(sizeof(*cache)); + + cache->tkwin = NULL; /* initialized later */ + cache->interp = interp; + Tcl_InitHashTable(&cache->fontTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&cache->colorTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&cache->borderTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&cache->imageTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&cache->namedColors, TCL_STRING_KEYS); + + return cache; +} + +/* + * Ttk_ClearCache -- + * Release references to all cached resources. + */ +static void Ttk_ClearCache(Ttk_ResourceCache cache) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + + /* + * Free fonts: + */ + entryPtr = Tcl_FirstHashEntry(&cache->fontTable, &search); + while (entryPtr != NULL) { + Tcl_Obj *fontObj = Tcl_GetHashValue(entryPtr); + if (fontObj) { + Tk_FreeFontFromObj(cache->tkwin, fontObj); + Tcl_DecrRefCount(fontObj); + } + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&cache->fontTable); + Tcl_InitHashTable(&cache->fontTable, TCL_STRING_KEYS); + + /* + * Free colors: + */ + entryPtr = Tcl_FirstHashEntry(&cache->colorTable, &search); + while (entryPtr != NULL) { + Tcl_Obj *colorObj = Tcl_GetHashValue(entryPtr); + if (colorObj) { + Tk_FreeColorFromObj(cache->tkwin, colorObj); + Tcl_DecrRefCount(colorObj); + } + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&cache->colorTable); + Tcl_InitHashTable(&cache->colorTable, TCL_STRING_KEYS); + + /* + * Free borders: + */ + entryPtr = Tcl_FirstHashEntry(&cache->borderTable, &search); + while (entryPtr != NULL) { + Tcl_Obj *borderObj = Tcl_GetHashValue(entryPtr); + if (borderObj) { + Tk_Free3DBorderFromObj(cache->tkwin, borderObj); + Tcl_DecrRefCount(borderObj); + } + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&cache->borderTable); + Tcl_InitHashTable(&cache->borderTable, TCL_STRING_KEYS); + + /* + * Free images: + */ + entryPtr = Tcl_FirstHashEntry(&cache->imageTable, &search); + while (entryPtr != NULL) { + Tk_Image image = Tcl_GetHashValue(entryPtr); + if (image) { + Tk_FreeImage(image); + } + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&cache->imageTable); + Tcl_InitHashTable(&cache->imageTable, TCL_STRING_KEYS); + + return; +} + +/* + * Ttk_FreeResourceCache -- + * Release references to all cached resources, delete the cache. + */ + +void Ttk_FreeResourceCache(Ttk_ResourceCache cache) +{ + Tcl_HashEntry *entryPtr; + Tcl_HashSearch search; + + Ttk_ClearCache(cache); + + Tcl_DeleteHashTable(&cache->colorTable); + Tcl_DeleteHashTable(&cache->fontTable); + Tcl_DeleteHashTable(&cache->imageTable); + + /* + * Free named colors: + */ + entryPtr = Tcl_FirstHashEntry(&cache->namedColors, &search); + while (entryPtr != NULL) { + Tcl_Obj *colorNameObj = Tcl_GetHashValue(entryPtr); + Tcl_DecrRefCount(colorNameObj); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&cache->namedColors); + + ckfree((ClientData)cache); +} + +/* + * CacheWinEventHandler -- + * Detect when the cache window is destroyed, clear cache. + */ +static void CacheWinEventHandler(ClientData clientData, XEvent *eventPtr) +{ + Ttk_ResourceCache cache = clientData; + + if (eventPtr->type != DestroyNotify) { + return; + } + Tk_DeleteEventHandler(cache->tkwin, StructureNotifyMask, + CacheWinEventHandler, clientData); + Ttk_ClearCache(cache); + cache->tkwin = NULL; +} + +/* + * InitCacheWindow -- + * Specify the cache window if not already set. + * @@@ SHOULD: use separate caches for each combination + * @@@ of display, visual, and colormap. + */ +static void InitCacheWindow(Ttk_ResourceCache cache, Tk_Window tkwin) +{ + if (cache->tkwin == NULL) { + cache->tkwin = tkwin; + Tk_CreateEventHandler(tkwin, StructureNotifyMask, + CacheWinEventHandler, cache); + } +} + +/* + * Ttk_RegisterNamedColor -- + * Specify an RGB triplet as a named color. + * Overrides any previous named color specification. + */ +void Ttk_RegisterNamedColor( + Ttk_ResourceCache cache, + const char *colorName, + XColor *colorPtr) +{ + int newEntry; + Tcl_HashEntry *entryPtr; + char nameBuf[14]; + Tcl_Obj *colorNameObj; + + sprintf(nameBuf, "#%04X%04X%04X", + colorPtr->red, colorPtr->green, colorPtr->blue); + colorNameObj = Tcl_NewStringObj(nameBuf, -1); + Tcl_IncrRefCount(colorNameObj); + + entryPtr = Tcl_CreateHashEntry(&cache->namedColors, colorName, &newEntry); + if (!newEntry) { + Tcl_Obj *oldColor = Tcl_GetHashValue(entryPtr); + Tcl_DecrRefCount(oldColor); + } + + Tcl_SetHashValue(entryPtr, colorNameObj); +} + +/* + * CheckNamedColor(objPtr) -- + * If objPtr is a registered color name, return a Tcl_Obj * + * containing the registered color value specification. + * Otherwise, return the input argument. + */ +static Tcl_Obj *CheckNamedColor(Ttk_ResourceCache cache, Tcl_Obj *objPtr) +{ + Tcl_HashEntry *entryPtr = + Tcl_FindHashEntry(&cache->namedColors, Tcl_GetString(objPtr)); + if (entryPtr) { /* Use named color instead */ + objPtr = Tcl_GetHashValue(entryPtr); + } + return objPtr; +} + +/* + * Template for allocation routines: + */ +typedef void *(*Allocator)(Tcl_Interp *, Tk_Window, Tcl_Obj *); + +static Tcl_Obj *Ttk_Use( + Tcl_Interp *interp, + Tcl_HashTable *table, + Allocator allocate, + Tk_Window tkwin, + Tcl_Obj *objPtr) +{ + int newEntry; + Tcl_HashEntry *entryPtr = + Tcl_CreateHashEntry(table,Tcl_GetString(objPtr),&newEntry); + Tcl_Obj *cacheObj; + + if (!newEntry) { + return Tcl_GetHashValue(entryPtr); + } + + cacheObj = Tcl_DuplicateObj(objPtr); + Tcl_IncrRefCount(cacheObj); + + if (allocate(interp, tkwin, cacheObj)) { + Tcl_SetHashValue(entryPtr, cacheObj); + return cacheObj; + } else { + Tcl_DecrRefCount(cacheObj); + Tcl_SetHashValue(entryPtr, NULL); + Tcl_BackgroundError(interp); + return NULL; + } +} + +/* + * Ttk_UseFont -- + * Acquire a font from the cache. + */ +Tcl_Obj *Ttk_UseFont(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr) +{ + InitCacheWindow(cache, tkwin); + return Ttk_Use(cache->interp, + &cache->fontTable,(Allocator)Tk_AllocFontFromObj, tkwin, objPtr); +} + +/* + * Ttk_UseColor -- + * Acquire a color from the cache. + */ +Tcl_Obj *Ttk_UseColor(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr) +{ + objPtr = CheckNamedColor(cache, objPtr); + InitCacheWindow(cache, tkwin); + return Ttk_Use(cache->interp, + &cache->colorTable,(Allocator)Tk_AllocColorFromObj, tkwin, objPtr); +} + +/* + * Ttk_UseBorder -- + * Acquire a Tk_3DBorder from the cache. + */ +Tcl_Obj *Ttk_UseBorder( + Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr) +{ + objPtr = CheckNamedColor(cache, objPtr); + InitCacheWindow(cache, tkwin); + return Ttk_Use(cache->interp, + &cache->borderTable,(Allocator)Tk_Alloc3DBorderFromObj, tkwin, objPtr); +} + +/* NullImageChanged -- + * Tk_ImageChangedProc for Ttk_UseImage + */ + +static void NullImageChanged(ClientData clientData, + int x, int y, int width, int height, int imageWidth, int imageHeight) +{ /* No-op */ } + +/* + * Ttk_UseImage -- + * Acquire a Tk_Image from the cache. + */ +Tk_Image Ttk_UseImage(Ttk_ResourceCache cache, Tk_Window tkwin, Tcl_Obj *objPtr) +{ + const char *imageName = Tcl_GetString(objPtr); + int newEntry; + Tcl_HashEntry *entryPtr = + Tcl_CreateHashEntry(&cache->imageTable,imageName,&newEntry); + Tk_Image image; + + InitCacheWindow(cache, tkwin); + + if (!newEntry) { + return Tcl_GetHashValue(entryPtr); + } + + image = Tk_GetImage(cache->interp, tkwin, imageName, NullImageChanged,0); + Tcl_SetHashValue(entryPtr, image); + + if (!image) { + Tcl_BackgroundError(cache->interp); + } + + return image; +} + +/*EOF*/ diff --git a/generic/ttk/ttkClamTheme.c b/generic/ttk/ttkClamTheme.c new file mode 100644 index 0000000..572f630 --- /dev/null +++ b/generic/ttk/ttkClamTheme.c @@ -0,0 +1,971 @@ +/* + * Copyright (C) 2004 Joe English + * + * "clam" theme; inspired by the XFCE family of Gnome themes. + */ + +#include <tk.h> +#include "ttkTheme.h" + +/* + * Under windows, the Tk-provided XDrawLine and XDrawArc have an + * off-by-one error in the end point. This is especially apparent with this + * theme. Defining this macro as true handles this case. + */ +#if defined(WIN32) && !defined(WIN32_XDRAWLINE_HACK) +# define WIN32_XDRAWLINE_HACK 1 +#else +# define WIN32_XDRAWLINE_HACK 0 +#endif + +#define STR(x) StR(x) +#define StR(x) #x + +#define SCROLLBAR_THICKNESS 14 + +#define FRAME_COLOR "#dcdad5" +#define LIGHT_COLOR "#ffffff" +#define DARK_COLOR "#cfcdc8" +#define DARKER_COLOR "#bab5ab" +#define DARKEST_COLOR "#9e9a91" + +/*------------------------------------------------------------------------ + * +++ Utilities. + */ + +static GC Ttk_GCForColor(Tk_Window tkwin, Tcl_Obj* colorObj, Drawable d) +{ + GC gc = Tk_GCForColor(Tk_GetColorFromObj(tkwin, colorObj), d); + +#ifdef MAC_OSX_TK + /* + * Workaround for Tk bug under Aqua where the default line width is 0. + */ + Display *display = Tk_Display(tkwin); + unsigned long mask = 0ul; + XGCValues gcValues; + + gcValues.line_width = 1; + mask = GCLineWidth; + + XChangeGC(display, gc, mask, &gcValues); +#endif + + return gc; +} + +static void DrawSmoothBorder( + Tk_Window tkwin, Drawable d, Ttk_Box b, + Tcl_Obj *outerColorObj, Tcl_Obj *upperColorObj, Tcl_Obj *lowerColorObj) +{ + Display *display = Tk_Display(tkwin); + int x1 = b.x, x2 = b.x + b.width - 1; + int y1 = b.y, y2 = b.y + b.height - 1; + const int w = WIN32_XDRAWLINE_HACK; + GC gc; + + if ( outerColorObj + && (gc=Ttk_GCForColor(tkwin,outerColorObj,d))) + { + XDrawLine(display,d,gc, x1+1,y1, x2-1+w,y1); /* N */ + XDrawLine(display,d,gc, x1+1,y2, x2-1+w,y2); /* S */ + XDrawLine(display,d,gc, x1,y1+1, x1,y2-1+w); /* E */ + XDrawLine(display,d,gc, x2,y1+1, x2,y2-1+w); /* W */ + } + + if ( upperColorObj + && (gc=Ttk_GCForColor(tkwin,upperColorObj,d))) + { + XDrawLine(display,d,gc, x1+1,y1+1, x2-1+w,y1+1); /* N */ + XDrawLine(display,d,gc, x1+1,y1+1, x1+1,y2-1); /* E */ + } + + if ( lowerColorObj + && (gc=Ttk_GCForColor(tkwin,lowerColorObj,d))) + { + XDrawLine(display,d,gc, x2-1,y2-1, x1+1-w,y2-1); /* S */ + XDrawLine(display,d,gc, x2-1,y2-1, x2-1,y1+1-w); /* W */ + } +} + +static GC BackgroundGC(Tk_Window tkwin, Tcl_Obj *backgroundObj) +{ + Tk_3DBorder bd = Tk_Get3DBorderFromObj(tkwin, backgroundObj); + return Tk_3DBorderGC(tkwin, bd, TK_3D_FLAT_GC); +} + +/*------------------------------------------------------------------------ + * +++ Border element. + */ + +typedef struct { + Tcl_Obj *borderColorObj; + Tcl_Obj *lightColorObj; + Tcl_Obj *darkColorObj; + Tcl_Obj *reliefObj; + Tcl_Obj *borderWidthObj; /* See <<NOTE-BORDERWIDTH>> */ +} BorderElement; + +static Ttk_ElementOptionSpec BorderElementOptions[] = { + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(BorderElement,borderColorObj), DARKEST_COLOR }, + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(BorderElement,lightColorObj), LIGHT_COLOR }, + { "-darkcolor", TK_OPTION_COLOR, + Tk_Offset(BorderElement,darkColorObj), DARK_COLOR }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(BorderElement,reliefObj), "flat" }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(BorderElement,borderWidthObj), "2" }, + { NULL, 0, 0, NULL } +}; + +/* + * <<NOTE-BORDERWIDTH>>: -borderwidth is only partially supported: + * in this theme, borders are always exactly 2 pixels thick. + * With -borderwidth 0, border is not drawn at all; + * otherwise a 2-pixel border is used. For -borderwidth > 2, + * the excess is used as padding. + */ + +static void BorderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + BorderElement *border = (BorderElement*)elementRecord; + int borderWidth = 2; + Tk_GetPixelsFromObj(NULL, tkwin, border->borderWidthObj, &borderWidth); + if (borderWidth == 1) ++borderWidth; + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void BorderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + BorderElement *border = elementRecord; + int relief = TK_RELIEF_FLAT; + int borderWidth = 2; + Tcl_Obj *outer = 0, *upper = 0, *lower = 0; + + Tk_GetReliefFromObj(NULL, border->reliefObj, &relief); + Tk_GetPixelsFromObj(NULL, tkwin, border->borderWidthObj, &borderWidth); + + if (borderWidth == 0) return; + + switch (relief) { + case TK_RELIEF_GROOVE : + case TK_RELIEF_RIDGE : + case TK_RELIEF_RAISED : + outer = border->borderColorObj; + upper = border->lightColorObj; + lower = border->darkColorObj; + break; + case TK_RELIEF_SUNKEN : + outer = border->borderColorObj; + upper = border->darkColorObj; + lower = border->lightColorObj; + break; + case TK_RELIEF_FLAT : + outer = upper = lower = 0; + break; + case TK_RELIEF_SOLID : + outer = upper = lower = border->borderColorObj; + break; + } + + DrawSmoothBorder(tkwin, d, b, outer, upper, lower); +} + +static Ttk_ElementSpec BorderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(BorderElement), + BorderElementOptions, + BorderElementSize, + BorderElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Field element. + */ + +typedef struct { + Tcl_Obj *borderColorObj; + Tcl_Obj *lightColorObj; + Tcl_Obj *darkColorObj; + Tcl_Obj *backgroundObj; +} FieldElement; + +static Ttk_ElementOptionSpec FieldElementOptions[] = { + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(FieldElement,borderColorObj), DARKEST_COLOR }, + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(FieldElement,lightColorObj), LIGHT_COLOR }, + { "-darkcolor", TK_OPTION_COLOR, + Tk_Offset(FieldElement,darkColorObj), DARK_COLOR }, + { "-fieldbackground", TK_OPTION_BORDER, + Tk_Offset(FieldElement,backgroundObj), "white" }, + { NULL, 0, 0, NULL } +}; + +static void FieldElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *paddingPtr = Ttk_UniformPadding(2); +} + +static void FieldElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + FieldElement *field = elementRecord; + Tk_3DBorder bg = Tk_Get3DBorderFromObj(tkwin, field->backgroundObj); + Ttk_Box f = Ttk_PadBox(b, Ttk_UniformPadding(2)); + Tcl_Obj *outer = field->borderColorObj, + *inner = field->lightColorObj; + + DrawSmoothBorder(tkwin, d, b, outer, inner, inner); + Tk_Fill3DRectangle( + tkwin, d, bg, f.x, f.y, f.width, f.height, 0, TK_RELIEF_SUNKEN); +} + +static Ttk_ElementSpec FieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + FieldElementSize, + FieldElementDraw +}; + +/* + * Modified field element for comboboxes: + * Right edge is expanded to overlap the dropdown button. + */ +static void ComboboxFieldElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + FieldElement *field = elementRecord; + GC gc = Ttk_GCForColor(tkwin,field->borderColorObj,d); + + ++b.width; + FieldElementDraw(clientData, elementRecord, tkwin, d, b, state); + + XDrawLine(Tk_Display(tkwin), d, gc, + b.x + b.width - 1, b.y, + b.x + b.width - 1, b.y + b.height - 1 + WIN32_XDRAWLINE_HACK); +} + +static Ttk_ElementSpec ComboboxFieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + FieldElementSize, + ComboboxFieldElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Indicator elements for check and radio buttons. + */ + +typedef struct { + Tcl_Obj *sizeObj; + Tcl_Obj *marginObj; + Tcl_Obj *backgroundObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *upperColorObj; + Tcl_Obj *lowerColorObj; +} IndicatorElement; + +static Ttk_ElementOptionSpec IndicatorElementOptions[] = { + { "-indicatorsize", TK_OPTION_PIXELS, + Tk_Offset(IndicatorElement,sizeObj), "10" }, + { "-indicatormargin", TK_OPTION_STRING, + Tk_Offset(IndicatorElement,marginObj), "1" }, + { "-indicatorbackground", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,backgroundObj), "white" }, + { "-indicatorforeground", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,foregroundObj), "black" }, + { "-upperbordercolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,upperColorObj), DARKEST_COLOR }, + { "-lowerbordercolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,lowerColorObj), DARK_COLOR }, + { NULL, 0, 0, NULL } +}; + +static void IndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + IndicatorElement *indicator = elementRecord; + Ttk_Padding margins; + int size = 10; + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + *widthPtr = size + Ttk_PaddingWidth(margins); + *heightPtr = size + Ttk_PaddingHeight(margins); +} + +static void RadioIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + IndicatorElement *indicator = elementRecord; + GC gcb=Ttk_GCForColor(tkwin,indicator->backgroundObj,d); + GC gcf=Ttk_GCForColor(tkwin,indicator->foregroundObj,d); + GC gcu=Ttk_GCForColor(tkwin,indicator->upperColorObj,d); + GC gcl=Ttk_GCForColor(tkwin,indicator->lowerColorObj,d); + Ttk_Padding padding; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &padding); + b = Ttk_PadBox(b, padding); + + XFillArc(Tk_Display(tkwin),d,gcb, b.x,b.y,b.width,b.height, 0,360*64); + XDrawArc(Tk_Display(tkwin),d,gcl, b.x,b.y,b.width,b.height, 225*64,180*64); + XDrawArc(Tk_Display(tkwin),d,gcu, b.x,b.y,b.width,b.height, 45*64,180*64); + + if (state & TTK_STATE_SELECTED) { + b = Ttk_PadBox(b,Ttk_UniformPadding(3)); + XFillArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 0,360*64); + XDrawArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 0,360*64); +#if WIN32_XDRAWLINE_HACK + XDrawArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 300*64,360*64); +#endif + } +} + +static void CheckIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + Display *display = Tk_Display(tkwin); + IndicatorElement *indicator = elementRecord; + GC gcb=Ttk_GCForColor(tkwin,indicator->backgroundObj,d); + GC gcf=Ttk_GCForColor(tkwin,indicator->foregroundObj,d); + GC gcu=Ttk_GCForColor(tkwin,indicator->upperColorObj,d); + GC gcl=Ttk_GCForColor(tkwin,indicator->lowerColorObj,d); + Ttk_Padding padding; + const int w = WIN32_XDRAWLINE_HACK; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &padding); + b = Ttk_PadBox(b, padding); + + XFillRectangle(display,d,gcb, b.x,b.y,b.width,b.height); + XDrawLine(display,d,gcl,b.x,b.y+b.height,b.x+b.width+w,b.y+b.height);/*S*/ + XDrawLine(display,d,gcl,b.x+b.width,b.y,b.x+b.width,b.y+b.height+w); /*E*/ + XDrawLine(display,d,gcu,b.x,b.y, b.x,b.y+b.height+w); /*W*/ + XDrawLine(display,d,gcu,b.x,b.y, b.x+b.width+w,b.y); /*N*/ + + if (state & TTK_STATE_SELECTED) { + int p,q,r,s; + + b = Ttk_PadBox(b,Ttk_UniformPadding(2)); + p = b.x, q = b.y, r = b.x+b.width, s = b.y+b.height; + + r+=w, s+=w; + XDrawLine(display, d, gcf, p, q, r, s); + XDrawLine(display, d, gcf, p+1, q, r, s-1); + XDrawLine(display, d, gcf, p, q+1, r-1, s); + + s-=w, q-=w; + XDrawLine(display, d, gcf, p, s, r, q); + XDrawLine(display, d, gcf, p+1, s, r, q+1); + XDrawLine(display, d, gcf, p, s-1, r-1, q); + } +} + +static Ttk_ElementSpec RadioIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(IndicatorElement), + IndicatorElementOptions, + IndicatorElementSize, + RadioIndicatorElementDraw +}; + +static Ttk_ElementSpec CheckIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(IndicatorElement), + IndicatorElementOptions, + IndicatorElementSize, + CheckIndicatorElementDraw +}; + +#define MENUBUTTON_ARROW_SIZE 5 + +typedef struct { + Tcl_Obj *sizeObj; + Tcl_Obj *colorObj; + Tcl_Obj *paddingObj; +} MenuIndicatorElement; + +static Ttk_ElementOptionSpec MenuIndicatorElementOptions[] = +{ + { "-arrowsize", TK_OPTION_PIXELS, + Tk_Offset(MenuIndicatorElement,sizeObj), + STR(MENUBUTTON_ARROW_SIZE)}, + { "-arrowcolor",TK_OPTION_COLOR, + Tk_Offset(MenuIndicatorElement,colorObj), + "black" }, + { "-arrowpadding",TK_OPTION_STRING, + Tk_Offset(MenuIndicatorElement,paddingObj), + "3" }, + { NULL, 0, 0, NULL } +}; + +static void MenuIndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + MenuIndicatorElement *indicator = elementRecord; + Ttk_Padding margins; + int size = MENUBUTTON_ARROW_SIZE; + Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->paddingObj, &margins); + TtkArrowSize(size, ARROW_DOWN, widthPtr, heightPtr); + *widthPtr += Ttk_PaddingWidth(margins); + *heightPtr += Ttk_PaddingHeight(margins); +} + +static void MenuIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + MenuIndicatorElement *indicator = elementRecord; + XColor *arrowColor = Tk_GetColorFromObj(tkwin, indicator->colorObj); + GC gc = Tk_GCForColor(arrowColor, d); + int size = MENUBUTTON_ARROW_SIZE; + int width, height; + + Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + + TtkArrowSize(size, ARROW_DOWN, &width, &height); + b = Ttk_StickBox(b, width, height, 0); + TtkFillArrow(Tk_Display(tkwin), d, gc, b, ARROW_DOWN); +} + +static Ttk_ElementSpec MenuIndicatorElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(MenuIndicatorElement), + MenuIndicatorElementOptions, + MenuIndicatorElementSize, + MenuIndicatorElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Grips. + * + * TODO: factor this with ThumbElementDraw + */ + +static Ttk_Orient GripClientData[] = { + TTK_ORIENT_HORIZONTAL, TTK_ORIENT_VERTICAL +}; + +typedef struct { + Tcl_Obj *lightColorObj; + Tcl_Obj *borderColorObj; + Tcl_Obj *gripCountObj; +} GripElement; + +static Ttk_ElementOptionSpec GripElementOptions[] = { + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(GripElement,lightColorObj), LIGHT_COLOR }, + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(GripElement,borderColorObj), DARKEST_COLOR }, + { "-gripcount", TK_OPTION_INT, + Tk_Offset(GripElement,gripCountObj), "5" }, + { NULL, 0, 0, NULL } +}; + +static void GripElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL; + GripElement *grip = elementRecord; + int gripCount = 0; + + Tcl_GetIntFromObj(NULL, grip->gripCountObj, &gripCount); + if (horizontal) { + *widthPtr = 2*gripCount; + } else { + *heightPtr = 2*gripCount; + } +} + +static void GripElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + const int w = WIN32_XDRAWLINE_HACK; + int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL; + GripElement *grip = elementRecord; + GC lightGC = Ttk_GCForColor(tkwin,grip->lightColorObj,d); + GC darkGC = Ttk_GCForColor(tkwin,grip->borderColorObj,d); + int gripPad = 1, gripCount = 0; + int i; + + Tcl_GetIntFromObj(NULL, grip->gripCountObj, &gripCount); + + if (horizontal) { + int x = b.x + b.width / 2 - gripCount; + int y1 = b.y + gripPad, y2 = b.y + b.height - gripPad - 1 + w; + for (i=0; i<gripCount; ++i) { + XDrawLine(Tk_Display(tkwin), d, darkGC, x,y1, x,y2); ++x; + XDrawLine(Tk_Display(tkwin), d, lightGC, x,y1, x,y2); ++x; + } + } else { + int y = b.y + b.height / 2 - gripCount; + int x1 = b.x + gripPad, x2 = b.x + b.width - gripPad - 1 + w; + for (i=0; i<gripCount; ++i) { + XDrawLine(Tk_Display(tkwin), d, darkGC, x1,y, x2,y); ++y; + XDrawLine(Tk_Display(tkwin), d, lightGC, x1,y, x2,y); ++y; + } + } +} + +static Ttk_ElementSpec GripElementSpec = { + TK_STYLE_VERSION_2, + sizeof(GripElement), + GripElementOptions, + GripElementSize, + GripElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Scrollbar elements: trough, arrows, thumb. + * + * Notice that the trough element has 0 internal padding; + * that way the thumb and arrow borders overlap the trough. + */ + +typedef struct { /* Common element record for scrollbar elements */ + Tcl_Obj *orientObj; + Tcl_Obj *backgroundObj; + Tcl_Obj *borderColorObj; + Tcl_Obj *troughColorObj; + Tcl_Obj *lightColorObj; + Tcl_Obj *darkColorObj; + Tcl_Obj *arrowColorObj; + Tcl_Obj *arrowSizeObj; + Tcl_Obj *gripCountObj; + Tcl_Obj *sliderlengthObj; +} ScrollbarElement; + +static Ttk_ElementOptionSpec ScrollbarElementOptions[] = { + { "-orient", TK_OPTION_ANY, + Tk_Offset(ScrollbarElement, orientObj), "horizontal" }, + { "-background", TK_OPTION_BORDER, + Tk_Offset(ScrollbarElement,backgroundObj), FRAME_COLOR }, + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(ScrollbarElement,borderColorObj), DARKEST_COLOR }, + { "-troughcolor", TK_OPTION_COLOR, + Tk_Offset(ScrollbarElement,troughColorObj), DARKER_COLOR }, + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(ScrollbarElement,lightColorObj), LIGHT_COLOR }, + { "-darkcolor", TK_OPTION_COLOR, + Tk_Offset(ScrollbarElement,darkColorObj), DARK_COLOR }, + { "-arrowcolor", TK_OPTION_COLOR, + Tk_Offset(ScrollbarElement,arrowColorObj), "#000000" }, + { "-arrowsize", TK_OPTION_PIXELS, + Tk_Offset(ScrollbarElement,arrowSizeObj), STR(SCROLLBAR_THICKNESS) }, + { "-gripcount", TK_OPTION_INT, + Tk_Offset(ScrollbarElement,gripCountObj), "5" }, + { "-sliderlength", TK_OPTION_INT, + Tk_Offset(ScrollbarElement,sliderlengthObj), "30" }, + { NULL, 0, 0, NULL } +}; + +static void TroughElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + ScrollbarElement *sb = elementRecord; + GC gcb = Ttk_GCForColor(tkwin,sb->borderColorObj,d); + GC gct = Ttk_GCForColor(tkwin,sb->troughColorObj,d); + XFillRectangle(Tk_Display(tkwin), d, gct, b.x, b.y, b.width-1, b.height-1); + XDrawRectangle(Tk_Display(tkwin), d, gcb, b.x, b.y, b.width-1, b.height-1); +} + +static Ttk_ElementSpec TroughElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + TtkNullElementSize, + TroughElementDraw +}; + +static void ThumbElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ScrollbarElement *sb = elementRecord; + int size = SCROLLBAR_THICKNESS; + Tcl_GetIntFromObj(NULL, sb->arrowSizeObj, &size); + *widthPtr = *heightPtr = size; +} + +static void ThumbElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + ScrollbarElement *sb = elementRecord; + int gripCount = 0, orient = TTK_ORIENT_HORIZONTAL; + GC lightGC, darkGC; + int x1, y1, x2, y2, dx, dy, i; + const int w = WIN32_XDRAWLINE_HACK; + + DrawSmoothBorder(tkwin, d, b, + sb->borderColorObj, sb->lightColorObj, sb->darkColorObj); + XFillRectangle( + Tk_Display(tkwin), d, BackgroundGC(tkwin, sb->backgroundObj), + b.x+2, b.y+2, b.width-4, b.height-4); + + /* + * Draw grip: + */ + Ttk_GetOrientFromObj(NULL, sb->orientObj, &orient); + Tcl_GetIntFromObj(NULL, sb->gripCountObj, &gripCount); + lightGC = Ttk_GCForColor(tkwin,sb->lightColorObj,d); + darkGC = Ttk_GCForColor(tkwin,sb->borderColorObj,d); + + if (orient == TTK_ORIENT_HORIZONTAL) { + dx = 1; dy = 0; + x1 = x2 = b.x + b.width / 2 - gripCount; + y1 = b.y + 2; + y2 = b.y + b.height - 3 + w; + } else { + dx = 0; dy = 1; + y1 = y2 = b.y + b.height / 2 - gripCount; + x1 = b.x + 2; + x2 = b.x + b.width - 3 + w; + } + + for (i=0; i<gripCount; ++i) { + XDrawLine(Tk_Display(tkwin), d, darkGC, x1,y1, x2,y2); + x1 += dx; x2 += dx; y1 += dy; y2 += dy; + XDrawLine(Tk_Display(tkwin), d, lightGC, x1,y1, x2,y2); + x1 += dx; x2 += dx; y1 += dy; y2 += dy; + } +} + +static Ttk_ElementSpec ThumbElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ThumbElementSize, + ThumbElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Slider element. + */ +static void SliderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ScrollbarElement *sb = elementRecord; + int length, thickness, orient; + + length = thickness = SCROLLBAR_THICKNESS; + Ttk_GetOrientFromObj(NULL, sb->orientObj, &orient); + Tcl_GetIntFromObj(NULL, sb->arrowSizeObj, &thickness); + Tk_GetPixelsFromObj(NULL, tkwin, sb->sliderlengthObj, &length); + if (orient == TTK_ORIENT_VERTICAL) { + *heightPtr = length; + *widthPtr = thickness; + } else { + *heightPtr = thickness; + *widthPtr = length; + } + +} + +static Ttk_ElementSpec SliderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + SliderElementSize, + ThumbElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Progress bar element + */ +static void PbarElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SliderElementSize(clientData, elementRecord, tkwin, + widthPtr, heightPtr, paddingPtr); + *paddingPtr = Ttk_UniformPadding(2); + *widthPtr += 4; + *heightPtr += 4; +} + +static void PbarElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + ScrollbarElement *sb = elementRecord; + + b = Ttk_PadBox(b, Ttk_UniformPadding(2)); + if (b.width > 4 && b.height > 4) { + DrawSmoothBorder(tkwin, d, b, + sb->borderColorObj, sb->lightColorObj, sb->darkColorObj); + XFillRectangle(Tk_Display(tkwin), d, + BackgroundGC(tkwin, sb->backgroundObj), + b.x+2, b.y+2, b.width-4, b.height-4); + } +} + +static Ttk_ElementSpec PbarElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + PbarElementSize, + PbarElementDraw +}; + + +/*------------------------------------------------------------------------ + * +++ Scrollbar arrows. + */ +static int ArrowElements[] = { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT }; + +static void ArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ScrollbarElement *sb = elementRecord; + int size = SCROLLBAR_THICKNESS; + Tcl_GetIntFromObj(NULL, sb->arrowSizeObj, &size); + *widthPtr = *heightPtr = size; +} + +static void ArrowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned state) +{ + ArrowDirection dir = *(ArrowDirection*)clientData; + ScrollbarElement *sb = elementRecord; + GC gc = Ttk_GCForColor(tkwin,sb->arrowColorObj, d); + int h, cx, cy; + + DrawSmoothBorder(tkwin, d, b, + sb->borderColorObj, sb->lightColorObj, sb->darkColorObj); + + XFillRectangle( + Tk_Display(tkwin), d, BackgroundGC(tkwin, sb->backgroundObj), + b.x+2, b.y+2, b.width-4, b.height-4); + + b = Ttk_PadBox(b, Ttk_UniformPadding(3)); + h = b.width < b.height ? b.width : b.height; + TtkArrowSize(h/2, dir, &cx, &cy); + b = Ttk_AnchorBox(b, cx, cy, TK_ANCHOR_CENTER); + + TtkFillArrow(Tk_Display(tkwin), d, gc, b, dir); +} + +static Ttk_ElementSpec ArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ArrowElementSize, + ArrowElementDraw +}; + + +/*------------------------------------------------------------------------ + * +++ Notebook elements. + * + * Note: Tabs, except for the rightmost, overlap the neighbor to + * their right by one pixel. + */ + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *borderColorObj; + Tcl_Obj *lightColorObj; + Tcl_Obj *darkColorObj; +} NotebookElement; + +static Ttk_ElementOptionSpec NotebookElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(NotebookElement,backgroundObj), FRAME_COLOR }, + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(NotebookElement,borderColorObj), DARKEST_COLOR }, + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(NotebookElement,lightColorObj), LIGHT_COLOR }, + { "-darkcolor", TK_OPTION_COLOR, + Tk_Offset(NotebookElement,darkColorObj), DARK_COLOR }, + { NULL, 0, 0, NULL } +}; + +static void TabElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + int borderWidth = 2; + paddingPtr->top = paddingPtr->left = paddingPtr->right = borderWidth; + paddingPtr->bottom = 0; +} + +static void TabElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + NotebookElement *tab = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, tab->backgroundObj); + Display *display = Tk_Display(tkwin); + int borderWidth = 2, dh = 0; + int x1,y1,x2,y2; + GC gc; + const int w = WIN32_XDRAWLINE_HACK; + + if (state & TTK_STATE_SELECTED) { + dh = borderWidth; + } + + if (state & TTK_STATE_USER2) { /* Rightmost tab */ + --b.width; + } + + Tk_Fill3DRectangle(tkwin, d, border, + b.x+2, b.y+2, b.width-1, b.height-2+dh, borderWidth, TK_RELIEF_FLAT); + + x1 = b.x, x2 = b.x + b.width; + y1 = b.y, y2 = b.y + b.height; + + + gc=Ttk_GCForColor(tkwin,tab->borderColorObj,d); + XDrawLine(display,d,gc, x1,y1+1, x1,y2+w); + XDrawLine(display,d,gc, x2,y1+1, x2,y2+w); + XDrawLine(display,d,gc, x1+1,y1, x2-1+w,y1); + + gc=Ttk_GCForColor(tkwin,tab->lightColorObj,d); + XDrawLine(display,d,gc, x1+1,y1+1, x1+1,y2-1+dh+w); + XDrawLine(display,d,gc, x1+1,y1+1, x2-1+w,y1+1); +} + +static Ttk_ElementSpec TabElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(NotebookElement), + NotebookElementOptions, + TabElementSize, + TabElementDraw +}; + +static void ClientElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + int borderWidth = 2; + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void ClientElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + NotebookElement *ce = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, ce->backgroundObj); + int borderWidth = 2; + + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, borderWidth,TK_RELIEF_FLAT); + DrawSmoothBorder(tkwin, d, b, + ce->borderColorObj, ce->lightColorObj, ce->darkColorObj); +} + +static Ttk_ElementSpec ClientElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(NotebookElement), + NotebookElementOptions, + ClientElementSize, + ClientElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Modified widget layouts. + */ + +TTK_BEGIN_LAYOUT_TABLE(LayoutTable) + +TTK_LAYOUT("TCombobox", + TTK_NODE("Combobox.downarrow", TTK_PACK_RIGHT|TTK_FILL_Y) + TTK_GROUP("Combobox.field", TTK_PACK_LEFT|TTK_FILL_BOTH|TTK_EXPAND, + TTK_GROUP("Combobox.padding", TTK_FILL_BOTH, + TTK_NODE("Combobox.textarea", TTK_FILL_BOTH)))) + +TTK_LAYOUT("Horizontal.Sash", + TTK_GROUP("Sash.hsash", TTK_FILL_BOTH, + TTK_NODE("Sash.hgrip", TTK_FILL_BOTH))) + +TTK_LAYOUT("Vertical.Sash", + TTK_GROUP("Sash.vsash", TTK_FILL_BOTH, + TTK_NODE("Sash.vgrip", TTK_FILL_BOTH))) + +TTK_END_LAYOUT_TABLE + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ + +MODULE_SCOPE int +TtkClamTheme_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_CreateTheme(interp, "clam", 0); + + if (!theme) { + return TCL_ERROR; + } + + Ttk_RegisterElement(interp, + theme, "border", &BorderElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "field", &FieldElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "Combobox.field", &ComboboxFieldElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "trough", &TroughElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "thumb", &ThumbElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "uparrow", &ArrowElementSpec, &ArrowElements[0]); + Ttk_RegisterElement(interp, + theme, "downarrow", &ArrowElementSpec, &ArrowElements[1]); + Ttk_RegisterElement(interp, + theme, "leftarrow", &ArrowElementSpec, &ArrowElements[2]); + Ttk_RegisterElement(interp, + theme, "rightarrow", &ArrowElementSpec, &ArrowElements[3]); + + Ttk_RegisterElement(interp, + theme, "Radiobutton.indicator", &RadioIndicatorElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "Checkbutton.indicator", &CheckIndicatorElementSpec, NULL); + Ttk_RegisterElement(interp, + theme, "Menubutton.indicator", &MenuIndicatorElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "tab", &TabElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "client", &ClientElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "bar", &PbarElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "pbar", &PbarElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "hgrip", + &GripElementSpec, &GripClientData[0]); + Ttk_RegisterElement(interp, theme, "vgrip", + &GripElementSpec, &GripClientData[1]); + + Ttk_RegisterLayouts(theme, LayoutTable); + + Tcl_PkgProvide(interp, "ttk::theme::clam", TTK_VERSION); + + return TCL_OK; +} diff --git a/generic/ttk/ttkClassicTheme.c b/generic/ttk/ttkClassicTheme.c new file mode 100644 index 0000000..2fbcd76 --- /dev/null +++ b/generic/ttk/ttkClassicTheme.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2004, Joe English + * + * "classic" theme; implements the classic Motif-like Tk look. + * + */ + +#include <tk.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "ttkTheme.h" + +#define DEFAULT_BORDERWIDTH "2" +#define DEFAULT_ARROW_SIZE "15" + +/*---------------------------------------------------------------------- + * +++ Highlight element implementation. + * Draw a solid highlight border to indicate focus. + */ + +typedef struct { + Tcl_Obj *highlightColorObj; + Tcl_Obj *highlightThicknessObj; +} HighlightElement; + +static Ttk_ElementOptionSpec HighlightElementOptions[] = { + { "-highlightcolor",TK_OPTION_COLOR, + Tk_Offset(HighlightElement,highlightColorObj), DEFAULT_BACKGROUND }, + { "-highlightthickness",TK_OPTION_PIXELS, + Tk_Offset(HighlightElement,highlightThicknessObj), "0" }, + { NULL, 0, 0, NULL } +}; + +static void HighlightElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + HighlightElement *hl = elementRecord; + int highlightThickness = 0; + + Tcl_GetIntFromObj(NULL,hl->highlightThicknessObj,&highlightThickness); + *paddingPtr = Ttk_UniformPadding((short)highlightThickness); +} + +static void HighlightElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + HighlightElement *hl = elementRecord; + int highlightThickness = 0; + XColor *highlightColor = Tk_GetColorFromObj(tkwin, hl->highlightColorObj); + + Tcl_GetIntFromObj(NULL,hl->highlightThicknessObj,&highlightThickness); + if (highlightColor && highlightThickness > 0) { + GC gc = Tk_GCForColor(highlightColor, d); + Tk_DrawFocusHighlight(tkwin, gc, highlightThickness, d); + } +} + +static Ttk_ElementSpec HighlightElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(HighlightElement), + HighlightElementOptions, + HighlightElementSize, + HighlightElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Button Border element: + * + * The Motif-style button border on X11 consists of (from outside-in): + * + * + focus indicator (controlled by -highlightcolor and -highlightthickness), + * + default ring (if -default active; blank if -default normal) + * + shaded border (controlled by -background, -borderwidth, and -relief) + */ + +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *defaultStateObj; +} ButtonBorderElement; + +static Ttk_ElementOptionSpec ButtonBorderElementOptions[] = +{ + { "-background", TK_OPTION_BORDER, + Tk_Offset(ButtonBorderElement,borderObj), DEFAULT_BACKGROUND }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(ButtonBorderElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(ButtonBorderElement,reliefObj), "flat" }, + { "-default", TK_OPTION_ANY, + Tk_Offset(ButtonBorderElement,defaultStateObj), "disabled" }, + { NULL, 0, 0, NULL } +}; + +static void ButtonBorderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ButtonBorderElement *bd = elementRecord; + int defaultState = TTK_BUTTON_DEFAULT_DISABLED; + int borderWidth = 0; + + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState); + + if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) { + borderWidth += 5; + } + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +/* + * (@@@ Note: ButtonBorderElement still still still buggy: + * padding for default ring is drawn in the wrong color + * when the button is active.) + */ +static void ButtonBorderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + ButtonBorderElement *bd = elementRecord; + Tk_3DBorder border = NULL; + int borderWidth = 1, relief = TK_RELIEF_FLAT; + int defaultState = TTK_BUTTON_DEFAULT_DISABLED; + int inset = 0; + + /* + * Get option values. + */ + border = Tk_Get3DBorderFromObj(tkwin, bd->borderObj); + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief); + Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState); + + /* + * Default ring: + */ + switch (defaultState) + { + case TTK_BUTTON_DEFAULT_DISABLED : + break; + case TTK_BUTTON_DEFAULT_NORMAL : + inset += 5; + break; + case TTK_BUTTON_DEFAULT_ACTIVE : + Tk_Draw3DRectangle(tkwin, d, border, + b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset, + 2, TK_RELIEF_FLAT); + inset += 2; + Tk_Draw3DRectangle(tkwin, d, border, + b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset, + 1, TK_RELIEF_SUNKEN); + ++inset; + Tk_Draw3DRectangle(tkwin, d, border, + b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset, + 2, TK_RELIEF_FLAT); + inset += 2; + break; + } + + /* + * 3-D border: + */ + if (border && borderWidth > 0) { + Tk_Draw3DRectangle(tkwin, d, border, + b.x+inset, b.y+inset, b.width - 2*inset, b.height - 2*inset, + borderWidth,relief); + } +} + +static Ttk_ElementSpec ButtonBorderElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(ButtonBorderElement), + ButtonBorderElementOptions, + ButtonBorderElementSize, + ButtonBorderElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Arrow element(s). + * + * Draws a 3-D shaded triangle. + * clientData is an enum ArrowDirection pointer. + */ + +static int ArrowElements[] = { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT }; +typedef struct +{ + Tcl_Obj *sizeObj; + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; +} ArrowElement; + +static Ttk_ElementOptionSpec ArrowElementOptions[] = +{ + { "-arrowsize", TK_OPTION_PIXELS, Tk_Offset(ArrowElement,sizeObj), + DEFAULT_ARROW_SIZE }, + { "-background", TK_OPTION_BORDER, Tk_Offset(ArrowElement,borderObj), + DEFAULT_BACKGROUND }, + { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(ArrowElement,borderWidthObj), + DEFAULT_BORDERWIDTH }, + { "-relief", TK_OPTION_RELIEF, Tk_Offset(ArrowElement,reliefObj),"raised" }, + { NULL, 0, 0, NULL } +}; + +static void ArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ArrowElement *arrow = elementRecord; + int size = 12; + + Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &size); + *widthPtr = *heightPtr = size; +} + +static void ArrowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + int direction = *(int *)clientData; + ArrowElement *arrow = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, arrow->borderObj); + int borderWidth = 2; + int relief = TK_RELIEF_RAISED; + int size = b.width < b.height ? b.width : b.height; + XPoint points[3]; + + Tk_GetPixelsFromObj(NULL, tkwin, arrow->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, arrow->reliefObj, &relief); + + + /* + * @@@ There are off-by-one pixel errors in the way these are drawn; + * @@@ need to take a look at Tk_Fill3DPolygon and X11 to find the + * @@@ exact rules. + */ + switch (direction) + { + case ARROW_UP: + points[2].x = b.x; points[2].y = b.y + size; + points[1].x = b.x + size/2; points[1].y = b.y; + points[0].x = b.x + size; points[0].y = b.y + size; + break; + case ARROW_DOWN: + points[0].x = b.x; points[0].y = b.y; + points[1].x = b.x + size/2; points[1].y = b.y + size; + points[2].x = b.x + size; points[2].y = b.y; + break; + case ARROW_LEFT: + points[0].x = b.x; points[0].y = b.y + size / 2; + points[1].x = b.x + size; points[1].y = b.y + size; + points[2].x = b.x + size; points[2].y = b.y; + break; + case ARROW_RIGHT: + points[0].x = b.x + size; points[0].y = b.y + size / 2; + points[1].x = b.x; points[1].y = b.y; + points[2].x = b.x; points[2].y = b.y + size; + break; + } + + Tk_Fill3DPolygon(tkwin, d, border, points, 3, borderWidth, relief); +} + +static Ttk_ElementSpec ArrowElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(ArrowElement), + ArrowElementOptions, + ArrowElementSize, + ArrowElementDraw +}; + + +/*------------------------------------------------------------------------ + * +++ Sash element (for ttk::panedwindow) + * + * NOTES: + * + * panedwindows with -orient horizontal use vertical sashes, and vice versa. + * + * Interpretation of -sashrelief 'groove' and 'ridge' are + * swapped wrt. the core panedwindow, which (I think) has them backwards. + * + * Default -sashrelief is sunken; the core panedwindow has default + * -sashrelief raised, but that looks wrong to me. + */ + +static Ttk_Orient SashClientData[] = { + TTK_ORIENT_HORIZONTAL, TTK_ORIENT_VERTICAL +}; + +typedef struct { + Tcl_Obj *borderObj; /* background color */ + Tcl_Obj *sashReliefObj; /* sash relief */ + Tcl_Obj *sashThicknessObj; /* overall thickness of sash */ + Tcl_Obj *sashPadObj; /* padding on either side of handle */ + Tcl_Obj *handleSizeObj; /* handle width and height */ + Tcl_Obj *handlePadObj; /* handle's distance from edge */ +} SashElement; + +static Ttk_ElementOptionSpec SashOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(SashElement,borderObj), DEFAULT_BACKGROUND }, + { "-sashrelief", TK_OPTION_RELIEF, + Tk_Offset(SashElement,sashReliefObj), "sunken" }, + { "-sashthickness", TK_OPTION_PIXELS, + Tk_Offset(SashElement,sashThicknessObj), "6" }, + { "-sashpad", TK_OPTION_PIXELS, + Tk_Offset(SashElement,sashPadObj), "2" }, + { "-handlesize", TK_OPTION_PIXELS, + Tk_Offset(SashElement,handleSizeObj), "8" }, + { "-handlepad", TK_OPTION_PIXELS, + Tk_Offset(SashElement,handlePadObj), "8" }, + { NULL, 0, 0, NULL } +}; + +static void SashElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SashElement *sash = elementRecord; + int sashPad = 2, sashThickness = 6, handleSize = 8; + int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL; + + Tk_GetPixelsFromObj(NULL, tkwin, sash->sashThicknessObj, &sashThickness); + Tk_GetPixelsFromObj(NULL, tkwin, sash->handleSizeObj, &handleSize); + Tk_GetPixelsFromObj(NULL, tkwin, sash->sashPadObj, &sashPad); + + if (sashThickness < handleSize + 2*sashPad) + sashThickness = handleSize + 2*sashPad; + + if (horizontal) + *heightPtr = sashThickness; + else + *widthPtr = sashThickness; +} + +static void SashElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + SashElement *sash = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, sash->borderObj); + GC gc1,gc2; + int relief = TK_RELIEF_RAISED; + int handleSize = 8, handlePad = 8; + int horizontal = *((Ttk_Orient*)clientData) == TTK_ORIENT_HORIZONTAL; + Ttk_Box hb; + + Tk_GetPixelsFromObj(NULL, tkwin, sash->handleSizeObj, &handleSize); + Tk_GetPixelsFromObj(NULL, tkwin, sash->handlePadObj, &handlePad); + Tk_GetReliefFromObj(NULL, sash->sashReliefObj, &relief); + + switch (relief) { + case TK_RELIEF_RAISED: case TK_RELIEF_RIDGE: + gc1 = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); + gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + break; + case TK_RELIEF_SUNKEN: case TK_RELIEF_GROOVE: + gc1 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); + break; + case TK_RELIEF_SOLID: + gc1 = gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + break; + case TK_RELIEF_FLAT: + default: + gc1 = gc2 = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC); + break; + } + + /* Draw sash line: + */ + if (horizontal) { + int y = b.y + b.height/2 - 1; + XDrawLine(Tk_Display(tkwin), d, gc1, b.x, y, b.x+b.width, y); ++y; + XDrawLine(Tk_Display(tkwin), d, gc2, b.x, y, b.x+b.width, y); + } else { + int x = b.x + b.width/2 - 1; + XDrawLine(Tk_Display(tkwin), d, gc1, x, b.y, x, b.y+b.height); ++x; + XDrawLine(Tk_Display(tkwin), d, gc2, x, b.y, x, b.y+b.height); + } + + /* Draw handle: + */ + if (handleSize >= 0) { + if (horizontal) { + hb = Ttk_StickBox(b, handleSize, handleSize, TTK_STICK_W); + hb.x += handlePad; + } else { + hb = Ttk_StickBox(b, handleSize, handleSize, TTK_STICK_N); + hb.y += handlePad; + } + Tk_Fill3DRectangle(tkwin, d, border, + hb.x, hb.y, hb.width, hb.height, 1, TK_RELIEF_RAISED); + } +} + +static Ttk_ElementSpec SashElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SashElement), + SashOptions, + SashElementSize, + SashElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Widget layouts. + */ + +TTK_BEGIN_LAYOUT_TABLE(LayoutTable) + +TTK_LAYOUT("TButton", + TTK_GROUP("Button.highlight", TTK_FILL_BOTH, + TTK_GROUP("Button.border", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Button.padding", TTK_FILL_BOTH, + TTK_NODE("Button.label", TTK_FILL_BOTH))))) + +TTK_LAYOUT("TCheckbutton", + TTK_GROUP("Checkbutton.highlight", TTK_FILL_BOTH, + TTK_GROUP("Checkbutton.border", TTK_FILL_BOTH, + TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH, + TTK_NODE("Checkbutton.indicator", TTK_PACK_LEFT) + TTK_NODE("Checkbutton.label", TTK_PACK_LEFT|TTK_FILL_BOTH))))) + +TTK_LAYOUT("TRadiobutton", + TTK_GROUP("Radiobutton.highlight", TTK_FILL_BOTH, + TTK_GROUP("Radiobutton.border", TTK_FILL_BOTH, + TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH, + TTK_NODE("Radiobutton.indicator", TTK_PACK_LEFT) + TTK_NODE("Radiobutton.label", TTK_PACK_LEFT|TTK_FILL_BOTH))))) + +TTK_LAYOUT("TMenubutton", + TTK_GROUP("Menubutton.highlight", TTK_FILL_BOTH, + TTK_GROUP("Menubutton.border", TTK_FILL_BOTH, + TTK_NODE("Menubutton.indicator", TTK_PACK_RIGHT) + TTK_GROUP("Menubutton.padding", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_X, + TTK_NODE("Menubutton.label", 0))))) + +/* "classic" entry, includes highlight border */ +TTK_LAYOUT("TEntry", + TTK_GROUP("Entry.highlight", TTK_FILL_BOTH, + TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Entry.padding", TTK_FILL_BOTH, + TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))) + +/* Notebook tabs -- omit focus ring */ +TTK_LAYOUT("Tab", + TTK_GROUP("Notebook.tab", TTK_FILL_BOTH, + TTK_GROUP("Notebook.padding", TTK_FILL_BOTH, + TTK_NODE("Notebook.label", TTK_FILL_BOTH)))) + +TTK_END_LAYOUT_TABLE + +/* POSSIBLY: include Scale layouts w/focus border + */ + +/*------------------------------------------------------------------------ + * TtkClassicTheme_Init -- + * Install classic theme. + */ + +MODULE_SCOPE int TtkClassicTheme_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_CreateTheme(interp, "classic", NULL); + + if (!theme) { + return TCL_ERROR; + } + + /* + * Register elements: + */ + Ttk_RegisterElement(interp, theme, "highlight", + &HighlightElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "Button.border", + &ButtonBorderElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "uparrow", + &ArrowElementSpec, &ArrowElements[0]); + Ttk_RegisterElement(interp, theme, "downarrow", + &ArrowElementSpec, &ArrowElements[1]); + Ttk_RegisterElement(interp, theme, "leftarrow", + &ArrowElementSpec, &ArrowElements[2]); + Ttk_RegisterElement(interp, theme, "rightarrow", + &ArrowElementSpec, &ArrowElements[3]); + Ttk_RegisterElement(interp, theme, "arrow", + &ArrowElementSpec, &ArrowElements[0]); + + Ttk_RegisterElement(interp, theme, "hsash", + &SashElementSpec, &SashClientData[0]); + Ttk_RegisterElement(interp, theme, "vsash", + &SashElementSpec, &SashClientData[1]); + + /* + * Register layouts: + */ + Ttk_RegisterLayouts(theme, LayoutTable); + + Tcl_PkgProvide(interp, "ttk::theme::classic", TTK_VERSION); + + return TCL_OK; +} + +/*EOF*/ diff --git a/generic/ttk/ttkDecls.h b/generic/ttk/ttkDecls.h new file mode 100644 index 0000000..ee679b7 --- /dev/null +++ b/generic/ttk/ttkDecls.h @@ -0,0 +1,272 @@ +/* + * This file is (mostly) automatically generated from ttk.decls. + */ + +#ifndef _TTKDECLS +#define _TTKDECLS + +#if defined(USE_TTK_STUBS) + +extern const char *TtkInitializeStubs( + Tcl_Interp *, const char *version, int epoch, int revision); +#define Ttk_InitStubs(interp) TtkInitializeStubs( \ + interp, TTK_VERSION, TTK_STUBS_EPOCH, TTK_STUBS_REVISION) +#else + +#define Ttk_InitStubs(interp) Tcl_PkgRequire(interp, "Ttk", TTK_VERSION, 0) + +#endif + + +/* !BEGIN!: Do not edit below this line. */ + +#define TTK_STUBS_EPOCH 0 +#define TTK_STUBS_REVISION 31 + +/* + * Exported function declarations: + */ + +/* 0 */ +TTKAPI Ttk_Theme Ttk_GetTheme(Tcl_Interp *interp, CONST char *name); +/* 1 */ +TTKAPI Ttk_Theme Ttk_GetDefaultTheme(Tcl_Interp *interp); +/* 2 */ +TTKAPI Ttk_Theme Ttk_GetCurrentTheme(Tcl_Interp *interp); +/* 3 */ +TTKAPI Ttk_Theme Ttk_CreateTheme(Tcl_Interp *interp, CONST char *name, + Ttk_Theme parent); +/* 4 */ +TTKAPI void Ttk_RegisterCleanup(Tcl_Interp *interp, + VOID *deleteData, + Ttk_CleanupProc *cleanupProc); +/* 5 */ +TTKAPI int Ttk_RegisterElementSpec(Ttk_Theme theme, + CONST char *elementName, + Ttk_ElementSpec *elementSpec, + VOID *clientData); +/* 6 */ +TTKAPI Ttk_ElementClass * Ttk_RegisterElement(Tcl_Interp *interp, + Ttk_Theme theme, CONST char *elementName, + Ttk_ElementSpec *elementSpec, + VOID *clientData); +/* 7 */ +TTKAPI int Ttk_RegisterElementFactory(Tcl_Interp *interp, + CONST char *name, + Ttk_ElementFactory factoryProc, + VOID *clientData); +/* 8 */ +TTKAPI void Ttk_RegisterLayout(Ttk_Theme theme, + CONST char *className, + Ttk_LayoutSpec layoutSpec); +/* Slot 9 is reserved */ +/* 10 */ +TTKAPI int Ttk_GetStateSpecFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, Ttk_StateSpec *spec_rtn); +/* 11 */ +TTKAPI Tcl_Obj * Ttk_NewStateSpecObj(unsigned int onbits, + unsigned int offbits); +/* 12 */ +TTKAPI Ttk_StateMap Ttk_GetStateMapFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr); +/* 13 */ +TTKAPI Tcl_Obj * Ttk_StateMapLookup(Tcl_Interp *interp, + Ttk_StateMap map, Ttk_State state); +/* 14 */ +TTKAPI int Ttk_StateTableLookup(Ttk_StateTable map[], + Ttk_State state); +/* Slot 15 is reserved */ +/* Slot 16 is reserved */ +/* Slot 17 is reserved */ +/* Slot 18 is reserved */ +/* Slot 19 is reserved */ +/* 20 */ +TTKAPI int Ttk_GetPaddingFromObj(Tcl_Interp *interp, + Tk_Window tkwin, Tcl_Obj *objPtr, + Ttk_Padding *pad_rtn); +/* 21 */ +TTKAPI int Ttk_GetBorderFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, Ttk_Padding *pad_rtn); +/* 22 */ +TTKAPI int Ttk_GetStickyFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, Ttk_Sticky *sticky_rtn); +/* 23 */ +TTKAPI Ttk_Padding Ttk_MakePadding(short l, short t, short r, short b); +/* 24 */ +TTKAPI Ttk_Padding Ttk_UniformPadding(short borderWidth); +/* 25 */ +TTKAPI Ttk_Padding Ttk_AddPadding(Ttk_Padding pad1, Ttk_Padding pad2); +/* 26 */ +TTKAPI Ttk_Padding Ttk_RelievePadding(Ttk_Padding padding, int relief, + int n); +/* 27 */ +TTKAPI Ttk_Box Ttk_MakeBox(int x, int y, int width, int height); +/* 28 */ +TTKAPI int Ttk_BoxContains(Ttk_Box box, int x, int y); +/* 29 */ +TTKAPI Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int w, int h, + Ttk_Side side); +/* 30 */ +TTKAPI Ttk_Box Ttk_StickBox(Ttk_Box parcel, int w, int h, + Ttk_Sticky sticky); +/* 31 */ +TTKAPI Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int w, int h, + Tk_Anchor anchor); +/* 32 */ +TTKAPI Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p); +/* 33 */ +TTKAPI Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p); +/* 34 */ +TTKAPI Ttk_Box Ttk_PlaceBox(Ttk_Box *cavity, int w, int h, + Ttk_Side side, Ttk_Sticky sticky); +/* 35 */ +TTKAPI Tcl_Obj * Ttk_NewBoxObj(Ttk_Box box); +/* Slot 36 is reserved */ +/* Slot 37 is reserved */ +/* Slot 38 is reserved */ +/* Slot 39 is reserved */ +/* 40 */ +TTKAPI int Ttk_GetOrientFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, int *orient); + +typedef struct TtkStubs { + int magic; + int epoch; + int revision; + const struct TtkStubHooks *hooks; + + Ttk_Theme (*ttk_GetTheme) (Tcl_Interp *interp, CONST char *name); /* 0 */ + Ttk_Theme (*ttk_GetDefaultTheme) (Tcl_Interp *interp); /* 1 */ + Ttk_Theme (*ttk_GetCurrentTheme) (Tcl_Interp *interp); /* 2 */ + Ttk_Theme (*ttk_CreateTheme) (Tcl_Interp *interp, CONST char *name, Ttk_Theme parent); /* 3 */ + void (*ttk_RegisterCleanup) (Tcl_Interp *interp, VOID *deleteData, Ttk_CleanupProc *cleanupProc); /* 4 */ + int (*ttk_RegisterElementSpec) (Ttk_Theme theme, CONST char *elementName, Ttk_ElementSpec *elementSpec, VOID *clientData); /* 5 */ + Ttk_ElementClass * (*ttk_RegisterElement) (Tcl_Interp *interp, Ttk_Theme theme, CONST char *elementName, Ttk_ElementSpec *elementSpec, VOID *clientData); /* 6 */ + int (*ttk_RegisterElementFactory) (Tcl_Interp *interp, CONST char *name, Ttk_ElementFactory factoryProc, VOID *clientData); /* 7 */ + void (*ttk_RegisterLayout) (Ttk_Theme theme, CONST char *className, Ttk_LayoutSpec layoutSpec); /* 8 */ + void (*reserved9)(void); + int (*ttk_GetStateSpecFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_StateSpec *spec_rtn); /* 10 */ + Tcl_Obj * (*ttk_NewStateSpecObj) (unsigned int onbits, unsigned int offbits); /* 11 */ + Ttk_StateMap (*ttk_GetStateMapFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 12 */ + Tcl_Obj * (*ttk_StateMapLookup) (Tcl_Interp *interp, Ttk_StateMap map, Ttk_State state); /* 13 */ + int (*ttk_StateTableLookup) (Ttk_StateTable map[], Ttk_State state); /* 14 */ + void (*reserved15)(void); + void (*reserved16)(void); + void (*reserved17)(void); + void (*reserved18)(void); + void (*reserved19)(void); + int (*ttk_GetPaddingFromObj) (Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr, Ttk_Padding *pad_rtn); /* 20 */ + int (*ttk_GetBorderFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Padding *pad_rtn); /* 21 */ + int (*ttk_GetStickyFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Sticky *sticky_rtn); /* 22 */ + Ttk_Padding (*ttk_MakePadding) (short l, short t, short r, short b); /* 23 */ + Ttk_Padding (*ttk_UniformPadding) (short borderWidth); /* 24 */ + Ttk_Padding (*ttk_AddPadding) (Ttk_Padding pad1, Ttk_Padding pad2); /* 25 */ + Ttk_Padding (*ttk_RelievePadding) (Ttk_Padding padding, int relief, int n); /* 26 */ + Ttk_Box (*ttk_MakeBox) (int x, int y, int width, int height); /* 27 */ + int (*ttk_BoxContains) (Ttk_Box box, int x, int y); /* 28 */ + Ttk_Box (*ttk_PackBox) (Ttk_Box *cavity, int w, int h, Ttk_Side side); /* 29 */ + Ttk_Box (*ttk_StickBox) (Ttk_Box parcel, int w, int h, Ttk_Sticky sticky); /* 30 */ + Ttk_Box (*ttk_AnchorBox) (Ttk_Box parcel, int w, int h, Tk_Anchor anchor); /* 31 */ + Ttk_Box (*ttk_PadBox) (Ttk_Box b, Ttk_Padding p); /* 32 */ + Ttk_Box (*ttk_ExpandBox) (Ttk_Box b, Ttk_Padding p); /* 33 */ + Ttk_Box (*ttk_PlaceBox) (Ttk_Box *cavity, int w, int h, Ttk_Side side, Ttk_Sticky sticky); /* 34 */ + Tcl_Obj * (*ttk_NewBoxObj) (Ttk_Box box); /* 35 */ + void (*reserved36)(void); + void (*reserved37)(void); + void (*reserved38)(void); + void (*reserved39)(void); + int (*ttk_GetOrientFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *orient); /* 40 */ +} TtkStubs; + +#ifdef __cplusplus +extern "C" { +#endif +extern const TtkStubs *ttkStubsPtr; +#ifdef __cplusplus +} +#endif + +#if defined(USE_TTK_STUBS) + +/* + * Inline function declarations: + */ + +#define Ttk_GetTheme \ + (ttkStubsPtr->ttk_GetTheme) /* 0 */ +#define Ttk_GetDefaultTheme \ + (ttkStubsPtr->ttk_GetDefaultTheme) /* 1 */ +#define Ttk_GetCurrentTheme \ + (ttkStubsPtr->ttk_GetCurrentTheme) /* 2 */ +#define Ttk_CreateTheme \ + (ttkStubsPtr->ttk_CreateTheme) /* 3 */ +#define Ttk_RegisterCleanup \ + (ttkStubsPtr->ttk_RegisterCleanup) /* 4 */ +#define Ttk_RegisterElementSpec \ + (ttkStubsPtr->ttk_RegisterElementSpec) /* 5 */ +#define Ttk_RegisterElement \ + (ttkStubsPtr->ttk_RegisterElement) /* 6 */ +#define Ttk_RegisterElementFactory \ + (ttkStubsPtr->ttk_RegisterElementFactory) /* 7 */ +#define Ttk_RegisterLayout \ + (ttkStubsPtr->ttk_RegisterLayout) /* 8 */ +/* Slot 9 is reserved */ +#define Ttk_GetStateSpecFromObj \ + (ttkStubsPtr->ttk_GetStateSpecFromObj) /* 10 */ +#define Ttk_NewStateSpecObj \ + (ttkStubsPtr->ttk_NewStateSpecObj) /* 11 */ +#define Ttk_GetStateMapFromObj \ + (ttkStubsPtr->ttk_GetStateMapFromObj) /* 12 */ +#define Ttk_StateMapLookup \ + (ttkStubsPtr->ttk_StateMapLookup) /* 13 */ +#define Ttk_StateTableLookup \ + (ttkStubsPtr->ttk_StateTableLookup) /* 14 */ +/* Slot 15 is reserved */ +/* Slot 16 is reserved */ +/* Slot 17 is reserved */ +/* Slot 18 is reserved */ +/* Slot 19 is reserved */ +#define Ttk_GetPaddingFromObj \ + (ttkStubsPtr->ttk_GetPaddingFromObj) /* 20 */ +#define Ttk_GetBorderFromObj \ + (ttkStubsPtr->ttk_GetBorderFromObj) /* 21 */ +#define Ttk_GetStickyFromObj \ + (ttkStubsPtr->ttk_GetStickyFromObj) /* 22 */ +#define Ttk_MakePadding \ + (ttkStubsPtr->ttk_MakePadding) /* 23 */ +#define Ttk_UniformPadding \ + (ttkStubsPtr->ttk_UniformPadding) /* 24 */ +#define Ttk_AddPadding \ + (ttkStubsPtr->ttk_AddPadding) /* 25 */ +#define Ttk_RelievePadding \ + (ttkStubsPtr->ttk_RelievePadding) /* 26 */ +#define Ttk_MakeBox \ + (ttkStubsPtr->ttk_MakeBox) /* 27 */ +#define Ttk_BoxContains \ + (ttkStubsPtr->ttk_BoxContains) /* 28 */ +#define Ttk_PackBox \ + (ttkStubsPtr->ttk_PackBox) /* 29 */ +#define Ttk_StickBox \ + (ttkStubsPtr->ttk_StickBox) /* 30 */ +#define Ttk_AnchorBox \ + (ttkStubsPtr->ttk_AnchorBox) /* 31 */ +#define Ttk_PadBox \ + (ttkStubsPtr->ttk_PadBox) /* 32 */ +#define Ttk_ExpandBox \ + (ttkStubsPtr->ttk_ExpandBox) /* 33 */ +#define Ttk_PlaceBox \ + (ttkStubsPtr->ttk_PlaceBox) /* 34 */ +#define Ttk_NewBoxObj \ + (ttkStubsPtr->ttk_NewBoxObj) /* 35 */ +/* Slot 36 is reserved */ +/* Slot 37 is reserved */ +/* Slot 38 is reserved */ +/* Slot 39 is reserved */ +#define Ttk_GetOrientFromObj \ + (ttkStubsPtr->ttk_GetOrientFromObj) /* 40 */ + +#endif /* defined(USE_TTK_STUBS) */ + +/* !END!: Do not edit above this line. */ + +#endif /* _TTKDECLS */ diff --git a/generic/ttk/ttkDefaultTheme.c b/generic/ttk/ttkDefaultTheme.c new file mode 100644 index 0000000..d2deee8 --- /dev/null +++ b/generic/ttk/ttkDefaultTheme.c @@ -0,0 +1,1130 @@ +/* + * Copyright (c) 2003, Joe English + * + * Tk alternate theme, intended to match the MSUE and Gtk's (old) default theme + */ + +#include <math.h> +#include <string.h> + +#include <tkInt.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "ttkTheme.h" + +#if defined(WIN32) +static const int WIN32_XDRAWLINE_HACK = 1; +#else +static const int WIN32_XDRAWLINE_HACK = 0; +#endif + +#define BORDERWIDTH 2 +#define SCROLLBAR_WIDTH 14 +#define MIN_THUMB_SIZE 8 + +/* + *---------------------------------------------------------------------- + * + * Helper routines for border drawing: + * + * NOTE: MSUE specifies a slightly different arrangement + * for button borders than for other elements; "shadowColors" + * is for button borders. + * + * Please excuse the gross misspelling "LITE" for "LIGHT", + * but it makes things line up nicer. + */ + +enum BorderColor { FLAT = 1, LITE = 2, DARK = 3, BRDR = 4 }; + +/* top-left outer, top-left inner, bottom-right inner, bottom-right outer */ +static int const shadowColors[6][4] = { + { FLAT, FLAT, FLAT, FLAT }, /* TK_RELIEF_FLAT = 0*/ + { DARK, LITE, DARK, LITE }, /* TK_RELIEF_GROOVE = 1*/ + { LITE, FLAT, DARK, BRDR }, /* TK_RELIEF_RAISED = 2*/ + { LITE, DARK, LITE, DARK }, /* TK_RELIEF_RIDGE = 3*/ + { BRDR, BRDR, BRDR, BRDR }, /* TK_RELIEF_SOLID = 4*/ + { BRDR, DARK, FLAT, LITE } /* TK_RELIEF_SUNKEN = 5*/ +}; + +/* top-left, bottom-right */ +static int const thinShadowColors[6][4] = { + { FLAT, FLAT }, /* TK_RELIEF_FLAT = 0*/ + { DARK, LITE }, /* TK_RELIEF_GROOVE = 1*/ + { LITE, DARK }, /* TK_RELIEF_RAISED = 2*/ + { LITE, DARK }, /* TK_RELIEF_RIDGE = 3*/ + { BRDR, BRDR }, /* TK_RELIEF_SOLID = 4*/ + { DARK, LITE } /* TK_RELIEF_SUNKEN = 5*/ +}; + +static void DrawCorner( + Tk_Window tkwin, + Drawable d, + Tk_3DBorder border, /* get most GCs from here... */ + GC borderGC, /* "window border" color GC */ + int x,int y, int width,int height, /* where to draw */ + int corner, /* 0 => top left; 1 => bottom right */ + enum BorderColor color) +{ + XPoint points[3]; + GC gc; + + --width; --height; + points[0].x = x; points[0].y = y+height; + points[1].x = x+width*corner; points[1].y = y+height*corner; + points[2].x = x+width; points[2].y = y; + + if (color == BRDR) + gc = borderGC; + else + gc = Tk_3DBorderGC(tkwin, border, (int)color); + + XDrawLines(Tk_Display(tkwin), d, gc, points, 3, CoordModeOrigin); +} + +static void DrawBorder( + Tk_Window tkwin, Drawable d, Tk_3DBorder border, XColor *borderColor, + Ttk_Box b, int borderWidth, int relief) +{ + GC borderGC = Tk_GCForColor(borderColor, d); + + switch (borderWidth) { + case 2: /* "thick" border */ + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 0,shadowColors[relief][0]); + DrawCorner(tkwin, d, border, borderGC, + b.x+1, b.y+1, b.width-2, b.height-2, 0,shadowColors[relief][1]); + DrawCorner(tkwin, d, border, borderGC, + b.x+1, b.y+1, b.width-2, b.height-2, 1,shadowColors[relief][2]); + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 1,shadowColors[relief][3]); + break; + case 1: /* "thin" border */ + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 0, thinShadowColors[relief][0]); + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 1, thinShadowColors[relief][1]); + break; + case 0: /* no border -- do nothing */ + break; + default: /* Fall back to Motif-style borders: */ + Tk_Draw3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, borderWidth,relief); + break; + } +} + +/* Alternate shadow colors for entry fields: + * NOTE: FLAT color is normally white, and the LITE color is a darker shade. + */ +static int fieldShadowColors[4] = { DARK, BRDR, LITE, FLAT }; + +static void DrawFieldBorder( + Tk_Window tkwin, Drawable d, Tk_3DBorder border, XColor *borderColor, + Ttk_Box b) +{ + GC borderGC = Tk_GCForColor(borderColor, d); + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 0,fieldShadowColors[0]); + DrawCorner(tkwin, d, border, borderGC, + b.x+1, b.y+1, b.width-2, b.height-2, 0,fieldShadowColors[1]); + DrawCorner(tkwin, d, border, borderGC, + b.x+1, b.y+1, b.width-2, b.height-2, 1,fieldShadowColors[2]); + DrawCorner(tkwin, d, border, borderGC, + b.x, b.y, b.width, b.height, 1,fieldShadowColors[3]); + return; +} + +/* + * ArrowPoints -- + * Compute points of arrow polygon. + */ +static void ArrowPoints(Ttk_Box b, ArrowDirection dir, XPoint points[4]) +{ + int cx, cy, h; + + switch (dir) { + case ARROW_UP: + h = (b.width - 1)/2; + cx = b.x + h; + cy = b.y; + if (b.height <= h) h = b.height - 1; + points[0].x = cx; points[0].y = cy; + points[1].x = cx - h; points[1].y = cy + h; + points[2].x = cx + h; points[2].y = cy + h; + break; + case ARROW_DOWN: + h = (b.width - 1)/2; + cx = b.x + h; + cy = b.y + b.height - 1; + if (b.height <= h) h = b.height - 1; + points[0].x = cx; points[0].y = cy; + points[1].x = cx - h; points[1].y = cy - h; + points[2].x = cx + h; points[2].y = cy - h; + break; + case ARROW_LEFT: + h = (b.height - 1)/2; + cx = b.x; + cy = b.y + h; + if (b.width <= h) h = b.width - 1; + points[0].x = cx; points[0].y = cy; + points[1].x = cx + h; points[1].y = cy - h; + points[2].x = cx + h; points[2].y = cy + h; + break; + case ARROW_RIGHT: + h = (b.height - 1)/2; + cx = b.x + b.width - 1; + cy = b.y + h; + if (b.width <= h) h = b.width - 1; + points[0].x = cx; points[0].y = cy; + points[1].x = cx - h; points[1].y = cy - h; + points[2].x = cx - h; points[2].y = cy + h; + break; + } + + points[3].x = points[0].x; + points[3].y = points[0].y; +} + +/*public*/ +void TtkArrowSize(int h, ArrowDirection dir, int *widthPtr, int *heightPtr) +{ + switch (dir) { + case ARROW_UP: + case ARROW_DOWN: *widthPtr = 2*h+1; *heightPtr = h+1; break; + case ARROW_LEFT: + case ARROW_RIGHT: *widthPtr = h+1; *heightPtr = 2*h+1; + } +} + +/* + * TtkDrawArrow, TtkFillArrow -- + * Draw an arrow in the indicated direction inside the specified box. + */ +/*public*/ +void TtkFillArrow( + Display *display, Drawable d, GC gc, Ttk_Box b, ArrowDirection dir) +{ + XPoint points[4]; + ArrowPoints(b, dir, points); + XFillPolygon(display, d, gc, points, 3, Convex, CoordModeOrigin); + XDrawLines(display, d, gc, points, 4, CoordModeOrigin); +} + +/*public*/ +void TtkDrawArrow( + Display *display, Drawable d, GC gc, Ttk_Box b, ArrowDirection dir) +{ + XPoint points[4]; + ArrowPoints(b, dir, points); + XDrawLines(display, d, gc, points, 4, CoordModeOrigin); +} + +/* + *---------------------------------------------------------------------- + * +++ Border element implementation. + * + * This border consists of (from outside-in): + * + * + a 1-pixel thick default indicator (defaultable widgets only) + * + 1- or 2- pixel shaded border (controlled by -background and -relief) + * + 1 pixel padding (???) + */ + +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderColorObj; /* Extra border color */ + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *defaultStateObj; /* for buttons */ +} BorderElement; + +static Ttk_ElementOptionSpec BorderElementOptions[] = { + { "-background", TK_OPTION_BORDER, Tk_Offset(BorderElement,borderObj), + DEFAULT_BACKGROUND }, + { "-bordercolor",TK_OPTION_COLOR, + Tk_Offset(BorderElement,borderColorObj), "black" }, + { "-default", TK_OPTION_ANY, Tk_Offset(BorderElement,defaultStateObj), + "disabled" }, + { "-borderwidth",TK_OPTION_PIXELS,Tk_Offset(BorderElement,borderWidthObj), + STRINGIFY(BORDERWIDTH) }, + { "-relief", TK_OPTION_RELIEF, Tk_Offset(BorderElement,reliefObj), + "flat" }, + { NULL, 0, 0, NULL } +}; + +static void BorderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + BorderElement *bd = elementRecord; + int borderWidth = 0; + int defaultState = TTK_BUTTON_DEFAULT_DISABLED; + + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState); + + if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) { + ++borderWidth; + } + + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void BorderElementDraw( + void *clientData, void *elementRecord, + Tk_Window tkwin, Drawable d, Ttk_Box b, unsigned int state) +{ + BorderElement *bd = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, bd->borderObj); + XColor *borderColor = Tk_GetColorFromObj(tkwin, bd->borderColorObj); + int borderWidth = 2; + int relief = TK_RELIEF_FLAT; + int defaultState = TTK_BUTTON_DEFAULT_DISABLED; + + /* + * Get option values. + */ + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief); + Ttk_GetButtonDefaultStateFromObj(NULL, bd->defaultStateObj, &defaultState); + + if (defaultState == TTK_BUTTON_DEFAULT_ACTIVE) { + GC gc = Tk_GCForColor(borderColor, d); + XDrawRectangle(Tk_Display(tkwin), d, gc, + b.x, b.y, b.width-1, b.height-1); + } + if (defaultState != TTK_BUTTON_DEFAULT_DISABLED) { + /* Space for default ring: */ + b = Ttk_PadBox(b, Ttk_UniformPadding(1)); + } + + DrawBorder(tkwin, d, border, borderColor, b, borderWidth, relief); +} + +static Ttk_ElementSpec BorderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(BorderElement), + BorderElementOptions, + BorderElementSize, + BorderElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Field element: + * Used for editable fields. + */ +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderColorObj; /* Extra border color */ +} FieldElement; + +static Ttk_ElementOptionSpec FieldElementOptions[] = { + { "-fieldbackground", TK_OPTION_BORDER, Tk_Offset(FieldElement,borderObj), + "white" }, + { "-bordercolor",TK_OPTION_COLOR, Tk_Offset(FieldElement,borderColorObj), + "black" }, + { NULL, 0, 0, NULL } +}; + +static void FieldElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *paddingPtr = Ttk_UniformPadding(2); +} + +static void FieldElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + FieldElement *field = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, field->borderObj); + XColor *borderColor = Tk_GetColorFromObj(tkwin, field->borderColorObj); + + Tk_Fill3DRectangle( + tkwin, d, border, b.x, b.y, b.width, b.height, 0, TK_RELIEF_SUNKEN); + DrawFieldBorder(tkwin, d, border, borderColor, b); +} + +static Ttk_ElementSpec FieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + FieldElementSize, + FieldElementDraw +}; + +/*------------------------------------------------------------------------ + * Indicators -- + * + * Code derived (probably incorrectly) from TIP 109 implementation, + * unix/tkUnixButton.c r 1.15. + */ + +/* + * Indicator bitmap descriptor: + */ +typedef struct { + int width; /* Width of each image */ + int height; /* Height of each image */ + int nimages; /* #images / row */ + const char *const *pixels; /* array[height] of char[width*nimage] */ + Ttk_StateTable *map;/* used to look up image index by state */ +} IndicatorSpec; + +#if 0 +/*XPM*/ +static const char *const button_images[] = { + /* width height ncolors chars_per_pixel */ + "52 13 8 1", + /* colors */ + "A c #808000000000 s shadow", + "B c #000080800000 s highlight", + "C c #808080800000 s 3dlight", + "D c #000000008080 s window", + "E c #808000008080 s 3ddark", + "F c #000080808080 s frame", + "G c #000000000000 s foreground", + "H c #000080800000 s disabledfg", +}; +#endif + +static Ttk_StateTable checkbutton_states[] = { + { 0, 0, TTK_STATE_SELECTED|TTK_STATE_DISABLED }, + { 1, TTK_STATE_SELECTED, TTK_STATE_DISABLED }, + { 2, TTK_STATE_DISABLED, TTK_STATE_SELECTED }, + { 3, TTK_STATE_SELECTED|TTK_STATE_DISABLED, 0 }, + { 0, 0, 0 } +}; + +static const char *const checkbutton_pixels[] = { + "AAAAAAAAAAAABAAAAAAAAAAAABAAAAAAAAAAAABAAAAAAAAAAAAB", + "AEEEEEEEEEECBAEEEEEEEEEECBAEEEEEEEEEECBAEEEEEEEEEECB", + "AEDDDDDDDDDCBAEDDDDDDDDDCBAEFFFFFFFFFCBAEFFFFFFFFFCB", + "AEDDDDDDDDDCBAEDDDDDDDGDCBAEFFFFFFFFFCBAEFFFFFFFHFCB", + "AEDDDDDDDDDCBAEDDDDDDGGDCBAEFFFFFFFFFCBAEFFFFFFHHFCB", + "AEDDDDDDDDDCBAEDGDDDGGGDCBAEFFFFFFFFFCBAEFHFFFHHHFCB", + "AEDDDDDDDDDCBAEDGGDGGGDDCBAEFFFFFFFFFCBAEFHHFHHHFFCB", + "AEDDDDDDDDDCBAEDGGGGGDDDCBAEFFFFFFFFFCBAEFHHHHHFFFCB", + "AEDDDDDDDDDCBAEDDGGGDDDDCBAEFFFFFFFFFCBAEFFHHHFFFFCB", + "AEDDDDDDDDDCBAEDDDGDDDDDCBAEFFFFFFFFFCBAEFFFHFFFFFCB", + "AEDDDDDDDDDCBAEDDDDDDDDDCBAEFFFFFFFFFCBAEFFFFFFFFFCB", + "ACCCCCCCCCCCBACCCCCCCCCCCBACCCCCCCCCCCBACCCCCCCCCCCB", + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", +}; + +static IndicatorSpec checkbutton_spec = { + 13, 13, 4, /* width, height, nimages */ + checkbutton_pixels, + checkbutton_states +}; + +static Ttk_StateTable radiobutton_states[] = { + { 0, 0, TTK_STATE_SELECTED|TTK_STATE_DISABLED }, + { 1, TTK_STATE_SELECTED, TTK_STATE_DISABLED }, + { 2, TTK_STATE_DISABLED, TTK_STATE_SELECTED }, + { 3, TTK_STATE_SELECTED|TTK_STATE_DISABLED, 0 }, + { 0, 0, 0 } +}; + +static const char *const radiobutton_pixels[] = { + "FFFFAAAAFFFFFFFFFAAAAFFFFFFFFFAAAAFFFFFFFFFAAAAFFFFF", + "FFAAEEEEAAFFFFFAAEEEEAAFFFFFAAEEEEAAFFFFFAAEEEEAAFFF", + "FAEEDDDDEEBFFFAEEDDDDEEBFFFAEEFFFFEEBFFFAEEFFFFEEBFF", + "FAEDDDDDDCBFFFAEDDDDDDCBFFFAEFFFFFFCBFFFAEFFFFFFCBFF", + "AEDDDDDDDDCBFAEDDDGGDDDCBFAEFFFFFFFFCBFAEFFFHHFFFCBF", + "AEDDDDDDDDCBFAEDDGGGGDDCBFAEFFFFFFFFCBFAEFFHHHHFFCBF", + "AEDDDDDDDDCBFAEDDGGGGDDCBFAEFFFFFFFFCBFAEFFHHHHFFCBF", + "AEDDDDDDDDCBFAEDDDGGDDDCBFAEFFFFFFFFCBFAEFFFHHFFFCBF", + "FAEDDDDDDCBFFFAEDDDDDDCBFFFAEFFFFFFCBFFFAEFFFFFFCBFF", + "FACCDDDDCCBFFFACCDDDDCCBFFFACCFFFFCCBFFFACCFFFFCCBFF", + "FFBBCCCCBBFFFFFBBCCCCBBFFFFFBBCCCCBBFFFFFBBCCCCBBFFF", + "FFFFBBBBFFFFFFFFFBBBBFFFFFFFFFBBBBFFFFFFFFFBBBBFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", +}; + +static IndicatorSpec radiobutton_spec = { + 13, 13, 4, /* width, height, nimages */ + radiobutton_pixels, + radiobutton_states +}; + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *colorObj; + Tcl_Obj *lightColorObj; + Tcl_Obj *shadeColorObj; + Tcl_Obj *borderColorObj; + Tcl_Obj *marginObj; +} IndicatorElement; + +static Ttk_ElementOptionSpec IndicatorElementOptions[] = { + { "-background", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,backgroundObj), DEFAULT_BACKGROUND }, + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,foregroundObj), DEFAULT_FOREGROUND }, + { "-indicatorcolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,colorObj), "#FFFFFF" }, + { "-lightcolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,lightColorObj), "#DDDDDD" }, + { "-shadecolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,shadeColorObj), "#888888" }, + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(IndicatorElement,borderColorObj), "black" }, + { "-indicatormargin", TK_OPTION_STRING, + Tk_Offset(IndicatorElement,marginObj), "0 2 4 2" }, + { NULL, 0, 0, NULL } +}; + +static void IndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + IndicatorSpec *spec = clientData; + IndicatorElement *indicator = elementRecord; + Ttk_Padding margins; + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins); + *widthPtr = spec->width + Ttk_PaddingWidth(margins); + *heightPtr = spec->height + Ttk_PaddingHeight(margins); +} + +static void IndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + IndicatorSpec *spec = clientData; + IndicatorElement *indicator = elementRecord; + Display *display = Tk_Display(tkwin); + Ttk_Padding padding; + XColor *fgColor, *frameColor, *shadeColor, *indicatorColor, *borderColor; + + int index, ix, iy; + XGCValues gcValues; + GC copyGC; + unsigned long imgColors[8]; + XImage *img; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &padding); + b = Ttk_PadBox(b, padding); + + if ( b.x < 0 + || b.y < 0 + || Tk_Width(tkwin) < b.x + spec->width + || Tk_Height(tkwin) < b.y + spec->height) + { + /* Oops! not enough room to display the image. + * Don't draw anything. + */ + return; + } + + /* + * Fill in imgColors palette: + * + * (SHOULD: take light and shade colors from the border object, + * but Tk doesn't provide easy access to these in the public API.) + */ + fgColor = Tk_GetColorFromObj(tkwin, indicator->foregroundObj); + frameColor = Tk_GetColorFromObj(tkwin, indicator->backgroundObj); + shadeColor = Tk_GetColorFromObj(tkwin, indicator->shadeColorObj); + indicatorColor = Tk_GetColorFromObj(tkwin, indicator->colorObj); + borderColor = Tk_GetColorFromObj(tkwin, indicator->borderColorObj); + + imgColors[0 /*A*/] = shadeColor->pixel; + imgColors[1 /*B*/] = indicatorColor->pixel; + imgColors[2 /*C*/] = frameColor->pixel; + imgColors[3 /*D*/] = indicatorColor->pixel; + imgColors[4 /*E*/] = borderColor->pixel; + imgColors[5 /*F*/] = frameColor->pixel; + imgColors[6 /*G*/] = fgColor->pixel; + imgColors[7 /*H*/] = fgColor->pixel; + + /* + * Create a scratch buffer to store the image: + */ + img = XGetImage(display,d, 0, 0, + (unsigned int)spec->width, (unsigned int)spec->height, + AllPlanes, ZPixmap); + if (img == NULL) + return; + + /* + * Create the image, painting it into an XImage one pixel at a time. + */ + index = Ttk_StateTableLookup(spec->map, state); + for (iy=0 ; iy<spec->height ; iy++) { + for (ix=0 ; ix<spec->width ; ix++) { + XPutPixel(img, ix, iy, + imgColors[spec->pixels[iy][index*spec->width+ix] - 'A'] ); + } + } + + /* + * Copy onto our target drawable surface. + */ + memset(&gcValues, 0, sizeof(gcValues)); + copyGC = Tk_GetGC(tkwin, 0, &gcValues); + + TkPutImage(NULL, 0, display, d, copyGC, img, 0, 0, b.x, b.y, + spec->width, spec->height); + + /* + * Tidy up. + */ + Tk_FreeGC(display, copyGC); + XDestroyImage(img); +} + +static Ttk_ElementSpec IndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(IndicatorElement), + IndicatorElementOptions, + IndicatorElementSize, + IndicatorElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Arrow element(s). + * + * Draws a solid triangle, inside a box. + * clientData is an enum ArrowDirection pointer. + */ + +static int ArrowElements[] = { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT }; +typedef struct { + Tcl_Obj *sizeObj; + Tcl_Obj *borderObj; + Tcl_Obj *borderColorObj; /* Extra color for borders */ + Tcl_Obj *reliefObj; + Tcl_Obj *colorObj; /* Arrow color */ +} ArrowElement; + +static Ttk_ElementOptionSpec ArrowElementOptions[] = { + { "-arrowsize", TK_OPTION_PIXELS, + Tk_Offset(ArrowElement,sizeObj), STRINGIFY(SCROLLBAR_WIDTH) }, + { "-background", TK_OPTION_BORDER, + Tk_Offset(ArrowElement,borderObj), DEFAULT_BACKGROUND }, + { "-bordercolor", TK_OPTION_COLOR, + Tk_Offset(ArrowElement,borderColorObj), "black" }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(ArrowElement,reliefObj),"raised"}, + { "-arrowcolor", TK_OPTION_COLOR, + Tk_Offset(ArrowElement,colorObj),"black"}, + { NULL, 0, 0, NULL } +}; + +/* + * Note asymmetric padding: + * top/left padding is 1 less than bottom/right, + * since in this theme 2-pixel borders are asymmetric. + */ +static Ttk_Padding ArrowPadding = { 3,3,4,4 }; + +static void ArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ArrowElement *arrow = elementRecord; + int direction = *(int *)clientData; + int width = SCROLLBAR_WIDTH; + + Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &width); + width -= Ttk_PaddingWidth(ArrowPadding); + TtkArrowSize(width/2, direction, widthPtr, heightPtr); + *widthPtr += Ttk_PaddingWidth(ArrowPadding); + *heightPtr += Ttk_PaddingHeight(ArrowPadding); +} + +static void ArrowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + int direction = *(int *)clientData; + ArrowElement *arrow = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, arrow->borderObj); + XColor *borderColor = Tk_GetColorFromObj(tkwin, arrow->borderColorObj); + XColor *arrowColor = Tk_GetColorFromObj(tkwin, arrow->colorObj); + int relief = TK_RELIEF_RAISED; + int borderWidth = 2; + + Tk_GetReliefFromObj(NULL, arrow->reliefObj, &relief); + + Tk_Fill3DRectangle( + tkwin, d, border, b.x, b.y, b.width, b.height, 0, TK_RELIEF_FLAT); + DrawBorder(tkwin,d,border,borderColor,b,borderWidth,relief); + + TtkFillArrow(Tk_Display(tkwin), d, Tk_GCForColor(arrowColor, d), + Ttk_PadBox(b, ArrowPadding), direction); +} + +static Ttk_ElementSpec ArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ArrowElement), + ArrowElementOptions, + ArrowElementSize, + ArrowElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Menubutton indicator: + * Draw an arrow in the direction where the menu will be posted. + */ + +#define MENUBUTTON_ARROW_SIZE 5 + +typedef struct { + Tcl_Obj *directionObj; + Tcl_Obj *sizeObj; + Tcl_Obj *colorObj; +} MenubuttonArrowElement; + +static const char *directionStrings[] = { /* See also: button.c */ + "above", "below", "left", "right", "flush", NULL +}; +enum { POST_ABOVE, POST_BELOW, POST_LEFT, POST_RIGHT, POST_FLUSH }; + +static Ttk_ElementOptionSpec MenubuttonArrowElementOptions[] = { + { "-direction", TK_OPTION_STRING, + Tk_Offset(MenubuttonArrowElement,directionObj), "below" }, + { "-arrowsize", TK_OPTION_PIXELS, + Tk_Offset(MenubuttonArrowElement,sizeObj), STRINGIFY(MENUBUTTON_ARROW_SIZE)}, + { "-arrowcolor",TK_OPTION_COLOR, + Tk_Offset(MenubuttonArrowElement,colorObj), "black"}, + { NULL, 0, 0, NULL } +}; + +static Ttk_Padding MenubuttonArrowPadding = { 3, 0, 3, 0 }; + +static void MenubuttonArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + MenubuttonArrowElement *arrow = elementRecord; + int size = MENUBUTTON_ARROW_SIZE; + Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &size); + *widthPtr = *heightPtr = 2 * size + 1; + *widthPtr += Ttk_PaddingWidth(MenubuttonArrowPadding); + *heightPtr += Ttk_PaddingHeight(MenubuttonArrowPadding); +} + +static void MenubuttonArrowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + MenubuttonArrowElement *arrow = elementRecord; + XColor *arrowColor = Tk_GetColorFromObj(tkwin, arrow->colorObj); + GC gc = Tk_GCForColor(arrowColor, d); + int size = MENUBUTTON_ARROW_SIZE; + int postDirection = POST_BELOW; + ArrowDirection arrowDirection = ARROW_DOWN; + int width = 0, height = 0; + + Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &size); + Tcl_GetIndexFromObj(NULL, arrow->directionObj, directionStrings, + ""/*message*/, 0/*flags*/, &postDirection); + + /* ... this might not be such a great idea ... */ + switch (postDirection) { + case POST_ABOVE: arrowDirection = ARROW_UP; break; + case POST_BELOW: arrowDirection = ARROW_DOWN; break; + case POST_LEFT: arrowDirection = ARROW_LEFT; break; + case POST_RIGHT: arrowDirection = ARROW_RIGHT; break; + case POST_FLUSH: arrowDirection = ARROW_DOWN; break; + } + + TtkArrowSize(size, arrowDirection, &width, &height); + b = Ttk_PadBox(b, MenubuttonArrowPadding); + b = Ttk_AnchorBox(b, width, height, TK_ANCHOR_CENTER); + TtkFillArrow(Tk_Display(tkwin), d, gc, b, arrowDirection); +} + +static Ttk_ElementSpec MenubuttonArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(MenubuttonArrowElement), + MenubuttonArrowElementOptions, + MenubuttonArrowElementSize, + MenubuttonArrowElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Trough element + * + * Used in scrollbars and the scale. + * + * The -groovewidth option can be used to set the size of the short axis + * for the drawn area. This will not affect the geometry, but can be used + * to draw a thin centered trough inside the packet alloted. This is used + * to show a win32-style scale widget. Use -1 or a large number to use the + * full area (default). + * + */ + +typedef struct { + Tcl_Obj *colorObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *grooveWidthObj; + Tcl_Obj *orientObj; +} TroughElement; + +static Ttk_ElementOptionSpec TroughElementOptions[] = { + { "-orient", TK_OPTION_ANY, + Tk_Offset(TroughElement, orientObj), "horizontal" }, + { "-troughborderwidth", TK_OPTION_PIXELS, + Tk_Offset(TroughElement,borderWidthObj), "1" }, + { "-troughcolor", TK_OPTION_BORDER, + Tk_Offset(TroughElement,colorObj), DEFAULT_BACKGROUND }, + { "-troughrelief",TK_OPTION_RELIEF, + Tk_Offset(TroughElement,reliefObj), "sunken" }, + { "-groovewidth", TK_OPTION_PIXELS, + Tk_Offset(TroughElement,grooveWidthObj), "-1" }, + { NULL, 0, 0, NULL } +}; + +static void TroughElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TroughElement *troughPtr = elementRecord; + int borderWidth = 2, grooveWidth = 0; + + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->borderWidthObj, &borderWidth); + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->grooveWidthObj, &grooveWidth); + + if (grooveWidth <= 0) { + *paddingPtr = Ttk_UniformPadding((short)borderWidth); + } +} + +static void TroughElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + TroughElement *troughPtr = elementRecord; + Tk_3DBorder border = NULL; + int borderWidth = 2, relief = TK_RELIEF_SUNKEN, groove = -1, orient; + + border = Tk_Get3DBorderFromObj(tkwin, troughPtr->colorObj); + Ttk_GetOrientFromObj(NULL, troughPtr->orientObj, &orient); + Tk_GetReliefFromObj(NULL, troughPtr->reliefObj, &relief); + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->borderWidthObj, &borderWidth); + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->grooveWidthObj, &groove); + + if (groove != -1 && groove < b.height && groove < b.width) { + if (orient == TTK_ORIENT_HORIZONTAL) { + b.y = b.y + b.height/2 - groove/2; + b.height = groove; + } else { + b.x = b.x + b.width/2 - groove/2; + b.width = groove; + } + } + + Tk_Fill3DRectangle(tkwin, d, border, b.x, b.y, b.width, b.height, + borderWidth, relief); +} + +static Ttk_ElementSpec TroughElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TroughElement), + TroughElementOptions, + TroughElementSize, + TroughElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Thumb element. + */ + +typedef struct { + Tcl_Obj *sizeObj; + Tcl_Obj *firstObj; + Tcl_Obj *lastObj; + Tcl_Obj *borderObj; + Tcl_Obj *borderColorObj; + Tcl_Obj *reliefObj; + Tcl_Obj *orientObj; +} ThumbElement; + +static Ttk_ElementOptionSpec ThumbElementOptions[] = { + { "-width", TK_OPTION_PIXELS, Tk_Offset(ThumbElement,sizeObj), + STRINGIFY(SCROLLBAR_WIDTH) }, + { "-background", TK_OPTION_BORDER, Tk_Offset(ThumbElement,borderObj), + DEFAULT_BACKGROUND }, + { "-bordercolor", TK_OPTION_COLOR, Tk_Offset(ThumbElement,borderColorObj), + "black" }, + { "-relief", TK_OPTION_RELIEF,Tk_Offset(ThumbElement,reliefObj),"raised" }, + { "-orient", TK_OPTION_ANY,Tk_Offset(ThumbElement,orientObj),"horizontal"}, + { NULL, 0, 0, NULL } +}; + +static void ThumbElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ThumbElement *thumb = elementRecord; + int orient, size; + Tk_GetPixelsFromObj(NULL, tkwin, thumb->sizeObj, &size); + Ttk_GetOrientFromObj(NULL, thumb->orientObj, &orient); + + if (orient == TTK_ORIENT_VERTICAL) { + *widthPtr = size; + *heightPtr = MIN_THUMB_SIZE; + } else { + *widthPtr = MIN_THUMB_SIZE; + *heightPtr = size; + } +} + +static void ThumbElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + ThumbElement *thumb = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, thumb->borderObj); + XColor *borderColor = Tk_GetColorFromObj(tkwin, thumb->borderColorObj); + int relief = TK_RELIEF_RAISED; + int borderWidth = 2; + + /* + * Don't draw the thumb if we are disabled. + * This makes it behave like Windows ... if that's what we want. + if (state & TTK_STATE_DISABLED) + return; + */ + + Tk_GetReliefFromObj(NULL, thumb->reliefObj, &relief); + + Tk_Fill3DRectangle( + tkwin, d, border, b.x,b.y,b.width,b.height, 0, TK_RELIEF_FLAT); + DrawBorder(tkwin, d, border, borderColor, b, borderWidth, relief); +} + +static Ttk_ElementSpec ThumbElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ThumbElement), + ThumbElementOptions, + ThumbElementSize, + ThumbElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Slider element. + * + * This is the moving part of the scale widget. + * + * The slider element is the thumb in the scale widget. This is drawn + * as an arrow-type element that can point up, down, left or right. + * + */ + +typedef struct { + Tcl_Obj *lengthObj; /* Long axis dimension */ + Tcl_Obj *thicknessObj; /* Short axis dimension */ + Tcl_Obj *reliefObj; /* Relief for this object */ + Tcl_Obj *borderObj; /* Border / background color */ + Tcl_Obj *borderColorObj; /* Additional border color */ + Tcl_Obj *borderWidthObj; + Tcl_Obj *orientObj; /* Orientation of overall slider */ +} SliderElement; + +static Ttk_ElementOptionSpec SliderElementOptions[] = { + { "-sliderlength", TK_OPTION_PIXELS, Tk_Offset(SliderElement,lengthObj), + "15" }, + { "-sliderthickness",TK_OPTION_PIXELS,Tk_Offset(SliderElement,thicknessObj), + "15" }, + { "-sliderrelief", TK_OPTION_RELIEF, Tk_Offset(SliderElement,reliefObj), + "raised" }, + { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(SliderElement,borderWidthObj), + STRINGIFY(BORDERWIDTH) }, + { "-background", TK_OPTION_BORDER, Tk_Offset(SliderElement,borderObj), + DEFAULT_BACKGROUND }, + { "-bordercolor", TK_OPTION_COLOR, Tk_Offset(ThumbElement,borderColorObj), + "black" }, + { "-orient", TK_OPTION_ANY, Tk_Offset(SliderElement,orientObj), + "horizontal" }, + { NULL, 0, 0, NULL } +}; + +static void SliderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SliderElement *slider = elementRecord; + int orient, length, thickness, borderWidth; + + Ttk_GetOrientFromObj(NULL, slider->orientObj, &orient); + Tk_GetPixelsFromObj(NULL, tkwin, slider->borderWidthObj, &borderWidth); + Tk_GetPixelsFromObj(NULL, tkwin, slider->lengthObj, &length); + Tk_GetPixelsFromObj(NULL, tkwin, slider->thicknessObj, &thickness); + + switch (orient) { + case TTK_ORIENT_VERTICAL: + *widthPtr = thickness + (borderWidth *2); + *heightPtr = *widthPtr/2; + break; + + case TTK_ORIENT_HORIZONTAL: + *heightPtr = thickness + (borderWidth *2); + *widthPtr = *heightPtr/2; + break; + } +} + +static void SliderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SliderElement *slider = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, slider->borderObj); + XColor *borderColor = Tk_GetColorFromObj(tkwin, slider->borderColorObj); + int relief = TK_RELIEF_RAISED, borderWidth = 2; + + Tk_GetPixelsFromObj(NULL, tkwin, slider->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, slider->reliefObj, &relief); + + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, + borderWidth, TK_RELIEF_FLAT); + DrawBorder(tkwin, d, border, borderColor, b, borderWidth, relief); +} + +static Ttk_ElementSpec SliderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SliderElement), + SliderElementOptions, + SliderElementSize, + SliderElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Tree indicator element. + */ + +#define TTK_STATE_OPEN TTK_STATE_USER1 /* XREF: treeview.c */ +#define TTK_STATE_LEAF TTK_STATE_USER2 + +typedef struct { + Tcl_Obj *colorObj; + Tcl_Obj *marginObj; + Tcl_Obj *diameterObj; +} TreeitemIndicator; + +static Ttk_ElementOptionSpec TreeitemIndicatorOptions[] = { + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(TreeitemIndicator,colorObj), DEFAULT_FOREGROUND }, + { "-diameter", TK_OPTION_PIXELS, + Tk_Offset(TreeitemIndicator,diameterObj), "9" }, + { "-indicatormargins", TK_OPTION_STRING, + Tk_Offset(TreeitemIndicator,marginObj), "2 2 4 2" }, + { NULL, 0, 0, NULL } +}; + +static void TreeitemIndicatorSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TreeitemIndicator *indicator = elementRecord; + int diameter = 0; + Ttk_Padding margins; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->diameterObj, &diameter); + *widthPtr = diameter + Ttk_PaddingWidth(margins); + *heightPtr = diameter + Ttk_PaddingHeight(margins); +} + +static void TreeitemIndicatorDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + TreeitemIndicator *indicator = elementRecord; + XColor *color = Tk_GetColorFromObj(tkwin, indicator->colorObj); + GC gc = Tk_GCForColor(color, d); + Ttk_Padding padding = Ttk_UniformPadding(0); + int w = WIN32_XDRAWLINE_HACK; + int cx, cy; + + if (state & TTK_STATE_LEAF) { + /* don't draw anything ... */ + return; + } + + Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginObj,&padding); + b = Ttk_PadBox(b, padding); + + XDrawRectangle(Tk_Display(tkwin), d, gc, + b.x, b.y, b.width - 1, b.height - 1); + + cx = b.x + (b.width - 1) / 2; + cy = b.y + (b.height - 1) / 2; + XDrawLine(Tk_Display(tkwin), d, gc, b.x+2, cy, b.x+b.width-3+w, cy); + + if (!(state & TTK_STATE_OPEN)) { + /* turn '-' into a '+' */ + XDrawLine(Tk_Display(tkwin), d, gc, cx, b.y+2, cx, b.y+b.height-3+w); + } +} + +static Ttk_ElementSpec TreeitemIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TreeitemIndicator), + TreeitemIndicatorOptions, + TreeitemIndicatorSize, + TreeitemIndicatorDraw +}; + +/*------------------------------------------------------------------------ + * TtkAltTheme_Init -- + * Install alternate theme. + */ +MODULE_SCOPE int TtkAltTheme_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_CreateTheme(interp, "alt", NULL); + + if (!theme) { + return TCL_ERROR; + } + + Ttk_RegisterElement(interp, theme, "border", &BorderElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "Checkbutton.indicator", + &IndicatorElementSpec, &checkbutton_spec); + Ttk_RegisterElement(interp, theme, "Radiobutton.indicator", + &IndicatorElementSpec, &radiobutton_spec); + Ttk_RegisterElement(interp, theme, "Menubutton.indicator", + &MenubuttonArrowElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "field", &FieldElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "trough", &TroughElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "thumb", &ThumbElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "uparrow", + &ArrowElementSpec, &ArrowElements[0]); + Ttk_RegisterElement(interp, theme, "downarrow", + &ArrowElementSpec, &ArrowElements[1]); + Ttk_RegisterElement(interp, theme, "leftarrow", + &ArrowElementSpec, &ArrowElements[2]); + Ttk_RegisterElement(interp, theme, "rightarrow", + &ArrowElementSpec, &ArrowElements[3]); + Ttk_RegisterElement(interp, theme, "arrow", + &ArrowElementSpec, &ArrowElements[0]); + + Ttk_RegisterElement(interp, theme, "arrow", + &ArrowElementSpec, &ArrowElements[0]); + + Ttk_RegisterElement(interp, theme, "Treeitem.indicator", + &TreeitemIndicatorElementSpec, 0); + + Tcl_PkgProvide(interp, "ttk::theme::alt", TTK_VERSION); + + return TCL_OK; +} + +/*EOF*/ diff --git a/generic/ttk/ttkElements.c b/generic/ttk/ttkElements.c new file mode 100644 index 0000000..22af1d6 --- /dev/null +++ b/generic/ttk/ttkElements.c @@ -0,0 +1,1281 @@ +/* + * Copyright (c) 2003, Joe English + * + * Default implementation for themed elements. + * + */ + +#include <tcl.h> +#include <tk.h> +#include <string.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEFAULT_BORDERWIDTH "2" +#define DEFAULT_ARROW_SIZE "15" +#define MIN_THUMB_SIZE 10 + +/*---------------------------------------------------------------------- + * +++ Null element. Does nothing; used as a stub. + * Null element methods, option table and element spec are public, + * and may be used in other engines. + */ + +/* public */ Ttk_ElementOptionSpec TtkNullElementOptions[] = { { NULL, 0, 0, NULL } }; + +/* public */ void +TtkNullElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ +} + +/* public */ void +TtkNullElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ +} + +/* public */ Ttk_ElementSpec ttkNullElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TtkNullElementSize, + TtkNullElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Background and fill elements. + * + * The fill element fills its parcel with the background color. + * The background element ignores the parcel, and fills the entire window. + * + * Ttk_GetLayout() automatically includes a background element. + */ + +typedef struct { + Tcl_Obj *backgroundObj; +} BackgroundElement; + +static Ttk_ElementOptionSpec BackgroundElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(BackgroundElement,backgroundObj), DEFAULT_BACKGROUND }, + { NULL, 0, 0, NULL } +}; + +static void FillElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + BackgroundElement *bg = elementRecord; + Tk_3DBorder backgroundPtr = Tk_Get3DBorderFromObj(tkwin,bg->backgroundObj); + + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + b.x, b.y, b.width, b.height); +} + +static void BackgroundElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + FillElementDraw( + clientData, elementRecord, tkwin, + d, Ttk_WinBox(tkwin), state); +} + +static Ttk_ElementSpec FillElementSpec = { + TK_STYLE_VERSION_2, + sizeof(BackgroundElement), + BackgroundElementOptions, + TtkNullElementSize, + FillElementDraw +}; + +static Ttk_ElementSpec BackgroundElementSpec = { + TK_STYLE_VERSION_2, + sizeof(BackgroundElement), + BackgroundElementOptions, + TtkNullElementSize, + BackgroundElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Border element. + */ + +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; +} BorderElement; + +static Ttk_ElementOptionSpec BorderElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(BorderElement,borderObj), DEFAULT_BACKGROUND }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(BorderElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(BorderElement,reliefObj), "flat" }, + { NULL, 0, 0, NULL } +}; + +static void BorderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + BorderElement *bd = elementRecord; + int borderWidth = 0; + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void BorderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + BorderElement *bd = elementRecord; + Tk_3DBorder border = NULL; + int borderWidth = 1, relief = TK_RELIEF_FLAT; + + border = Tk_Get3DBorderFromObj(tkwin, bd->borderObj); + Tcl_GetIntFromObj(NULL, bd->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, bd->reliefObj, &relief); + + if (border && borderWidth > 0 && relief != TK_RELIEF_FLAT) { + Tk_Draw3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, borderWidth,relief); + } +} + +static Ttk_ElementSpec BorderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(BorderElement), + BorderElementOptions, + BorderElementSize, + BorderElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Field element. + * Used for editable fields. + */ +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; +} FieldElement; + +static Ttk_ElementOptionSpec FieldElementOptions[] = { + { "-fieldbackground", TK_OPTION_BORDER, + Tk_Offset(FieldElement,borderObj), "white" }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(FieldElement,borderWidthObj), "2" }, + { NULL, 0, 0, NULL } +}; + +static void FieldElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + FieldElement *field = elementRecord; + int borderWidth = 2; + Tk_GetPixelsFromObj(NULL, tkwin, field->borderWidthObj, &borderWidth); + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void FieldElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + FieldElement *field = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, field->borderObj); + int borderWidth = 2; + + Tk_GetPixelsFromObj(NULL, tkwin, field->borderWidthObj, &borderWidth); + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, borderWidth, TK_RELIEF_SUNKEN); +} + +static Ttk_ElementSpec FieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + FieldElementSize, + FieldElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Padding element. + * + * This element has no visual representation, only geometry. + * It adds a (possibly non-uniform) internal border. + * In addition, if "-shiftrelief" is specified, + * adds additional pixels to shift child elements "in" or "out" + * depending on the -relief. + */ + +typedef struct { + Tcl_Obj *paddingObj; + Tcl_Obj *reliefObj; + Tcl_Obj *shiftreliefObj; +} PaddingElement; + +static Ttk_ElementOptionSpec PaddingElementOptions[] = { + { "-padding", TK_OPTION_STRING, + Tk_Offset(PaddingElement,paddingObj), "0" }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(PaddingElement,reliefObj), "flat" }, + { "-shiftrelief", TK_OPTION_INT, + Tk_Offset(PaddingElement,shiftreliefObj), "0" }, + { NULL, 0, 0, NULL } +}; + +static void PaddingElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + PaddingElement *padding = elementRecord; + int shiftRelief = 0; + int relief = TK_RELIEF_FLAT; + Ttk_Padding pad; + + Tk_GetReliefFromObj(NULL, padding->reliefObj, &relief); + Tcl_GetIntFromObj(NULL, padding->shiftreliefObj, &shiftRelief); + Ttk_GetPaddingFromObj(NULL,tkwin,padding->paddingObj,&pad); + *paddingPtr = Ttk_RelievePadding(pad, relief, shiftRelief); +} + +static Ttk_ElementSpec PaddingElementSpec = { + TK_STYLE_VERSION_2, + sizeof(PaddingElement), + PaddingElementOptions, + PaddingElementSize, + TtkNullElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Focus ring element. + * Draws a dashed focus ring, if the widget has keyboard focus. + */ +typedef struct { + Tcl_Obj *focusColorObj; + Tcl_Obj *focusThicknessObj; +} FocusElement; + +/* + * DrawFocusRing -- + * Draw a dotted rectangle to indicate focus. + */ +static void DrawFocusRing( + Tk_Window tkwin, Drawable d, Tcl_Obj *colorObj, Ttk_Box b) +{ + XColor *color = Tk_GetColorFromObj(tkwin, colorObj); + unsigned long mask = 0UL; + XGCValues gcvalues; + GC gc; + + gcvalues.foreground = color->pixel; + gcvalues.line_style = LineOnOffDash; + gcvalues.line_width = 1; + gcvalues.dashes = 1; + gcvalues.dash_offset = 1; + mask = GCForeground | GCLineStyle | GCDashList | GCDashOffset | GCLineWidth; + + gc = Tk_GetGC(tkwin, mask, &gcvalues); + XDrawRectangle(Tk_Display(tkwin), d, gc, b.x, b.y, b.width-1, b.height-1); + Tk_FreeGC(Tk_Display(tkwin), gc); +} + +static Ttk_ElementOptionSpec FocusElementOptions[] = { + { "-focuscolor",TK_OPTION_COLOR, + Tk_Offset(FocusElement,focusColorObj), "black" }, + { "-focusthickness",TK_OPTION_PIXELS, + Tk_Offset(FocusElement,focusThicknessObj), "1" }, + { NULL, 0, 0, NULL } +}; + +static void FocusElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + FocusElement *focus = elementRecord; + int focusThickness = 0; + + Tcl_GetIntFromObj(NULL, focus->focusThicknessObj, &focusThickness); + *paddingPtr = Ttk_UniformPadding((short)focusThickness); +} + +static void FocusElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + FocusElement *focus = elementRecord; + int focusThickness = 0; + + if (state & TTK_STATE_FOCUS) { + Tcl_GetIntFromObj(NULL,focus->focusThicknessObj,&focusThickness); + DrawFocusRing(tkwin, d, focus->focusColorObj, b); + } +} + +static Ttk_ElementSpec FocusElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FocusElement), + FocusElementOptions, + FocusElementSize, + FocusElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Separator element. + * Just draws a horizontal or vertical bar. + * Three elements are defined: horizontal, vertical, and general; + * the general separator checks the "-orient" option. + */ + +typedef struct { + Tcl_Obj *orientObj; + Tcl_Obj *borderObj; +} SeparatorElement; + +static Ttk_ElementOptionSpec SeparatorElementOptions[] = { + { "-orient", TK_OPTION_ANY, + Tk_Offset(SeparatorElement, orientObj), "horizontal" }, + { "-background", TK_OPTION_BORDER, + Tk_Offset(SeparatorElement,borderObj), DEFAULT_BACKGROUND }, + { NULL, 0, 0, NULL } +}; + +static void SeparatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *widthPtr = *heightPtr = 2; +} + +static void HorizontalSeparatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SeparatorElement *separator = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, separator->borderObj); + GC lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); + GC darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + + XDrawLine(Tk_Display(tkwin), d, darkGC, b.x, b.y, b.x + b.width, b.y); + XDrawLine(Tk_Display(tkwin), d, lightGC, b.x, b.y+1, b.x + b.width, b.y+1); +} + +static void VerticalSeparatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SeparatorElement *separator = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, separator->borderObj); + GC lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); + GC darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + + XDrawLine(Tk_Display(tkwin), d, darkGC, b.x, b.y, b.x, b.y + b.height); + XDrawLine(Tk_Display(tkwin), d, lightGC, b.x+1, b.y, b.x+1, b.y+b.height); +} + +static void GeneralSeparatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SeparatorElement *separator = elementRecord; + int orient; + Ttk_GetOrientFromObj(NULL, separator->orientObj, &orient); + switch (orient) { + case TTK_ORIENT_HORIZONTAL: + HorizontalSeparatorElementDraw( + clientData, elementRecord, tkwin, d, b, state); + break; + case TTK_ORIENT_VERTICAL: + VerticalSeparatorElementDraw( + clientData, elementRecord, tkwin, d, b, state); + break; + } +} + +static Ttk_ElementSpec HorizontalSeparatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SeparatorElement), + SeparatorElementOptions, + SeparatorElementSize, + HorizontalSeparatorElementDraw +}; + +static Ttk_ElementSpec VerticalSeparatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SeparatorElement), + SeparatorElementOptions, + SeparatorElementSize, + HorizontalSeparatorElementDraw +}; + +static Ttk_ElementSpec SeparatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SeparatorElement), + SeparatorElementOptions, + SeparatorElementSize, + GeneralSeparatorElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Sizegrip: lower-right corner grip handle for resizing window. + */ + +typedef struct { + Tcl_Obj *backgroundObj; +} SizegripElement; + +static Ttk_ElementOptionSpec SizegripOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(SizegripElement,backgroundObj), DEFAULT_BACKGROUND }, + {0,0,0,0} +}; + +static void SizegripSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + int gripCount = 3, gripSpace = 2, gripThickness = 3; + *widthPtr = *heightPtr = gripCount * (gripSpace + gripThickness); +} + +static void SizegripDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + SizegripElement *grip = elementRecord; + int gripCount = 3, gripSpace = 2; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, grip->backgroundObj); + GC lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); + GC darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); + int x1 = b.x + b.width-1, y1 = b.y + b.height-1, x2 = x1, y2 = y1; + + while (gripCount--) { + x1 -= gripSpace; y2 -= gripSpace; + XDrawLine(Tk_Display(tkwin), d, darkGC, x1,y1, x2,y2); --x1; --y2; + XDrawLine(Tk_Display(tkwin), d, darkGC, x1,y1, x2,y2); --x1; --y2; + XDrawLine(Tk_Display(tkwin), d, lightGC, x1,y1, x2,y2); --x1; --y2; + } +} + +static Ttk_ElementSpec SizegripElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SizegripElement), + SizegripOptions, + SizegripSize, + SizegripDraw +}; + +/*---------------------------------------------------------------------- + * +++ Indicator element. + * + * Draws the on/off indicator for checkbuttons and radiobuttons. + * + * Draws a 3-D square (or diamond), raised if off, sunken if on. + * + * This is actually a regression from Tk 8.5 back to the ugly old Motif + * style; use "altTheme" for the newer, nicer version. + */ + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *reliefObj; + Tcl_Obj *colorObj; + Tcl_Obj *diameterObj; + Tcl_Obj *marginObj; + Tcl_Obj *borderWidthObj; +} IndicatorElement; + +static Ttk_ElementOptionSpec IndicatorElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(IndicatorElement,backgroundObj), DEFAULT_BACKGROUND }, + { "-indicatorcolor", TK_OPTION_BORDER, + Tk_Offset(IndicatorElement,colorObj), DEFAULT_BACKGROUND }, + { "-indicatorrelief", TK_OPTION_RELIEF, + Tk_Offset(IndicatorElement,reliefObj), "raised" }, + { "-indicatordiameter", TK_OPTION_PIXELS, + Tk_Offset(IndicatorElement,diameterObj), "12" }, + { "-indicatormargin", TK_OPTION_STRING, + Tk_Offset(IndicatorElement,marginObj), "0 2 4 2" }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(IndicatorElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { NULL, 0, 0, NULL } +}; + +/* + * Checkbutton indicators (default): 3-D square. + */ +static void SquareIndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + IndicatorElement *indicator = elementRecord; + Ttk_Padding margins; + int diameter = 0; + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->diameterObj, &diameter); + *widthPtr = diameter + Ttk_PaddingWidth(margins); + *heightPtr = diameter + Ttk_PaddingHeight(margins); +} + +static void SquareIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + IndicatorElement *indicator = elementRecord; + Tk_3DBorder border = 0, interior = 0; + int relief = TK_RELIEF_RAISED; + Ttk_Padding padding; + int borderWidth = 2; + int diameter; + + interior = Tk_Get3DBorderFromObj(tkwin, indicator->colorObj); + border = Tk_Get3DBorderFromObj(tkwin, indicator->backgroundObj); + Tcl_GetIntFromObj(NULL,indicator->borderWidthObj,&borderWidth); + Tk_GetReliefFromObj(NULL,indicator->reliefObj,&relief); + Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginObj,&padding); + + b = Ttk_PadBox(b, padding); + + diameter = b.width < b.height ? b.width : b.height; + Tk_Fill3DRectangle(tkwin, d, interior, b.x, b.y, + diameter, diameter,borderWidth, TK_RELIEF_FLAT); + Tk_Draw3DRectangle(tkwin, d, border, b.x, b.y, + diameter, diameter, borderWidth, relief); +} + +/* + * Radiobutton indicators: 3-D diamond. + */ +static void DiamondIndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + IndicatorElement *indicator = elementRecord; + Ttk_Padding margins; + int diameter = 0; + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->diameterObj, &diameter); + *widthPtr = diameter + 3 + Ttk_PaddingWidth(margins); + *heightPtr = diameter + 3 + Ttk_PaddingHeight(margins); +} + +static void DiamondIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + IndicatorElement *indicator = elementRecord; + Tk_3DBorder border = 0, interior = 0; + int borderWidth = 2; + int relief = TK_RELIEF_RAISED; + int diameter, radius; + XPoint points[4]; + Ttk_Padding padding; + + interior = Tk_Get3DBorderFromObj(tkwin, indicator->colorObj); + border = Tk_Get3DBorderFromObj(tkwin, indicator->backgroundObj); + Tcl_GetIntFromObj(NULL,indicator->borderWidthObj,&borderWidth); + Tk_GetReliefFromObj(NULL,indicator->reliefObj,&relief); + Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginObj,&padding); + + b = Ttk_PadBox(b, padding); + + diameter = b.width < b.height ? b.width : b.height; + radius = diameter / 2; + + points[0].x = b.x; + points[0].y = b.y + radius; + points[1].x = b.x + radius; + points[1].y = b.y + 2*radius; + points[2].x = b.x + 2*radius; + points[2].y = b.y + radius; + points[3].x = b.x + radius; + points[3].y = b.y; + + Tk_Fill3DPolygon(tkwin,d,interior,points,4,borderWidth,TK_RELIEF_FLAT); + Tk_Draw3DPolygon(tkwin,d,border,points,4,borderWidth,relief); +} + +static Ttk_ElementSpec CheckbuttonIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(IndicatorElement), + IndicatorElementOptions, + SquareIndicatorElementSize, + SquareIndicatorElementDraw +}; + +static Ttk_ElementSpec RadiobuttonIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(IndicatorElement), + IndicatorElementOptions, + DiamondIndicatorElementSize, + DiamondIndicatorElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Menubutton indicators. + * + * These aren't functional like radio/check indicators, + * they're just affordability indicators. + * + * Standard Tk sets the indicator size to 4.0 mm by 1.7 mm. + * I have no idea where these numbers came from. + */ + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *widthObj; + Tcl_Obj *heightObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *marginObj; +} MenuIndicatorElement; + +static Ttk_ElementOptionSpec MenuIndicatorElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(MenuIndicatorElement,backgroundObj), DEFAULT_BACKGROUND }, + { "-indicatorwidth", TK_OPTION_PIXELS, + Tk_Offset(MenuIndicatorElement,widthObj), "4.0m" }, + { "-indicatorheight", TK_OPTION_PIXELS, + Tk_Offset(MenuIndicatorElement,heightObj), "1.7m" }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(MenuIndicatorElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { "-indicatorrelief", TK_OPTION_RELIEF, + Tk_Offset(MenuIndicatorElement,reliefObj),"raised" }, + { "-indicatormargin", TK_OPTION_STRING, + Tk_Offset(MenuIndicatorElement,marginObj), "5 0" }, + { NULL, 0, 0, NULL } +}; + +static void MenuIndicatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + MenuIndicatorElement *mi = elementRecord; + Ttk_Padding margins; + Tk_GetPixelsFromObj(NULL, tkwin, mi->widthObj, widthPtr); + Tk_GetPixelsFromObj(NULL, tkwin, mi->heightObj, heightPtr); + Ttk_GetPaddingFromObj(NULL,tkwin,mi->marginObj, &margins); + *widthPtr += Ttk_PaddingWidth(margins); + *heightPtr += Ttk_PaddingHeight(margins); +} + +static void MenuIndicatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + MenuIndicatorElement *mi = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, mi->backgroundObj); + Ttk_Padding margins; + int borderWidth = 2; + + Ttk_GetPaddingFromObj(NULL,tkwin,mi->marginObj,&margins); + b = Ttk_PadBox(b, margins); + Tk_GetPixelsFromObj(NULL, tkwin, mi->borderWidthObj, &borderWidth); + Tk_Fill3DRectangle(tkwin, d, border, b.x, b.y, b.width, b.height, + borderWidth, TK_RELIEF_RAISED); +} + +static Ttk_ElementSpec MenuIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(MenuIndicatorElement), + MenuIndicatorElementOptions, + MenuIndicatorElementSize, + MenuIndicatorElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Arrow elements. + * + * Draws a solid triangle inside a box. + * clientData is an enum ArrowDirection pointer. + */ + +static int ArrowElements[] = { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT }; +typedef struct { + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *sizeObj; + Tcl_Obj *colorObj; +} ArrowElement; + +static Ttk_ElementOptionSpec ArrowElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(ArrowElement,borderObj), DEFAULT_BACKGROUND }, + { "-relief",TK_OPTION_RELIEF, + Tk_Offset(ArrowElement,reliefObj),"raised"}, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(ArrowElement,borderWidthObj), "1" }, + { "-arrowcolor",TK_OPTION_COLOR, + Tk_Offset(ArrowElement,colorObj),"black"}, + { "-arrowsize", TK_OPTION_PIXELS, + Tk_Offset(ArrowElement,sizeObj), "14" }, + { NULL, 0, 0, NULL } +}; + +static Ttk_Padding ArrowMargins = { 3,3,3,3 }; + +static void ArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ArrowElement *arrow = elementRecord; + int direction = *(int *)clientData; + int width = 14; + + Tk_GetPixelsFromObj(NULL, tkwin, arrow->sizeObj, &width); + width -= Ttk_PaddingWidth(ArrowMargins); + TtkArrowSize(width/2, direction, widthPtr, heightPtr); + *widthPtr += Ttk_PaddingWidth(ArrowMargins); + *heightPtr += Ttk_PaddingWidth(ArrowMargins); +} + +static void ArrowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + int direction = *(int *)clientData; + ArrowElement *arrow = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, arrow->borderObj); + XColor *arrowColor = Tk_GetColorFromObj(tkwin, arrow->colorObj); + int relief = TK_RELIEF_RAISED; + int borderWidth = 1; + + Tk_GetReliefFromObj(NULL, arrow->reliefObj, &relief); + + Tk_Fill3DRectangle( + tkwin, d, border, b.x, b.y, b.width, b.height, borderWidth, relief); + + TtkFillArrow(Tk_Display(tkwin), d, Tk_GCForColor(arrowColor, d), + Ttk_PadBox(b, ArrowMargins), direction); +} + +static Ttk_ElementSpec ArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ArrowElement), + ArrowElementOptions, + ArrowElementSize, + ArrowElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Trough element. + * + * Used in scrollbars and scales in place of "border". + */ + +typedef struct { + Tcl_Obj *colorObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; +} TroughElement; + +static Ttk_ElementOptionSpec TroughElementOptions[] = { + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(TroughElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { "-troughcolor", TK_OPTION_BORDER, + Tk_Offset(TroughElement,colorObj), DEFAULT_BACKGROUND }, + { "-troughrelief",TK_OPTION_RELIEF, + Tk_Offset(TroughElement,reliefObj), "sunken" }, + { NULL, 0, 0, NULL } +}; + +static void TroughElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TroughElement *troughPtr = elementRecord; + int borderWidth = 2; + + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->borderWidthObj, &borderWidth); + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static void TroughElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + TroughElement *troughPtr = elementRecord; + Tk_3DBorder border = NULL; + int borderWidth = 2, relief = TK_RELIEF_SUNKEN; + + border = Tk_Get3DBorderFromObj(tkwin, troughPtr->colorObj); + Tk_GetReliefFromObj(NULL, troughPtr->reliefObj, &relief); + Tk_GetPixelsFromObj(NULL, tkwin, troughPtr->borderWidthObj, &borderWidth); + + Tk_Fill3DRectangle(tkwin, d, border, b.x, b.y, b.width, b.height, + borderWidth, relief); +} + +static Ttk_ElementSpec TroughElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TroughElement), + TroughElementOptions, + TroughElementSize, + TroughElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Thumb element. + * + * Used in scrollbars. + */ + +typedef struct { + Tcl_Obj *orientObj; + Tcl_Obj *thicknessObj; + Tcl_Obj *reliefObj; + Tcl_Obj *borderObj; + Tcl_Obj *borderWidthObj; +} ThumbElement; + +static Ttk_ElementOptionSpec ThumbElementOptions[] = { + { "-orient", TK_OPTION_ANY, + Tk_Offset(ThumbElement, orientObj), "horizontal" }, + { "-width", TK_OPTION_PIXELS, + Tk_Offset(ThumbElement,thicknessObj), DEFAULT_ARROW_SIZE }, + { "-relief", TK_OPTION_RELIEF, + Tk_Offset(ThumbElement,reliefObj), "raised" }, + { "-background", TK_OPTION_BORDER, + Tk_Offset(ThumbElement,borderObj), DEFAULT_BACKGROUND }, + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(ThumbElement,borderWidthObj), DEFAULT_BORDERWIDTH }, + { NULL, 0, 0, NULL } +}; + +static void ThumbElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ThumbElement *thumb = elementRecord; + int orient, thickness; + + Tk_GetPixelsFromObj(NULL, tkwin, thumb->thicknessObj, &thickness); + Ttk_GetOrientFromObj(NULL, thumb->orientObj, &orient); + + if (orient == TTK_ORIENT_VERTICAL) { + *widthPtr = thickness; + *heightPtr = MIN_THUMB_SIZE; + } else { + *widthPtr = MIN_THUMB_SIZE; + *heightPtr = thickness; + } +} + +static void ThumbElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + ThumbElement *thumb = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, thumb->borderObj); + int borderWidth = 2, relief = TK_RELIEF_RAISED; + + Tk_GetPixelsFromObj(NULL, tkwin, thumb->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, thumb->reliefObj, &relief); + Tk_Fill3DRectangle(tkwin, d, border, b.x, b.y, b.width, b.height, + borderWidth, relief); +} + +static Ttk_ElementSpec ThumbElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ThumbElement), + ThumbElementOptions, + ThumbElementSize, + ThumbElementDraw +}; + +/* + *---------------------------------------------------------------------- + * +++ Slider element. + * + * This is the moving part of the scale widget. Drawn as a raised box. + */ + +typedef struct { + Tcl_Obj *orientObj; /* orientation of overall slider */ + Tcl_Obj *lengthObj; /* slider length */ + Tcl_Obj *thicknessObj; /* slider thickness */ + Tcl_Obj *reliefObj; /* the relief for this object */ + Tcl_Obj *borderObj; /* the background color */ + Tcl_Obj *borderWidthObj; /* the size of the border */ +} SliderElement; + +static Ttk_ElementOptionSpec SliderElementOptions[] = { + { "-sliderlength", TK_OPTION_PIXELS, Tk_Offset(SliderElement,lengthObj), + "30" }, + { "-sliderthickness",TK_OPTION_PIXELS,Tk_Offset(SliderElement,thicknessObj), + "15" }, + { "-sliderrelief", TK_OPTION_RELIEF, Tk_Offset(SliderElement,reliefObj), + "raised" }, + { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(SliderElement,borderWidthObj), + DEFAULT_BORDERWIDTH }, + { "-background", TK_OPTION_BORDER, Tk_Offset(SliderElement,borderObj), + DEFAULT_BACKGROUND }, + { "-orient", TK_OPTION_ANY, Tk_Offset(SliderElement,orientObj), + "horizontal" }, + { NULL, 0, 0, NULL } +}; + +static void SliderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SliderElement *slider = elementRecord; + int orient, length, thickness; + + Ttk_GetOrientFromObj(NULL, slider->orientObj, &orient); + Tk_GetPixelsFromObj(NULL, tkwin, slider->lengthObj, &length); + Tk_GetPixelsFromObj(NULL, tkwin, slider->thicknessObj, &thickness); + + switch (orient) { + case TTK_ORIENT_VERTICAL: + *widthPtr = thickness; + *heightPtr = length; + break; + + case TTK_ORIENT_HORIZONTAL: + *widthPtr = length; + *heightPtr = thickness; + break; + } +} + +static void SliderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SliderElement *slider = elementRecord; + Tk_3DBorder border = NULL; + int relief = TK_RELIEF_RAISED, borderWidth = 2, orient; + + border = Tk_Get3DBorderFromObj(tkwin, slider->borderObj); + Ttk_GetOrientFromObj(NULL, slider->orientObj, &orient); + Tk_GetPixelsFromObj(NULL, tkwin, slider->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, slider->reliefObj, &relief); + + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, + borderWidth, relief); + + if (relief != TK_RELIEF_FLAT) { + if (orient == TTK_ORIENT_HORIZONTAL) { + if (b.width > 4) { + b.x += b.width/2; + XDrawLine(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC), + b.x-1, b.y+borderWidth, b.x-1, b.y+b.height-borderWidth); + XDrawLine(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC), + b.x, b.y+borderWidth, b.x, b.y+b.height-borderWidth); + } + } else { + if (b.height > 4) { + b.y += b.height/2; + XDrawLine(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC), + b.x+borderWidth, b.y-1, b.x+b.width-borderWidth, b.y-1); + XDrawLine(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC), + b.x+borderWidth, b.y, b.x+b.width-borderWidth, b.y); + } + } + } +} + +static Ttk_ElementSpec SliderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SliderElement), + SliderElementOptions, + SliderElementSize, + SliderElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Progress bar element: + * Draws the moving part of the progress bar. + * + * -thickness specifies the size along the short axis of the bar. + * -length specifies the default size along the long axis; + * the bar will be this long in indeterminate mode. + */ + +#define DEFAULT_PBAR_THICKNESS "15" +#define DEFAULT_PBAR_LENGTH "30" + +typedef struct { + Tcl_Obj *orientObj; /* widget orientation */ + Tcl_Obj *thicknessObj; /* the height/width of the bar */ + Tcl_Obj *lengthObj; /* default width/height of the bar */ + Tcl_Obj *reliefObj; /* border relief for this object */ + Tcl_Obj *borderObj; /* background color */ + Tcl_Obj *borderWidthObj; /* thickness of the border */ +} PbarElement; + +static Ttk_ElementOptionSpec PbarElementOptions[] = { + { "-orient", TK_OPTION_ANY, Tk_Offset(PbarElement,orientObj), + "horizontal" }, + { "-thickness", TK_OPTION_PIXELS, Tk_Offset(PbarElement,thicknessObj), + DEFAULT_PBAR_THICKNESS }, + { "-barsize", TK_OPTION_PIXELS, Tk_Offset(PbarElement,lengthObj), + DEFAULT_PBAR_LENGTH }, + { "-pbarrelief", TK_OPTION_RELIEF, Tk_Offset(PbarElement,reliefObj), + "raised" }, + { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(PbarElement,borderWidthObj), + DEFAULT_BORDERWIDTH }, + { "-background", TK_OPTION_BORDER, Tk_Offset(PbarElement,borderObj), + DEFAULT_BACKGROUND }, + { NULL, 0, 0, NULL } +}; + +static void PbarElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + PbarElement *pbar = elementRecord; + int orient, thickness = 15, length = 30, borderWidth = 2; + + Ttk_GetOrientFromObj(NULL, pbar->orientObj, &orient); + Tk_GetPixelsFromObj(NULL, tkwin, pbar->thicknessObj, &thickness); + Tk_GetPixelsFromObj(NULL, tkwin, pbar->lengthObj, &length); + Tk_GetPixelsFromObj(NULL, tkwin, pbar->borderWidthObj, &borderWidth); + + switch (orient) { + case TTK_ORIENT_HORIZONTAL: + *widthPtr = length + 2 * borderWidth; + *heightPtr = thickness + 2 * borderWidth; + break; + case TTK_ORIENT_VERTICAL: + *widthPtr = thickness + 2 * borderWidth; + *heightPtr = length + 2 * borderWidth; + break; + } +} + +static void PbarElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + PbarElement *pbar = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, pbar->borderObj); + int relief = TK_RELIEF_RAISED, borderWidth = 2; + + Tk_GetPixelsFromObj(NULL, tkwin, pbar->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, pbar->reliefObj, &relief); + + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, + borderWidth, relief); +} + +static Ttk_ElementSpec PbarElementSpec = { + TK_STYLE_VERSION_2, + sizeof(PbarElement), + PbarElementOptions, + PbarElementSize, + PbarElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Notebook tabs and client area. + */ + +typedef struct { + Tcl_Obj *borderWidthObj; + Tcl_Obj *backgroundObj; +} TabElement; + +static Ttk_ElementOptionSpec TabElementOptions[] = { + { "-borderwidth", TK_OPTION_PIXELS, + Tk_Offset(TabElement,borderWidthObj),"1" }, + { "-background", TK_OPTION_BORDER, + Tk_Offset(TabElement,backgroundObj), DEFAULT_BACKGROUND }, + {0,0,0,0} +}; + +static void TabElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TabElement *tab = elementRecord; + int borderWidth = 1; + Tk_GetPixelsFromObj(0, tkwin, tab->borderWidthObj, &borderWidth); + paddingPtr->top = paddingPtr->left = paddingPtr->right = borderWidth; + paddingPtr->bottom = 0; +} + +static void TabElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + TabElement *tab = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, tab->backgroundObj); + int borderWidth = 1; + int cut = 2; + XPoint pts[6]; + int n = 0; + + Tcl_GetIntFromObj(NULL, tab->borderWidthObj, &borderWidth); + + if (state & TTK_STATE_SELECTED) { + /* + * Draw slightly outside of the allocated parcel, + * to overwrite the client area border. + */ + b.height += borderWidth; + } + + pts[n].x = b.x; pts[n].y = b.y + b.height - 1; ++n; + pts[n].x = b.x; pts[n].y = b.y + cut; ++n; + pts[n].x = b.x + cut; pts[n].y = b.y; ++n; + pts[n].x = b.x + b.width-1-cut; pts[n].y = b.y; ++n; + pts[n].x = b.x + b.width-1; pts[n].y = b.y + cut; ++n; + pts[n].x = b.x + b.width-1; pts[n].y = b.y + b.height; ++n; + + XFillPolygon(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC), + pts, 6, Convex, CoordModeOrigin); + +#ifndef WIN32 + /* + * Account for whether XDrawLines draws endpoints by platform + */ + --pts[5].y; +#endif + + while (borderWidth--) { + XDrawLines(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC), + pts, 4, CoordModeOrigin); + XDrawLines(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC), + pts+3, 3, CoordModeOrigin); + ++pts[0].x; ++pts[1].x; ++pts[2].x; --pts[4].x; --pts[5].x; + ++pts[2].y; ++pts[3].y; + } + +} + +static Ttk_ElementSpec TabElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TabElement), + TabElementOptions, + TabElementSize, + TabElementDraw +}; + +/* + * Client area element: + * Uses same resources as tab element. + */ +typedef TabElement ClientElement; +#define ClientElementOptions TabElementOptions + +static void ClientElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + ClientElement *ce = elementRecord; + Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, ce->backgroundObj); + int borderWidth = 1; + + Tcl_GetIntFromObj(NULL, ce->borderWidthObj, &borderWidth); + + Tk_Fill3DRectangle(tkwin, d, border, + b.x, b.y, b.width, b.height, borderWidth,TK_RELIEF_RAISED); +} + +static void ClientElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ClientElement *ce = elementRecord; + int borderWidth = 1; + Tk_GetPixelsFromObj(0, tkwin, ce->borderWidthObj, &borderWidth); + *paddingPtr = Ttk_UniformPadding((short)borderWidth); +} + +static Ttk_ElementSpec ClientElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ClientElement), + ClientElementOptions, + ClientElementSize, + ClientElementDraw +}; + +/*---------------------------------------------------------------------- + * TtkElements_Init -- + * Register default element implementations. + */ + +MODULE_SCOPE +void TtkElements_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + /* + * Elements: + */ + Ttk_RegisterElement(interp, theme, "background", + &BackgroundElementSpec,NULL); + + Ttk_RegisterElement(interp, theme, "fill", &FillElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "border", &BorderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "field", &FieldElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "focus", &FocusElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "padding", &PaddingElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "Checkbutton.indicator", + &CheckbuttonIndicatorElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "Radiobutton.indicator", + &RadiobuttonIndicatorElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "Menubutton.indicator", + &MenuIndicatorElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "indicator", &ttkNullElementSpec,NULL); + + Ttk_RegisterElement(interp, theme, "uparrow", + &ArrowElementSpec, &ArrowElements[0]); + Ttk_RegisterElement(interp, theme, "downarrow", + &ArrowElementSpec, &ArrowElements[1]); + Ttk_RegisterElement(interp, theme, "leftarrow", + &ArrowElementSpec, &ArrowElements[2]); + Ttk_RegisterElement(interp, theme, "rightarrow", + &ArrowElementSpec, &ArrowElements[3]); + Ttk_RegisterElement(interp, theme, "arrow", + &ArrowElementSpec, &ArrowElements[0]); + + Ttk_RegisterElement(interp, theme, "trough", &TroughElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "thumb", &ThumbElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "pbar", &PbarElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "separator", + &SeparatorElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "hseparator", + &HorizontalSeparatorElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "vseparator", + &VerticalSeparatorElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "sizegrip", &SizegripElementSpec, NULL); + + Ttk_RegisterElement(interp, theme, "tab", &TabElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "client", &ClientElementSpec, NULL); + + /* + * Register "default" as a user-loadable theme (for now): + */ + Tcl_PkgProvide(interp, "ttk::theme::default", TTK_VERSION); +} + +/*EOF*/ diff --git a/generic/ttk/ttkEntry.c b/generic/ttk/ttkEntry.c new file mode 100644 index 0000000..5c280e4 --- /dev/null +++ b/generic/ttk/ttkEntry.c @@ -0,0 +1,2048 @@ +/* + * DERIVED FROM: tk/generic/tkEntry.c r1.35. + * + * Copyright (c) 1990-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 2000 Ajuba Solutions. + * Copyright (c) 2002 ActiveState Corporation. + * Copyright (c) 2004 Joe English + */ + +#include <string.h> +#include <stdio.h> +#include <tkInt.h> +#include <X11/Xatom.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" + +/* + * Extra bits for core.flags: + */ +#define GOT_SELECTION (WIDGET_USER_FLAG<<1) +#define SYNCING_VARIABLE (WIDGET_USER_FLAG<<2) +#define VALIDATING (WIDGET_USER_FLAG<<3) +#define VALIDATION_SET_VALUE (WIDGET_USER_FLAG<<4) + +/* + * Definitions for -validate option values: + */ +typedef enum validateMode { + VMODE_ALL, VMODE_KEY, VMODE_FOCUS, VMODE_FOCUSIN, VMODE_FOCUSOUT, VMODE_NONE +} VMODE; + +static const char *const validateStrings[] = { + "all", "key", "focus", "focusin", "focusout", "none", NULL +}; + +/* + * Validation reasons: + */ +typedef enum validateReason { + VALIDATE_INSERT, VALIDATE_DELETE, + VALIDATE_FOCUSIN, VALIDATE_FOCUSOUT, + VALIDATE_FORCED +} VREASON; + +static const char *const validateReasonStrings[] = { + "key", "key", "focusin", "focusout", "forced", NULL +}; + +/*------------------------------------------------------------------------ + * +++ Entry widget record. + * + * Dependencies: + * + * textVariableTrace : textVariableObj + * + * numBytes,numChars : string + * displayString : numChars, showChar + * layoutHeight, + * layoutWidth, + * textLayout : fontObj, displayString + * layoutX, layoutY : textLayout, justify, xscroll.first + * + * Invariants: + * + * 0 <= insertPos <= numChars + * 0 <= selectFirst < selectLast <= numChars || selectFirst == selectLast == -1 + * displayString points to string if showChar == NULL, + * or to malloc'ed storage if showChar != NULL. + */ + +/* Style parameters: + */ +typedef struct { + Tcl_Obj *foregroundObj; /* Foreground color for normal text */ + Tcl_Obj *backgroundObj; /* Entry widget background color */ + Tcl_Obj *selBorderObj; /* Border and background for selection */ + Tcl_Obj *selBorderWidthObj; /* Width of selection border */ + Tcl_Obj *selForegroundObj; /* Foreground color for selected text */ + Tcl_Obj *insertColorObj; /* Color of insertion cursor */ + Tcl_Obj *insertWidthObj; /* Insert cursor width */ +} EntryStyleData; + +typedef struct { + /* + * Internal state: + */ + char *string; /* Storage for string (malloced) */ + int numBytes; /* Length of string in bytes. */ + int numChars; /* Length of string in characters. */ + + int insertPos; /* Insert index */ + int selectFirst; /* Index of start of selection, or -1 */ + int selectLast; /* Index of end of selection, or -1 */ + + Scrollable xscroll; /* Current scroll position */ + ScrollHandle xscrollHandle; + + /* + * Options managed by Tk_SetOptions: + */ + Tcl_Obj *textVariableObj; /* Name of linked variable */ + int exportSelection; /* Tie internal selection to X selection? */ + + VMODE validate; /* Validation mode */ + char *validateCmd; /* Validation script template */ + char *invalidCmd; /* Invalid callback script template */ + + char *showChar; /* Used to derive displayString */ + + Tcl_Obj *fontObj; /* Text font to use */ + Tcl_Obj *widthObj; /* Desired width of window (in avgchars) */ + Tk_Justify justify; /* Text justification */ + + EntryStyleData styleData; /* Display style data (widget options) */ + EntryStyleData styleDefaults;/* Style defaults (fallback values) */ + + Tcl_Obj *stateObj; /* Compatibility option -- see CheckStateObj */ + + /* + * Derived resources: + */ + Ttk_TraceHandle *textVariableTrace; + + char *displayString; /* String to use when displaying */ + Tk_TextLayout textLayout; /* Cached text layout information. */ + int layoutWidth; /* textLayout width */ + int layoutHeight; /* textLayout height */ + + int layoutX, layoutY; /* Origin for text layout. */ + +} EntryPart; + +typedef struct { + WidgetCore core; + EntryPart entry; +} Entry; + +/* + * Extra mask bits for Tk_SetOptions() + */ +#define STATE_CHANGED (0x100) /* -state option changed */ +#define TEXTVAR_CHANGED (0x200) /* -textvariable option changed */ +#define SCROLLCMD_CHANGED (0x400) /* -xscrollcommand option changed */ + +/* + * Default option values: + */ +#define DEF_SELECT_BG "#000000" +#define DEF_SELECT_FG "#ffffff" +#define DEF_INSERT_BG "black" +#define DEF_ENTRY_WIDTH "20" +#define DEF_ENTRY_FONT "TkTextFont" +#define DEF_LIST_HEIGHT "10" + +static Tk_OptionSpec EntryOptionSpecs[] = { + {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", + "ExportSelection", "1", -1, Tk_Offset(Entry, entry.exportSelection), + 0,0,0 }, + {TK_OPTION_FONT, "-font", "font", "Font", + DEF_ENTRY_FONT, Tk_Offset(Entry, entry.fontObj),-1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand", + NULL, -1, Tk_Offset(Entry, entry.invalidCmd), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify", + "left", -1, Tk_Offset(Entry, entry.justify), + 0, 0, GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-show", "show", "Show", + NULL, -1, Tk_Offset(Entry, entry.showChar), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_STRING, "-state", "state", "State", + "normal", Tk_Offset(Entry, entry.stateObj), -1, + 0,0,STATE_CHANGED}, + {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", + NULL, Tk_Offset(Entry, entry.textVariableObj), -1, + TK_OPTION_NULL_OK,0,TEXTVAR_CHANGED}, + {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate", + "none", -1, Tk_Offset(Entry, entry.validate), + 0, (ClientData) validateStrings, 0}, + {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand", + NULL, -1, Tk_Offset(Entry, entry.validateCmd), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_INT, "-width", "width", "Width", + DEF_ENTRY_WIDTH, Tk_Offset(Entry, entry.widthObj), -1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", + NULL, -1, Tk_Offset(Entry, entry.xscroll.scrollCmd), + TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, + + /* EntryStyleData options: + */ + {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor", + NULL, Tk_Offset(Entry, entry.styleData.foregroundObj), -1, + TK_OPTION_NULL_OK,0,0}, + {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor", + NULL, Tk_Offset(Entry, entry.styleData.backgroundObj), -1, + TK_OPTION_NULL_OK,0,0}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ EntryStyleData management. + * This is still more awkward than it should be; + * it should be able to use the Element API instead. + */ + +/* EntryInitStyleDefaults -- + * Initialize EntryStyleData record to fallback values. + */ +static void EntryInitStyleDefaults(EntryStyleData *es) +{ +#define INIT(member, value) \ + es->member = Tcl_NewStringObj(value, -1); \ + Tcl_IncrRefCount(es->member); + INIT(foregroundObj, DEFAULT_FOREGROUND) + INIT(selBorderObj, DEF_SELECT_BG) + INIT(selForegroundObj, DEF_SELECT_FG) + INIT(insertColorObj, DEFAULT_FOREGROUND) + INIT(selBorderWidthObj, "0") + INIT(insertWidthObj, "1") +#undef INIT +} + +static void EntryFreeStyleDefaults(EntryStyleData *es) +{ + Tcl_DecrRefCount(es->foregroundObj); + Tcl_DecrRefCount(es->selBorderObj); + Tcl_DecrRefCount(es->selForegroundObj); + Tcl_DecrRefCount(es->insertColorObj); + Tcl_DecrRefCount(es->selBorderWidthObj); + Tcl_DecrRefCount(es->insertWidthObj); +} + +/* + * EntryInitStyleData -- + * Look up style-specific data for an entry widget. + */ +static void EntryInitStyleData(Entry *entryPtr, EntryStyleData *es) +{ + Ttk_State state = entryPtr->core.state; + Ttk_ResourceCache cache = Ttk_GetResourceCache(entryPtr->core.interp); + Tk_Window tkwin = entryPtr->core.tkwin; + Tcl_Obj *tmp; + + /* Initialize to fallback values: + */ + *es = entryPtr->entry.styleDefaults; + +# define INIT(member, name) \ + if ((tmp=Ttk_QueryOption(entryPtr->core.layout,name,state))) \ + es->member=tmp; + INIT(foregroundObj, "-foreground"); + INIT(selBorderObj, "-selectbackground") + INIT(selBorderWidthObj, "-selectborderwidth") + INIT(selForegroundObj, "-selectforeground") + INIT(insertColorObj, "-insertcolor") + INIT(insertWidthObj, "-insertwidth") +#undef INIT + + /* Reacquire color & border resources from resource cache. + */ + es->foregroundObj = Ttk_UseColor(cache, tkwin, es->foregroundObj); + es->selForegroundObj = Ttk_UseColor(cache, tkwin, es->selForegroundObj); + es->insertColorObj = Ttk_UseColor(cache, tkwin, es->insertColorObj); + es->selBorderObj = Ttk_UseBorder(cache, tkwin, es->selBorderObj); +} + +/*------------------------------------------------------------------------ + * +++ Resource management. + */ + +/* EntryDisplayString -- + * Return a malloc'ed string consisting of 'numChars' copies + * of (the first character in the string) 'showChar'. + * Used to compute the displayString if -show is non-NULL. + */ +static char *EntryDisplayString(const char *showChar, int numChars) +{ + char *displayString, *p; + int size; + Tcl_UniChar ch; + char buf[TCL_UTF_MAX]; + + Tcl_UtfToUniChar(showChar, &ch); + size = Tcl_UniCharToUtf(ch, buf); + p = displayString = ckalloc(numChars * size + 1); + + while (numChars--) { + p += Tcl_UniCharToUtf(ch, p); + } + *p = '\0'; + + return displayString; +} + +/* EntryUpdateTextLayout -- + * Recompute textLayout, layoutWidth, and layoutHeight + * from displayString and fontObj. + */ +static void EntryUpdateTextLayout(Entry *entryPtr) +{ + Tk_FreeTextLayout(entryPtr->entry.textLayout); + entryPtr->entry.textLayout = Tk_ComputeTextLayout( + Tk_GetFontFromObj(entryPtr->core.tkwin, entryPtr->entry.fontObj), + entryPtr->entry.displayString, entryPtr->entry.numChars, + 0/*wraplength*/, entryPtr->entry.justify, TK_IGNORE_NEWLINES, + &entryPtr->entry.layoutWidth, &entryPtr->entry.layoutHeight); +} + +/* EntryEditable -- + * Returns 1 if the entry widget accepts user changes, 0 otherwise + */ +static int +EntryEditable(Entry *entryPtr) +{ + return !(entryPtr->core.state & (TTK_STATE_DISABLED|TTK_STATE_READONLY)); +} + +/*------------------------------------------------------------------------ + * +++ Selection management. + */ + +/* EntryFetchSelection -- + * Selection handler for entry widgets. + */ +static int +EntryFetchSelection( + ClientData clientData, int offset, char *buffer, int maxBytes) +{ + Entry *entryPtr = (Entry *) clientData; + size_t byteCount; + const char *string; + const char *selStart, *selEnd; + + if (entryPtr->entry.selectFirst < 0 || !entryPtr->entry.exportSelection) { + return -1; + } + string = entryPtr->entry.displayString; + + selStart = Tcl_UtfAtIndex(string, entryPtr->entry.selectFirst); + selEnd = Tcl_UtfAtIndex(selStart, + entryPtr->entry.selectLast - entryPtr->entry.selectFirst); + byteCount = selEnd - selStart - offset; + if (byteCount > (size_t)maxBytes) { + /* @@@POSSIBLE BUG: Can transfer partial UTF-8 sequences. Is this OK? */ + byteCount = maxBytes; + } + if (byteCount <= 0) { + return 0; + } + memcpy(buffer, selStart + offset, byteCount); + buffer[byteCount] = '\0'; + return byteCount; +} + +/* EntryLostSelection -- + * Tk_LostSelProc for Entry widgets; called when an entry + * loses ownership of the selection. + */ +static void EntryLostSelection(ClientData clientData) +{ + Entry *entryPtr = (Entry *) clientData; + entryPtr->core.flags &= ~GOT_SELECTION; + entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; + TtkRedisplayWidget(&entryPtr->core); +} + +/* EntryOwnSelection -- + * Assert ownership of the PRIMARY selection, + * if -exportselection set and selection is present. + */ +static void EntryOwnSelection(Entry *entryPtr) +{ + if (entryPtr->entry.exportSelection + && !(entryPtr->core.flags & GOT_SELECTION)) { + Tk_OwnSelection(entryPtr->core.tkwin, XA_PRIMARY, EntryLostSelection, + (ClientData) entryPtr); + entryPtr->core.flags |= GOT_SELECTION; + } +} + +/*------------------------------------------------------------------------ + * +++ Validation. + */ + +/* ExpandPercents -- + * Expand an entry validation script template (-validatecommand + * or -invalidcommand). + */ +static void +ExpandPercents( + Entry *entryPtr, /* Entry that needs validation. */ + const char *template, /* Script template */ + const char *new, /* Potential new value of entry string */ + int index, /* index of insert/delete */ + int count, /* #changed characters */ + VREASON reason, /* Reason for change */ + Tcl_DString *dsPtr) /* Result of %-substitutions */ +{ + int spaceNeeded, cvtFlags; + int number, length; + const char *string; + int stringLength; + Tcl_UniChar ch; + char numStorage[2*TCL_INTEGER_SPACE]; + + while (*template) { + /* Find everything up to the next % character and append it + * to the result string. + */ + string = Tcl_UtfFindFirst(template, '%'); + if (string == NULL) { + /* No more %-sequences to expand. + * Copy the rest of the template. + */ + Tcl_DStringAppend(dsPtr, template, -1); + return; + } + if (string != template) { + Tcl_DStringAppend(dsPtr, template, string - template); + template = string; + } + + /* There's a percent sequence here. Process it. + */ + ++template; /* skip over % */ + if (*template != '\0') { + template += Tcl_UtfToUniChar(template, &ch); + } else { + ch = '%'; + } + + stringLength = -1; + switch (ch) { + case 'd': /* Type of call that caused validation */ + if (reason == VALIDATE_INSERT) { + number = 1; + } else if (reason == VALIDATE_DELETE) { + number = 0; + } else { + number = -1; + } + sprintf(numStorage, "%d", number); + string = numStorage; + break; + case 'i': /* index of insert/delete */ + sprintf(numStorage, "%d", index); + string = numStorage; + break; + case 'P': /* 'Peeked' new value of the string */ + string = new; + break; + case 's': /* Current string value */ + string = entryPtr->entry.string; + break; + case 'S': /* string to be inserted/deleted, if any */ + if (reason == VALIDATE_INSERT) { + string = Tcl_UtfAtIndex(new, index); + stringLength = Tcl_UtfAtIndex(string, count) - string; + } else if (reason == VALIDATE_DELETE) { + string = Tcl_UtfAtIndex(entryPtr->entry.string, index); + stringLength = Tcl_UtfAtIndex(string, count) - string; + } else { + string = ""; + stringLength = 0; + } + break; + case 'v': /* type of validation currently set */ + string = validateStrings[entryPtr->entry.validate]; + break; + case 'V': /* type of validation in effect */ + string = validateReasonStrings[reason]; + break; + case 'W': /* widget name */ + string = Tk_PathName(entryPtr->core.tkwin); + break; + default: + length = Tcl_UniCharToUtf(ch, numStorage); + numStorage[length] = '\0'; + string = numStorage; + break; + } + + spaceNeeded = Tcl_ScanCountedElement(string, stringLength, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertCountedElement(string, stringLength, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + } +} + +/* RunValidationScript -- + * Build and evaluate an entry validation script. + * If the script raises an error, disable validation + * by setting '-validate none' + */ +static int RunValidationScript( + Tcl_Interp *interp, /* Interpreter to use */ + Entry *entryPtr, /* Entry being validated */ + const char *template, /* Script template */ + const char *optionName, /* "-validatecommand", "-invalidcommand" */ + const char *new, /* Potential new value of entry string */ + int index, /* index of insert/delete */ + int count, /* #changed characters */ + VREASON reason) /* Reason for change */ +{ + Tcl_DString script; + int code; + + Tcl_DStringInit(&script); + ExpandPercents(entryPtr, template, new, index, count, reason, &script); + code = Tcl_EvalEx(interp, + Tcl_DStringValue(&script), Tcl_DStringLength(&script), + TCL_EVAL_GLOBAL); + Tcl_DStringFree(&script); + if (WidgetDestroyed(&entryPtr->core)) + return TCL_ERROR; + + if (code != TCL_OK && code != TCL_RETURN) { + Tcl_AddErrorInfo(interp, "\n\t(in "); + Tcl_AddErrorInfo(interp, optionName); + Tcl_AddErrorInfo(interp, " validation command executed by "); + Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->core.tkwin)); + Tcl_AddErrorInfo(interp, ")"); + entryPtr->entry.validate = VMODE_NONE; + return TCL_ERROR; + } + return TCL_OK; +} + +/* EntryNeedsValidation -- + * Determine whether the specified VREASON should trigger validation + * in the current VMODE. + */ +static int EntryNeedsValidation(VMODE vmode, VREASON reason) +{ + return (reason == VALIDATE_FORCED) + || (vmode == VMODE_ALL) + || (reason == VALIDATE_FOCUSIN + && (vmode == VMODE_FOCUSIN || vmode == VMODE_FOCUS)) + || (reason == VALIDATE_FOCUSOUT + && (vmode == VMODE_FOCUSOUT || vmode == VMODE_FOCUS)) + || (reason == VALIDATE_INSERT && vmode == VMODE_KEY) + || (reason == VALIDATE_DELETE && vmode == VMODE_KEY) + ; +} + +/* EntryValidateChange -- + * Validate a proposed change to the entry widget's value if required. + * Call the -invalidcommand if validation fails. + * + * Returns: + * TCL_OK if the change is accepted + * TCL_BREAK if the change is rejected + * TCL_ERROR if any errors occured + * + * The change will be rejected if -validatecommand returns 0, + * or if -validatecommand or -invalidcommand modifies the value. + */ +static int +EntryValidateChange( + Entry *entryPtr, /* Entry that needs validation. */ + const char *newValue, /* Potential new value of entry string */ + int index, /* index of insert/delete, -1 otherwise */ + int count, /* #changed characters */ + VREASON reason) /* Reason for change */ +{ + Tcl_Interp *interp = entryPtr->core.interp; + VMODE vmode = entryPtr->entry.validate; + int code, change_ok; + + if ( (entryPtr->entry.validateCmd == NULL) + || (entryPtr->core.flags & VALIDATING) + || !EntryNeedsValidation(vmode, reason) ) + { + return TCL_OK; + } + + entryPtr->core.flags |= VALIDATING; + + /* Run -validatecommand and check return value: + */ + code = RunValidationScript(interp, entryPtr, + entryPtr->entry.validateCmd, "-validatecommand", + newValue, index, count, reason); + if (code != TCL_OK) { + goto done; + } + + code = Tcl_GetBooleanFromObj(interp,Tcl_GetObjResult(interp), &change_ok); + if (code != TCL_OK) { + entryPtr->entry.validate = VMODE_NONE; /* Disable validation */ + Tcl_AddErrorInfo(interp, + "\n(validation command did not return valid boolean)"); + goto done; + } + + /* Run the -invalidcommand if validation failed: + */ + if (!change_ok && entryPtr->entry.invalidCmd != NULL) { + code = RunValidationScript(interp, entryPtr, + entryPtr->entry.invalidCmd, "-invalidcommand", + newValue, index, count, reason); + if (code != TCL_OK) { + goto done; + } + } + + /* Reject the pending change if validation failed + * or if a validation script changed the value. + */ + if (!change_ok || (entryPtr->core.flags & VALIDATION_SET_VALUE)) { + code = TCL_BREAK; + } + +done: + entryPtr->core.flags &= ~(VALIDATING|VALIDATION_SET_VALUE); + return code; +} + +/* EntryRevalidate -- + * Revalidate the current value of an entry widget, + * update the TTK_STATE_INVALID bit. + * + * Returns: + * TCL_OK if valid, TCL_BREAK if invalid, TCL_ERROR on error. + */ +static int EntryRevalidate(Tcl_Interp *interp, Entry *entryPtr, VREASON reason) +{ + int code = EntryValidateChange( + entryPtr, entryPtr->entry.string, -1,0, reason); + + if (code == TCL_BREAK) { + TtkWidgetChangeState(&entryPtr->core, TTK_STATE_INVALID, 0); + } else if (code == TCL_OK) { + TtkWidgetChangeState(&entryPtr->core, 0, TTK_STATE_INVALID); + } + + return code; +} + +/* EntryRevalidateBG -- + * Revalidate in the background (called from event handler). + */ +static void EntryRevalidateBG(Entry *entryPtr, VREASON reason) +{ + Tcl_Interp *interp = entryPtr->core.interp; + if (EntryRevalidate(interp, entryPtr, reason) == TCL_ERROR) { + Tcl_BackgroundError(interp); + } +} + +/*------------------------------------------------------------------------ + * +++ Entry widget modification. + */ + +/* AdjustIndex -- + * Adjust index to account for insertion (nChars > 0) + * or deletion (nChars < 0) at specified index. + */ +static int AdjustIndex(int i0, int index, int nChars) +{ + if (i0 >= index) { + i0 += nChars; + if (i0 < index) { /* index was inside deleted range */ + i0 = index; + } + } + return i0; +} + +/* AdjustIndices -- + * Adjust all internal entry indexes to account for change. + * Note that insertPos, and selectFirst have "right gravity", + * while leftIndex (=xscroll.first) and selectLast have "left gravity". + */ +static void AdjustIndices(Entry *entryPtr, int index, int nChars) +{ + EntryPart *e = &entryPtr->entry; + int g = nChars > 0; /* left gravity adjustment */ + + e->insertPos = AdjustIndex(e->insertPos, index, nChars); + e->selectFirst = AdjustIndex(e->selectFirst, index, nChars); + e->selectLast = AdjustIndex(e->selectLast, index+g, nChars); + e->xscroll.first= AdjustIndex(e->xscroll.first, index+g, nChars); + + if (e->selectLast <= e->selectFirst) + e->selectFirst = e->selectLast = -1; +} + +/* EntryStoreValue -- + * Replace the contents of a text entry with a given value, + * recompute dependent resources, and schedule a redisplay. + * + * See also: EntrySetValue(). + */ +static void +EntryStoreValue(Entry *entryPtr, const char *value) +{ + size_t numBytes = strlen(value); + int numChars = Tcl_NumUtfChars(value, numBytes); + + if (entryPtr->core.flags & VALIDATING) + entryPtr->core.flags |= VALIDATION_SET_VALUE; + + /* Make sure all indices remain in bounds: + */ + if (numChars < entryPtr->entry.numChars) + AdjustIndices(entryPtr, numChars, numChars - entryPtr->entry.numChars); + + /* Free old value: + */ + if (entryPtr->entry.displayString != entryPtr->entry.string) + ckfree(entryPtr->entry.displayString); + ckfree(entryPtr->entry.string); + + /* Store new value: + */ + entryPtr->entry.string = ckalloc(numBytes + 1); + strcpy(entryPtr->entry.string, value); + entryPtr->entry.numBytes = numBytes; + entryPtr->entry.numChars = numChars; + + entryPtr->entry.displayString + = entryPtr->entry.showChar + ? EntryDisplayString(entryPtr->entry.showChar, numChars) + : entryPtr->entry.string + ; + + /* Update layout, schedule redisplay: + */ + EntryUpdateTextLayout(entryPtr); + TtkRedisplayWidget(&entryPtr->core); +} + +/* EntrySetValue -- + * Stores a new value in the entry widget and updates the + * linked -textvariable, if any. The write trace on the + * text variable is temporarily disabled; however, other + * write traces may change the value of the variable. + * If so, the widget is updated again with the new value. + * + * Returns: + * TCL_OK if successful, TCL_ERROR otherwise. + */ +static int EntrySetValue(Entry *entryPtr, const char *value) +{ + EntryStoreValue(entryPtr, value); + + if (entryPtr->entry.textVariableObj) { + const char *textVarName = + Tcl_GetString(entryPtr->entry.textVariableObj); + if (textVarName && *textVarName) { + entryPtr->core.flags |= SYNCING_VARIABLE; + value = Tcl_SetVar(entryPtr->core.interp, textVarName, + value, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); + entryPtr->core.flags &= ~SYNCING_VARIABLE; + if (!value || WidgetDestroyed(&entryPtr->core)) { + return TCL_ERROR; + } else if (strcmp(value, entryPtr->entry.string) != 0) { + /* Some write trace has changed the variable value. + */ + EntryStoreValue(entryPtr, value); + } + } + } + + return TCL_OK; +} + +/* EntryTextVariableTrace -- + * Variable trace procedure for entry -textvariable + */ +static void EntryTextVariableTrace(void *recordPtr, const char *value) +{ + Entry *entryPtr = recordPtr; + + if (WidgetDestroyed(&entryPtr->core)) { + return; + } + + if (entryPtr->core.flags & SYNCING_VARIABLE) { + /* Trace was fired due to Tcl_SetVar call in EntrySetValue. + * Don't do anything. + */ + return; + } + + EntryStoreValue(entryPtr, value ? value : ""); +} + +/*------------------------------------------------------------------------ + * +++ Insertion and deletion. + */ + +/* InsertChars -- + * Add new characters to an entry widget. + */ +static int +InsertChars( + Entry *entryPtr, /* Entry that is to get the new elements. */ + int index, /* Insert before this index */ + const char *value) /* New characters to add */ +{ + char *string = entryPtr->entry.string; + size_t byteIndex = Tcl_UtfAtIndex(string, index) - string; + size_t byteCount = strlen(value); + int charsAdded = Tcl_NumUtfChars(value, byteCount); + size_t newByteCount = entryPtr->entry.numBytes + byteCount + 1; + char *new; + int code; + + if (byteCount == 0) { + return TCL_OK; + } + + new = ckalloc(newByteCount); + memcpy(new, string, byteIndex); + strcpy(new + byteIndex, value); + strcpy(new + byteIndex + byteCount, string + byteIndex); + + code = EntryValidateChange( + entryPtr, new, index, charsAdded, VALIDATE_INSERT); + + if (code == TCL_OK) { + AdjustIndices(entryPtr, index, charsAdded); + code = EntrySetValue(entryPtr, new); + } else if (code == TCL_BREAK) { + code = TCL_OK; + } + + ckfree(new); + return code; +} + +/* DeleteChars -- + * Remove one or more characters from an entry widget. + */ +static int +DeleteChars( + Entry *entryPtr, /* Entry widget to modify. */ + int index, /* Index of first character to delete. */ + int count) /* How many characters to delete. */ +{ + char *string = entryPtr->entry.string; + size_t byteIndex, byteCount, newByteCount; + char *new; + int code; + + if (index < 0) { + index = 0; + } + if (count > entryPtr->entry.numChars - index) { + count = entryPtr->entry.numChars - index; + } + if (count <= 0) { + return TCL_OK; + } + + byteIndex = Tcl_UtfAtIndex(string, index) - string; + byteCount = Tcl_UtfAtIndex(string+byteIndex, count) - (string+byteIndex); + + newByteCount = entryPtr->entry.numBytes + 1 - byteCount; + new = ckalloc(newByteCount); + memcpy(new, string, byteIndex); + strcpy(new + byteIndex, string + byteIndex + byteCount); + + code = EntryValidateChange( + entryPtr, new, index, count, VALIDATE_DELETE); + + if (code == TCL_OK) { + AdjustIndices(entryPtr, index, -count); + code = EntrySetValue(entryPtr, new); + } else if (code == TCL_BREAK) { + code = TCL_OK; + } + ckfree(new); + + return code; +} + +/*------------------------------------------------------------------------ + * +++ Event handler. + */ + +/* EntryEventProc -- + * Extra event handling for entry widgets: + * Triggers validation on FocusIn and FocusOut events. + */ +#define EntryEventMask (FocusChangeMask) +static void +EntryEventProc(ClientData clientData, XEvent *eventPtr) +{ + Entry *entryPtr = (Entry *) clientData; + + Tcl_Preserve(clientData); + switch (eventPtr->type) { + case DestroyNotify: + Tk_DeleteEventHandler(entryPtr->core.tkwin, + EntryEventMask, EntryEventProc, clientData); + break; + case FocusIn: + EntryRevalidateBG(entryPtr, VALIDATE_FOCUSIN); + break; + case FocusOut: + EntryRevalidateBG(entryPtr, VALIDATE_FOCUSOUT); + break; + } + Tcl_Release(clientData); +} + +/*------------------------------------------------------------------------ + * +++ Initialization and cleanup. + */ + +static void +EntryInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Entry *entryPtr = recordPtr; + + Tk_CreateEventHandler( + entryPtr->core.tkwin, EntryEventMask, EntryEventProc, entryPtr); + Tk_CreateSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING, + EntryFetchSelection, (ClientData) entryPtr, XA_STRING); + TtkBlinkCursor(&entryPtr->core); + + entryPtr->entry.string = ckalloc(1); + *entryPtr->entry.string = '\0'; + entryPtr->entry.displayString = entryPtr->entry.string; + entryPtr->entry.textVariableTrace = 0; + entryPtr->entry.numBytes = entryPtr->entry.numChars = 0; + + EntryInitStyleDefaults(&entryPtr->entry.styleDefaults); + + entryPtr->entry.xscrollHandle = + TtkCreateScrollHandle(&entryPtr->core, &entryPtr->entry.xscroll); + + entryPtr->entry.insertPos = 0; + entryPtr->entry.selectFirst = -1; + entryPtr->entry.selectLast = -1; +} + +static void +EntryCleanup(void *recordPtr) +{ + Entry *entryPtr = recordPtr; + + if (entryPtr->entry.textVariableTrace) + Ttk_UntraceVariable(entryPtr->entry.textVariableTrace); + + TtkFreeScrollHandle(entryPtr->entry.xscrollHandle); + + EntryFreeStyleDefaults(&entryPtr->entry.styleDefaults); + + Tk_DeleteSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING); + + Tk_FreeTextLayout(entryPtr->entry.textLayout); + if (entryPtr->entry.displayString != entryPtr->entry.string) + ckfree(entryPtr->entry.displayString); + ckfree(entryPtr->entry.string); +} + +/* EntryConfigure -- + * Configure hook for Entry widgets. + */ +static int EntryConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Entry *entryPtr = recordPtr; + Tcl_Obj *textVarName = entryPtr->entry.textVariableObj; + Ttk_TraceHandle *vt = 0; + + if (mask & TEXTVAR_CHANGED) { + if (textVarName && *Tcl_GetString(textVarName)) { + vt = Ttk_TraceVariable(interp, + textVarName,EntryTextVariableTrace,entryPtr); + if (!vt) return TCL_ERROR; + } + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { + if (vt) Ttk_UntraceVariable(vt); + return TCL_ERROR; + } + + /* Update derived resources: + */ + if (mask & TEXTVAR_CHANGED) { + if (entryPtr->entry.textVariableTrace) + Ttk_UntraceVariable(entryPtr->entry.textVariableTrace); + entryPtr->entry.textVariableTrace = vt; + } + + /* Claim the selection, in case we've suddenly started exporting it. + */ + if (entryPtr->entry.exportSelection && entryPtr->entry.selectFirst != -1) { + EntryOwnSelection(entryPtr); + } + + /* Handle -state compatibility option: + */ + if (mask & STATE_CHANGED) { + TtkCheckStateOption(&entryPtr->core, entryPtr->entry.stateObj); + } + + /* Force scrollbar update if needed: + */ + if (mask & SCROLLCMD_CHANGED) { + TtkScrollbarUpdateRequired(entryPtr->entry.xscrollHandle); + } + + /* Recompute the displayString, in case showChar changed: + */ + if (entryPtr->entry.displayString != entryPtr->entry.string) + ckfree(entryPtr->entry.displayString); + + entryPtr->entry.displayString + = entryPtr->entry.showChar + ? EntryDisplayString(entryPtr->entry.showChar, entryPtr->entry.numChars) + : entryPtr->entry.string + ; + + /* Update textLayout: + */ + EntryUpdateTextLayout(entryPtr); + return TCL_OK; +} + +/* EntryPostConfigure -- + * Post-configuration hook for entry widgets. + */ +static int EntryPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Entry *entryPtr = recordPtr; + int status = TCL_OK; + + if ((mask & TEXTVAR_CHANGED) && entryPtr->entry.textVariableTrace != NULL) { + status = Ttk_FireTrace(entryPtr->entry.textVariableTrace); + } + + return status; +} + +/*------------------------------------------------------------------------ + * +++ Layout and display. + */ + +/* EntryCharPosition -- + * Return the X coordinate of the specified character index. + * Precondition: textLayout and layoutX up-to-date. + */ +static int +EntryCharPosition(Entry *entryPtr, int index) +{ + int xPos; + Tk_CharBbox(entryPtr->entry.textLayout, index, &xPos, NULL, NULL, NULL); + return xPos + entryPtr->entry.layoutX; +} + +/* EntryDoLayout -- + * Layout hook for entry widgets. + * + * Determine position of textLayout based on xscroll.first, justify, + * and display area. + * + * Recalculates layoutX, layoutY, and rightIndex, + * and updates xscroll accordingly. + * May adjust xscroll.first to ensure the maximum #characters are onscreen. + */ +static void +EntryDoLayout(void *recordPtr) +{ + Entry *entryPtr = recordPtr; + WidgetCore *corePtr = &entryPtr->core; + Tk_TextLayout textLayout = entryPtr->entry.textLayout; + int leftIndex = entryPtr->entry.xscroll.first; + int rightIndex; + Ttk_Box textarea; + + Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); + textarea = Ttk_ClientRegion(corePtr->layout, "textarea"); + + /* Center the text vertically within the available parcel: + */ + entryPtr->entry.layoutY = textarea.y + + (textarea.height - entryPtr->entry.layoutHeight)/2; + + /* Recompute where the leftmost character on the display will + * be drawn (layoutX) and adjust leftIndex if necessary. + */ + if (entryPtr->entry.layoutWidth <= textarea.width) { + /* Everything fits. Set leftIndex to zero (no need to scroll), + * and compute layoutX based on -justify. + */ + int extraSpace = textarea.width - entryPtr->entry.layoutWidth; + leftIndex = 0; + rightIndex = entryPtr->entry.numChars; + entryPtr->entry.layoutX = textarea.x; + if (entryPtr->entry.justify == TK_JUSTIFY_RIGHT) { + entryPtr->entry.layoutX += extraSpace; + } else if (entryPtr->entry.justify == TK_JUSTIFY_CENTER) { + entryPtr->entry.layoutX += extraSpace / 2; + } + } else { + /* The whole string doesn't fit in the window. + * Limit leftIndex to leave at most one character's worth + * of empty space on the right. + */ + int overflow = entryPtr->entry.layoutWidth - textarea.width; + int maxLeftIndex = 1 + Tk_PointToChar(textLayout, overflow, 0); + int leftX; + + if (leftIndex > maxLeftIndex) { + leftIndex = maxLeftIndex; + } + + /* Compute layoutX and rightIndex. + * rightIndex is set to one past the last fully-visible character. + */ + Tk_CharBbox(textLayout, leftIndex, &leftX, NULL, NULL, NULL); + rightIndex = Tk_PointToChar(textLayout, leftX + textarea.width, 0); + entryPtr->entry.layoutX = textarea.x - leftX; + } + + TtkScrolled(entryPtr->entry.xscrollHandle, + leftIndex, rightIndex, entryPtr->entry.numChars); +} + +/* EntryGetGC -- Helper routine. + * Get a GC using the specified foreground color and the entry's font. + * Result must be freed with Tk_FreeGC(). + */ +static GC EntryGetGC(Entry *entryPtr, Tcl_Obj *colorObj, TkRegion clip) +{ + Tk_Window tkwin = entryPtr->core.tkwin; + Tk_Font font = Tk_GetFontFromObj(tkwin, entryPtr->entry.fontObj); + XColor *colorPtr; + unsigned long mask = 0ul; + XGCValues gcValues; + GC gc; + + gcValues.line_width = 1; mask |= GCLineWidth; + gcValues.font = Tk_FontId(font); mask |= GCFont; + if (colorObj != 0 && (colorPtr=Tk_GetColorFromObj(tkwin,colorObj)) != 0) { + gcValues.foreground = colorPtr->pixel; + mask |= GCForeground; + } + gc = Tk_GetGC(entryPtr->core.tkwin, mask, &gcValues); + if (clip != None) { + TkSetRegion(Tk_Display(entryPtr->core.tkwin), gc, clip); + } + return gc; +} + +/* EntryDisplay -- + * Redraws the contents of an entry window. + */ +static void EntryDisplay(void *clientData, Drawable d) +{ + Entry *entryPtr = clientData; + Tk_Window tkwin = entryPtr->core.tkwin; + int leftIndex = entryPtr->entry.xscroll.first, + rightIndex = entryPtr->entry.xscroll.last + 1, + selFirst = entryPtr->entry.selectFirst, + selLast = entryPtr->entry.selectLast; + EntryStyleData es; + GC gc; + int showSelection, showCursor; + Ttk_Box textarea; + TkRegion clipRegion; + XRectangle rect; + + EntryInitStyleData(entryPtr, &es); + + textarea = Ttk_ClientRegion(entryPtr->core.layout, "textarea"); + showCursor = + (entryPtr->core.flags & CURSOR_ON) != 0 + && EntryEditable(entryPtr) + && entryPtr->entry.insertPos >= leftIndex + && entryPtr->entry.insertPos <= rightIndex + ; + showSelection = + (entryPtr->core.state & TTK_STATE_DISABLED) == 0 + && selFirst > -1 + && selLast > leftIndex + && selFirst <= rightIndex + ; + + /* Adjust selection range to keep in display bounds. + */ + if (showSelection) { + if (selFirst < leftIndex) + selFirst = leftIndex; + if (selLast > rightIndex) + selLast = rightIndex; + } + + /* Draw widget background & border + */ + Ttk_DrawLayout(entryPtr->core.layout, entryPtr->core.state, d); + + /* Draw selection background + */ + if (showSelection && es.selBorderObj) { + Tk_3DBorder selBorder = Tk_Get3DBorderFromObj(tkwin, es.selBorderObj); + int selStartX = EntryCharPosition(entryPtr, selFirst); + int selEndX = EntryCharPosition(entryPtr, selLast); + int borderWidth = 1; + + Tcl_GetIntFromObj(NULL, es.selBorderWidthObj, &borderWidth); + + if (selBorder) { + Tk_Fill3DRectangle(tkwin, d, selBorder, + selStartX - borderWidth, entryPtr->entry.layoutY - borderWidth, + selEndX - selStartX + 2*borderWidth, + entryPtr->entry.layoutHeight + 2*borderWidth, + borderWidth, TK_RELIEF_RAISED); + } + } + + /* Initialize the clip region. Note that Xft does _not_ derive its + * clipping area from the GC, so we have to supply that by other means. + */ + + rect.x = textarea.x; + rect.y = textarea.y; + rect.width = textarea.width; + rect.height = textarea.height; + clipRegion = TkCreateRegion(); + TkUnionRectWithRegion(&rect, clipRegion, clipRegion); +#ifdef HAVE_XFT + TkUnixSetXftClipRegion(clipRegion); +#endif + + /* Draw cursor: + */ + if (showCursor) { + int cursorX = EntryCharPosition(entryPtr, entryPtr->entry.insertPos), + cursorY = entryPtr->entry.layoutY, + cursorHeight = entryPtr->entry.layoutHeight, + cursorWidth = 1; + + Tcl_GetIntFromObj(NULL,es.insertWidthObj,&cursorWidth); + if (cursorWidth <= 0) { + cursorWidth = 1; + } + + /* @@@ should: maybe: SetCaretPos even when blinked off */ + Tk_SetCaretPos(tkwin, cursorX, cursorY, cursorHeight); + + gc = EntryGetGC(entryPtr, es.insertColorObj, clipRegion); + XFillRectangle(Tk_Display(tkwin), d, gc, + cursorX-cursorWidth/2, cursorY, cursorWidth, cursorHeight); + XSetClipMask(Tk_Display(tkwin), gc, None); + Tk_FreeGC(Tk_Display(tkwin), gc); + } + + /* Draw the text: + */ + gc = EntryGetGC(entryPtr, es.foregroundObj, clipRegion); + Tk_DrawTextLayout( + Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout, + entryPtr->entry.layoutX, entryPtr->entry.layoutY, + leftIndex, rightIndex); + XSetClipMask(Tk_Display(tkwin), gc, None); + Tk_FreeGC(Tk_Display(tkwin), gc); + + /* Overwrite the selected portion (if any) in the -selectforeground color: + */ + if (showSelection) { + gc = EntryGetGC(entryPtr, es.selForegroundObj, clipRegion); + Tk_DrawTextLayout( + Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout, + entryPtr->entry.layoutX, entryPtr->entry.layoutY, + selFirst, selLast); + XSetClipMask(Tk_Display(tkwin), gc, None); + Tk_FreeGC(Tk_Display(tkwin), gc); + } + + /* Drop the region. Note that we have to manually remove the reference to + * it from the Xft guts (if they're being used). + */ +#ifdef HAVE_XFT + TkUnixSetXftClipRegion(None); +#endif + TkDestroyRegion(clipRegion); +} + +/*------------------------------------------------------------------------ + * +++ Widget commands. + */ + +/* EntryIndex -- + * Parse an index into an entry and return either its value + * or an error. + * + * Results: + * A standard Tcl result. If all went well, then *indexPtr is + * filled in with the character index (into entryPtr) corresponding to + * string. The index value is guaranteed to lie between 0 and + * the number of characters in the string, inclusive. If an + * error occurs then an error message is left in the interp's result. + */ +static int +EntryIndex( + Tcl_Interp *interp, /* For error messages. */ + Entry *entryPtr, /* Entry widget to query */ + Tcl_Obj *indexObj, /* Symbolic index name */ + int *indexPtr) /* Return value */ +{ +# define EntryWidth(e) (Tk_Width(entryPtr->core.tkwin)) /* Not Right */ + int length; + const char *string = Tcl_GetStringFromObj(indexObj, &length); + + if (strncmp(string, "end", length) == 0) { + *indexPtr = entryPtr->entry.numChars; + } else if (strncmp(string, "insert", length) == 0) { + *indexPtr = entryPtr->entry.insertPos; + } else if (strncmp(string, "left", length) == 0) { /* for debugging */ + *indexPtr = entryPtr->entry.xscroll.first; + } else if (strncmp(string, "right", length) == 0) { /* for debugging */ + *indexPtr = entryPtr->entry.xscroll.last; + } else if (strncmp(string, "sel.", 4) == 0) { + if (entryPtr->entry.selectFirst < 0) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "selection isn't in widget ", + Tk_PathName(entryPtr->core.tkwin), NULL); + return TCL_ERROR; + } + if (strncmp(string, "sel.first", length) == 0) { + *indexPtr = entryPtr->entry.selectFirst; + } else if (strncmp(string, "sel.last", length) == 0) { + *indexPtr = entryPtr->entry.selectLast; + } else { + goto badIndex; + } + } else if (string[0] == '@') { + int roundUp = 0; + int maxWidth = EntryWidth(entryPtr); + int x; + + if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) { + goto badIndex; + } + if (x > maxWidth) { + x = maxWidth; + roundUp = 1; + } + *indexPtr = Tk_PointToChar(entryPtr->entry.textLayout, + x - entryPtr->entry.layoutX, 0); + + if (*indexPtr < entryPtr->entry.xscroll.first) { + *indexPtr = entryPtr->entry.xscroll.first; + } + + /* + * Special trick: if the x-position was off-screen to the right, + * round the index up to refer to the character just after the + * last visible one on the screen. This is needed to enable the + * last character to be selected, for example. + */ + + if (roundUp && (*indexPtr < entryPtr->entry.numChars)) { + *indexPtr += 1; + } + } else { + if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) { + goto badIndex; + } + if (*indexPtr < 0) { + *indexPtr = 0; + } else if (*indexPtr > entryPtr->entry.numChars) { + *indexPtr = entryPtr->entry.numChars; + } + } + return TCL_OK; + +badIndex: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad entry index \"", string, "\"", NULL); + return TCL_ERROR; +} + +/* $entry bbox $index -- + * Return the bounding box of the character at the specified index. + */ +static int +EntryBBoxCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + Ttk_Box b; + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + return TCL_ERROR; + } + if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + if ((index == entryPtr->entry.numChars) && (index > 0)) { + index--; + } + Tk_CharBbox(entryPtr->entry.textLayout, index, + &b.x, &b.y, &b.width, &b.height); + b.x += entryPtr->entry.layoutX; + b.y += entryPtr->entry.layoutY; + Tcl_SetObjResult(interp, Ttk_NewBoxObj(b)); + return TCL_OK; +} + +/* $entry delete $from ?$to? -- + * Delete the characters in the range [$from,$to). + * $to defaults to $from+1 if not specified. + */ +static int +EntryDeleteCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + int first, last; + + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?"); + return TCL_ERROR; + } + if (EntryIndex(interp, entryPtr, objv[2], &first) != TCL_OK) { + return TCL_ERROR; + } + if (objc == 3) { + last = first + 1; + } else if (EntryIndex(interp, entryPtr, objv[3], &last) != TCL_OK) { + return TCL_ERROR; + } + + if (last >= first && EntryEditable(entryPtr)) { + return DeleteChars(entryPtr, first, last - first); + } + return TCL_OK; +} + +/* $entry get -- + * Return the current value of the entry widget. + */ +static int +EntryGetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + Tcl_SetResult(interp, entryPtr->entry.string, TCL_VOLATILE); + return TCL_OK; +} + +/* $entry icursor $index -- + * Set the insert cursor position. + */ +static int +EntryICursorCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "pos"); + return TCL_ERROR; + } + if (EntryIndex(interp, entryPtr, objv[2], + &entryPtr->entry.insertPos) != TCL_OK) { + return TCL_ERROR; + } + TtkRedisplayWidget(&entryPtr->core); + return TCL_OK; +} + +/* $entry index $index -- + * Return numeric value (0..numChars) of the specified index. + */ +static int +EntryIndexCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "string"); + return TCL_ERROR; + } + if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + return TCL_OK; +} + +/* $entry insert $index $text -- + * Insert $text after position $index. + * Silent no-op if the entry is disabled or read-only. + */ +static int +EntryInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + int index; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "index text"); + return TCL_ERROR; + } + if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + if (EntryEditable(entryPtr)) { + return InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + } + return TCL_OK; +} + +/* $entry selection clear -- + * Clear selection. + */ +static int EntrySelectionClearCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + return TCL_ERROR; + } + entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; + TtkRedisplayWidget(&entryPtr->core); + return TCL_OK; +} + +/* $entry selection present -- + * Returns 1 if any characters are selected, 0 otherwise. + */ +static int EntrySelectionPresentCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(entryPtr->entry.selectFirst >= 0)); + return TCL_OK; +} + +/* $entry selection range $start $end -- + * Explicitly set the selection range. + */ +static int EntrySelectionRangeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + int start, end; + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "start end"); + return TCL_ERROR; + } + if ( EntryIndex(interp, entryPtr, objv[3], &start) != TCL_OK + || EntryIndex(interp, entryPtr, objv[4], &end) != TCL_OK) { + return TCL_ERROR; + } + if (entryPtr->core.state & TTK_STATE_DISABLED) { + return TCL_OK; + } + + if (start >= end) { + entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1; + } else { + entryPtr->entry.selectFirst = start; + entryPtr->entry.selectLast = end; + EntryOwnSelection(entryPtr); + } + TtkRedisplayWidget(&entryPtr->core); + return TCL_OK; +} + +static const Ttk_Ensemble EntrySelectionCommands[] = { + { "clear", EntrySelectionClearCommand,0 }, + { "present", EntrySelectionPresentCommand,0 }, + { "range", EntrySelectionRangeCommand,0 }, + { 0,0,0 } +}; + +/* $entry set $value + * Sets the value of an entry widget. + */ +static int EntrySetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "value"); + return TCL_ERROR; + } + EntrySetValue(entryPtr, Tcl_GetString(objv[2])); + return TCL_OK; +} + +/* $entry validate -- + * Trigger forced validation. Returns 1/0 if validation succeeds/fails + * or error status from -validatecommand / -invalidcommand. + */ +static int EntryValidateCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + int code; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, NULL); + return TCL_ERROR; + } + + code = EntryRevalidate(interp, entryPtr, VALIDATE_FORCED); + + if (code == TCL_ERROR) + return code; + + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(code == TCL_OK)); + return TCL_OK; +} + +/* $entry xview -- horizontal scrolling interface + */ +static int EntryXViewCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Entry *entryPtr = recordPtr; + return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle); +} + +static const Ttk_Ensemble EntryCommands[] = { + { "bbox", EntryBBoxCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "delete", EntryDeleteCommand,0 }, + { "get", EntryGetCommand,0 }, + { "icursor", EntryICursorCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "index", EntryIndexCommand,0 }, + { "insert", EntryInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "selection", 0,EntrySelectionCommands }, + { "state", TtkWidgetStateCommand,0 }, + { "validate", EntryValidateCommand,0 }, + { "xview", EntryXViewCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Entry widget definition. + */ + +static WidgetSpec EntryWidgetSpec = { + "TEntry", /* className */ + sizeof(Entry), /* recordSize */ + EntryOptionSpecs, /* optionSpecs */ + EntryCommands, /* subcommands */ + EntryInitialize, /* initializeProc */ + EntryCleanup, /* cleanupProc */ + EntryConfigure, /* configureProc */ + EntryPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + EntryDoLayout, /* layoutProc */ + EntryDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Combobox widget record. + */ + +typedef struct { + Tcl_Obj *postCommandObj; + Tcl_Obj *valuesObj; + Tcl_Obj *heightObj; + int currentIndex; +} ComboboxPart; + +typedef struct { + WidgetCore core; + EntryPart entry; + ComboboxPart combobox; +} Combobox; + +static Tk_OptionSpec ComboboxOptionSpecs[] = { + {TK_OPTION_STRING, "-height", "height", "Height", + DEF_LIST_HEIGHT, Tk_Offset(Combobox, combobox.heightObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-postcommand", "postCommand", "PostCommand", + "", Tk_Offset(Combobox, combobox.postCommandObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-values", "values", "Values", + "", Tk_Offset(Combobox, combobox.valuesObj), -1, + 0,0,0 }, + WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) +}; + +/* ComboboxInitialize -- + * Initialization hook for combobox widgets. + */ +static void +ComboboxInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Combobox *cb = recordPtr; + + cb->combobox.currentIndex = -1; + TtkTrackElementState(&cb->core); + EntryInitialize(interp, recordPtr); +} + +/* ComboboxConfigure -- + * Configuration hook for combobox widgets. + */ +static int +ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Combobox *cbPtr = recordPtr; + int unused; + + /* Make sure -values is a valid list: + */ + if (Tcl_ListObjLength(interp,cbPtr->combobox.valuesObj,&unused) != TCL_OK) + return TCL_ERROR; + + return EntryConfigure(interp, recordPtr, mask); +} + +/* $cb current ?newIndex? -- get or set current index. + * Setting the current index updates the combobox value, + * but the value and -values may be changed independently + * of the index. Instead of trying to keep currentIndex + * in sync at all times, [$cb current] double-checks + */ +static int ComboboxCurrentCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Combobox *cbPtr = recordPtr; + int currentIndex = cbPtr->combobox.currentIndex; + const char *currentValue = cbPtr->entry.string; + int nValues; + Tcl_Obj **values; + + Tcl_ListObjGetElements(interp,cbPtr->combobox.valuesObj,&nValues,&values); + + if (objc == 2) { + /* Check if currentIndex still valid: + */ + if ( currentIndex < 0 + || currentIndex >= nValues + || strcmp(currentValue,Tcl_GetString(values[currentIndex])) + ) + { + /* Not valid. Check current value against each element in -values: + */ + for (currentIndex = 0; currentIndex < nValues; ++currentIndex) { + if (!strcmp(currentValue,Tcl_GetString(values[currentIndex]))) { + break; + } + } + if (currentIndex >= nValues) { + /* Not found */ + currentIndex = -1; + } + } + cbPtr->combobox.currentIndex = currentIndex; + Tcl_SetObjResult(interp, Tcl_NewIntObj(currentIndex)); + return TCL_OK; + } else if (objc == 3) { + if (Tcl_GetIntFromObj(interp, objv[2], ¤tIndex) != TCL_OK) { + return TCL_ERROR; + } + if (currentIndex < 0 || currentIndex >= nValues) { + Tcl_AppendResult(interp, + "Index ", Tcl_GetString(objv[2]), " out of range", + NULL); + return TCL_ERROR; + } + + cbPtr->combobox.currentIndex = currentIndex; + + return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex])); + } else { + Tcl_WrongNumArgs(interp, 2, objv, "?newIndex?"); + return TCL_ERROR; + } + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Combobox widget definition. + */ +static const Ttk_Ensemble ComboboxCommands[] = { + { "bbox", EntryBBoxCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "current", ComboboxCurrentCommand,0 }, + { "delete", EntryDeleteCommand,0 }, + { "get", EntryGetCommand,0 }, + { "icursor", EntryICursorCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "index", EntryIndexCommand,0 }, + { "insert", EntryInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "selection", 0,EntrySelectionCommands }, + { "state", TtkWidgetStateCommand,0 }, + { "set", EntrySetCommand,0 }, + { "validate", EntryValidateCommand,0 }, + { "xview", EntryXViewCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec ComboboxWidgetSpec = { + "TCombobox", /* className */ + sizeof(Combobox), /* recordSize */ + ComboboxOptionSpecs, /* optionSpecs */ + ComboboxCommands, /* subcommands */ + ComboboxInitialize, /* initializeProc */ + EntryCleanup, /* cleanupProc */ + ComboboxConfigure, /* configureProc */ + EntryPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + EntryDoLayout, /* layoutProc */ + EntryDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Spinbox widget. + */ + +typedef struct { + Tcl_Obj *valuesObj; + + Tcl_Obj *fromObj; + Tcl_Obj *toObj; + Tcl_Obj *incrementObj; + Tcl_Obj *formatObj; + + Tcl_Obj *wrapObj; + Tcl_Obj *commandObj; +} SpinboxPart; + +typedef struct { + WidgetCore core; + EntryPart entry; + SpinboxPart spinbox; +} Spinbox; + +static Tk_OptionSpec SpinboxOptionSpecs[] = { + {TK_OPTION_STRING, "-values", "values", "Values", + "", Tk_Offset(Spinbox, spinbox.valuesObj), -1, + 0,0,0 }, + + {TK_OPTION_DOUBLE, "-from", "from", "From", + "0", Tk_Offset(Spinbox,spinbox.fromObj), -1, + 0,0,0 }, + {TK_OPTION_DOUBLE, "-to", "to", "To", + "0", Tk_Offset(Spinbox,spinbox.toObj), -1, + 0,0,0 }, + {TK_OPTION_DOUBLE, "-increment", "increment", "Increment", + "1", Tk_Offset(Spinbox,spinbox.incrementObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-format", "format", "Format", + "", Tk_Offset(Spinbox, spinbox.formatObj), -1, + 0,0,0 }, + + {TK_OPTION_STRING, "-command", "command", "Command", + "", Tk_Offset(Spinbox, spinbox.commandObj), -1, + 0,0,0 }, + {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap", + "0", Tk_Offset(Spinbox,spinbox.wrapObj), -1, + 0,0,0 }, + + WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) +}; + +/* SpinboxInitialize -- + * Initialization hook for spinbox widgets. + */ +static void +SpinboxInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Spinbox *sb = recordPtr; + TtkTrackElementState(&sb->core); + EntryInitialize(interp, recordPtr); +} + +/* SpinboxConfigure -- + * Configuration hook for spinbox widgets. + */ +static int +SpinboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Spinbox *sb = recordPtr; + int unused; + + /* Make sure -values is a valid list: + */ + if (Tcl_ListObjLength(interp,sb->spinbox.valuesObj,&unused) != TCL_OK) + return TCL_ERROR; + + return EntryConfigure(interp, recordPtr, mask); +} + +static const Ttk_Ensemble SpinboxCommands[] = { + { "bbox", EntryBBoxCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "delete", EntryDeleteCommand,0 }, + { "get", EntryGetCommand,0 }, + { "icursor", EntryICursorCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "index", EntryIndexCommand,0 }, + { "insert", EntryInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "selection", 0,EntrySelectionCommands }, + { "state", TtkWidgetStateCommand,0 }, + { "set", EntrySetCommand,0 }, + { "validate", EntryValidateCommand,0 }, + { "xview", EntryXViewCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec SpinboxWidgetSpec = { + "TSpinbox", /* className */ + sizeof(Spinbox), /* recordSize */ + SpinboxOptionSpecs, /* optionSpecs */ + SpinboxCommands, /* subcommands */ + SpinboxInitialize, /* initializeProc */ + EntryCleanup, /* cleanupProc */ + SpinboxConfigure, /* configureProc */ + EntryPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + EntryDoLayout, /* layoutProc */ + EntryDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Textarea element. + * + * Text display area for Entry widgets. + * Just computes requested size; display is handled by the widget itself. + */ + +typedef struct { + Tcl_Obj *fontObj; + Tcl_Obj *widthObj; +} TextareaElement; + +static Ttk_ElementOptionSpec TextareaElementOptions[] = { + { "-font", TK_OPTION_FONT, + Tk_Offset(TextareaElement,fontObj), DEF_ENTRY_FONT }, + { "-width", TK_OPTION_INT, + Tk_Offset(TextareaElement,widthObj), "20" }, + { NULL, 0, 0, NULL } +}; + +static void TextareaElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TextareaElement *textarea = elementRecord; + Tk_Font font = Tk_GetFontFromObj(tkwin, textarea->fontObj); + int avgWidth = Tk_TextWidth(font, "0", 1); + Tk_FontMetrics fm; + int prefWidth = 1; + + Tk_GetFontMetrics(font, &fm); + Tcl_GetIntFromObj(NULL, textarea->widthObj, &prefWidth); + if (prefWidth <= 0) + prefWidth = 1; + + *heightPtr = fm.linespace; + *widthPtr = prefWidth * avgWidth; +} + +static Ttk_ElementSpec TextareaElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TextareaElement), + TextareaElementOptions, + TextareaElementSize, + TtkNullElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Widget layouts. + */ + +TTK_BEGIN_LAYOUT(EntryLayout) + TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Entry.padding", TTK_FILL_BOTH, + TTK_NODE("Entry.textarea", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(ComboboxLayout) + TTK_GROUP("Combobox.field", TTK_FILL_BOTH, + TTK_NODE("Combobox.downarrow", TTK_PACK_RIGHT|TTK_FILL_Y) + TTK_GROUP("Combobox.padding", TTK_FILL_BOTH|TTK_PACK_LEFT|TTK_EXPAND, + TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(SpinboxLayout) + TTK_GROUP("Spinbox.field", TTK_PACK_TOP|TTK_FILL_X, + TTK_GROUP("null", TTK_PACK_RIGHT, + TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E) + TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E)) + TTK_GROUP("Spinbox.padding", TTK_FILL_BOTH, + TTK_NODE("Spinbox.textarea", TTK_FILL_BOTH))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ +MODULE_SCOPE +void TtkEntry_Init(Tcl_Interp *interp) +{ + Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterElement(interp, themePtr, "textarea", &TextareaElementSpec, 0); + + Ttk_RegisterLayout(themePtr, "TEntry", EntryLayout); + Ttk_RegisterLayout(themePtr, "TCombobox", ComboboxLayout); + Ttk_RegisterLayout(themePtr, "TSpinbox", SpinboxLayout); + + RegisterWidget(interp, "ttk::entry", &EntryWidgetSpec); + RegisterWidget(interp, "ttk::combobox", &ComboboxWidgetSpec); + RegisterWidget(interp, "ttk::spinbox", &SpinboxWidgetSpec); +} + +/*EOF*/ diff --git a/generic/ttk/ttkFrame.c b/generic/ttk/ttkFrame.c new file mode 100644 index 0000000..7860024 --- /dev/null +++ b/generic/ttk/ttkFrame.c @@ -0,0 +1,654 @@ +/* + * Copyright (c) 2004, Joe English + * + * ttk::frame and ttk::labelframe widgets. + */ + +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" +#include "ttkManager.h" + +/* ====================================================================== + * +++ Frame widget: + */ + +typedef struct { + Tcl_Obj *borderWidthObj; + Tcl_Obj *paddingObj; + Tcl_Obj *reliefObj; + Tcl_Obj *widthObj; + Tcl_Obj *heightObj; +} FramePart; + +typedef struct { + WidgetCore core; + FramePart frame; +} Frame; + +static Tk_OptionSpec FrameOptionSpecs[] = { + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", NULL, + Tk_Offset(Frame,frame.borderWidthObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-padding", "padding", "Pad", NULL, + Tk_Offset(Frame,frame.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", NULL, + Tk_Offset(Frame,frame.reliefObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_PIXELS, "-width", "width", "Width", "0", + Tk_Offset(Frame,frame.widthObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_PIXELS, "-height", "height", "Height", "0", + Tk_Offset(Frame,frame.heightObj), -1, + 0,0,GEOMETRY_CHANGED }, + + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +static const Ttk_Ensemble FrameCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { 0,0,0 } +}; + +/* + * FrameMargins -- + * Compute internal margins for a frame widget. + * This includes the -borderWidth, plus any additional -padding. + */ +static Ttk_Padding FrameMargins(Frame *framePtr) +{ + Ttk_Padding margins = Ttk_UniformPadding(0); + + /* Check -padding: + */ + if (framePtr->frame.paddingObj) { + Ttk_GetPaddingFromObj(NULL, + framePtr->core.tkwin, framePtr->frame.paddingObj, &margins); + } + + /* Add padding for border: + */ + if (framePtr->frame.borderWidthObj) { + int border = 0; + Tk_GetPixelsFromObj(NULL, + framePtr->core.tkwin, framePtr->frame.borderWidthObj, &border); + margins = Ttk_AddPadding(margins, Ttk_UniformPadding((short)border)); + } + + return margins; +} + +/* FrameSize procedure -- + * The frame doesn't request a size of its own by default, + * but it does have an internal border. See also <<NOTE-SIZE>> + */ +static int FrameSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Frame *framePtr = recordPtr; + Ttk_SetMargins(framePtr->core.tkwin, FrameMargins(framePtr)); + return 0; +} + +/* + * FrameConfigure -- configure hook. + * <<NOTE-SIZE>> Usually the size of a frame is controlled by + * a geometry manager (pack, grid); the -width and -height + * options are only effective if geometry propagation is turned + * off or if the [place] GM is used for child widgets. + * + * To avoid geometry blinking, we issue a geometry request + * in the Configure hook instead of the Size hook, and only + * if -width and/or -height is nonzero and one of them + * or the other size-related options (-borderwidth, -padding) + * has been changed. + */ + +static int FrameConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Frame *framePtr = recordPtr; + int width, height; + + /* + * Make sure -padding resource, if present, is correct: + */ + if (framePtr->frame.paddingObj) { + Ttk_Padding unused; + if (Ttk_GetPaddingFromObj(interp, + framePtr->core.tkwin, + framePtr->frame.paddingObj, + &unused) != TCL_OK) { + return TCL_ERROR; + } + } + + /* See <<NOTE-SIZE>> + */ + if ( TCL_OK != Tk_GetPixelsFromObj( + interp,framePtr->core.tkwin,framePtr->frame.widthObj,&width) + || TCL_OK != Tk_GetPixelsFromObj( + interp,framePtr->core.tkwin,framePtr->frame.heightObj,&height) + ) + { + return TCL_ERROR; + } + + if ((width > 0 || height > 0) && (mask & GEOMETRY_CHANGED)) { + Tk_GeometryRequest(framePtr->core.tkwin, width, height); + } + + return TtkCoreConfigure(interp, recordPtr, mask); +} + +static WidgetSpec FrameWidgetSpec = { + "TFrame", /* className */ + sizeof(Frame), /* recordSize */ + FrameOptionSpecs, /* optionSpecs */ + FrameCommands, /* subcommands */ + TtkNullInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + FrameConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + FrameSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(FrameLayout) + TTK_NODE("Frame.border", TTK_FILL_BOTH) +TTK_END_LAYOUT + +/* ====================================================================== + * +++ Labelframe widget: + */ + +#define DEFAULT_LABELINSET 8 +#define DEFAULT_BORDERWIDTH 2 + +int TtkGetLabelAnchorFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_PositionSpec *anchorPtr) +{ + const char *string = Tcl_GetString(objPtr); + char c = *string++; + Ttk_PositionSpec flags = 0; + + /* First character determines side: + */ + switch (c) { + case 'w' : flags = TTK_PACK_LEFT; break; + case 'e' : flags = TTK_PACK_RIGHT; break; + case 'n' : flags = TTK_PACK_TOP; break; + case 's' : flags = TTK_PACK_BOTTOM; break; + default : goto error; + } + + /* Remaining characters are as per -sticky: + */ + while ((c = *string++) != '\0') { + switch (c) { + case 'w' : flags |= TTK_STICK_W; break; + case 'e' : flags |= TTK_STICK_E; break; + case 'n' : flags |= TTK_STICK_N; break; + case 's' : flags |= TTK_STICK_S; break; + default : goto error; + } + } + + *anchorPtr = flags; + return TCL_OK; + +error: + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Bad label anchor specification ", Tcl_GetString(objPtr), + NULL); + } + return TCL_ERROR; +} + +/* LabelAnchorSide -- + * Returns the side corresponding to a LabelAnchor value. + */ +static Ttk_Side LabelAnchorSide(Ttk_PositionSpec flags) +{ + if (flags & TTK_PACK_LEFT) return TTK_SIDE_LEFT; + else if (flags & TTK_PACK_RIGHT) return TTK_SIDE_RIGHT; + else if (flags & TTK_PACK_TOP) return TTK_SIDE_TOP; + else if (flags & TTK_PACK_BOTTOM) return TTK_SIDE_BOTTOM; + /*NOTREACHED*/ + return TTK_SIDE_TOP; +} + +/* + * Labelframe widget record: + */ +typedef struct { + Tcl_Obj *labelAnchorObj; + Tcl_Obj *textObj; + Tcl_Obj *underlineObj; + Tk_Window labelWidget; + + Ttk_Manager *mgr; + Ttk_Layout labelLayout; /* Sublayout for label */ + Ttk_Box labelParcel; /* Set in layoutProc */ +} LabelframePart; + +typedef struct { + WidgetCore core; + FramePart frame; + LabelframePart label; +} Labelframe; + +#define LABELWIDGET_CHANGED 0x100 + +static Tk_OptionSpec LabelframeOptionSpecs[] = { + {TK_OPTION_STRING, "-labelanchor", "labelAnchor", "LabelAnchor", + "nw", Tk_Offset(Labelframe, label.labelAnchorObj),-1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-text", "text", "Text", "", + Tk_Offset(Labelframe,label.textObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-underline", "underline", "Underline", + "-1", Tk_Offset(Labelframe,label.underlineObj), -1, + 0,0,0 }, + {TK_OPTION_WINDOW, "-labelwidget", "labelWidget", "LabelWidget", NULL, + -1, Tk_Offset(Labelframe,label.labelWidget), + TK_OPTION_NULL_OK,0,LABELWIDGET_CHANGED|GEOMETRY_CHANGED }, + + WIDGET_INHERIT_OPTIONS(FrameOptionSpecs) +}; + +/* + * Labelframe style parameters: + */ +typedef struct { + int borderWidth; /* border width */ + Ttk_Padding padding; /* internal padding */ + Ttk_PositionSpec labelAnchor; /* corner/side to place label */ + Ttk_Padding labelMargins; /* extra space around label */ + int labelOutside; /* true=>place label outside border */ +} LabelframeStyle; + +static void LabelframeStyleOptions(Labelframe *lf, LabelframeStyle *style) +{ + Ttk_Layout layout = lf->core.layout; + Tcl_Obj *objPtr; + + style->borderWidth = DEFAULT_BORDERWIDTH; + style->padding = Ttk_UniformPadding(0); + style->labelAnchor = TTK_PACK_TOP | TTK_STICK_W; + style->labelOutside = 0; + + if ((objPtr = Ttk_QueryOption(layout, "-borderwidth", 0)) != NULL) { + Tk_GetPixelsFromObj(NULL, lf->core.tkwin, objPtr, &style->borderWidth); + } + if ((objPtr = Ttk_QueryOption(layout, "-padding", 0)) != NULL) { + Ttk_GetPaddingFromObj(NULL, lf->core.tkwin, objPtr, &style->padding); + } + if ((objPtr = Ttk_QueryOption(layout,"-labelanchor", 0)) != NULL) { + TtkGetLabelAnchorFromObj(NULL, objPtr, &style->labelAnchor); + } + if ((objPtr = Ttk_QueryOption(layout,"-labelmargins", 0)) != NULL) { + Ttk_GetBorderFromObj(NULL, objPtr, &style->labelMargins); + } else { + if (style->labelAnchor & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) { + style->labelMargins = + Ttk_MakePadding(DEFAULT_LABELINSET,0,DEFAULT_LABELINSET,0); + } else { + style->labelMargins = + Ttk_MakePadding(0,DEFAULT_LABELINSET,0,DEFAULT_LABELINSET); + } + } + if ((objPtr = Ttk_QueryOption(layout,"-labeloutside", 0)) != NULL) { + Tcl_GetBooleanFromObj(NULL, objPtr, &style->labelOutside); + } + + return; +} + +/* LabelframeLabelSize -- + * Extract the requested width and height of the labelframe's label: + * taken from the label widget if specified, otherwise the text label. + */ +static void +LabelframeLabelSize(Labelframe *lframePtr, int *widthPtr, int *heightPtr) +{ + Tk_Window labelWidget = lframePtr->label.labelWidget; + Ttk_Layout labelLayout = lframePtr->label.labelLayout; + + if (labelWidget) { + *widthPtr = Tk_ReqWidth(labelWidget); + *heightPtr = Tk_ReqHeight(labelWidget); + } else if (labelLayout) { + Ttk_LayoutSize(labelLayout, 0, widthPtr, heightPtr); + } else { + *widthPtr = *heightPtr = 0; + } +} + +/* + * LabelframeSize -- + * Like the frame, this doesn't request a size of its own + * but it does have internal padding and a minimum size. + */ +static int LabelframeSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Labelframe *lframePtr = recordPtr; + WidgetCore *corePtr = &lframePtr->core; + Ttk_Padding margins; + LabelframeStyle style; + int labelWidth, labelHeight; + + LabelframeStyleOptions(lframePtr, &style); + + /* Compute base margins (See also: FrameMargins) + */ + margins = Ttk_AddPadding( + style.padding, Ttk_UniformPadding((short)style.borderWidth)); + + /* Adjust margins based on label size and position: + */ + LabelframeLabelSize(lframePtr, &labelWidth, &labelHeight); + labelWidth += Ttk_PaddingWidth(style.labelMargins); + labelHeight += Ttk_PaddingHeight(style.labelMargins); + + switch (LabelAnchorSide(style.labelAnchor)) { + case TTK_SIDE_LEFT: margins.left += labelWidth; break; + case TTK_SIDE_RIGHT: margins.right += labelWidth; break; + case TTK_SIDE_TOP: margins.top += labelHeight; break; + case TTK_SIDE_BOTTOM: margins.bottom += labelHeight; break; + } + + Ttk_SetMargins(corePtr->tkwin,margins); + + /* Request minimum size based on border width and label size: + */ + Tk_SetMinimumRequestSize(corePtr->tkwin, + labelWidth + 2*style.borderWidth, + labelHeight + 2*style.borderWidth); + + return 0; +} + +/* + * LabelframeGetLayout -- + * Getlayout widget hook. + */ + +static Ttk_Layout LabelframeGetLayout( + Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Labelframe *lf = recordPtr; + Ttk_Layout frameLayout = TtkWidgetGetLayout(interp, theme, recordPtr); + Ttk_Layout labelLayout; + + if (!frameLayout) { + return NULL; + } + + labelLayout = Ttk_CreateSublayout( + interp, theme, frameLayout, ".Label", lf->core.optionTable); + + if (labelLayout) { + if (lf->label.labelLayout) { + Ttk_FreeLayout(lf->label.labelLayout); + } + Ttk_RebindSublayout(labelLayout, recordPtr); + lf->label.labelLayout = labelLayout; + } + + return frameLayout; +} + +/* + * LabelframeDoLayout -- + * Labelframe layout hook. + * + * Side effects: Computes labelParcel. + */ + +static void LabelframeDoLayout(void *recordPtr) +{ + Labelframe *lframePtr = recordPtr; + WidgetCore *corePtr = &lframePtr->core; + int lw, lh; /* Label width and height */ + LabelframeStyle style; + Ttk_Box borderParcel = Ttk_WinBox(lframePtr->core.tkwin); + Ttk_Box labelParcel; + + /* + * Compute label parcel: + */ + LabelframeStyleOptions(lframePtr, &style); + LabelframeLabelSize(lframePtr, &lw, &lh); + lw += Ttk_PaddingWidth(style.labelMargins); + lh += Ttk_PaddingHeight(style.labelMargins); + + labelParcel = Ttk_PadBox( + Ttk_PositionBox(&borderParcel, lw, lh, style.labelAnchor), + style.labelMargins); + + if (!style.labelOutside) { + /* Move border edge so it's over label: + */ + switch (LabelAnchorSide(style.labelAnchor)) { + case TTK_SIDE_LEFT: borderParcel.x -= lw / 2; + case TTK_SIDE_RIGHT: borderParcel.width += lw/2; break; + case TTK_SIDE_TOP: borderParcel.y -= lh / 2; + case TTK_SIDE_BOTTOM: borderParcel.height += lh / 2; break; + } + } + + /* + * Place border and label: + */ + Ttk_PlaceLayout(corePtr->layout, corePtr->state, borderParcel); + if (lframePtr->label.labelLayout) { + Ttk_PlaceLayout( + lframePtr->label.labelLayout, corePtr->state, labelParcel); + } + /* labelWidget placed in LabelframePlaceSlaves GM hook */ + lframePtr->label.labelParcel = labelParcel; +} + +static void LabelframeDisplay(void *recordPtr, Drawable d) +{ + Labelframe *lframePtr = recordPtr; + Ttk_DrawLayout(lframePtr->core.layout, lframePtr->core.state, d); + if (lframePtr->label.labelLayout) { + Ttk_DrawLayout(lframePtr->label.labelLayout, lframePtr->core.state, d); + } +} + +/* +++ Labelframe geometry manager hooks. + */ + +/* LabelframePlaceSlaves -- + * Sets the position and size of the labelwidget. + */ +static void LabelframePlaceSlaves(void *recordPtr) +{ + Labelframe *lframe = recordPtr; + + if (Ttk_NumberSlaves(lframe->label.mgr) == 1) { + Ttk_Box b; + LabelframeDoLayout(recordPtr); + b = lframe->label.labelParcel; + /* ASSERT: slave #0 is lframe->label.labelWidget */ + Ttk_PlaceSlave(lframe->label.mgr, 0, b.x,b.y,b.width,b.height); + } +} + +static int LabelRequest(void *managerData, int index, int width, int height) +{ + return 1; +} + +/* LabelRemoved -- + * Unset the -labelwidget option. + * + * <<NOTE-LABELREMOVED>>: + * This routine is also called when the widget voluntarily forgets + * the slave in LabelframeConfigure. + */ +static void LabelRemoved(void *managerData, int slaveIndex) +{ + Labelframe *lframe = managerData; + lframe->label.labelWidget = 0; +} + +static Ttk_ManagerSpec LabelframeManagerSpec = { + { "labelframe", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + LabelframeSize, + LabelframePlaceSlaves, + LabelRequest, + LabelRemoved +}; + +/* LabelframeInitialize -- + * Initialization hook. + */ +static void LabelframeInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Labelframe *lframe = recordPtr; + + lframe->label.mgr = Ttk_CreateManager( + &LabelframeManagerSpec, lframe, lframe->core.tkwin); + lframe->label.labelWidget = 0; + lframe->label.labelLayout = 0; + lframe->label.labelParcel = Ttk_MakeBox(-1,-1,-1,-1); +} + +/* LabelframeCleanup -- + * Cleanup hook. + */ +static void LabelframeCleanup(void *recordPtr) +{ + Labelframe *lframe = recordPtr; + Ttk_DeleteManager(lframe->label.mgr); + if (lframe->label.labelLayout) { + Ttk_FreeLayout(lframe->label.labelLayout); + } +} + +/* RaiseLabelWidget -- + * Raise the -labelwidget to ensure that the labelframe doesn't + * obscure it (if it's not a direct child), or bring it to + * the top of the stacking order (if it is). + */ +static void RaiseLabelWidget(Labelframe *lframe) +{ + Tk_Window parent = Tk_Parent(lframe->label.labelWidget); + Tk_Window sibling = NULL; + Tk_Window w = lframe->core.tkwin; + + while (w && w != parent) { + sibling = w; + w = Tk_Parent(w); + } + + Tk_RestackWindow(lframe->label.labelWidget, Above, sibling); +} + +/* LabelframeConfigure -- + * Configuration hook. + */ +static int LabelframeConfigure(Tcl_Interp *interp,void *recordPtr,int mask) +{ + Labelframe *lframePtr = recordPtr; + Tk_Window labelWidget = lframePtr->label.labelWidget; + Ttk_PositionSpec unused; + + /* Validate options: + */ + if (mask & LABELWIDGET_CHANGED && labelWidget != NULL) { + if (!Ttk_Maintainable(interp, labelWidget, lframePtr->core.tkwin)) { + return TCL_ERROR; + } + } + + if (TtkGetLabelAnchorFromObj( + interp, lframePtr->label.labelAnchorObj, &unused) != TCL_OK) + { + return TCL_ERROR; + } + + /* Base class configuration: + */ + if (FrameConfigure(interp, recordPtr, mask) != TCL_OK) { + return TCL_ERROR; + } + + /* Update -labelwidget changes, if any: + */ + if (mask & LABELWIDGET_CHANGED) { + if (Ttk_NumberSlaves(lframePtr->label.mgr) == 1) { + Ttk_ForgetSlave(lframePtr->label.mgr, 0); + /* Restore labelWidget field (see <<NOTE-LABELREMOVED>>) + */ + lframePtr->label.labelWidget = labelWidget; + } + + if (labelWidget) { + Ttk_InsertSlave(lframePtr->label.mgr, 0, labelWidget, NULL); + RaiseLabelWidget(lframePtr); + } + } + + if (mask & GEOMETRY_CHANGED) { + Ttk_ManagerSizeChanged(lframePtr->label.mgr); + Ttk_ManagerLayoutChanged(lframePtr->label.mgr); + } + + return TCL_OK; +} + +static WidgetSpec LabelframeWidgetSpec = { + "TLabelframe", /* className */ + sizeof(Labelframe), /* recordSize */ + LabelframeOptionSpecs, /* optionSpecs */ + FrameCommands, /* subcommands */ + LabelframeInitialize, /* initializeProc */ + LabelframeCleanup, /* cleanupProc */ + LabelframeConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + LabelframeGetLayout, /* getLayoutProc */ + LabelframeSize, /* sizeProc */ + LabelframeDoLayout, /* layoutProc */ + LabelframeDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(LabelframeLayout) + TTK_NODE("Labelframe.border", TTK_FILL_BOTH) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(LabelSublayout) + TTK_GROUP("Label.fill", TTK_FILL_BOTH, + TTK_NODE("Label.text", TTK_FILL_BOTH)) +TTK_END_LAYOUT + +/* ====================================================================== + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkFrame_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "TFrame", FrameLayout); + Ttk_RegisterLayout(theme, "TLabelframe", LabelframeLayout); + Ttk_RegisterLayout(theme, "Label", LabelSublayout); + + RegisterWidget(interp, "ttk::frame", &FrameWidgetSpec); + RegisterWidget(interp, "ttk::labelframe", &LabelframeWidgetSpec); +} + diff --git a/generic/ttk/ttkGenStubs.tcl b/generic/ttk/ttkGenStubs.tcl new file mode 100644 index 0000000..90dea25 --- /dev/null +++ b/generic/ttk/ttkGenStubs.tcl @@ -0,0 +1,921 @@ +# ttkGenStubs.tcl -- +# +# This script generates a set of stub files for a given +# interface. +# +# +# Copyright (c) 1998-1999 by Scriptics Corporation. +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# SOURCE: tcl/tools/genStubs.tcl, revision 1.20 +# +# CHANGES: +# + Remove xxx_TCL_DECLARED #ifdeffery +# + Use application-defined storage class specifier instead of "EXTERN" +# + Add "epoch" and "revision" fields to stubs table record +# + Remove dead code related to USE_*_STUB_PROCS (emitStubs, makeStub) +# + Second argument to "declare" is used as a status guard +# instead of a platform guard. +# + Use void (*reserved$i)(void) = 0 instead of void *reserved$i = NULL +# for unused stub entries, in case pointer-to-function and +# pointer-to-object are different sizes. +# + Allow trailing semicolon in function declarations +# + stubs table is const-qualified +# + +package require Tcl 8 + +namespace eval genStubs { + # libraryName -- + # + # The name of the entire library. This value is used to compute + # the USE_*_STUBS macro and the name of the init file. + + variable libraryName "UNKNOWN" + + # interfaces -- + # + # An array indexed by interface name that is used to maintain + # the set of valid interfaces. The value is empty. + + array set interfaces {} + + # curName -- + # + # The name of the interface currently being defined. + + variable curName "UNKNOWN" + + # scspec -- + # + # Storage class specifier for external function declarations. + # Normally "extern", may be set to something like XYZAPI + # + variable scspec "extern" + + # epoch, revision -- + # + # The epoch and revision numbers of the interface currently being defined. + # (@@@TODO: should be an array mapping interface names -> numbers) + # + + variable epoch 0 + variable revision 0 + + # hooks -- + # + # An array indexed by interface name that contains the set of + # subinterfaces that should be defined for a given interface. + + array set hooks {} + + # stubs -- + # + # This three dimensional array is indexed first by interface name, + # second by field name, and third by a numeric offset or the + # constant "lastNum". The lastNum entry contains the largest + # numeric offset used for a given interface. + # + # Field "decl,$i" contains the C function specification that + # should be used for the given entry in the stub table. The spec + # consists of a list in the form returned by parseDecl. + # Other fields TBD later. + + array set stubs {} + + # outDir -- + # + # The directory where the generated files should be placed. + + variable outDir . +} + +# genStubs::library -- +# +# This function is used in the declarations file to set the name +# of the library that the interfaces are associated with (e.g. "tcl"). +# This value will be used to define the inline conditional macro. +# +# Arguments: +# name The library name. +# +# Results: +# None. + +proc genStubs::library {name} { + variable libraryName $name +} + +# genStubs::interface -- +# +# This function is used in the declarations file to set the name +# of the interface currently being defined. +# +# Arguments: +# name The name of the interface. +# +# Results: +# None. + +proc genStubs::interface {name} { + variable curName $name + variable interfaces + variable stubs + + set interfaces($name) {} + set stubs($name,lastNum) 0 + return +} + +# genStubs::scspec -- +# +# Define the storage class macro used for external function declarations. +# Typically, this will be a macro like XYZAPI or EXTERN that +# expands to either DLLIMPORT or DLLEXPORT, depending on whether +# -DBUILD_XYZ has been set. +# +proc genStubs::scspec {value} { + variable scspec $value +} + +# genStubs::epoch -- +# +# Define the epoch number for this library. The epoch +# should be incrememented when a release is made that +# contains incompatible changes to the public API. +# +proc genStubs::epoch {value} { + variable epoch $value +} + +# genStubs::hooks -- +# +# This function defines the subinterface hooks for the current +# interface. +# +# Arguments: +# names The ordered list of interfaces that are reachable through the +# hook vector. +# +# Results: +# None. + +proc genStubs::hooks {names} { + variable curName + variable hooks + + set hooks($curName) $names + return +} + +# genStubs::declare -- +# +# This function is used in the declarations file to declare a new +# interface entry. +# +# Arguments: +# index The index number of the interface. +# status Status of the interface: one of "current", +# "deprecated", or "obsolete". +# decl The C function declaration, or {} for an undefined +# entry. +# +proc genStubs::declare {args} { + variable stubs + variable curName + variable revision + + incr revision + + if {[llength $args] == 2} { + lassign $args index decl + set status current + } elseif {[llength $args] == 3} { + lassign $args index status decl + } else { + puts stderr "wrong # args: declare $args" + return + } + + # Check for duplicate declarations, then add the declaration and + # bump the lastNum counter if necessary. + + if {[info exists stubs($curName,decl,$index)]} { + puts stderr "Duplicate entry: $index" + } + regsub -all const $decl CONST decl + regsub -all "\[ \t\n\]+" [string trim $decl] " " decl + set decl [parseDecl $decl] + + set stubs($curName,status,$index) $status + set stubs($curName,decl,$index) $decl + + if {$index > $stubs($curName,lastNum)} { + set stubs($curName,lastNum) $index + } + + return +} + +# genStubs::rewriteFile -- +# +# This function replaces the machine generated portion of the +# specified file with new contents. It looks for the !BEGIN! and +# !END! comments to determine where to place the new text. +# +# Arguments: +# file The name of the file to modify. +# text The new text to place in the file. +# +# Results: +# None. + +proc genStubs::rewriteFile {file text} { + if {![file exists $file]} { + puts stderr "Cannot find file: $file" + return + } + set in [open ${file} r] + set out [open ${file}.new w] + fconfigure $out -translation lf + + while {![eof $in]} { + set line [gets $in] + if {[string match "*!BEGIN!*" $line]} { + break + } + puts $out $line + } + puts $out "/* !BEGIN!: Do not edit below this line. */" + puts $out $text + while {![eof $in]} { + set line [gets $in] + if {[string match "*!END!*" $line]} { + break + } + } + puts $out "/* !END!: Do not edit above this line. */" + puts -nonewline $out [read $in] + close $in + close $out + file rename -force ${file}.new ${file} + return +} + +# genStubs::addPlatformGuard -- +# +# Wrap a string inside a platform #ifdef. +# +# Arguments: +# plat Platform to test. +# +# Results: +# Returns the original text inside an appropriate #ifdef. + +proc genStubs::addPlatformGuard {plat text} { + switch $plat { + win { + return "#ifdef __WIN32__\n${text}#endif /* __WIN32__ */\n" + } + unix { + return "#if !defined(__WIN32__) /* UNIX */\n${text}#endif /* UNIX */\n" + } + macosx { + return "#ifdef MAC_OSX_TCL\n${text}#endif /* MAC_OSX_TCL */\n" + } + aqua { + return "#ifdef MAC_OSX_TK\n${text}#endif /* MAC_OSX_TK */\n" + } + x11 { + return "#if !(defined(__WIN32__) || defined(MAC_OSX_TK)) /* X11 */\n${text}#endif /* X11 */\n" + } + } + return $text +} + +# genStubs::emitSlots -- +# +# Generate the stub table slots for the given interface. +# +# Arguments: +# name The name of the interface being emitted. +# textVar The variable to use for output. +# +# Results: +# None. + +proc genStubs::emitSlots {name textVar} { + upvar $textVar text + forAllStubs $name makeSlot noGuard text {" void (*reserved$i)(void);\n"} + return +} + +# genStubs::parseDecl -- +# +# Parse a C function declaration into its component parts. +# +# Arguments: +# decl The function declaration. +# +# Results: +# Returns a list of the form {returnType name args}. The args +# element consists of a list of type/name pairs, or a single +# element "void". If the function declaration is malformed +# then an error is displayed and the return value is {}. + +proc genStubs::parseDecl {decl} { + if {![regexp {^(.*)\((.*)\);?$} $decl all prefix args]} { + set prefix $decl + set args {} + } + set prefix [string trim $prefix] + if {![regexp {^(.+[ ][*]*)([^ *]+)$} $prefix all rtype fname]} { + puts stderr "Bad return type: $decl" + return + } + set rtype [string trim $rtype] + if {$args == ""} { + return [list $rtype $fname {}] + } + foreach arg [split $args ,] { + lappend argList [string trim $arg] + } + if {![string compare [lindex $argList end] "..."]} { + set args TCL_VARARGS + foreach arg [lrange $argList 0 end-1] { + set argInfo [parseArg $arg] + if {[llength $argInfo] == 2 || [llength $argInfo] == 3} { + lappend args $argInfo + } else { + puts stderr "Bad argument: '$arg' in '$decl'" + return + } + } + } else { + set args {} + foreach arg $argList { + set argInfo [parseArg $arg] + if {![string compare $argInfo "void"]} { + lappend args "void" + break + } elseif {[llength $argInfo] == 2 || [llength $argInfo] == 3} { + lappend args $argInfo + } else { + puts stderr "Bad argument: '$arg' in '$decl'" + return + } + } + } + return [list $rtype $fname $args] +} + +# genStubs::parseArg -- +# +# This function parses a function argument into a type and name. +# +# Arguments: +# arg The argument to parse. +# +# Results: +# Returns a list of type and name with an optional third array +# indicator. If the argument is malformed, returns "". + +proc genStubs::parseArg {arg} { + if {![regexp {^(.+[ ][*]*)([^][ *]+)(\[\])?$} $arg all type name array]} { + if {$arg == "void"} { + return $arg + } else { + return + } + } + set result [list [string trim $type] $name] + if {$array != ""} { + lappend result $array + } + return $result +} + +# genStubs::makeDecl -- +# +# Generate the prototype for a function. +# +# Arguments: +# name The interface name. +# decl The function declaration. +# index The slot index for this function. +# +# Results: +# Returns the formatted declaration string. + +proc genStubs::makeDecl {name decl index} { + variable scspec + + lassign $decl rtype fname args + + append text "/* $index */\n" + if {$rtype != "void"} { + regsub -all void $rtype VOID rtype + } + set line "$scspec $rtype" + set count [expr {2 - ([string length $line] / 8)}] + append line [string range "\t\t\t" 0 $count] + set pad [expr {24 - [string length $line]}] + if {$pad <= 0} { + append line " " + set pad 0 + } + if {$args == ""} { + append line $fname + append text $line + append text ";\n" + return $text + } + append line $fname + + regsub -all void $args VOID args + set arg1 [lindex $args 0] + switch -exact $arg1 { + VOID { + append line "(void)" + } + TCL_VARARGS { + set sep "(" + foreach arg [lrange $args 1 end] { + append line $sep + set next {} + append next [lindex $arg 0] + if {[string index $next end] ne "*"} { + append next " " + } + append next [lindex $arg 1] [lindex $arg 2] + if {[string length $line] + [string length $next] \ + + $pad > 76} { + append text [string trimright $line] \n + set line "\t\t\t\t" + set pad 28 + } + append line $next + set sep ", " + } + append line ", ...)" + } + default { + set sep "(" + foreach arg $args { + append line $sep + set next {} + append next [lindex $arg 0] + if {[string index $next end] ne "*"} { + append next " " + } + append next [lindex $arg 1] [lindex $arg 2] + if {[string length $line] + [string length $next] \ + + $pad > 76} { + append text [string trimright $line] \n + set line "\t\t\t\t" + set pad 28 + } + append line $next + set sep ", " + } + append line ")" + } + } + return "$text$line;\n" +} + +# genStubs::makeMacro -- +# +# Generate the inline macro for a function. +# +# Arguments: +# name The interface name. +# decl The function declaration. +# index The slot index for this function. +# +# Results: +# Returns the formatted macro definition. + +proc genStubs::makeMacro {name decl index} { + lassign $decl rtype fname args + + set lfname [string tolower [string index $fname 0]] + append lfname [string range $fname 1 end] + + set text "#define $fname \\\n\t(" + if {$args == ""} { + append text "*" + } + append text "${name}StubsPtr->$lfname)" + append text " /* $index */\n" + return $text +} + +# genStubs::makeSlot -- +# +# Generate the stub table entry for a function. +# +# Arguments: +# name The interface name. +# decl The function declaration. +# index The slot index for this function. +# +# Results: +# Returns the formatted table entry. + +proc genStubs::makeSlot {name decl index} { + lassign $decl rtype fname args + + set lfname [string tolower [string index $fname 0]] + append lfname [string range $fname 1 end] + + set text " " + if {$rtype != "void"} { + regsub -all void $rtype VOID rtype + } + if {$args == ""} { + append text $rtype " *" $lfname "; /* $index */\n" + return $text + } + append text $rtype " (*" $lfname ") " + + regsub -all void $args VOID args + set arg1 [lindex $args 0] + switch -exact $arg1 { + VOID { + append text "(void)" + } + TCL_VARARGS { + set sep "(" + foreach arg [lrange $args 1 end] { + append text $sep [lindex $arg 0] + if {[string index $text end] ne "*"} { + append text " " + } + append text [lindex $arg 1] [lindex $arg 2] + set sep ", " + } + append text ", ...)" + } + default { + set sep "(" + foreach arg $args { + append text $sep [lindex $arg 0] + if {[string index $text end] ne "*"} { + append text " " + } + append text [lindex $arg 1] [lindex $arg 2] + set sep ", " + } + append text ")" + } + } + + append text "; /* $index */\n" + return $text +} + +# genStubs::makeInit -- +# +# Generate the prototype for a function. +# +# Arguments: +# name The interface name. +# decl The function declaration. +# index The slot index for this function. +# +# Results: +# Returns the formatted declaration string. + +proc genStubs::makeInit {name decl index} { + if {[lindex $decl 2] == ""} { + append text " &" [lindex $decl 1] ", /* " $index " */\n" + } else { + append text " " [lindex $decl 1] ", /* " $index " */\n" + } + return $text +} + +# genStubs::forAllStubs -- +# +# This function iterates over all of the slots and invokes +# a callback for each slot. The result of the callback is then +# placed inside appropriate guards. +# +# Arguments: +# name The interface name. +# slotProc The proc to invoke to handle the slot. It will +# have the interface name, the declaration, and +# the index appended. +# guardProc The proc to invoke to add guards. It will have +# the slot status and text appended. +# textVar The variable to use for output. +# skipString The string to emit if a slot is skipped. This +# string will be subst'ed in the loop so "$i" can +# be used to substitute the index value. +# +# Results: +# None. + +proc genStubs::forAllStubs {name slotProc guardProc textVar + {skipString {"/* Slot $i is reserved */\n"}}} { + variable stubs + upvar $textVar text + + set lastNum $stubs($name,lastNum) + + for {set i 0} {$i <= $lastNum} {incr i} { + if {[info exists stubs($name,decl,$i)]} { + append text [$guardProc $stubs($name,status,$i) \ + [$slotProc $name $stubs($name,decl,$i) $i]] + } else { + eval {append text} $skipString + } + } +} + +proc genStubs::noGuard {status text} { return $text } + +proc genStubs::addGuard {status text} { + variable libraryName + set upName [string toupper $libraryName] + + switch -- $status { + current { + # No change + } + deprecated { + set text [ifdeffed "${upName}_DEPRECATED" $text] + } + obsolete { + set text "" + } + default { + puts stderr "Unrecognized status code $status" + } + } + return $text +} + +proc genStubs::ifdeffed {macro text} { + join [list "#ifdef $macro" $text "#endif" ""] \n +} + +# genStubs::emitDeclarations -- +# +# This function emits the function declarations for this interface. +# +# Arguments: +# name The interface name. +# textVar The variable to use for output. +# +# Results: +# None. + +proc genStubs::emitDeclarations {name textVar} { + upvar $textVar text + + append text "\n/*\n * Exported function declarations:\n */\n\n" + forAllStubs $name makeDecl noGuard text + return +} + +# genStubs::emitMacros -- +# +# This function emits the inline macros for an interface. +# +# Arguments: +# name The name of the interface being emitted. +# textVar The variable to use for output. +# +# Results: +# None. + +proc genStubs::emitMacros {name textVar} { + variable libraryName + upvar $textVar text + + set upName [string toupper $libraryName] + append text "\n#if defined(USE_${upName}_STUBS)\n" + append text "\n/*\n * Inline function declarations:\n */\n\n" + + forAllStubs $name makeMacro addGuard text + + append text "\n#endif /* defined(USE_${upName}_STUBS) */\n" + return +} + +# genStubs::emitHeader -- +# +# This function emits the body of the <name>Decls.h file for +# the specified interface. +# +# Arguments: +# name The name of the interface being emitted. +# +# Results: +# None. + +proc genStubs::emitHeader {name} { + variable outDir + variable hooks + variable epoch + variable revision + + set capName [string toupper [string index $name 0]] + append capName [string range $name 1 end] + + set CAPName [string toupper $name] + append text "\n" + append text "#define ${CAPName}_STUBS_EPOCH $epoch\n" + append text "#define ${CAPName}_STUBS_REVISION $revision\n" + + emitDeclarations $name text + + if {[info exists hooks($name)]} { + append text "\ntypedef struct ${capName}StubHooks {\n" + foreach hook $hooks($name) { + set capHook [string toupper [string index $hook 0]] + append capHook [string range $hook 1 end] + append text " const struct ${capHook}Stubs *${hook}Stubs;\n" + } + append text "} ${capName}StubHooks;\n" + } + append text "\ntypedef struct ${capName}Stubs {\n" + append text " int magic;\n" + append text " int epoch;\n" + append text " int revision;\n" + append text " const struct ${capName}StubHooks *hooks;\n\n" + + emitSlots $name text + + append text "} ${capName}Stubs;\n\n" + + append text "#ifdef __cplusplus\nextern \"C\" {\n#endif\n" + append text "extern const ${capName}Stubs *${name}StubsPtr;\n" + append text "#ifdef __cplusplus\n}\n#endif\n" + + emitMacros $name text + + rewriteFile [file join $outDir ${name}Decls.h] $text + return +} + +# genStubs::emitInit -- +# +# Generate the table initializers for an interface. +# +# Arguments: +# name The name of the interface to initialize. +# textVar The variable to use for output. +# +# Results: +# Returns the formatted output. + +proc genStubs::emitInit {name textVar} { + variable hooks + variable interfaces + variable epoch + variable revision + + upvar $textVar text + set root 1 + + set capName [string toupper [string index $name 0]] + append capName [string range $name 1 end] + set CAPName [string toupper $name] + + if {[info exists hooks($name)]} { + append text "\nstatic const ${capName}StubHooks ${name}StubHooks = \{\n" + set sep " " + foreach sub $hooks($name) { + append text $sep "&${sub}Stubs" + set sep ",\n " + } + append text "\n\};\n" + } + foreach intf [array names interfaces] { + if {[info exists hooks($intf)]} { + if {0<=[lsearch -exact $hooks($intf) $name]} { + set root 0 + break; + } + } + } + + if {$root} { + append text "\nconst ${capName}Stubs ${name}Stubs = \{\n" + } else { + append text "\nstatic const ${capName}Stubs ${name}Stubs = \{\n" + } + append text " TCL_STUB_MAGIC,\n" + append text " ${CAPName}_STUBS_EPOCH,\n" + append text " ${CAPName}_STUBS_REVISION,\n" + if {[info exists hooks($name)]} { + append text " &${name}StubHooks,\n" + } else { + append text " 0,\n" + } + + forAllStubs $name makeInit noGuard text {" 0, /* $i */\n"} + + append text "\};\n" + return +} + +# genStubs::emitInits -- +# +# This function emits the body of the <name>StubInit.c file for +# the specified interface. +# +# Arguments: +# name The name of the interface being emitted. +# +# Results: +# None. + +proc genStubs::emitInits {} { + variable hooks + variable outDir + variable libraryName + variable interfaces + + # Assuming that dependencies only go one level deep, we need to emit + # all of the leaves first to avoid needing forward declarations. + + set leaves {} + set roots {} + foreach name [lsort [array names interfaces]] { + if {[info exists hooks($name)]} { + lappend roots $name + } else { + lappend leaves $name + } + } + foreach name $leaves { + emitInit $name text + } + foreach name $roots { + emitInit $name text + } + + rewriteFile [file join $outDir ${libraryName}StubInit.c] $text +} + +# genStubs::init -- +# +# This is the main entry point. +# +# Arguments: +# None. +# +# Results: +# None. + +proc genStubs::init {} { + global argv argv0 + variable outDir + variable interfaces + + if {[llength $argv] < 2} { + puts stderr "usage: $argv0 outDir declFile ?declFile...?" + exit 1 + } + + set outDir [lindex $argv 0] + + foreach file [lrange $argv 1 end] { + source $file + } + + foreach name [lsort [array names interfaces]] { + puts "Emitting $name" + emitHeader $name + } + + emitInits +} + +# lassign -- +# +# This function emulates the TclX lassign command. +# +# Arguments: +# valueList A list containing the values to be assigned. +# args The list of variables to be assigned. +# +# Results: +# Returns any values that were not assigned to variables. + +if {[string length [namespace which lassign]] == 0} { + proc lassign {valueList args} { + if {[llength $args] == 0} { + error "wrong # args: should be \"lassign list varName ?varName ...?\"" + } + uplevel [list foreach $args $valueList {break}] + return [lrange $valueList [llength $args] end] + } +} + +genStubs::init diff --git a/generic/ttk/ttkImage.c b/generic/ttk/ttkImage.c new file mode 100644 index 0000000..2b12864 --- /dev/null +++ b/generic/ttk/ttkImage.c @@ -0,0 +1,415 @@ +/* + * Image specifications and image element factory. + * + * Copyright (C) 2004 Pat Thoyts <patthoyts@users.sf.net> + * Copyright (C) 2004 Joe English + * + * An imageSpec is a multi-element list; the first element + * is the name of the default image to use, the remainder of the + * list is a sequence of statespec/imagename options as per + * [style map]. + */ + +#include <string.h> +#include <tk.h> +#include "ttkTheme.h" + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/*------------------------------------------------------------------------ + * +++ ImageSpec management. + */ + +struct TtkImageSpec { + Tk_Image baseImage; /* Base image to use */ + int mapCount; /* #state-specific overrides */ + Ttk_StateSpec *states; /* array[mapCount] of states ... */ + Tk_Image *images; /* ... per-state images to use */ +}; + +/* NullImageChanged -- + * Do-nothing Tk_ImageChangedProc. + */ +static void NullImageChanged(ClientData clientData, + int x, int y, int width, int height, int imageWidth, int imageHeight) +{ /* No-op */ } + +/* TtkGetImageSpec -- + * Constructs a Ttk_ImageSpec * from a Tcl_Obj *. + * Result must be released using TtkFreeImageSpec. + * + * TODO: Need a variant of this that takes a user-specified ImageChanged proc + */ +Ttk_ImageSpec * +TtkGetImageSpec(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr) +{ + Ttk_ImageSpec *imageSpec = 0; + int i = 0, n = 0, objc; + Tcl_Obj **objv; + + imageSpec = (Ttk_ImageSpec *)ckalloc(sizeof(*imageSpec)); + imageSpec->baseImage = 0; + imageSpec->mapCount = 0; + imageSpec->states = 0; + imageSpec->images = 0; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + goto error; + } + + if ((objc % 2) != 1) { + if (interp) { + Tcl_SetResult(interp, + "image specification must contain an odd number of elements", + TCL_STATIC); + } + goto error; + } + + n = (objc - 1) / 2; + imageSpec->states = (Ttk_StateSpec*)ckalloc(n * sizeof(Ttk_StateSpec)); + imageSpec->images = (Tk_Image*)ckalloc(n * sizeof(Tk_Image *)); + + /* Get base image: + */ + imageSpec->baseImage = Tk_GetImage( + interp, tkwin, Tcl_GetString(objv[0]), NullImageChanged, NULL); + if (!imageSpec->baseImage) { + goto error; + } + + /* Extract state and image specifications: + */ + for (i = 0; i < n; ++i) { + Tcl_Obj *stateSpec = objv[2*i + 1]; + const char *imageName = Tcl_GetString(objv[2*i + 2]); + Ttk_StateSpec state; + + if (Ttk_GetStateSpecFromObj(interp, stateSpec, &state) != TCL_OK) { + goto error; + } + imageSpec->states[i] = state; + + imageSpec->images[i] = Tk_GetImage( + interp, tkwin, imageName, NullImageChanged, NULL); + if (imageSpec->images[i] == NULL) { + goto error; + } + imageSpec->mapCount = i+1; + } + + return imageSpec; + +error: + TtkFreeImageSpec(imageSpec); + return NULL; +} + +/* TtkFreeImageSpec -- + * Dispose of an image specification. + */ +void TtkFreeImageSpec(Ttk_ImageSpec *imageSpec) +{ + int i; + + for (i=0; i < imageSpec->mapCount; ++i) { + Tk_FreeImage(imageSpec->images[i]); + } + + if (imageSpec->baseImage) { Tk_FreeImage(imageSpec->baseImage); } + if (imageSpec->states) { ckfree((ClientData)imageSpec->states); } + if (imageSpec->images) { ckfree((ClientData)imageSpec->images); } + + ckfree((ClientData)imageSpec); +} + +/* TtkSelectImage -- + * Return a state-specific image from an ImageSpec + */ +Tk_Image TtkSelectImage(Ttk_ImageSpec *imageSpec, Ttk_State state) +{ + int i; + for (i = 0; i < imageSpec->mapCount; ++i) { + if (Ttk_StateMatches(state, imageSpec->states+i)) { + return imageSpec->images[i]; + } + } + return imageSpec->baseImage; +} + +/*------------------------------------------------------------------------ + * +++ Drawing utilities. + */ + +/* LPadding, CPadding, RPadding -- + * Split a box+padding pair into left, center, and right boxes. + */ +static Ttk_Box LPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x, b.y, p.left, b.height); } + +static Ttk_Box CPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x+p.left, b.y, b.width-p.left-p.right, b.height); } + +static Ttk_Box RPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x+b.width-p.right, b.y, p.right, b.height); } + +/* TPadding, MPadding, BPadding -- + * Split a box+padding pair into top, middle, and bottom parts. + */ +static Ttk_Box TPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x, b.y, b.width, p.top); } + +static Ttk_Box MPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x, b.y+p.top, b.width, b.height-p.top-p.bottom); } + +static Ttk_Box BPadding(Ttk_Box b, Ttk_Padding p) + { return Ttk_MakeBox(b.x, b.y+b.height-p.bottom, b.width, p.bottom); } + +/* Ttk_Fill -- + * Fill the destination area of the drawable by replicating + * the source area of the image. + */ +static void Ttk_Fill( + Tk_Window tkwin, Drawable d, Tk_Image image, Ttk_Box src, Ttk_Box dst) +{ + int dr = dst.x + dst.width; + int db = dst.y + dst.height; + int x,y; + + if (!(src.width && src.height && dst.width && dst.height)) + return; + + for (x = dst.x; x < dr; x += src.width) { + int cw = MIN(src.width, dr - x); + for (y = dst.y; y <= db; y += src.height) { + int ch = MIN(src.height, db - y); + Tk_RedrawImage(image, src.x, src.y, cw, ch, d, x, y); + } + } +} + +/* Ttk_Stripe -- + * Fill a horizontal stripe of the destination drawable. + */ +static void Ttk_Stripe( + Tk_Window tkwin, Drawable d, Tk_Image image, + Ttk_Box src, Ttk_Box dst, Ttk_Padding p) +{ + Ttk_Fill(tkwin, d, image, LPadding(src,p), LPadding(dst,p)); + Ttk_Fill(tkwin, d, image, CPadding(src,p), CPadding(dst,p)); + Ttk_Fill(tkwin, d, image, RPadding(src,p), RPadding(dst,p)); +} + +/* Ttk_Tile -- + * Fill successive horizontal stripes of the destination drawable. + */ +static void Ttk_Tile( + Tk_Window tkwin, Drawable d, Tk_Image image, + Ttk_Box src, Ttk_Box dst, Ttk_Padding p) +{ + Ttk_Stripe(tkwin, d, image, TPadding(src,p), TPadding(dst,p), p); + Ttk_Stripe(tkwin, d, image, MPadding(src,p), MPadding(dst,p), p); + Ttk_Stripe(tkwin, d, image, BPadding(src,p), BPadding(dst,p), p); +} + +/*------------------------------------------------------------------------ + * +++ Image element definition. + */ + +typedef struct { /* ClientData for image elements */ + Ttk_ImageSpec *imageSpec; /* Image(s) to use */ + int minWidth; /* Minimum width; overrides image width */ + int minHeight; /* Minimum width; overrides image width */ + Ttk_Sticky sticky; /* -stickiness specification */ + Ttk_Padding border; /* Fixed border region */ + Ttk_Padding padding; /* Internal padding */ + +#if TILE_07_COMPAT + Ttk_ResourceCache cache; /* Resource cache for images */ + Ttk_StateMap imageMap; /* State-based lookup table for images */ +#endif +} ImageData; + +static void FreeImageData(void *clientData) +{ + ImageData *imageData = clientData; + if (imageData->imageSpec) { TtkFreeImageSpec(imageData->imageSpec); } +#if TILE_07_COMPAT + if (imageData->imageMap) { Tcl_DecrRefCount(imageData->imageMap); } +#endif + ckfree(clientData); +} + +static void ImageElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ImageData *imageData = clientData; + Tk_Image image = imageData->imageSpec->baseImage; + + if (image) { + Tk_SizeOfImage(image, widthPtr, heightPtr); + } + if (imageData->minWidth >= 0) { + *widthPtr = imageData->minWidth; + } + if (imageData->minHeight >= 0) { + *heightPtr = imageData->minHeight; + } + + *paddingPtr = imageData->padding; +} + +static void ImageElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + ImageData *imageData = clientData; + Tk_Image image = 0; + int imgWidth, imgHeight; + Ttk_Box src, dst; + +#if TILE_07_COMPAT + if (imageData->imageMap) { + Tcl_Obj *imageObj = Ttk_StateMapLookup(NULL,imageData->imageMap,state); + if (imageObj) { + image = Ttk_UseImage(imageData->cache, tkwin, imageObj); + } + } + if (!image) { + image = TtkSelectImage(imageData->imageSpec, state); + } +#else + image = TtkSelectImage(imageData->imageSpec, state); +#endif + + if (!image) { + return; + } + + Tk_SizeOfImage(image, &imgWidth, &imgHeight); + src = Ttk_MakeBox(0, 0, imgWidth, imgHeight); + dst = Ttk_StickBox(b, imgWidth, imgHeight, imageData->sticky); + + Ttk_Tile(tkwin, d, image, src, dst, imageData->border); +} + +static Ttk_ElementSpec ImageElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + ImageElementSize, + ImageElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Image element factory. + */ +static int +Ttk_CreateImageElement( + Tcl_Interp *interp, + void *clientData, + Ttk_Theme theme, + const char *elementName, + int objc, Tcl_Obj *const objv[]) +{ + static const char *optionStrings[] = + { "-border","-height","-padding","-sticky","-width",NULL }; + enum { O_BORDER, O_HEIGHT, O_PADDING, O_STICKY, O_WIDTH }; + + Ttk_ImageSpec *imageSpec = 0; + ImageData *imageData = 0; + int padding_specified = 0; + int i; + + if (objc <= 0) { + Tcl_AppendResult(interp, "Must supply a base image", NULL); + return TCL_ERROR; + } + + imageSpec = TtkGetImageSpec(interp, Tk_MainWindow(interp), objv[0]); + if (!imageSpec) { + return TCL_ERROR; + } + + imageData = (ImageData*)ckalloc(sizeof(*imageData)); + imageData->imageSpec = imageSpec; + imageData->minWidth = imageData->minHeight = -1; + imageData->sticky = TTK_FILL_BOTH; + imageData->border = imageData->padding = Ttk_UniformPadding(0); +#if TILE_07_COMPAT + imageData->cache = Ttk_GetResourceCache(interp); + imageData->imageMap = 0; +#endif + + for (i = 1; i < objc; i += 2) { + int option; + + if (i == objc - 1) { + Tcl_AppendResult(interp, + "Value for ", Tcl_GetString(objv[i]), " missing", + NULL); + goto error; + } + +#if TILE_07_COMPAT + if (!strcmp("-map", Tcl_GetString(objv[i]))) { + imageData->imageMap = objv[i+1]; + Tcl_IncrRefCount(imageData->imageMap); + continue; + } +#endif + + if (Tcl_GetIndexFromObj(interp, objv[i], optionStrings, + "option", 0, &option) != TCL_OK) { goto error; } + + switch (option) { + case O_BORDER: + if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->border) + != TCL_OK) { goto error; } + if (!padding_specified) { + imageData->padding = imageData->border; + } + break; + case O_PADDING: + if (Ttk_GetBorderFromObj(interp, objv[i+1], &imageData->padding) + != TCL_OK) { goto error; } + padding_specified = 1; + break; + case O_WIDTH: + if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minWidth) + != TCL_OK) { goto error; } + break; + case O_HEIGHT: + if (Tcl_GetIntFromObj(interp, objv[i+1], &imageData->minHeight) + != TCL_OK) { goto error; } + break; + case O_STICKY: + if (Ttk_GetStickyFromObj(interp, objv[i+1], &imageData->sticky) + != TCL_OK) { goto error; } + } + } + + if (!Ttk_RegisterElement(interp, theme, elementName, &ImageElementSpec, + imageData)) + { + goto error; + } + + Ttk_RegisterCleanup(interp, imageData, FreeImageData); + Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1)); + return TCL_OK; + +error: + FreeImageData(imageData); + return TCL_ERROR; +} + +MODULE_SCOPE +void TtkImage_Init(Tcl_Interp *interp) +{ + Ttk_RegisterElementFactory(interp, "image", Ttk_CreateImageElement, NULL); +} + +/*EOF*/ diff --git a/generic/ttk/ttkInit.c b/generic/ttk/ttkInit.c new file mode 100644 index 0000000..78676c6 --- /dev/null +++ b/generic/ttk/ttkInit.c @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2003, Joe English + * + * Ttk package: initialization routine and miscellaneous utilities. + */ + +#include <string.h> +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +/* + * Legal values for the button -default option. + * See also: enum Ttk_ButtonDefaultState. + */ +const char *ttkDefaultStrings[] = { + "normal", "active", "disabled", NULL +}; + +int Ttk_GetButtonDefaultStateFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, int *statePtr) +{ + *statePtr = TTK_BUTTON_DEFAULT_DISABLED; + return Tcl_GetIndexFromObj(interp, objPtr, + ttkDefaultStrings, "default state", 0, statePtr); +} + +/* + * Legal values for the -compound option. + * See also: enum Ttk_Compound. + */ +const char *ttkCompoundStrings[] = { + "none", "text", "image", "center", + "top", "bottom", "left", "right", NULL +}; + +int Ttk_GetCompoundFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, int *statePtr) +{ + *statePtr = TTK_COMPOUND_NONE; + return Tcl_GetIndexFromObj(interp, objPtr, + ttkCompoundStrings, "compound layout", 0, statePtr); +} + +/* + * Legal values for the -orient option. + * See also: enum Ttk_Orient. + */ +const char *ttkOrientStrings[] = { + "horizontal", "vertical", NULL +}; + +int Ttk_GetOrientFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, int *resultPtr) +{ + *resultPtr = TTK_ORIENT_HORIZONTAL; + return Tcl_GetIndexFromObj(interp, objPtr, + ttkOrientStrings, "orientation", 0, resultPtr); +} + +/* + * Recognized values for the -state compatibility option. + * Other options are accepted and interpreted as synonyms for "normal". + */ +static const char *ttkStateStrings[] = { + "normal", "readonly", "disabled", "active", NULL +}; +enum { + TTK_COMPAT_STATE_NORMAL, + TTK_COMPAT_STATE_READONLY, + TTK_COMPAT_STATE_DISABLED, + TTK_COMPAT_STATE_ACTIVE +}; + +/* TtkCheckStateOption -- + * Handle -state compatibility option. + * + * NOTE: setting -state disabled / -state enabled affects the + * widget state, but the internal widget state does *not* affect + * the value of the -state option. + * This option is present for compatibility only. + */ +void TtkCheckStateOption(WidgetCore *corePtr, Tcl_Obj *objPtr) +{ + int stateOption = TTK_COMPAT_STATE_NORMAL; + unsigned all = TTK_STATE_DISABLED|TTK_STATE_READONLY|TTK_STATE_ACTIVE; +# define SETFLAGS(f) TtkWidgetChangeState(corePtr, f, all^f) + + (void)Tcl_GetIndexFromObj(NULL,objPtr,ttkStateStrings,"",0,&stateOption); + switch (stateOption) { + case TTK_COMPAT_STATE_NORMAL: + default: + SETFLAGS(0); + break; + case TTK_COMPAT_STATE_READONLY: + SETFLAGS(TTK_STATE_READONLY); + break; + case TTK_COMPAT_STATE_DISABLED: + SETFLAGS(TTK_STATE_DISABLED); + break; + case TTK_COMPAT_STATE_ACTIVE: + SETFLAGS(TTK_STATE_ACTIVE); + break; + } +# undef SETFLAGS +} + +/* TtkSendVirtualEvent -- + * Send a virtual event notification to the specified target window. + * Equivalent to "event generate $tgtWindow <<$eventName>>" + * + * Note that we use Tk_QueueWindowEvent, not Tk_HandleEvent, + * so this routine does not reenter the interpreter. + */ +void TtkSendVirtualEvent(Tk_Window tgtWin, const char *eventName) +{ + union {XEvent general; XVirtualEvent virtual;} event; + + memset(&event, 0, sizeof(event)); + event.general.xany.type = VirtualEvent; + event.general.xany.serial = NextRequest(Tk_Display(tgtWin)); + event.general.xany.send_event = False; + event.general.xany.window = Tk_WindowId(tgtWin); + event.general.xany.display = Tk_Display(tgtWin); + event.virtual.name = Tk_GetUid(eventName); + + Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL); +} + +/* TtkEnumerateOptions, TtkGetOptionValue -- + * Common factors for data accessor commands. + */ +int TtkEnumerateOptions( + Tcl_Interp *interp, void *recordPtr, const Tk_OptionSpec *specPtr, + Tk_OptionTable optionTable, Tk_Window tkwin) +{ + Tcl_Obj *result = Tcl_NewListObj(0,0); + while (specPtr->type != TK_OPTION_END) + { + Tcl_Obj *optionName = Tcl_NewStringObj(specPtr->optionName, -1); + Tcl_Obj *optionValue = + Tk_GetOptionValue(interp,recordPtr,optionTable,optionName,tkwin); + if (optionValue) { + Tcl_ListObjAppendElement(interp, result, optionName); + Tcl_ListObjAppendElement(interp, result, optionValue); + } + ++specPtr; + + if (specPtr->type == TK_OPTION_END && specPtr->clientData != NULL) { + /* Chain to next option spec array: */ + specPtr = specPtr->clientData; + } + } + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +int TtkGetOptionValue( + Tcl_Interp *interp, void *recordPtr, Tcl_Obj *optionName, + Tk_OptionTable optionTable, Tk_Window tkwin) +{ + Tcl_Obj *result = + Tk_GetOptionValue(interp,recordPtr,optionTable,optionName,tkwin); + if (result) { + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + return TCL_ERROR; +} + + +/*------------------------------------------------------------------------ + * Core Option specifications: + * type name dbName dbClass default objOffset intOffset flags clientData mask + */ + +/* public */ +Tk_OptionSpec ttkCoreOptionSpecs[] = +{ + {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", NULL, + Tk_Offset(WidgetCore, cursorObj), -1, TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_STRING, "-style", "style", "Style", "", + Tk_Offset(WidgetCore,styleObj), -1, 0,0,STYLE_CHANGED}, + {TK_OPTION_STRING, "-class", "", "", NULL, + Tk_Offset(WidgetCore,classObj), -1, 0,0,READONLY_OPTION}, + {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0} +}; + +/*------------------------------------------------------------------------ + * +++ Initialization: elements and element factories. + */ + +extern void TtkElements_Init(Tcl_Interp *); +extern void TtkLabel_Init(Tcl_Interp *); +extern void TtkImage_Init(Tcl_Interp *); + +static void RegisterElements(Tcl_Interp *interp) +{ + TtkElements_Init(interp); + TtkLabel_Init(interp); + TtkImage_Init(interp); +} + +/*------------------------------------------------------------------------ + * +++ Initialization: Widget definitions. + */ + +extern void TtkButton_Init(Tcl_Interp *); +extern void TtkEntry_Init(Tcl_Interp *); +extern void TtkFrame_Init(Tcl_Interp *); +extern void TtkNotebook_Init(Tcl_Interp *); +extern void TtkPanedwindow_Init(Tcl_Interp *); +extern void TtkProgressbar_Init(Tcl_Interp *); +extern void TtkScale_Init(Tcl_Interp *); +extern void TtkScrollbar_Init(Tcl_Interp *); +extern void TtkSeparator_Init(Tcl_Interp *); +extern void TtkTreeview_Init(Tcl_Interp *); + +#ifdef TTK_SQUARE_WIDGET +extern int TtkSquareWidget_Init(Tcl_Interp *); +#endif + +static void RegisterWidgets(Tcl_Interp *interp) +{ + TtkButton_Init(interp); + TtkEntry_Init(interp); + TtkFrame_Init(interp); + TtkNotebook_Init(interp); + TtkPanedwindow_Init(interp); + TtkProgressbar_Init(interp); + TtkScale_Init(interp); + TtkScrollbar_Init(interp); + TtkSeparator_Init(interp); + TtkTreeview_Init(interp); +#ifdef TTK_SQUARE_WIDGET + TtkSquareWidget_Init(interp); +#endif +} + +/*------------------------------------------------------------------------ + * +++ Initialization: Built-in themes. + */ + +extern int TtkAltTheme_Init(Tcl_Interp *); +extern int TtkClassicTheme_Init(Tcl_Interp *); +extern int TtkClamTheme_Init(Tcl_Interp *); + +static void RegisterThemes(Tcl_Interp *interp) +{ + + TtkAltTheme_Init(interp); + TtkClassicTheme_Init(interp); + TtkClamTheme_Init(interp); +} + +/* + * Ttk initialization. + */ + +extern const TtkStubs ttkStubs; + +MODULE_SCOPE int +Ttk_Init(Tcl_Interp *interp) +{ + /* + * This will be run for both safe and regular interp init. + * Use Tcl_IsSafe if necessary to not initialize unsafe bits. + */ + Ttk_StylePkgInit(interp); + + RegisterElements(interp); + RegisterWidgets(interp); + RegisterThemes(interp); + + Ttk_PlatformInit(interp); + + Tcl_PkgProvideEx(interp, "Ttk", TTK_PATCH_LEVEL, (ClientData)&ttkStubs); + + return TCL_OK; +} + +/*EOF*/ diff --git a/generic/ttk/ttkLabel.c b/generic/ttk/ttkLabel.c new file mode 100644 index 0000000..597682f --- /dev/null +++ b/generic/ttk/ttkLabel.c @@ -0,0 +1,693 @@ +/* + * text, image, and label elements. + * + * The label element combines text and image elements, + * with layout determined by the "-compound" option. + * + */ + +#include <tcl.h> +#include <tkInt.h> +#include "ttkTheme.h" + +/*---------------------------------------------------------------------- + * +++ Text element. + * + * This element displays a textual label in the foreground color. + * + * Optionally underlines the mnemonic character if the -underline resource + * is present and >= 0. + */ + +typedef struct { + /* + * Element options: + */ + Tcl_Obj *textObj; + Tcl_Obj *fontObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *underlineObj; + Tcl_Obj *widthObj; + Tcl_Obj *anchorObj; + Tcl_Obj *justifyObj; + Tcl_Obj *wrapLengthObj; + Tcl_Obj *embossedObj; + + /* + * Computed resources: + */ + Tk_Font tkfont; + Tk_TextLayout textLayout; + int width; + int height; + int embossed; + +} TextElement; + +/* Text element options table. + * NB: Keep in sync with label element option table. + */ +static Ttk_ElementOptionSpec TextElementOptions[] = { + { "-text", TK_OPTION_STRING, + Tk_Offset(TextElement,textObj), "" }, + { "-font", TK_OPTION_FONT, + Tk_Offset(TextElement,fontObj), DEFAULT_FONT }, + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(TextElement,foregroundObj), "black" }, + { "-underline", TK_OPTION_INT, + Tk_Offset(TextElement,underlineObj), "-1"}, + { "-width", TK_OPTION_INT, + Tk_Offset(TextElement,widthObj), "-1"}, + { "-anchor", TK_OPTION_ANCHOR, + Tk_Offset(TextElement,anchorObj), "w"}, + { "-justify", TK_OPTION_JUSTIFY, + Tk_Offset(TextElement,justifyObj), "left" }, + { "-wraplength", TK_OPTION_PIXELS, + Tk_Offset(TextElement,wrapLengthObj), "0" }, + { "-embossed", TK_OPTION_INT, + Tk_Offset(TextElement,embossedObj), "0"}, + { NULL, 0, 0, NULL } +}; + +static int TextSetup(TextElement *text, Tk_Window tkwin) +{ + const char *string = Tcl_GetString(text->textObj); + Tk_Justify justify = TK_JUSTIFY_LEFT; + int wrapLength = 0; + + text->tkfont = Tk_GetFontFromObj(tkwin, text->fontObj); + Tk_GetJustifyFromObj(NULL, text->justifyObj, &justify); + Tk_GetPixelsFromObj(NULL, tkwin, text->wrapLengthObj, &wrapLength); + Tcl_GetBooleanFromObj(NULL, text->embossedObj, &text->embossed); + + text->textLayout = Tk_ComputeTextLayout( + text->tkfont, string, -1/*numChars*/, wrapLength, justify, + 0/*flags*/, &text->width, &text->height); + + return 1; +} + +/* + * TextReqWidth -- compute the requested width of a text element. + * + * If -width is positive, use that as the width + * If -width is negative, use that as the minimum width + * If not specified or empty, use the natural size of the text + */ + +static int TextReqWidth(TextElement *text) +{ + int reqWidth; + + if ( text->widthObj + && Tcl_GetIntFromObj(NULL, text->widthObj, &reqWidth) == TCL_OK) + { + int avgWidth = Tk_TextWidth(text->tkfont, "0", 1); + if (reqWidth <= 0) { + int specWidth = avgWidth * -reqWidth; + if (specWidth > text->width) + return specWidth; + } else { + return avgWidth * reqWidth; + } + } + return text->width; +} + +static void TextCleanup(TextElement *text) +{ + Tk_FreeTextLayout(text->textLayout); +} + +/* + * TextDraw -- + * Draw a text element. + * Called by TextElementDraw() and LabelElementDraw(). + */ +static void TextDraw(TextElement *text, Tk_Window tkwin, Drawable d, Ttk_Box b) +{ + XColor *color = Tk_GetColorFromObj(tkwin, text->foregroundObj); + int underline = -1; + XGCValues gcValues; + GC gc1, gc2; + Tk_Anchor anchor = TK_ANCHOR_CENTER; + TkRegion clipRegion = NULL; + + gcValues.font = Tk_FontId(text->tkfont); + gcValues.foreground = color->pixel; + gc1 = Tk_GetGC(tkwin, GCFont | GCForeground, &gcValues); + gcValues.foreground = WhitePixelOfScreen(Tk_Screen(tkwin)); + gc2 = Tk_GetGC(tkwin, GCFont | GCForeground, &gcValues); + + /* + * Place text according to -anchor: + */ + Tk_GetAnchorFromObj(NULL, text->anchorObj, &anchor); + b = Ttk_AnchorBox(b, text->width, text->height, anchor); + + /* + * Clip text if it's too wide: + */ + if (b.width < text->width) { + XRectangle rect; + + clipRegion = TkCreateRegion(); + rect.x = b.x; + rect.y = b.y; + rect.width = b.width + (text->embossed ? 1 : 0); + rect.height = b.height + (text->embossed ? 1 : 0); + TkUnionRectWithRegion(&rect, clipRegion, clipRegion); + TkSetRegion(Tk_Display(tkwin), gc1, clipRegion); + TkSetRegion(Tk_Display(tkwin), gc2, clipRegion); +#ifdef HAVE_XFT + TkUnixSetXftClipRegion(clipRegion); +#endif + } + + if (text->embossed) { + Tk_DrawTextLayout(Tk_Display(tkwin), d, gc2, + text->textLayout, b.x+1, b.y+1, 0/*firstChar*/, -1/*lastChar*/); + } + Tk_DrawTextLayout(Tk_Display(tkwin), d, gc1, + text->textLayout, b.x, b.y, 0/*firstChar*/, -1/*lastChar*/); + + Tcl_GetIntFromObj(NULL, text->underlineObj, &underline); + if (underline >= 0) { + if (text->embossed) { + Tk_UnderlineTextLayout(Tk_Display(tkwin), d, gc2, + text->textLayout, b.x+1, b.y+1, underline); + } + Tk_UnderlineTextLayout(Tk_Display(tkwin), d, gc1, + text->textLayout, b.x, b.y, underline); + } + + if (clipRegion != NULL) { +#ifdef HAVE_XFT + TkUnixSetXftClipRegion(None); +#endif + XSetClipMask(Tk_Display(tkwin), gc1, None); + XSetClipMask(Tk_Display(tkwin), gc2, None); + TkDestroyRegion(clipRegion); + } + Tk_FreeGC(Tk_Display(tkwin), gc1); + Tk_FreeGC(Tk_Display(tkwin), gc2); +} + +static void TextElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TextElement *text = elementRecord; + + if (!TextSetup(text, tkwin)) + return; + + *heightPtr = text->height; + *widthPtr = TextReqWidth(text); + + TextCleanup(text); + + return; +} + +static void TextElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + TextElement *text = elementRecord; + if (TextSetup(text, tkwin)) { + TextDraw(text, tkwin, d, b); + TextCleanup(text); + } +} + +static Ttk_ElementSpec TextElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TextElement), + TextElementOptions, + TextElementSize, + TextElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Image element. + * Draws an image. + */ + +typedef struct { + Tcl_Obj *imageObj; + Tcl_Obj *stippleObj; /* For TTK_STATE_DISABLED */ + Tcl_Obj *backgroundObj; /* " " */ + + Ttk_ImageSpec *imageSpec; + Tk_Image tkimg; + int width; + int height; +} ImageElement; + +/* ===> NB: Keep in sync with label element option table. <=== + */ +static Ttk_ElementOptionSpec ImageElementOptions[] = { + { "-image", TK_OPTION_STRING, + Tk_Offset(ImageElement,imageObj), "" }, + { "-stipple", TK_OPTION_STRING, /* Really: TK_OPTION_BITMAP */ + Tk_Offset(ImageElement,stippleObj), "gray50" }, + { "-background", TK_OPTION_COLOR, + Tk_Offset(ImageElement,backgroundObj), DEFAULT_BACKGROUND }, + { NULL, 0, 0, NULL } +}; + +/* + * ImageSetup() -- + * Look up the Tk_Image from the image element's imageObj resource. + * Caller must release the image with ImageCleanup(). + * + * Returns: + * 1 if successful, 0 if there was an error (unreported) + * or the image resource was not specified. + */ + +static int ImageSetup( + ImageElement *image, Tk_Window tkwin, Ttk_State state) +{ + + if (!image->imageObj) { + return 0; + } + image->imageSpec = TtkGetImageSpec(NULL, tkwin, image->imageObj); + if (!image->imageSpec) { + return 0; + } + image->tkimg = TtkSelectImage(image->imageSpec, state); + if (!image->tkimg) { + TtkFreeImageSpec(image->imageSpec); + return 0; + } + Tk_SizeOfImage(image->tkimg, &image->width, &image->height); + + return 1; +} + +static void ImageCleanup(ImageElement *image) +{ + TtkFreeImageSpec(image->imageSpec); +} + +/* + * StippleOver -- + * Draw a stipple over the image area, to make it look "grayed-out" + * when TTK_STATE_DISABLED is set. + */ +static void StippleOver( + ImageElement *image, Tk_Window tkwin, Drawable d, int x, int y) +{ + Pixmap stipple = Tk_AllocBitmapFromObj(NULL, tkwin, image->stippleObj); + XColor *color = Tk_GetColorFromObj(tkwin, image->backgroundObj); + + if (stipple != None) { + unsigned long mask = GCFillStyle | GCStipple | GCForeground; + XGCValues gcvalues; + GC gc; + gcvalues.foreground = color->pixel; + gcvalues.fill_style = FillStippled; + gcvalues.stipple = stipple; + gc = Tk_GetGC(tkwin, mask, &gcvalues); + XFillRectangle(Tk_Display(tkwin),d,gc,x,y,image->width,image->height); + Tk_FreeGC(Tk_Display(tkwin), gc); + Tk_FreeBitmapFromObj(tkwin, image->stippleObj); + } +} + +static void ImageDraw( + ImageElement *image, Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state) +{ + int width = image->width, height = image->height; + + /* Clip width and height to remain within window bounds: + */ + if (b.x + width > Tk_Width(tkwin)) { + width = Tk_Width(tkwin) - b.x; + } + if (b.y + height > Tk_Height(tkwin)) { + height = Tk_Height(tkwin) - b.y; + } + + if (height <= 0 || width <= 0) { + /* Completely clipped - bail out. + */ + return; + } + + Tk_RedrawImage(image->tkimg, 0,0, width, height, d, b.x, b.y); + + /* If we're disabled there's no state-specific 'disabled' image, + * stipple the image. + * @@@ Possibly: Don't do disabled-stippling at all; + * @@@ it's ugly and out of fashion. + * Do not stipple at all under Aqua, just draw the image: it shows up + * as a white rectangle otherwise. + */ + if (state & TTK_STATE_DISABLED) { + if (TtkSelectImage(image->imageSpec, 0ul) == image->tkimg) { +#ifndef MAC_OSX_TK + StippleOver(image, tkwin, d, b.x,b.y); +#endif + } + } +} + +static void ImageElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ImageElement *image = elementRecord; + + if (ImageSetup(image, tkwin, 0)) { + *widthPtr = image->width; + *heightPtr = image->height; + ImageCleanup(image); + } +} + +static void ImageElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ImageElement *image = elementRecord; + + if (ImageSetup(image, tkwin, state)) { + ImageDraw(image, tkwin, d, b, state); + ImageCleanup(image); + } +} + +static Ttk_ElementSpec ImageElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ImageElement), + ImageElementOptions, + ImageElementSize, + ImageElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Label element. + * + * Displays an image and/or text, as determined by the -compound option. + * + * Differences from Tk 8.4 compound elements: + * + * This adds two new values for the -compound option, "text" + * and "image". (This is useful for configuring toolbars to + * display icons, text and icons, or text only, as found in + * many browsers.) + * + * "-compound none" is supported, but I'd like to get rid of it; + * it makes the logic more complex, and the only benefit is + * backwards compatibility with Tk < 8.3.0 scripts. + * + * This adds a new resource, -space, for determining how much + * space to leave between the text and image; Tk 8.4 reuses the + * -padx or -pady option for this purpose. + * + * -width always specifies the length in characters of the text part; + * in Tk 8.4 it's either characters or pixels, depending on the + * value of -compound. + * + * Negative values of -width are interpreted as a minimum width + * on all platforms, not just on Windows. + * + * Tk 8.4 ignores -padx and -pady if -compound is set to "none". + * Here, padding is handled by a different element. + */ + +typedef struct { + /* + * Element options: + */ + Tcl_Obj *compoundObj; + Tcl_Obj *spaceObj; + TextElement text; + ImageElement image; + + /* + * Computed values (see LabelSetup) + */ + Ttk_Compound compound; + int space; + int totalWidth, totalHeight; +} LabelElement; + +static Ttk_ElementOptionSpec LabelElementOptions[] = { + { "-compound", TK_OPTION_ANY, + Tk_Offset(LabelElement,compoundObj), "none" }, + { "-space", TK_OPTION_PIXELS, + Tk_Offset(LabelElement,spaceObj), "4" }, + + /* Text element part: + * NB: Keep in sync with TextElementOptions. + */ + { "-text", TK_OPTION_STRING, + Tk_Offset(LabelElement,text.textObj), "" }, + { "-font", TK_OPTION_FONT, + Tk_Offset(LabelElement,text.fontObj), DEFAULT_FONT }, + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(LabelElement,text.foregroundObj), "black" }, + { "-underline", TK_OPTION_INT, + Tk_Offset(LabelElement,text.underlineObj), "-1"}, + { "-width", TK_OPTION_INT, + Tk_Offset(LabelElement,text.widthObj), ""}, + { "-anchor", TK_OPTION_ANCHOR, + Tk_Offset(LabelElement,text.anchorObj), "w"}, + { "-justify", TK_OPTION_JUSTIFY, + Tk_Offset(LabelElement,text.justifyObj), "left" }, + { "-wraplength", TK_OPTION_PIXELS, + Tk_Offset(LabelElement,text.wrapLengthObj), "0" }, + { "-embossed", TK_OPTION_INT, + Tk_Offset(LabelElement,text.embossedObj), "0"}, + + /* Image element part: + * NB: Keep in sync with ImageElementOptions. + */ + { "-image", TK_OPTION_STRING, + Tk_Offset(LabelElement,image.imageObj), "" }, + { "-stipple", TK_OPTION_STRING, /* Really: TK_OPTION_BITMAP */ + Tk_Offset(LabelElement,image.stippleObj), "gray50" }, + { "-background", TK_OPTION_COLOR, + Tk_Offset(LabelElement,image.backgroundObj), DEFAULT_BACKGROUND }, + { NULL, 0, 0, NULL } +}; + +/* + * LabelSetup -- + * Fills in computed fields of the label element. + * + * Calculate the text, image, and total width and height. + */ + +#define MAX(a,b) ((a) > (b) ? a : b); +static void LabelSetup( + LabelElement *c, Tk_Window tkwin, Ttk_State state) +{ + Ttk_Compound *compoundPtr = &c->compound; + + Tk_GetPixelsFromObj(NULL,tkwin,c->spaceObj,&c->space); + Ttk_GetCompoundFromObj(NULL,c->compoundObj,(int*)compoundPtr); + + /* + * Deal with TTK_COMPOUND_NONE. + */ + if (c->compound == TTK_COMPOUND_NONE) { + if (ImageSetup(&c->image, tkwin, state)) { + c->compound = TTK_COMPOUND_IMAGE; + } else { + c->compound = TTK_COMPOUND_TEXT; + } + } else if (c->compound != TTK_COMPOUND_TEXT) { + if (!ImageSetup(&c->image, tkwin, state)) { + c->compound = TTK_COMPOUND_TEXT; + } + } + if (c->compound != TTK_COMPOUND_IMAGE) + TextSetup(&c->text, tkwin); + + /* + * ASSERT: + * if c->compound != IMAGE, then TextSetup() has been called + * if c->compound != TEXT, then ImageSetup() has returned successfully + * c->compound != COMPOUND_NONE. + */ + + switch (c->compound) + { + case TTK_COMPOUND_NONE: + /* Can't happen */ + break; + case TTK_COMPOUND_TEXT: + c->totalWidth = c->text.width; + c->totalHeight = c->text.height; + break; + case TTK_COMPOUND_IMAGE: + c->totalWidth = c->image.width; + c->totalHeight = c->image.height; + break; + case TTK_COMPOUND_CENTER: + c->totalWidth = MAX(c->image.width, c->text.width); + c->totalHeight = MAX(c->image.height, c->text.height); + break; + case TTK_COMPOUND_TOP: + case TTK_COMPOUND_BOTTOM: + c->totalWidth = MAX(c->image.width, c->text.width); + c->totalHeight = c->image.height + c->text.height + c->space; + break; + + case TTK_COMPOUND_LEFT: + case TTK_COMPOUND_RIGHT: + c->totalWidth = c->image.width + c->text.width + c->space; + c->totalHeight = MAX(c->image.height, c->text.height); + break; + } +} + +static void LabelCleanup(LabelElement *c) +{ + if (c->compound != TTK_COMPOUND_TEXT) + ImageCleanup(&c->image); + if (c->compound != TTK_COMPOUND_IMAGE) + TextCleanup(&c->text); +} + +static void LabelElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + LabelElement *label = elementRecord; + int textReqWidth = 0; + + LabelSetup(label, tkwin, 0); + + *heightPtr = label->totalHeight; + + /* Requested width based on -width option, not actual text width: + */ + if (label->compound != TTK_COMPOUND_IMAGE) + textReqWidth = TextReqWidth(&label->text); + + switch (label->compound) + { + case TTK_COMPOUND_TEXT: + *widthPtr = textReqWidth; + break; + case TTK_COMPOUND_IMAGE: + *widthPtr = label->image.width; + break; + case TTK_COMPOUND_TOP: + case TTK_COMPOUND_BOTTOM: + case TTK_COMPOUND_CENTER: + *widthPtr = MAX(label->image.width, textReqWidth); + break; + case TTK_COMPOUND_LEFT: + case TTK_COMPOUND_RIGHT: + *widthPtr = label->image.width + textReqWidth + label->space; + break; + case TTK_COMPOUND_NONE: + break; /* Can't happen */ + } + + LabelCleanup(label); +} + +/* + * DrawCompound -- + * Helper routine for LabelElementDraw; + * Handles layout for -compound {left,right,top,bottom} + */ +static void DrawCompound( + LabelElement *l, Ttk_Box b, Tk_Window tkwin, Drawable d, Ttk_State state, + int imageSide, int textSide) +{ + Ttk_Box imageBox = + Ttk_PlaceBox(&b, l->image.width, l->image.height, imageSide, 0); + Ttk_Box textBox = + Ttk_PlaceBox(&b, l->text.width, l->text.height, textSide, 0); + ImageDraw(&l->image,tkwin,d,imageBox,state); + TextDraw(&l->text,tkwin,d,textBox); +} + +static void LabelElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + LabelElement *l = elementRecord; + Tk_Anchor anchor = TK_ANCHOR_CENTER; + + LabelSetup(l, tkwin, state); + + /* + * Adjust overall parcel based on -anchor: + */ + Tk_GetAnchorFromObj(NULL, l->text.anchorObj, &anchor); + b = Ttk_AnchorBox(b, l->totalWidth, l->totalHeight, anchor); + + /* + * Draw text and/or image parts based on -compound: + */ + switch (l->compound) + { + case TTK_COMPOUND_NONE: + /* Can't happen */ + break; + case TTK_COMPOUND_TEXT: + TextDraw(&l->text,tkwin,d,b); + break; + case TTK_COMPOUND_IMAGE: + ImageDraw(&l->image,tkwin,d,b,state); + break; + case TTK_COMPOUND_CENTER: + { + Ttk_Box pb = Ttk_AnchorBox( + b, l->image.width, l->image.height, TK_ANCHOR_CENTER); + ImageDraw(&l->image, tkwin, d, pb, state); + pb = Ttk_AnchorBox( + b, l->text.width, l->text.height, TK_ANCHOR_CENTER); + TextDraw(&l->text, tkwin, d, pb); + break; + } + case TTK_COMPOUND_TOP: + DrawCompound(l, b, tkwin, d, state, TTK_SIDE_TOP, TTK_SIDE_BOTTOM); + break; + case TTK_COMPOUND_BOTTOM: + DrawCompound(l, b, tkwin, d, state, TTK_SIDE_BOTTOM, TTK_SIDE_TOP); + break; + case TTK_COMPOUND_LEFT: + DrawCompound(l, b, tkwin, d, state, TTK_SIDE_LEFT, TTK_SIDE_RIGHT); + break; + case TTK_COMPOUND_RIGHT: + DrawCompound(l, b, tkwin, d, state, TTK_SIDE_RIGHT, TTK_SIDE_LEFT); + break; + } + + LabelCleanup(l); +} + +static Ttk_ElementSpec LabelElementSpec = { + TK_STYLE_VERSION_2, + sizeof(LabelElement), + LabelElementOptions, + LabelElementSize, + LabelElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkLabel_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterElement(interp, theme, "text", &TextElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "image", &ImageElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "label", &LabelElementSpec, NULL); +} + diff --git a/generic/ttk/ttkLayout.c b/generic/ttk/ttkLayout.c new file mode 100644 index 0000000..58c99eb --- /dev/null +++ b/generic/ttk/ttkLayout.c @@ -0,0 +1,1252 @@ +/* + * ttkLayout.c -- + * + * Generic layout processing. + * + * Copyright (c) 2003 Joe English. Freely redistributable. + */ + +#include <string.h> +#include <tk.h> +#include "ttkThemeInt.h" + +#define MAX(a,b) (a > b ? a : b) +#define MIN(a,b) (a < b ? a : b) + +/*------------------------------------------------------------------------ + * +++ Ttk_Box and Ttk_Padding utilities: + */ + +Ttk_Box +Ttk_MakeBox(int x, int y, int width, int height) +{ + Ttk_Box b; + b.x = x; b.y = y; b.width = width; b.height = height; + return b; +} + +int +Ttk_BoxContains(Ttk_Box box, int x, int y) +{ + return box.x <= x && x < box.x + box.width + && box.y <= y && y < box.y + box.height; +} + +Tcl_Obj * +Ttk_NewBoxObj(Ttk_Box box) +{ + Tcl_Obj *result[4]; + + result[0] = Tcl_NewIntObj(box.x); + result[1] = Tcl_NewIntObj(box.y); + result[2] = Tcl_NewIntObj(box.width); + result[3] = Tcl_NewIntObj(box.height); + + return Tcl_NewListObj(4, result); +} + +/* + * packTop, packBottom, packLeft, packRight -- + * Carve out a parcel of the specified height (resp width) + * from the specified cavity. + * + * Returns: + * The new parcel. + * + * Side effects: + * Adjust the cavity. + */ + +static Ttk_Box packTop(Ttk_Box *cavity, int height) +{ + Ttk_Box parcel; + height = MIN(height, cavity->height); + parcel = Ttk_MakeBox(cavity->x, cavity->y, cavity->width, height); + cavity->y += height; + cavity->height -= height; + return parcel; +} + +static Ttk_Box packBottom(Ttk_Box *cavity, int height) +{ + height = MIN(height, cavity->height); + cavity->height -= height; + return Ttk_MakeBox( + cavity->x, cavity->y + cavity->height, + cavity->width, height); +} + +static Ttk_Box packLeft(Ttk_Box *cavity, int width) +{ + Ttk_Box parcel; + width = MIN(width, cavity->width); + parcel = Ttk_MakeBox(cavity->x, cavity->y, width,cavity->height); + cavity->x += width; + cavity->width -= width; + return parcel; +} + +static Ttk_Box packRight(Ttk_Box *cavity, int width) +{ + width = MIN(width, cavity->width); + cavity->width -= width; + return Ttk_MakeBox(cavity->x + cavity->width, + cavity->y, width, cavity->height); +} + +/* + * Ttk_PackBox -- + * Carve out a parcel of the specified size on the specified side + * in the specified cavity. + * + * Returns: + * The new parcel. + * + * Side effects: + * Adjust the cavity. + */ + +Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int width, int height, Ttk_Side side) +{ + switch (side) { + default: + case TTK_SIDE_TOP: return packTop(cavity, height); + case TTK_SIDE_BOTTOM: return packBottom(cavity, height); + case TTK_SIDE_LEFT: return packLeft(cavity, width); + case TTK_SIDE_RIGHT: return packRight(cavity, width); + } +} + +/* + * Ttk_PadBox -- + * Shrink a box by the specified padding amount. + */ +Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p) +{ + b.x += p.left; + b.y += p.top; + b.width -= (p.left + p.right); + b.height -= (p.top + p.bottom); + if (b.width <= 0) b.width = 1; + if (b.height <= 0) b.height = 1; + return b; +} + +/* + * Ttk_ExpandBox -- + * Grow a box by the specified padding amount. + */ +Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p) +{ + b.x -= p.left; + b.y -= p.top; + b.width += (p.left + p.right); + b.height += (p.top + p.bottom); + return b; +} + +/* + * Ttk_StickBox -- + * Place a box of size w * h in the specified parcel, + * according to the specified sticky bits. + */ +Ttk_Box Ttk_StickBox(Ttk_Box parcel, int width, int height, unsigned sticky) +{ + int dx, dy; + + if (width > parcel.width) width = parcel.width; + if (height > parcel.height) height = parcel.height; + + dx = parcel.width - width; + dy = parcel.height - height; + + /* + * X coordinate adjustment: + */ + switch (sticky & (TTK_STICK_W | TTK_STICK_E)) + { + case TTK_STICK_W | TTK_STICK_E: + /* no-op -- use entire parcel width */ + break; + case TTK_STICK_W: + parcel.width = width; + break; + case TTK_STICK_E: + parcel.x += dx; + parcel.width = width; + break; + default : + parcel.x += dx / 2; + parcel.width = width; + break; + } + + /* + * Y coordinate adjustment: + */ + switch (sticky & (TTK_STICK_N | TTK_STICK_S)) + { + case TTK_STICK_N | TTK_STICK_S: + /* use entire parcel height */ + break; + case TTK_STICK_N: + parcel.height = height; + break; + case TTK_STICK_S: + parcel.y += dy; + parcel.height = height; + break; + default : + parcel.y += dy / 2; + parcel.height = height; + break; + } + + return parcel; +} + +/* + * AnchorToSticky -- + * Convert a Tk_Anchor enum to a TTK_STICKY bitmask. + */ +static Ttk_Sticky AnchorToSticky(Tk_Anchor anchor) +{ + switch (anchor) + { + case TK_ANCHOR_N: return TTK_STICK_N; + case TK_ANCHOR_NE: return TTK_STICK_N | TTK_STICK_E; + case TK_ANCHOR_E: return TTK_STICK_E; + case TK_ANCHOR_SE: return TTK_STICK_S | TTK_STICK_E; + case TK_ANCHOR_S: return TTK_STICK_S; + case TK_ANCHOR_SW: return TTK_STICK_S | TTK_STICK_W; + case TK_ANCHOR_W: return TTK_STICK_W; + case TK_ANCHOR_NW: return TTK_STICK_N | TTK_STICK_W; + default: + case TK_ANCHOR_CENTER: return 0; + } +} + +/* + * Ttk_AnchorBox -- + * Place a box of size w * h in the specified parcel, + * according to the specified anchor. + */ +Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int width, int height, Tk_Anchor anchor) +{ + return Ttk_StickBox(parcel, width, height, AnchorToSticky(anchor)); +} + +/* + * Ttk_PlaceBox -- + * Combine Ttk_PackBox() and Ttk_StickBox(). + */ +Ttk_Box Ttk_PlaceBox( + Ttk_Box *cavity, int width, int height, Ttk_Side side, unsigned sticky) +{ + return Ttk_StickBox( + Ttk_PackBox(cavity, width, height, side), width, height, sticky); +} + +/* + * Ttk_PositionBox -- + * Pack and stick a box according to PositionSpec flags. + */ +MODULE_SCOPE Ttk_Box +Ttk_PositionBox(Ttk_Box *cavity, int width, int height, Ttk_PositionSpec flags) +{ + Ttk_Box parcel; + + if (flags & TTK_EXPAND) parcel = *cavity; + else if (flags & TTK_PACK_TOP) parcel = packTop(cavity, height); + else if (flags & TTK_PACK_LEFT) parcel = packLeft(cavity, width); + else if (flags & TTK_PACK_BOTTOM) parcel = packBottom(cavity, height); + else if (flags & TTK_PACK_RIGHT) parcel = packRight(cavity, width); + else parcel = *cavity; + + return Ttk_StickBox(parcel, width, height, flags); +} + +/* + * TTKInitPadding -- + * Common factor of Ttk_GetPaddingFromObj and Ttk_GetBorderFromObj. + * Initializes Ttk_Padding record, supplying default values + * for missing entries. + */ +static void TTKInitPadding(int padc, int pixels[4], Ttk_Padding *pad) +{ + switch (padc) + { + case 0: pixels[0] = 0; /*FALLTHRU*/ + case 1: pixels[1] = pixels[0]; /*FALLTHRU*/ + case 2: pixels[2] = pixels[0]; /*FALLTHRU*/ + case 3: pixels[3] = pixels[1]; /*FALLTHRU*/ + } + + pad->left = (short)pixels[0]; + pad->top = (short)pixels[1]; + pad->right = (short)pixels[2]; + pad->bottom = (short)pixels[3]; +} + +/* + * Ttk_GetPaddingFromObj -- + * + * Extract a padding specification from a Tcl_Obj * scaled + * to work with a particular Tk_Window. + * + * The string representation of a Ttk_Padding is a list + * of one to four Tk_Pixel specifications, corresponding + * to the left, top, right, and bottom padding. + * + * If the 'bottom' (fourth) element is missing, it defaults to 'top'. + * If the 'right' (third) element is missing, it defaults to 'left'. + * If the 'top' (second) element is missing, it defaults to 'left'. + * + * The internal representation is a Tcl_ListObj containing + * one to four Tk_PixelObj objects. + * + * Returns: + * TCL_OK or TCL_ERROR. In the latter case an error message is + * left in 'interp' and '*paddingPtr' is set to all-zeros. + * Otherwise, *paddingPtr is filled in with the padding specification. + * + */ +int Ttk_GetPaddingFromObj( + Tcl_Interp *interp, + Tk_Window tkwin, + Tcl_Obj *objPtr, + Ttk_Padding *pad) +{ + Tcl_Obj **padv; + int i, padc, pixels[4]; + + if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) { + goto error; + } + + if (padc > 4) { + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Wrong #elements in padding spec", NULL); + } + goto error; + } + + for (i=0; i < padc; ++i) { + if (Tk_GetPixelsFromObj(interp, tkwin, padv[i], &pixels[i]) != TCL_OK) { + goto error; + } + } + + TTKInitPadding(padc, pixels, pad); + return TCL_OK; + +error: + pad->left = pad->top = pad->right = pad->bottom = 0; + return TCL_ERROR; +} + +/* Ttk_GetBorderFromObj -- + * Same as Ttk_GetPaddingFromObj, except padding is a list of integers + * instead of Tk_Pixel specifications. Does not require a Tk_Window + * parameter. + * + */ +int Ttk_GetBorderFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Padding *pad) +{ + Tcl_Obj **padv; + int i, padc, pixels[4]; + + if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) { + goto error; + } + + if (padc > 4) { + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Wrong #elements in border spec", NULL); + } + goto error; + } + + for (i=0; i < padc; ++i) { + if (Tcl_GetIntFromObj(interp, padv[i], &pixels[i]) != TCL_OK) { + goto error; + } + } + + TTKInitPadding(padc, pixels, pad); + return TCL_OK; + +error: + pad->left = pad->top = pad->right = pad->bottom = 0; + return TCL_ERROR; +} + +/* + * Ttk_MakePadding -- + * Return an initialized Ttk_Padding structure. + */ +Ttk_Padding Ttk_MakePadding(short left, short top, short right, short bottom) +{ + Ttk_Padding pad; + pad.left = left; + pad.top = top; + pad.right = right; + pad.bottom = bottom; + return pad; +} + +/* + * Ttk_UniformPadding -- + * Returns a uniform Ttk_Padding structure, with the same + * border width on all sides. + */ +Ttk_Padding Ttk_UniformPadding(short borderWidth) +{ + Ttk_Padding pad; + pad.left = pad.top = pad.right = pad.bottom = borderWidth; + return pad; +} + +/* + * Ttk_AddPadding -- + * Combine two padding records. + */ +Ttk_Padding Ttk_AddPadding(Ttk_Padding p1, Ttk_Padding p2) +{ + p1.left += p2.left; + p1.top += p2.top; + p1.right += p2.right; + p1.bottom += p2.bottom; + return p1; +} + +/* Ttk_RelievePadding -- + * Add an extra n pixels of padding according to specified relief. + * This may be used in element geometry procedures to simulate + * a "pressed-in" look for pushbuttons. + */ +Ttk_Padding Ttk_RelievePadding(Ttk_Padding padding, int relief, int n) +{ + switch (relief) + { + case TK_RELIEF_RAISED: + padding.right += n; + padding.bottom += n; + break; + case TK_RELIEF_SUNKEN: /* shift */ + padding.left += n; + padding.top += n; + break; + default: + { + int h1 = n/2, h2 = h1 + n % 2; + padding.left += h1; + padding.top += h1; + padding.right += h2; + padding.bottom += h2; + break; + } + } + return padding; +} + +/* + * Ttk_GetStickyFromObj -- + * Returns a stickiness specification from the specified Tcl_Obj*, + * consisting of any combination of n, s, e, and w. + * + * Returns: TCL_OK if objPtr holds a valid stickiness specification, + * otherwise TCL_ERROR. interp is used for error reporting if non-NULL. + * + */ +int Ttk_GetStickyFromObj( + Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Sticky *result) +{ + const char *string = Tcl_GetString(objPtr); + Ttk_Sticky sticky = 0; + char c; + + while ((c = *string++) != '\0') { + switch (c) { + case 'w': case 'W': sticky |= TTK_STICK_W; break; + case 'e': case 'E': sticky |= TTK_STICK_E; break; + case 'n': case 'N': sticky |= TTK_STICK_N; break; + case 's': case 'S': sticky |= TTK_STICK_S; break; + default: + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Bad -sticky specification ", + Tcl_GetString(objPtr), + NULL); + } + return TCL_ERROR; + } + } + + *result = sticky; + return TCL_OK; +} + +/* Ttk_NewStickyObj -- + * Construct a new Tcl_Obj * containing a stickiness specification. + */ +Tcl_Obj *Ttk_NewStickyObj(Ttk_Sticky sticky) +{ + char buf[5]; + char *p = buf; + + if (sticky & TTK_STICK_N) *p++ = 'n'; + if (sticky & TTK_STICK_S) *p++ = 's'; + if (sticky & TTK_STICK_W) *p++ = 'w'; + if (sticky & TTK_STICK_E) *p++ = 'e'; + + *p = '\0'; + return Tcl_NewStringObj(buf, p - buf); +} + +/*------------------------------------------------------------------------ + * +++ Layout nodes. + */ + +typedef struct Ttk_LayoutNode_ Ttk_LayoutNode; +struct Ttk_LayoutNode_ +{ + unsigned flags; /* Packing and sticky flags */ + Ttk_ElementClass *eclass; /* Class record */ + Ttk_State state; /* Current state */ + Ttk_Box parcel; /* allocated parcel */ + Ttk_LayoutNode *next, *child; +}; + +static Ttk_LayoutNode *Ttk_NewLayoutNode( + unsigned flags, Ttk_ElementClass *elementClass) +{ + Ttk_LayoutNode *node = (Ttk_LayoutNode*)ckalloc(sizeof(*node)); + + node->flags = flags; + node->eclass = elementClass; + node->state = 0u; + node->next = node->child = 0; + node->parcel = Ttk_MakeBox(0,0,0,0); + + return node; +} + +static void Ttk_FreeLayoutNode(Ttk_LayoutNode *node) +{ + while (node) { + Ttk_LayoutNode *next = node->next; + Ttk_FreeLayoutNode(node->child); + ckfree((ClientData)node); + node = next; + } +} + +/*------------------------------------------------------------------------ + * +++ Layout templates. + */ + +struct Ttk_TemplateNode_ { + char *name; + unsigned flags; + struct Ttk_TemplateNode_ *next, *child; +}; + +static Ttk_TemplateNode *Ttk_NewTemplateNode(const char *name, unsigned flags) +{ + Ttk_TemplateNode *op = (Ttk_TemplateNode*)ckalloc(sizeof(*op)); + op->name = ckalloc(strlen(name) + 1); strcpy(op->name, name); + op->flags = flags; + op->next = op->child = 0; + return op; +} + +void Ttk_FreeLayoutTemplate(Ttk_LayoutTemplate op) +{ + while (op) { + Ttk_LayoutTemplate next = op->next; + Ttk_FreeLayoutTemplate(op->child); + ckfree(op->name); + ckfree((ClientData)op); + op = next; + } +} + +/* InstantiateLayout -- + * Create a layout tree from a template. + */ +static Ttk_LayoutNode * +Ttk_InstantiateLayout(Ttk_Theme theme, Ttk_TemplateNode *op) +{ + Ttk_ElementClass *elementClass = Ttk_GetElement(theme, op->name); + Ttk_LayoutNode *node = Ttk_NewLayoutNode(op->flags, elementClass); + + if (op->next) { + node->next = Ttk_InstantiateLayout(theme,op->next); + } + if (op->child) { + node->child = Ttk_InstantiateLayout(theme,op->child); + } + + return node; +} + +/* + * Ttk_ParseLayoutTemplate -- + * Convert a Tcl list into a layout template. + * + * Syntax: + * layoutSpec ::= { elementName ?-option value ...? }+ + */ + +/* NB: This must match bit definitions TTK_PACK_LEFT etc. */ +static const char *packSideStrings[] = + { "left", "right", "top", "bottom", NULL }; + +Ttk_LayoutTemplate Ttk_ParseLayoutTemplate(Tcl_Interp *interp, Tcl_Obj *objPtr) +{ + enum { OP_SIDE, OP_STICKY, OP_EXPAND, OP_BORDER, OP_UNIT, OP_CHILDREN }; + static const char *optStrings[] = { + "-side", "-sticky", "-expand", "-border", "-unit", "-children", 0 }; + + int i = 0, objc; + Tcl_Obj **objv; + Ttk_TemplateNode *head = 0, *tail = 0; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) + return 0; + + while (i < objc) { + const char *elementName = Tcl_GetString(objv[i]); + unsigned flags = 0x0, sticky = TTK_FILL_BOTH; + Tcl_Obj *childSpec = 0; + + /* + * Parse options: + */ + ++i; + while (i < objc) { + const char *optName = Tcl_GetString(objv[i]); + int option, value; + + if (optName[0] != '-') + break; + + if (Tcl_GetIndexFromObj( + interp, objv[i], optStrings, "option", 0, &option) + != TCL_OK) + { + goto error; + } + + if (++i >= objc) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Missing value for option ",Tcl_GetString(objv[i-1]), + NULL); + goto error; + } + + switch (option) { + case OP_SIDE: /* <<NOTE-PACKSIDE>> */ + if (Tcl_GetIndexFromObj(interp, objv[i], packSideStrings, + "side", 0, &value) != TCL_OK) + { + goto error; + } + flags |= (TTK_PACK_LEFT << value); + + break; + case OP_STICKY: + if (Ttk_GetStickyFromObj(interp,objv[i],&sticky) != TCL_OK) + goto error; + break; + case OP_EXPAND: + if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) + goto error; + if (value) + flags |= TTK_EXPAND; + break; + case OP_BORDER: + if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) + goto error; + if (value) + flags |= TTK_BORDER; + break; + case OP_UNIT: + if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK) + goto error; + if (value) + flags |= TTK_UNIT; + break; + case OP_CHILDREN: + childSpec = objv[i]; + break; + } + ++i; + } + + /* + * Build new node: + */ + if (tail) { + tail->next = Ttk_NewTemplateNode(elementName, flags | sticky); + tail = tail->next; + } else { + head = tail = Ttk_NewTemplateNode(elementName, flags | sticky); + } + if (childSpec) { + tail->child = Ttk_ParseLayoutTemplate(interp, childSpec); + if (!tail->child) { + goto error; + } + } + } + + return head; + +error: + Ttk_FreeLayoutTemplate(head); + return 0; +} + +/* Ttk_BuildLayoutTemplate -- + * Build a layout template tree from a statically defined + * Ttk_LayoutSpec array. + */ +Ttk_LayoutTemplate Ttk_BuildLayoutTemplate(Ttk_LayoutSpec spec) +{ + Ttk_TemplateNode *first = 0, *last = 0; + + for ( ; !(spec->opcode & _TTK_LAYOUT_END) ; ++spec) { + if (spec->elementName) { + Ttk_TemplateNode *node = + Ttk_NewTemplateNode(spec->elementName, spec->opcode); + + if (last) { + last->next = node; + } else { + first = node; + } + last = node; + } + + if (spec->opcode & _TTK_CHILDREN && last) { + int depth = 1; + last->child = Ttk_BuildLayoutTemplate(spec+1); + + /* Skip to end of group: + */ + while (depth) { + ++spec; + if (spec->opcode & _TTK_CHILDREN) { + ++depth; + } + if (spec->opcode & _TTK_LAYOUT_END) { + --depth; + } + } + } + + } /* for */ + + return first; +} + +void Ttk_RegisterLayouts(Ttk_Theme theme, Ttk_LayoutSpec spec) +{ + while (!(spec->opcode & _TTK_LAYOUT_END)) { + Ttk_LayoutTemplate layoutTemplate = Ttk_BuildLayoutTemplate(spec+1); + Ttk_RegisterLayoutTemplate(theme, spec->elementName, layoutTemplate); + do { + ++spec; + } while (!(spec->opcode & _TTK_LAYOUT)); + } +} + +Tcl_Obj *Ttk_UnparseLayoutTemplate(Ttk_TemplateNode *node) +{ + Tcl_Obj *result = Tcl_NewListObj(0,0); + +# define APPENDOBJ(obj) Tcl_ListObjAppendElement(NULL, result, obj) +# define APPENDSTR(str) APPENDOBJ(Tcl_NewStringObj(str,-1)) + + while (node) { + unsigned flags = node->flags; + + APPENDSTR(node->name); + + /* Back-compute -side. <<NOTE-PACKSIDE>> + * @@@ NOTES: Ick. + */ + if (flags & TTK_EXPAND) { + APPENDSTR("-expand"); + APPENDSTR("1"); + } else { + if (flags & _TTK_MASK_PACK) { + int side = 0; + unsigned sideFlags = flags & _TTK_MASK_PACK; + + while ((sideFlags & TTK_PACK_LEFT) == 0) { + ++side; + sideFlags >>= 1; + } + APPENDSTR("-side"); + APPENDSTR(packSideStrings[side]); + } + } + + /* In Ttk_ParseLayoutTemplate, default -sticky is "nsew", + * so always include this even if no sticky bits are set. + */ + APPENDSTR("-sticky"); + APPENDOBJ(Ttk_NewStickyObj(flags & _TTK_MASK_STICK)); + + /* @@@ Check again: are these necessary? */ + if (flags & TTK_BORDER) { APPENDSTR("-border"); APPENDSTR("1"); } + if (flags & TTK_UNIT) { APPENDSTR("-unit"); APPENDSTR("1"); } + + if (node->child) { + APPENDSTR("-children"); + APPENDOBJ(Ttk_UnparseLayoutTemplate(node->child)); + } + node = node->next; + } + +# undef APPENDOBJ +# undef APPENDSTR + + return result; +} + +/*------------------------------------------------------------------------ + * +++ Layouts. + */ +struct Ttk_Layout_ +{ + Ttk_Style style; + void *recordPtr; + Tk_OptionTable optionTable; + Tk_Window tkwin; + Ttk_LayoutNode *root; +}; + +static Ttk_Layout TTKNewLayout( + Ttk_Style style, + void *recordPtr,Tk_OptionTable optionTable, Tk_Window tkwin, + Ttk_LayoutNode *root) +{ + Ttk_Layout layout = (Ttk_Layout)ckalloc(sizeof(*layout)); + layout->style = style; + layout->recordPtr = recordPtr; + layout->optionTable = optionTable; + layout->tkwin = tkwin; + layout->root = root; + return layout; +} + +void Ttk_FreeLayout(Ttk_Layout layout) +{ + Ttk_FreeLayoutNode(layout->root); + ckfree((ClientData)layout); +} + +/* + * Ttk_CreateLayout -- + * Create a layout from the specified theme and style name. + * Returns: New layout, 0 on error. + * Leaves an error message in interp's result if there is an error. + */ +Ttk_Layout Ttk_CreateLayout( + Tcl_Interp *interp, /* where to leave error messages */ + Ttk_Theme themePtr, + const char *styleName, + void *recordPtr, + Tk_OptionTable optionTable, + Tk_Window tkwin) +{ + Ttk_Style style = Ttk_GetStyle(themePtr, styleName); + Ttk_LayoutTemplate layoutTemplate = + Ttk_FindLayoutTemplate(themePtr,styleName); + Ttk_ElementClass *bgelement = Ttk_GetElement(themePtr, "background"); + Ttk_LayoutNode *bgnode; + + if (!layoutTemplate) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Layout ", styleName, " not found", NULL); + return 0; + } + + bgnode = Ttk_NewLayoutNode(TTK_FILL_BOTH, bgelement); + bgnode->next = Ttk_InstantiateLayout(themePtr, layoutTemplate); + + return TTKNewLayout(style, recordPtr, optionTable, tkwin, bgnode); +} + +/* Ttk_CreateSublayout -- + * Creates a new sublayout. + * + * Sublayouts are used to draw subparts of a compound widget. + * They use the same Tk_Window, but a different option table + * and data record. + */ +Ttk_Layout +Ttk_CreateSublayout( + Tcl_Interp *interp, + Ttk_Theme themePtr, + Ttk_Layout parentLayout, + const char *baseName, + Tk_OptionTable optionTable) +{ + Tcl_DString buf; + const char *styleName; + Ttk_Style style; + Ttk_LayoutTemplate layoutTemplate; + + Tcl_DStringInit(&buf); + Tcl_DStringAppend(&buf, Ttk_StyleName(parentLayout->style), -1); + Tcl_DStringAppend(&buf, baseName, -1); + styleName = Tcl_DStringValue(&buf); + + style = Ttk_GetStyle(themePtr, styleName); + layoutTemplate = Ttk_FindLayoutTemplate(themePtr, styleName); + + if (!layoutTemplate) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Layout ", styleName, " not found", NULL); + return 0; + } + + Tcl_DStringFree(&buf); + + return TTKNewLayout( + style, 0, optionTable, parentLayout->tkwin, + Ttk_InstantiateLayout(themePtr, layoutTemplate)); +} + +/* Ttk_RebindSublayout -- + * Bind sublayout to new data source. + */ +void Ttk_RebindSublayout(Ttk_Layout layout, void *recordPtr) +{ + layout->recordPtr = recordPtr; +} + +/* + * Ttk_QueryOption -- + * Look up an option from a layout's associated option. + */ +Tcl_Obj *Ttk_QueryOption( + Ttk_Layout layout, const char *optionName, Ttk_State state) +{ + return Ttk_QueryStyle( + layout->style,layout->recordPtr,layout->optionTable,optionName,state); +} + +/* + * Ttk_LayoutStyle -- + * Extract Ttk_Style from Ttk_Layout. + */ +Ttk_Style Ttk_LayoutStyle(Ttk_Layout layout) +{ + return layout->style; +} + +/*------------------------------------------------------------------------ + * +++ Size computation. + */ +static void Ttk_NodeListSize( + Ttk_Layout layout, Ttk_LayoutNode *node, + Ttk_State state, int *widthPtr, int *heightPtr); /* Forward */ + +static void Ttk_NodeSize( + Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + int elementWidth, elementHeight, subWidth, subHeight; + Ttk_Padding elementPadding; + + Ttk_ElementSize(node->eclass, + layout->style, layout->recordPtr,layout->optionTable, layout->tkwin, + state|node->state, + &elementWidth, &elementHeight, &elementPadding); + + Ttk_NodeListSize(layout,node->child,state,&subWidth,&subHeight); + subWidth += Ttk_PaddingWidth(elementPadding); + subHeight += Ttk_PaddingHeight(elementPadding); + + *widthPtr = MAX(elementWidth, subWidth); + *heightPtr = MAX(elementHeight, subHeight); + *paddingPtr = elementPadding; +} + +static void Ttk_NodeListSize( + Ttk_Layout layout, Ttk_LayoutNode *node, + Ttk_State state, int *widthPtr, int *heightPtr) +{ + if (!node) { + *widthPtr = *heightPtr = 0; + } else { + int width, height, restWidth, restHeight; + Ttk_Padding unused; + + Ttk_NodeSize(layout, node, state, &width, &height, &unused); + Ttk_NodeListSize(layout, node->next, state, &restWidth, &restHeight); + + if (node->flags & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) { + *widthPtr = width + restWidth; + } else { + *widthPtr = MAX(width, restWidth); + } + + if (node->flags & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) { + *heightPtr = height + restHeight; + } else { + *heightPtr = MAX(height, restHeight); + } + } +} + +/* + * Ttk_LayoutNodeInternalPadding -- + * Returns the internal padding of a layout node. + */ +Ttk_Padding Ttk_LayoutNodeInternalPadding( + Ttk_Layout layout, Ttk_LayoutNode *node) +{ + int unused; + Ttk_Padding padding; + Ttk_ElementSize(node->eclass, + layout->style, layout->recordPtr, layout->optionTable, layout->tkwin, + 0/*state*/, &unused, &unused, &padding); + return padding; +} + +/* + * Ttk_LayoutNodeInternalParcel -- + * Returns the inner area of a specified layout node, + * based on current parcel and element's internal padding. + */ +Ttk_Box Ttk_LayoutNodeInternalParcel(Ttk_Layout layout, Ttk_LayoutNode *node) +{ + Ttk_Padding padding = Ttk_LayoutNodeInternalPadding(layout, node); + return Ttk_PadBox(node->parcel, padding); +} + +/* Ttk_LayoutSize -- + * Compute requested size of a layout. + */ +void Ttk_LayoutSize( + Ttk_Layout layout, Ttk_State state, int *widthPtr, int *heightPtr) +{ + Ttk_NodeListSize(layout, layout->root, state, widthPtr, heightPtr); +} + +void Ttk_LayoutNodeReqSize( /* @@@ Rename this */ + Ttk_Layout layout, Ttk_LayoutNode *node, int *widthPtr, int *heightPtr) +{ + Ttk_Padding unused; + Ttk_NodeSize(layout, node, 0/*state*/, widthPtr, heightPtr, &unused); +} + +/*------------------------------------------------------------------------ + * +++ Layout placement. + */ + +/* Ttk_PlaceNodeList -- + * Compute parcel for each node in a layout tree + * according to position specification and overall size. + */ +static void Ttk_PlaceNodeList( + Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state, Ttk_Box cavity) +{ + for (; node; node = node->next) + { + int width, height; + Ttk_Padding padding; + + /* Compute node size: (@@@ cache this instead?) + */ + Ttk_NodeSize(layout, node, state, &width, &height, &padding); + + /* Compute parcel: + */ + node->parcel = Ttk_PositionBox(&cavity, width, height, node->flags); + + /* Place child nodes: + */ + if (node->child) { + Ttk_Box childBox = Ttk_PadBox(node->parcel, padding); + Ttk_PlaceNodeList(layout,node->child, state, childBox); + } + } +} + +void Ttk_PlaceLayout(Ttk_Layout layout, Ttk_State state, Ttk_Box b) +{ + Ttk_PlaceNodeList(layout, layout->root, state, b); +} + +/*------------------------------------------------------------------------ + * +++ Layout drawing. + */ + +/* + * Ttk_DrawLayout -- + * Draw a layout tree. + */ +static void Ttk_DrawNodeList( + Ttk_Layout layout, Ttk_State state, Ttk_LayoutNode *node, Drawable d) +{ + for (; node; node = node->next) + { + int border = node->flags & TTK_BORDER; + int substate = state; + + if (node->flags & TTK_UNIT) + substate |= node->state; + + if (node->child && border) + Ttk_DrawNodeList(layout, substate, node->child, d); + + Ttk_DrawElement( + node->eclass, + layout->style,layout->recordPtr,layout->optionTable,layout->tkwin, + d, node->parcel, state | node->state); + + if (node->child && !border) + Ttk_DrawNodeList(layout, substate, node->child, d); + } +} + +void Ttk_DrawLayout(Ttk_Layout layout, Ttk_State state, Drawable d) +{ + Ttk_DrawNodeList(layout, state, layout->root, d); +} + +/*------------------------------------------------------------------------ + * +++ Inquiry and modification. + */ + +/* + * Ttk_IdentifyElement -- + * Find the element at the specified x,y coordinate. + */ +static Ttk_Element IdentifyNode(Ttk_Element node, int x, int y) +{ + Ttk_Element closest = NULL; + + for (; node; node = node->next) { + if (Ttk_BoxContains(node->parcel, x, y)) { + closest = node; + if (node->child && !(node->flags & TTK_UNIT)) { + Ttk_Element childNode = IdentifyNode(node->child, x,y); + if (childNode) { + closest = childNode; + } + } + } + } + return closest; +} + +Ttk_Element Ttk_IdentifyElement(Ttk_Layout layout, int x, int y) +{ + return IdentifyNode(layout->root, x, y); +} + +/* + * tail -- + * Return the last component of an element name, e.g., + * "Scrollbar.thumb" => "thumb" + */ +static const char *tail(const char *elementName) +{ + const char *dot; + while ((dot=strchr(elementName,'.')) != NULL) + elementName = dot + 1; + return elementName; +} + +/* + * Ttk_FindElement -- + * Look up an element by name + */ +static Ttk_Element +FindNode(Ttk_Element node, const char *nodeName) +{ + for (; node ; node = node->next) { + if (!strcmp(tail(Ttk_ElementName(node)), nodeName)) + return node; + + if (node->child) { + Ttk_Element childNode = FindNode(node->child, nodeName); + if (childNode) + return childNode; + } + } + return 0; +} + +Ttk_Element Ttk_FindElement(Ttk_Layout layout, const char *nodeName) +{ + return FindNode(layout->root, nodeName); +} + +/* + * Ttk_ClientRegion -- + * Find the internal parcel of a named element within a given layout. + * If the element is not present, use the entire window. + */ +Ttk_Box Ttk_ClientRegion(Ttk_Layout layout, const char *elementName) +{ + Ttk_Element element = Ttk_FindElement(layout, elementName); + return element + ? Ttk_LayoutNodeInternalParcel(layout, element) + : Ttk_WinBox(layout->tkwin) + ; +} + +/* + * Ttk_ElementName -- + * Return the name (class name) of the element. + */ +const char *Ttk_ElementName(Ttk_Element node) +{ + return Ttk_ElementClassName(node->eclass); +} + +/* + * Ttk_ElementParcel -- + * Return the element's current parcel. + */ +Ttk_Box Ttk_ElementParcel(Ttk_Element node) +{ + return node->parcel; +} + +/* + * Ttk_PlaceElement -- + * Explicitly specify an element's parcel. + */ +void Ttk_PlaceElement(Ttk_Layout layout, Ttk_Element node, Ttk_Box b) +{ + node->parcel = b; + if (node->child) { + Ttk_PlaceNodeList(layout, node->child, 0, + Ttk_PadBox(b, Ttk_LayoutNodeInternalPadding(layout, node))); + } +} + +/* + * Ttk_ChangeElementState -- + */ +void Ttk_ChangeElementState(Ttk_LayoutNode *node,unsigned set,unsigned clr) +{ + node->state = (node->state | set) & ~clr; +} + +/*EOF*/ diff --git a/generic/ttk/ttkManager.c b/generic/ttk/ttkManager.c new file mode 100644 index 0000000..ba9e5c0 --- /dev/null +++ b/generic/ttk/ttkManager.c @@ -0,0 +1,552 @@ +/* + * Copyright 2005, Joe English. Freely redistributable. + * + * Support routines for geometry managers. + */ + +#include <string.h> +#include <tk.h> +#include "ttkManager.h" + +/*------------------------------------------------------------------------ + * +++ The Geometry Propagation Dance. + * + * When a slave window requests a new size or some other parameter changes, + * the manager recomputes the required size for the master window and calls + * Tk_GeometryRequest(). This is scheduled as an idle handler so multiple + * updates can be processed as a single batch. + * + * If all goes well, the master's manager will process the request + * (and so on up the chain to the toplevel window), and the master + * window will eventually receive a <Configure> event. At this point + * it recomputes the size and position of all slaves and places them. + * + * If all does not go well, however, the master's request may be ignored + * (typically because the top-level window has a fixed, user-specified size). + * Tk doesn't provide any notification when this happens; to account for this, + * we also schedule an idle handler to call the layout procedure + * after making a geometry request. + * + * +++ Slave removal <<NOTE-LOSTSLAVE>>. + * + * There are three conditions under which a slave is removed: + * + * (1) Another GM claims control + * (2) Manager voluntarily relinquishes control + * (3) Slave is destroyed + * + * In case (1), Tk calls the manager's lostSlaveProc. + * Case (2) is performed by calling Tk_ManageGeometry(slave,NULL,0); + * in this case Tk does _not_ call the LostSlaveProc (documented behavior). + * Tk doesn't handle case (3) either; to account for that we + * register an event handler on the slave widget to track <Destroy> events. + */ + +/* ++ Data structures. + */ +typedef struct +{ + Tk_Window slaveWindow; + Ttk_Manager *manager; + void *slaveData; + unsigned flags; +} Ttk_Slave; + +/* slave->flags bits: + */ +#define SLAVE_MAPPED 0x1 /* slave to be mapped when master is */ + +struct TtkManager_ +{ + Ttk_ManagerSpec *managerSpec; + void *managerData; + Tk_Window masterWindow; + unsigned flags; + int nSlaves; + Ttk_Slave **slaves; +}; + +/* manager->flags bits: + */ +#define MGR_UPDATE_PENDING 0x1 +#define MGR_RESIZE_REQUIRED 0x2 +#define MGR_RELAYOUT_REQUIRED 0x4 + +static void ManagerIdleProc(void *); /* forward */ + +/* ++ ScheduleUpdate -- + * Schedule a call to recompute the size and/or layout, + * depending on flags. + */ +static void ScheduleUpdate(Ttk_Manager *mgr, unsigned flags) +{ + if (!(mgr->flags & MGR_UPDATE_PENDING)) { + Tcl_DoWhenIdle(ManagerIdleProc, mgr); + mgr->flags |= MGR_UPDATE_PENDING; + } + mgr->flags |= flags; +} + +/* ++ RecomputeSize -- + * Recomputes the required size of the master window, + * makes geometry request. + */ +static void RecomputeSize(Ttk_Manager *mgr) +{ + int width = 1, height = 1; + + if (mgr->managerSpec->RequestedSize(mgr->managerData, &width, &height)) { + Tk_GeometryRequest(mgr->masterWindow, width, height); + ScheduleUpdate(mgr, MGR_RELAYOUT_REQUIRED); + } + mgr->flags &= ~MGR_RESIZE_REQUIRED; +} + +/* ++ RecomputeLayout -- + * Recompute geometry of all slaves. + */ +static void RecomputeLayout(Ttk_Manager *mgr) +{ + mgr->managerSpec->PlaceSlaves(mgr->managerData); + mgr->flags &= ~MGR_RELAYOUT_REQUIRED; +} + +/* ++ ManagerIdleProc -- + * DoWhenIdle procedure for deferred updates. + */ +static void ManagerIdleProc(ClientData clientData) +{ + Ttk_Manager *mgr = clientData; + mgr->flags &= ~MGR_UPDATE_PENDING; + + if (mgr->flags & MGR_RESIZE_REQUIRED) { + RecomputeSize(mgr); + } + if (mgr->flags & MGR_RELAYOUT_REQUIRED) { + if (mgr->flags & MGR_UPDATE_PENDING) { + /* RecomputeSize has scheduled another update; relayout later */ + return; + } + RecomputeLayout(mgr); + } +} + +/*------------------------------------------------------------------------ + * +++ Event handlers. + */ + +/* ++ ManagerEventHandler -- + * Recompute slave layout when master widget is resized. + * Keep the slave's map state in sync with the master's. + */ +static const int ManagerEventMask = StructureNotifyMask; +static void ManagerEventHandler(ClientData clientData, XEvent *eventPtr) +{ + Ttk_Manager *mgr = clientData; + int i; + + switch (eventPtr->type) + { + case ConfigureNotify: + RecomputeLayout(mgr); + break; + case MapNotify: + for (i = 0; i < mgr->nSlaves; ++i) { + Ttk_Slave *slave = mgr->slaves[i]; + if (slave->flags & SLAVE_MAPPED) { + Tk_MapWindow(slave->slaveWindow); + } + } + break; + case UnmapNotify: + for (i = 0; i < mgr->nSlaves; ++i) { + Ttk_Slave *slave = mgr->slaves[i]; + Tk_UnmapWindow(slave->slaveWindow); + } + break; + } +} + +/* ++ SlaveEventHandler -- + * Notifies manager when a slave is destroyed + * (see <<NOTE-LOSTSLAVE>>). + */ +static const unsigned SlaveEventMask = StructureNotifyMask; +static void SlaveEventHandler(ClientData clientData, XEvent *eventPtr) +{ + Ttk_Slave *slave = clientData; + if (eventPtr->type == DestroyNotify) { + slave->manager->managerSpec->tkGeomMgr.lostSlaveProc( + slave->manager, slave->slaveWindow); + } +} + +/*------------------------------------------------------------------------ + * +++ Slave initialization and cleanup. + */ + +static Ttk_Slave *NewSlave( + Ttk_Manager *mgr, Tk_Window slaveWindow, void *slaveData) +{ + Ttk_Slave *slave = (Ttk_Slave*)ckalloc(sizeof(*slave)); + + slave->slaveWindow = slaveWindow; + slave->manager = mgr; + slave->flags = 0; + slave->slaveData = slaveData; + + return slave; +} + +static void DeleteSlave(Ttk_Slave *slave) +{ + ckfree((ClientData)slave); +} + +/*------------------------------------------------------------------------ + * +++ Manager initialization and cleanup. + */ + +Ttk_Manager *Ttk_CreateManager( + Ttk_ManagerSpec *managerSpec, void *managerData, Tk_Window masterWindow) +{ + Ttk_Manager *mgr = (Ttk_Manager*)ckalloc(sizeof(*mgr)); + + mgr->managerSpec = managerSpec; + mgr->managerData = managerData; + mgr->masterWindow = masterWindow; + mgr->nSlaves = 0; + mgr->slaves = NULL; + mgr->flags = 0; + + Tk_CreateEventHandler( + mgr->masterWindow, ManagerEventMask, ManagerEventHandler, mgr); + + return mgr; +} + +void Ttk_DeleteManager(Ttk_Manager *mgr) +{ + Tk_DeleteEventHandler( + mgr->masterWindow, ManagerEventMask, ManagerEventHandler, mgr); + + while (mgr->nSlaves > 0) { + Ttk_ForgetSlave(mgr, mgr->nSlaves - 1); + } + if (mgr->slaves) { + ckfree((ClientData)mgr->slaves); + } + + Tk_CancelIdleCall(ManagerIdleProc, mgr); + + ckfree((ClientData)mgr); +} + +/*------------------------------------------------------------------------ + * +++ Slave management. + */ + +/* ++ InsertSlave -- + * Adds slave to the list of managed windows. + */ +static void InsertSlave(Ttk_Manager *mgr, Ttk_Slave *slave, int index) +{ + int endIndex = mgr->nSlaves++; + mgr->slaves = (Ttk_Slave**)ckrealloc( + (ClientData)mgr->slaves, mgr->nSlaves * sizeof(Ttk_Slave *)); + + while (endIndex > index) { + mgr->slaves[endIndex] = mgr->slaves[endIndex - 1]; + --endIndex; + } + + mgr->slaves[index] = slave; + + Tk_ManageGeometry(slave->slaveWindow, + &mgr->managerSpec->tkGeomMgr, (ClientData)mgr); + + Tk_CreateEventHandler(slave->slaveWindow, + SlaveEventMask, SlaveEventHandler, (ClientData)slave); + + ScheduleUpdate(mgr, MGR_RESIZE_REQUIRED); +} + +/* RemoveSlave -- + * Unmanage and delete the slave. + * + * NOTES/ASSUMPTIONS: + * + * [1] It's safe to call Tk_UnmapWindow / Tk_UnmaintainGeometry even if this + * routine is called from the slave's DestroyNotify event handler. + */ +static void RemoveSlave(Ttk_Manager *mgr, int index) +{ + Ttk_Slave *slave = mgr->slaves[index]; + int i; + + /* Notify manager: + */ + mgr->managerSpec->SlaveRemoved(mgr->managerData, index); + + /* Remove from array: + */ + --mgr->nSlaves; + for (i = index ; i < mgr->nSlaves; ++i) { + mgr->slaves[i] = mgr->slaves[i+1]; + } + + /* Clean up: + */ + Tk_DeleteEventHandler( + slave->slaveWindow, SlaveEventMask, SlaveEventHandler, slave); + + /* Note [1] */ + Tk_UnmaintainGeometry(slave->slaveWindow, mgr->masterWindow); + Tk_UnmapWindow(slave->slaveWindow); + + DeleteSlave(slave); + + ScheduleUpdate(mgr, MGR_RESIZE_REQUIRED); +} + +/*------------------------------------------------------------------------ + * +++ Tk_GeomMgr hooks. + */ + +void Ttk_GeometryRequestProc(ClientData clientData, Tk_Window slaveWindow) +{ + Ttk_Manager *mgr = clientData; + int slaveIndex = Ttk_SlaveIndex(mgr, slaveWindow); + int reqWidth = Tk_ReqWidth(slaveWindow); + int reqHeight= Tk_ReqHeight(slaveWindow); + + if (mgr->managerSpec->SlaveRequest( + mgr->managerData, slaveIndex, reqWidth, reqHeight)) + { + ScheduleUpdate(mgr, MGR_RESIZE_REQUIRED); + } +} + +void Ttk_LostSlaveProc(ClientData clientData, Tk_Window slaveWindow) +{ + Ttk_Manager *mgr = clientData; + int index = Ttk_SlaveIndex(mgr, slaveWindow); + + /* ASSERT: index >= 0 */ + RemoveSlave(mgr, index); +} + +/*------------------------------------------------------------------------ + * +++ Public API. + */ + +/* ++ Ttk_InsertSlave -- + * Add a new slave window at the specified index. + */ +void Ttk_InsertSlave( + Ttk_Manager *mgr, int index, Tk_Window tkwin, void *slaveData) +{ + Ttk_Slave *slave = NewSlave(mgr, tkwin, slaveData); + InsertSlave(mgr, slave, index); +} + +/* ++ Ttk_ForgetSlave -- + * Unmanage the specified slave. + */ +void Ttk_ForgetSlave(Ttk_Manager *mgr, int slaveIndex) +{ + Tk_Window slaveWindow = mgr->slaves[slaveIndex]->slaveWindow; + RemoveSlave(mgr, slaveIndex); + Tk_ManageGeometry(slaveWindow, NULL, 0); +} + +/* ++ Ttk_PlaceSlave -- + * Set the position and size of the specified slave window. + * + * NOTES: + * Contrary to documentation, Tk_MaintainGeometry doesn't always + * map the slave. + */ +void Ttk_PlaceSlave( + Ttk_Manager *mgr, int slaveIndex, int x, int y, int width, int height) +{ + Ttk_Slave *slave = mgr->slaves[slaveIndex]; + Tk_MaintainGeometry(slave->slaveWindow,mgr->masterWindow,x,y,width,height); + slave->flags |= SLAVE_MAPPED; + if (Tk_IsMapped(mgr->masterWindow)) { + Tk_MapWindow(slave->slaveWindow); + } +} + +/* ++ Ttk_UnmapSlave -- + * Unmap the specified slave, but leave it managed. + */ +void Ttk_UnmapSlave(Ttk_Manager *mgr, int slaveIndex) +{ + Ttk_Slave *slave = mgr->slaves[slaveIndex]; + Tk_UnmaintainGeometry(slave->slaveWindow, mgr->masterWindow); + slave->flags &= ~SLAVE_MAPPED; + /* Contrary to documentation, Tk_UnmaintainGeometry doesn't always + * unmap the slave: + */ + Tk_UnmapWindow(slave->slaveWindow); +} + +/* LayoutChanged, SizeChanged -- + * Schedule a relayout, resp. resize request. + */ +void Ttk_ManagerLayoutChanged(Ttk_Manager *mgr) +{ + ScheduleUpdate(mgr, MGR_RELAYOUT_REQUIRED); +} + +void Ttk_ManagerSizeChanged(Ttk_Manager *mgr) +{ + ScheduleUpdate(mgr, MGR_RESIZE_REQUIRED); +} + +/* +++ Accessors. + */ +int Ttk_NumberSlaves(Ttk_Manager *mgr) +{ + return mgr->nSlaves; +} +void *Ttk_SlaveData(Ttk_Manager *mgr, int slaveIndex) +{ + return mgr->slaves[slaveIndex]->slaveData; +} +Tk_Window Ttk_SlaveWindow(Ttk_Manager *mgr, int slaveIndex) +{ + return mgr->slaves[slaveIndex]->slaveWindow; +} + +/*------------------------------------------------------------------------ + * +++ Utility routines. + */ + +/* ++ Ttk_SlaveIndex -- + * Returns the index of specified slave window, -1 if not found. + */ +int Ttk_SlaveIndex(Ttk_Manager *mgr, Tk_Window slaveWindow) +{ + int index; + for (index = 0; index < mgr->nSlaves; ++index) + if (mgr->slaves[index]->slaveWindow == slaveWindow) + return index; + return -1; +} + +/* ++ Ttk_GetSlaveIndexFromObj(interp, mgr, objPtr, indexPtr) -- + * Return the index of the slave specified by objPtr. + * Slaves may be specified as an integer index or + * as the name of the managed window. + * + * Returns: + * Standard Tcl completion code. Leaves an error message in case of error. + */ + +int Ttk_GetSlaveIndexFromObj( + Tcl_Interp *interp, Ttk_Manager *mgr, Tcl_Obj *objPtr, int *indexPtr) +{ + const char *string = Tcl_GetString(objPtr); + int slaveIndex = 0; + Tk_Window tkwin; + + /* Try interpreting as an integer first: + */ + if (Tcl_GetIntFromObj(NULL, objPtr, &slaveIndex) == TCL_OK) { + if (slaveIndex < 0 || slaveIndex >= mgr->nSlaves) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Slave index ", Tcl_GetString(objPtr), " out of bounds", + NULL); + return TCL_ERROR; + } + *indexPtr = slaveIndex; + return TCL_OK; + } + + /* Try interpreting as a slave window name; + */ + if ( (*string == '.') + && (tkwin = Tk_NameToWindow(interp, string, mgr->masterWindow))) + { + slaveIndex = Ttk_SlaveIndex(mgr, tkwin); + if (slaveIndex < 0) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + string, " is not managed by ", Tk_PathName(mgr->masterWindow), + NULL); + return TCL_ERROR; + } + *indexPtr = slaveIndex; + return TCL_OK; + } + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Invalid slave specification ", string, NULL); + return TCL_ERROR; +} + +/* ++ Ttk_ReorderSlave(mgr, fromIndex, toIndex) -- + * Change slave order. + */ +void Ttk_ReorderSlave(Ttk_Manager *mgr, int fromIndex, int toIndex) +{ + Ttk_Slave *moved = mgr->slaves[fromIndex]; + + /* Shuffle down: */ + while (fromIndex > toIndex) { + mgr->slaves[fromIndex] = mgr->slaves[fromIndex - 1]; + --fromIndex; + } + /* Or, shuffle up: */ + while (fromIndex < toIndex) { + mgr->slaves[fromIndex] = mgr->slaves[fromIndex + 1]; + ++fromIndex; + } + /* ASSERT: fromIndex == toIndex */ + mgr->slaves[fromIndex] = moved; + + /* Schedule a relayout. In general, rearranging slaves + * may also change the size: + */ + ScheduleUpdate(mgr, MGR_RESIZE_REQUIRED); +} + +/* ++ Ttk_Maintainable(interp, slave, master) -- + * Utility routine. Verifies that 'master' may be used to maintain + * the geometry of 'slave' via Tk_MaintainGeometry: + * + * + 'master' is either 'slave's parent -OR- + * + 'master is a descendant of 'slave's parent. + * + 'slave' is not a toplevel window + * + 'slave' belongs to the same toplevel as 'master' + * + * Returns: 1 if OK; otherwise 0, leaving an error message in 'interp'. + */ +int Ttk_Maintainable(Tcl_Interp *interp, Tk_Window slave, Tk_Window master) +{ + Tk_Window ancestor = master, parent = Tk_Parent(slave); + + if (Tk_IsTopLevel(slave) || slave == master) { + goto badWindow; + } + + while (ancestor != parent) { + if (Tk_IsTopLevel(ancestor)) { + goto badWindow; + } + ancestor = Tk_Parent(ancestor); + } + + return 1; + +badWindow: + Tcl_AppendResult(interp, + "can't add ", Tk_PathName(slave), + " as slave of ", Tk_PathName(master), + NULL); + return 0; +} + diff --git a/generic/ttk/ttkManager.h b/generic/ttk/ttkManager.h new file mode 100644 index 0000000..d22ff98 --- /dev/null +++ b/generic/ttk/ttkManager.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2005, Joe English. Freely redistributable. + * + * Geometry manager utilities. + */ + +#ifndef _TTKMANAGER +#define _TTKMANAGER + +#include "ttkTheme.h" + +typedef struct TtkManager_ Ttk_Manager; + +/* + * Geometry manager specification record: + * + * RequestedSize computes the requested size of the master window. + * + * PlaceSlaves sets the position and size of all managed slaves + * by calling Ttk_PlaceSlave(). + * + * SlaveRemoved() is called immediately before a slave is removed. + * NB: the associated slave window may have been destroyed when this + * routine is called. + * + * SlaveRequest() is called when a slave requests a size change. + * It should return 1 if the request should propagate, 0 otherwise. + */ +typedef struct { /* Manager hooks */ + Tk_GeomMgr tkGeomMgr; /* "real" Tk Geometry Manager */ + + int (*RequestedSize)(void *managerData, int *widthPtr, int *heightPtr); + void (*PlaceSlaves)(void *managerData); + int (*SlaveRequest)(void *managerData, int slaveIndex, int w, int h); + void (*SlaveRemoved)(void *managerData, int slaveIndex); +} Ttk_ManagerSpec; + +/* + * Default implementations for Tk_GeomMgr hooks: + */ +MODULE_SCOPE void Ttk_GeometryRequestProc(ClientData, Tk_Window slave); +MODULE_SCOPE void Ttk_LostSlaveProc(ClientData, Tk_Window slave); + +/* + * Public API: + */ +MODULE_SCOPE Ttk_Manager *Ttk_CreateManager( + Ttk_ManagerSpec *, void *managerData, Tk_Window masterWindow); +MODULE_SCOPE void Ttk_DeleteManager(Ttk_Manager *); + +MODULE_SCOPE void Ttk_InsertSlave( + Ttk_Manager *, int position, Tk_Window, void *slaveData); + +MODULE_SCOPE void Ttk_ForgetSlave(Ttk_Manager *, int slaveIndex); + +MODULE_SCOPE void Ttk_ReorderSlave(Ttk_Manager *, int fromIndex, int toIndex); + /* Rearrange slave positions */ + +MODULE_SCOPE void Ttk_PlaceSlave( + Ttk_Manager *, int slaveIndex, int x, int y, int width, int height); + /* Position and map the slave */ + +MODULE_SCOPE void Ttk_UnmapSlave(Ttk_Manager *, int slaveIndex); + /* Unmap the slave */ + +MODULE_SCOPE void Ttk_ManagerSizeChanged(Ttk_Manager *); +MODULE_SCOPE void Ttk_ManagerLayoutChanged(Ttk_Manager *); + /* Notify manager that size (resp. layout) needs to be recomputed */ + +/* Utilities: + */ +MODULE_SCOPE int Ttk_SlaveIndex(Ttk_Manager *, Tk_Window); + /* Returns: index in slave array of specified window, -1 if not found */ + +MODULE_SCOPE int Ttk_GetSlaveIndexFromObj( + Tcl_Interp *, Ttk_Manager *, Tcl_Obj *, int *indexPtr); + +/* Accessor functions: + */ +MODULE_SCOPE int Ttk_NumberSlaves(Ttk_Manager *); + /* Returns: number of managed slaves */ + +MODULE_SCOPE void *Ttk_SlaveData(Ttk_Manager *, int slaveIndex); + /* Returns: client data associated with slave */ + +MODULE_SCOPE Tk_Window Ttk_SlaveWindow(Ttk_Manager *, int slaveIndex); + /* Returns: slave window */ + +MODULE_SCOPE int Ttk_Maintainable(Tcl_Interp *, Tk_Window slave, Tk_Window master); + /* Returns: 1 if master can manage slave; 0 otherwise leaving error msg */ + +#endif /* _TTKMANAGER */ diff --git a/generic/ttk/ttkNotebook.c b/generic/ttk/ttkNotebook.c new file mode 100644 index 0000000..551f4a6 --- /dev/null +++ b/generic/ttk/ttkNotebook.c @@ -0,0 +1,1413 @@ +/* + * Copyright (c) 2004, Joe English + */ + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" +#include "ttkManager.h" + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +/*------------------------------------------------------------------------ + * +++ Tab resources. + */ + +#define DEFAULT_MIN_TAB_WIDTH 24 + +static const char *const TabStateStrings[] = { "normal", "disabled", "hidden", 0 }; +typedef enum { + TAB_STATE_NORMAL, TAB_STATE_DISABLED, TAB_STATE_HIDDEN +} TAB_STATE; + +typedef struct +{ + /* Internal data: + */ + int width, height; /* Requested size of tab */ + Ttk_Box parcel; /* Tab position */ + + /* Tab options: + */ + TAB_STATE state; + + /* Child window options: + */ + Tcl_Obj *paddingObj; /* Padding inside pane */ + Ttk_Padding padding; + Tcl_Obj *stickyObj; + Ttk_Sticky sticky; + + /* Label options: + */ + Tcl_Obj *textObj; + Tcl_Obj *imageObj; + Tcl_Obj *compoundObj; + Tcl_Obj *underlineObj; + +} Tab; + +/* Two different option tables are used for tabs: + * TabOptionSpecs is used to draw the tab, and only includes resources + * relevant to the tab. + * + * PaneOptionSpecs includes additional options for child window placement + * and is used to configure the slave. + */ +static Tk_OptionSpec TabOptionSpecs[] = +{ + {TK_OPTION_STRING_TABLE, "-state", "", "", + "normal", -1,Tk_Offset(Tab,state), + 0,(ClientData)TabStateStrings,0 }, + {TK_OPTION_STRING, "-text", "text", "Text", "", + Tk_Offset(Tab,textObj), -1, 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-image", "image", "Image", NULL/*default*/, + Tk_Offset(Tab,imageObj), -1, TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound", + "none", Tk_Offset(Tab,compoundObj), -1, + 0,(ClientData)ttkCompoundStrings,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-underline", "underline", "Underline", "-1", + Tk_Offset(Tab,underlineObj), -1, 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0 } +}; + +static Tk_OptionSpec PaneOptionSpecs[] = +{ + {TK_OPTION_STRING, "-padding", "padding", "Padding", "0", + Tk_Offset(Tab,paddingObj), -1, 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-sticky", "sticky", "Sticky", "nsew", + Tk_Offset(Tab,stickyObj), -1, 0,0,GEOMETRY_CHANGED }, + + WIDGET_INHERIT_OPTIONS(TabOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Notebook resources. + */ +typedef struct +{ + Tcl_Obj *widthObj; /* Default width */ + Tcl_Obj *heightObj; /* Default height */ + Tcl_Obj *paddingObj; /* Padding around notebook */ + + Ttk_Manager *mgr; /* Geometry manager */ + Tk_OptionTable tabOptionTable; /* Tab options */ + Tk_OptionTable paneOptionTable; /* Tab+pane options */ + int currentIndex; /* index of currently selected tab */ + int activeIndex; /* index of currently active tab */ + Ttk_Layout tabLayout; /* Sublayout for tabs */ + + Ttk_Box clientArea; /* Where to pack slave widgets */ +} NotebookPart; + +typedef struct +{ + WidgetCore core; + NotebookPart notebook; +} Notebook; + +static Tk_OptionSpec NotebookOptionSpecs[] = +{ + {TK_OPTION_INT, "-width", "width", "Width", "0", + Tk_Offset(Notebook,notebook.widthObj),-1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-height", "height", "Height", "0", + Tk_Offset(Notebook,notebook.heightObj),-1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-padding", "padding", "Padding", NULL, + Tk_Offset(Notebook,notebook.paddingObj),-1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/* Notebook style options: + */ +typedef struct +{ + Ttk_PositionSpec tabPosition; /* Where to place tabs */ + Ttk_Padding tabMargins; /* Margins around tab row */ + Ttk_PositionSpec tabPlacement; /* How to pack tabs within tab row */ + Ttk_Orient tabOrient; /* ... */ + int minTabWidth; /* Minimum tab width */ + Ttk_Padding padding; /* External padding */ +} NotebookStyle; + +static void NotebookStyleOptions(Notebook *nb, NotebookStyle *nbstyle) +{ + Tcl_Obj *objPtr; + + nbstyle->tabPosition = TTK_PACK_TOP | TTK_STICK_W; + if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabposition", 0)) != 0) { + TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPosition); + } + + /* Guess default tabPlacement as function of tabPosition: + */ + if (nbstyle->tabPosition & TTK_PACK_LEFT) { + nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_E; + } else if (nbstyle->tabPosition & TTK_PACK_RIGHT) { + nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_W; + } else if (nbstyle->tabPosition & TTK_PACK_BOTTOM) { + nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_N; + } else { /* Assume TTK_PACK_TOP */ + nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_S; + } + if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabplacement", 0)) != 0) { + TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPlacement); + } + + /* Compute tabOrient as function of tabPlacement: + */ + if (nbstyle->tabPlacement & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) { + nbstyle->tabOrient = TTK_ORIENT_HORIZONTAL; + } else { + nbstyle->tabOrient = TTK_ORIENT_VERTICAL; + } + + nbstyle->tabMargins = Ttk_UniformPadding(0); + if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabmargins", 0)) != 0) { + Ttk_GetBorderFromObj(NULL, objPtr, &nbstyle->tabMargins); + } + + nbstyle->padding = Ttk_UniformPadding(0); + if ((objPtr = Ttk_QueryOption(nb->core.layout, "-padding", 0)) != 0) { + Ttk_GetPaddingFromObj(NULL,nb->core.tkwin,objPtr,&nbstyle->padding); + } + + nbstyle->minTabWidth = DEFAULT_MIN_TAB_WIDTH; + if ((objPtr = Ttk_QueryOption(nb->core.layout, "-mintabwidth", 0)) != 0) { + Tcl_GetIntFromObj(NULL, objPtr, &nbstyle->minTabWidth); + } +} + +/*------------------------------------------------------------------------ + * +++ Tab management. + */ + +static Tab *CreateTab(Tcl_Interp *interp, Notebook *nb, Tk_Window slaveWindow) +{ + Tk_OptionTable optionTable = nb->notebook.paneOptionTable; + void *record = ckalloc(sizeof(Tab)); + memset(record, 0, sizeof(Tab)); + + if (Tk_InitOptions(interp, record, optionTable, slaveWindow) != TCL_OK) { + ckfree(record); + return NULL; + } + + return record; +} + +static void DestroyTab(Notebook *nb, Tab *tab) +{ + void *record = tab; + Tk_FreeConfigOptions(record, nb->notebook.paneOptionTable, nb->core.tkwin); + ckfree(record); +} + +static int ConfigureTab( + Tcl_Interp *interp, Notebook *nb, Tab *tab, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Ttk_Sticky sticky = tab->sticky; + Ttk_Padding padding = tab->padding; + Tk_SavedOptions savedOptions; + int mask = 0; + + if (Tk_SetOptions(interp, (ClientData)tab, nb->notebook.paneOptionTable, + objc, objv, slaveWindow, &savedOptions, &mask) != TCL_OK) + { + return TCL_ERROR; + } + + /* Check options: + * @@@ TODO: validate -image option. + */ + if (Ttk_GetStickyFromObj(interp, tab->stickyObj, &sticky) != TCL_OK) + { + goto error; + } + if (Ttk_GetPaddingFromObj(interp, slaveWindow, tab->paddingObj, &padding) + != TCL_OK) + { + goto error; + } + + tab->sticky = sticky; + tab->padding = padding; + + Tk_FreeSavedOptions(&savedOptions); + Ttk_ManagerSizeChanged(nb->notebook.mgr); + TtkRedisplayWidget(&nb->core); + + return TCL_OK; +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + +/* + * IdentifyTab -- + * Return the index of the tab at point x,y, + * or -1 if no tab at that point. + */ +static int IdentifyTab(Notebook *nb, int x, int y) +{ + int index; + for (index = 0; index < Ttk_NumberSlaves(nb->notebook.mgr); ++index) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr,index); + if ( tab->state != TAB_STATE_HIDDEN + && Ttk_BoxContains(tab->parcel, x,y)) + { + return index; + } + } + return -1; +} + +/* + * ActivateTab -- + * Set the active tab index, redisplay if necessary. + */ +static void ActivateTab(Notebook *nb, int index) +{ + if (index != nb->notebook.activeIndex) { + nb->notebook.activeIndex = index; + TtkRedisplayWidget(&nb->core); + } +} + +/* + * TabState -- + * Return the state of the specified tab, based on + * notebook state, currentIndex, activeIndex, and user-specified tab state. + * The USER1 bit is set for the leftmost tab, and USER2 + * is set for the rightmost tab. + */ +static Ttk_State TabState(Notebook *nb, int index) +{ + Ttk_State state = nb->core.state; + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index); + + if (index == nb->notebook.currentIndex) { + state |= TTK_STATE_SELECTED; + } else { + state &= ~TTK_STATE_FOCUS; + } + + if (index == nb->notebook.activeIndex) { + state |= TTK_STATE_ACTIVE; + } + if (index == 0) { + state |= TTK_STATE_USER1; + } + if (index == Ttk_NumberSlaves(nb->notebook.mgr) - 1) { + state |= TTK_STATE_USER2; + } + if (tab->state == TAB_STATE_DISABLED) { + state |= TTK_STATE_DISABLED; + } + + return state; +} + +/*------------------------------------------------------------------------ + * +++ Geometry management - size computation. + */ + +/* TabrowSize -- + * Compute max height and total width of all tabs (horizontal layouts) + * or total height and max width (vertical layouts). + * + * Side effects: + * Sets width and height fields for all tabs. + * + * Notes: + * Hidden tabs are included in the perpendicular computation + * (max height/width) but not parallel (total width/height). + */ +static void TabrowSize( + Notebook *nb, Ttk_Orient orient, int *widthPtr, int *heightPtr) +{ + Ttk_Layout tabLayout = nb->notebook.tabLayout; + int tabrowWidth = 0, tabrowHeight = 0; + int i; + + for (i = 0; i < Ttk_NumberSlaves(nb->notebook.mgr); ++i) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i); + Ttk_State tabState = TabState(nb,i); + + Ttk_RebindSublayout(tabLayout, tab); + Ttk_LayoutSize(tabLayout,tabState,&tab->width,&tab->height); + + if (orient == TTK_ORIENT_HORIZONTAL) { + tabrowHeight = MAX(tabrowHeight, tab->height); + if (tab->state != TAB_STATE_HIDDEN) { tabrowWidth += tab->width; } + } else { + tabrowWidth = MAX(tabrowWidth, tab->width); + if (tab->state != TAB_STATE_HIDDEN) { tabrowHeight += tab->height; } + } + } + + *widthPtr = tabrowWidth; + *heightPtr = tabrowHeight; +} + +/* NotebookSize -- GM and widget size hook. + * + * Total height is tab height + client area height + pane internal padding + * Total width is max(client width, tab width) + pane internal padding + * Client area size determined by max size of slaves, + * overridden by -width and/or -height if nonzero. + */ + +static int NotebookSize(void *clientData, int *widthPtr, int *heightPtr) +{ + Notebook *nb = clientData; + NotebookStyle nbstyle; + Ttk_Padding padding; + Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client"); + int clientWidth = 0, clientHeight = 0, + reqWidth = 0, reqHeight = 0, + tabrowWidth = 0, tabrowHeight = 0; + int i; + + NotebookStyleOptions(nb, &nbstyle); + + /* Compute max requested size of all slaves: + */ + for (i = 0; i < Ttk_NumberSlaves(nb->notebook.mgr); ++i) { + Tk_Window slaveWindow = Ttk_SlaveWindow(nb->notebook.mgr, i); + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i); + int slaveWidth + = Tk_ReqWidth(slaveWindow) + Ttk_PaddingWidth(tab->padding); + int slaveHeight + = Tk_ReqHeight(slaveWindow) + Ttk_PaddingHeight(tab->padding); + + clientWidth = MAX(clientWidth, slaveWidth); + clientHeight = MAX(clientHeight, slaveHeight); + } + + /* Client width/height overridable by widget options: + */ + Tcl_GetIntFromObj(NULL, nb->notebook.widthObj,&reqWidth); + Tcl_GetIntFromObj(NULL, nb->notebook.heightObj,&reqHeight); + if (reqWidth > 0) + clientWidth = reqWidth; + if (reqHeight > 0) + clientHeight = reqHeight; + + /* Tab row: + */ + TabrowSize(nb, nbstyle.tabOrient, &tabrowWidth, &tabrowHeight); + tabrowHeight += Ttk_PaddingHeight(nbstyle.tabMargins); + tabrowWidth += Ttk_PaddingWidth(nbstyle.tabMargins); + + /* Account for exterior and interior padding: + */ + padding = nbstyle.padding; + if (clientNode) { + Ttk_Padding ipad = + Ttk_LayoutNodeInternalPadding(nb->core.layout, clientNode); + padding = Ttk_AddPadding(padding, ipad); + } + + if (nbstyle.tabPosition & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) { + *widthPtr = MAX(tabrowWidth, clientWidth) + Ttk_PaddingWidth(padding); + *heightPtr = tabrowHeight + clientHeight + Ttk_PaddingHeight(padding); + } else { + *widthPtr = tabrowWidth + clientWidth + Ttk_PaddingWidth(padding); + *heightPtr = MAX(tabrowHeight,clientHeight) + Ttk_PaddingHeight(padding); + } + + return 1; +} + +/*------------------------------------------------------------------------ + * +++ Geometry management - layout. + */ + +/* SqueezeTabs -- + * Squeeze or stretch tabs to fit within the tab area parcel. + * + * All tabs are adjusted by an equal amount, but will not be made + * smaller than the minimum width. (If all the tabs still do + * not fit in the available space, the rightmost ones will + * be further squozen by PlaceTabs()). + * + * The algorithm does not always yield an optimal layout, but does + * have the important property that decreasing the available width + * by one pixel will cause at most one tab to shrink by one pixel; + * this means that tabs resize "smoothly" when the window shrinks + * and grows. + * + * @@@ <<NOTE-TABPOSITION>> bug: only works for horizontal orientations + * @@@ <<NOTE-SQUEEZE-HIDDEN>> does not account for hidden tabs. + */ + +static void SqueezeTabs( + Notebook *nb, int needed, int available, int minTabWidth) +{ + int nTabs = Ttk_NumberSlaves(nb->notebook.mgr); + + if (nTabs > 0) { + int difference = available - needed, + delta = difference / nTabs, + remainder = difference % nTabs, + slack = 0; + int i; + + if (remainder < 0) { remainder += nTabs; --delta; } + + for (i = 0; i < nTabs; ++i) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr,i); + int adj = delta + (i < remainder) + slack; + + if (tab->width + adj >= minTabWidth) { + tab->width += adj; + slack = 0; + } else { + slack = adj - (minTabWidth - tab->width); + tab->width = minTabWidth; + } + } + } +} + +/* PlaceTabs -- + * Compute all tab parcels. + */ +static void PlaceTabs( + Notebook *nb, Ttk_Box tabrowBox, Ttk_PositionSpec tabPlacement) +{ + Ttk_Layout tabLayout = nb->notebook.tabLayout; + int nTabs = Ttk_NumberSlaves(nb->notebook.mgr); + int i; + + for (i = 0; i < nTabs; ++i) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i); + Ttk_State tabState = TabState(nb, i); + + if (tab->state != TAB_STATE_HIDDEN) { + Ttk_Padding expand = Ttk_UniformPadding(0); + Tcl_Obj *expandObj = Ttk_QueryOption(tabLayout,"-expand",tabState); + + if (expandObj) { + Ttk_GetBorderFromObj(NULL, expandObj, &expand); + } + + tab->parcel = + Ttk_ExpandBox( + Ttk_PositionBox(&tabrowBox, + tab->width, tab->height, tabPlacement), + expand); + } + } +} + +/* NotebookDoLayout -- + * Computes notebook layout and places tabs. + * + * Side effects: + * Sets clientArea, used to place slave panes. + */ +static void NotebookDoLayout(void *recordPtr) +{ + Notebook *nb = recordPtr; + Tk_Window nbwin = nb->core.tkwin; + Ttk_Box cavity = Ttk_WinBox(nbwin); + int tabrowWidth = 0, tabrowHeight = 0; + Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client"); + Ttk_Box tabrowBox; + NotebookStyle nbstyle; + + NotebookStyleOptions(nb, &nbstyle); + + /* Notebook internal padding: + */ + cavity = Ttk_PadBox(cavity, nbstyle.padding); + + /* Layout for notebook background (base layout): + */ + Ttk_PlaceLayout(nb->core.layout, nb->core.state, Ttk_WinBox(nbwin)); + + /* Place tabs: + */ + TabrowSize(nb, nbstyle.tabOrient, &tabrowWidth, &tabrowHeight); + tabrowBox = Ttk_PadBox( + Ttk_PositionBox(&cavity, + tabrowWidth + Ttk_PaddingWidth(nbstyle.tabMargins), + tabrowHeight + Ttk_PaddingHeight(nbstyle.tabMargins), + nbstyle.tabPosition), + nbstyle.tabMargins); + + SqueezeTabs(nb, tabrowWidth, tabrowBox.width, nbstyle.minTabWidth); + PlaceTabs(nb, tabrowBox, nbstyle.tabPlacement); + + /* Layout for client area frame: + */ + if (clientNode) { + Ttk_PlaceElement(nb->core.layout, clientNode, cavity); + cavity = Ttk_LayoutNodeInternalParcel(nb->core.layout, clientNode); + } + + if (cavity.height <= 0) cavity.height = 1; + if (cavity.width <= 0) cavity.width = 1; + + nb->notebook.clientArea = cavity; +} + +/* + * NotebookPlaceSlave -- + * Set the position and size of a child widget + * based on the current client area and slave options: + */ +static void NotebookPlaceSlave(Notebook *nb, int slaveIndex) +{ + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, slaveIndex); + Tk_Window slaveWindow = Ttk_SlaveWindow(nb->notebook.mgr, slaveIndex); + Ttk_Box slaveBox = + Ttk_StickBox(Ttk_PadBox(nb->notebook.clientArea, tab->padding), + Tk_ReqWidth(slaveWindow), Tk_ReqHeight(slaveWindow),tab->sticky); + + Ttk_PlaceSlave(nb->notebook.mgr, slaveIndex, + slaveBox.x, slaveBox.y, slaveBox.width, slaveBox.height); +} + +/* NotebookPlaceSlaves -- + * Geometry manager hook. + */ +static void NotebookPlaceSlaves(void *recordPtr) +{ + Notebook *nb = recordPtr; + int currentIndex = nb->notebook.currentIndex; + if (currentIndex >= 0) { + NotebookDoLayout(nb); + NotebookPlaceSlave(nb, currentIndex); + } +} + +/* + * SelectTab(nb, index) -- + * Change the currently-selected tab. + */ +static void SelectTab(Notebook *nb, int index) +{ + Tab *tab = Ttk_SlaveData(nb->notebook.mgr,index); + int currentIndex = nb->notebook.currentIndex; + + if (index == currentIndex) { + return; + } + + if (TabState(nb, index) & TTK_STATE_DISABLED) { + return; + } + + /* Unhide the tab if it is currently hidden and being selected. + */ + if (tab->state == TAB_STATE_HIDDEN) { + tab->state = TAB_STATE_NORMAL; + } + + if (currentIndex >= 0) { + Ttk_UnmapSlave(nb->notebook.mgr, currentIndex); + } + + NotebookPlaceSlave(nb, index); + + nb->notebook.currentIndex = index; + TtkRedisplayWidget(&nb->core); + + TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged"); +} + +/* NextTab -- + * Returns the index of the next tab after the specified tab + * in the normal state (e.g., not hidden or disabled), + * or -1 if all tabs are disabled or hidden. + */ +static int NextTab(Notebook *nb, int index) +{ + int nTabs = Ttk_NumberSlaves(nb->notebook.mgr); + int nextIndex; + + /* Scan forward for following usable tab: + */ + for (nextIndex = index + 1; nextIndex < nTabs; ++nextIndex) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, nextIndex); + if (tab->state == TAB_STATE_NORMAL) { + return nextIndex; + } + } + + /* Not found -- scan backwards. + */ + for (nextIndex = index - 1; nextIndex >= 0; --nextIndex) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, nextIndex); + if (tab->state == TAB_STATE_NORMAL) { + return nextIndex; + } + } + + /* Still nothing. Give up. + */ + return -1; +} + +/* SelectNearestTab -- + * Handles the case where the current tab is forgotten, hidden, + * or destroyed. + * + * Unmap the current tab and schedule the next available one + * to be mapped at the next GM update. + */ +static void SelectNearestTab(Notebook *nb) +{ + int currentIndex = nb->notebook.currentIndex; + int nextIndex = NextTab(nb, currentIndex); + + if (currentIndex >= 0) { + Ttk_UnmapSlave(nb->notebook.mgr, currentIndex); + } + if (currentIndex != nextIndex) { + TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged"); + } + + nb->notebook.currentIndex = nextIndex; + Ttk_ManagerLayoutChanged(nb->notebook.mgr); + TtkRedisplayWidget(&nb->core); +} + +/* TabRemoved -- GM SlaveRemoved hook. + * Select the next tab if the current one is being removed. + * Adjust currentIndex to account for removed slave. + */ +static void TabRemoved(void *managerData, int index) +{ + Notebook *nb = managerData; + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index); + + if (index == nb->notebook.currentIndex) { + SelectNearestTab(nb); + } + + if (index < nb->notebook.currentIndex) { + --nb->notebook.currentIndex; + } + + DestroyTab(nb, tab); + + TtkRedisplayWidget(&nb->core); +} + +static int TabRequest(void *managerData, int index, int width, int height) +{ + return 1; +} + +/* AddTab -- + * Add new tab at specified index. + */ +static int AddTab( + Tcl_Interp *interp, Notebook *nb, + int destIndex, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Tab *tab; + if (!Ttk_Maintainable(interp, slaveWindow, nb->core.tkwin)) { + return TCL_ERROR; + } +#if 0 /* can't happen */ + if (Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow) >= 0) { + Tcl_AppendResult(interp, + Tk_PathName(slaveWindow), " already added", + NULL); + return TCL_ERROR; + } +#endif + + /* Create and insert tab. + */ + tab = CreateTab(interp, nb, slaveWindow); + if (!tab) { + return TCL_ERROR; + } + if (ConfigureTab(interp, nb, tab, slaveWindow, objc, objv) != TCL_OK) { + DestroyTab(nb, tab); + return TCL_ERROR; + } + + Ttk_InsertSlave(nb->notebook.mgr, destIndex, slaveWindow, tab); + + /* Adjust indices and/or autoselect first tab: + */ + if (nb->notebook.currentIndex < 0) { + SelectTab(nb, destIndex); + } else if (nb->notebook.currentIndex >= destIndex) { + ++nb->notebook.currentIndex; + } + + return TCL_OK; +} + +static Ttk_ManagerSpec NotebookManagerSpec = { + { "notebook", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + NotebookSize, + NotebookPlaceSlaves, + TabRequest, + TabRemoved +}; + +/*------------------------------------------------------------------------ + * +++ Event handlers. + */ + +/* NotebookEventHandler -- + * Tracks the active tab. + */ +static const int NotebookEventMask + = StructureNotifyMask + | PointerMotionMask + | LeaveWindowMask + ; +static void NotebookEventHandler(ClientData clientData, XEvent *eventPtr) +{ + Notebook *nb = clientData; + + if (eventPtr->type == DestroyNotify) { /* Remove self */ + Tk_DeleteEventHandler(nb->core.tkwin, + NotebookEventMask, NotebookEventHandler, clientData); + } else if (eventPtr->type == MotionNotify) { + int index = IdentifyTab(nb, eventPtr->xmotion.x, eventPtr->xmotion.y); + ActivateTab(nb, index); + } else if (eventPtr->type == LeaveNotify) { + ActivateTab(nb, -1); + } +} + +/*------------------------------------------------------------------------ + * +++ Utilities. + */ + +/* FindTabIndex -- + * Find the index of the specified tab. + * Tab identifiers are one of: + * + * + positional specifications @x,y, + * + "current", + * + numeric indices [0..nTabs], + * + slave window names + * + * Stores index of specified tab in *index_rtn, -1 if not found. + * + * Returns TCL_ERROR and leaves an error message in interp->result + * if the tab identifier was incorrect. + * + * See also: GetTabIndex. + */ +static int FindTabIndex( + Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn) +{ + const char *string = Tcl_GetString(objPtr); + int x, y; + + *index_rtn = -1; + + /* Check for @x,y ... + */ + if (string[0] == '@' && sscanf(string, "@%d,%d",&x,&y) == 2) { + *index_rtn = IdentifyTab(nb, x, y); + return TCL_OK; + } + + /* ... or "current" ... + */ + if (!strcmp(string, "current")) { + *index_rtn = nb->notebook.currentIndex; + return TCL_OK; + } + + /* ... or integer index or slave window name: + */ + if (Ttk_GetSlaveIndexFromObj( + interp, nb->notebook.mgr, objPtr, index_rtn) == TCL_OK) + { + return TCL_OK; + } + + /* Nothing matched; Ttk_GetSlaveIndexFromObj will have left error message. + */ + return TCL_ERROR; +} + +/* GetTabIndex -- + * Get the index of an existing tab. + * Tab identifiers are as per FindTabIndex. + * Returns TCL_ERROR if the tab does not exist. + */ +static int GetTabIndex( + Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn) +{ + int status = FindTabIndex(interp, nb, objPtr, index_rtn); + + if (status == TCL_OK && *index_rtn < 0) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "tab '", Tcl_GetString(objPtr), "' not found", + NULL); + status = TCL_ERROR; + } + return status; +} + +/*------------------------------------------------------------------------ + * +++ Widget command routines. + */ + +/* $nb add window ?options ... ? + */ +static int NotebookAddCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + int index = Ttk_NumberSlaves(nb->notebook.mgr); + Tk_Window slaveWindow; + int slaveIndex; + Tab *tab; + + if (objc <= 2 || objc % 2 != 1) { + Tcl_WrongNumArgs(interp, 2, objv, "window ?-option value ...?"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow(interp,Tcl_GetString(objv[2]),nb->core.tkwin); + if (!slaveWindow) { + return TCL_ERROR; + } + slaveIndex = Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow); + + if (slaveIndex < 0) { /* New tab */ + return AddTab(interp, nb, index, slaveWindow, objc-3,objv+3); + } + + tab = Ttk_SlaveData(nb->notebook.mgr, slaveIndex); + if (tab->state == TAB_STATE_HIDDEN) { + tab->state = TAB_STATE_NORMAL; + } + if (ConfigureTab(interp, nb, tab, slaveWindow, objc-4,objv+4) != TCL_OK) { + return TCL_ERROR; + } + + TtkRedisplayWidget(&nb->core); + + return TCL_OK; +} + +/* $nb insert $index $tab ?-option value ...? + * Insert new tab, or move existing one. + */ +static int NotebookInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + int current = nb->notebook.currentIndex; + int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr); + int srcIndex, destIndex; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?"); + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(objv[2]), "end")) { + destIndex = Ttk_NumberSlaves(nb->notebook.mgr); + } else if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp, nb->notebook.mgr, objv[2], &destIndex)) { + return TCL_ERROR; + } + + if (Tcl_GetString(objv[3])[0] == '.') { + /* Window name -- could be new or existing slave. + */ + Tk_Window slaveWindow = + Tk_NameToWindow(interp,Tcl_GetString(objv[3]),nb->core.tkwin); + + if (!slaveWindow) { + return TCL_ERROR; + } + + srcIndex = Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow); + if (srcIndex < 0) { /* New slave */ + return AddTab(interp, nb, destIndex, slaveWindow, objc-4,objv+4); + } + } else if (Ttk_GetSlaveIndexFromObj( + interp, nb->notebook.mgr, objv[3], &srcIndex) != TCL_OK) + { + return TCL_ERROR; + } + + /* Move existing slave: + */ + if (ConfigureTab(interp, nb, + Ttk_SlaveData(nb->notebook.mgr,srcIndex), + Ttk_SlaveWindow(nb->notebook.mgr,srcIndex), + objc-4,objv+4) != TCL_OK) + { + return TCL_ERROR; + } + + if (destIndex >= nSlaves) { + destIndex = nSlaves - 1; + } + Ttk_ReorderSlave(nb->notebook.mgr, srcIndex, destIndex); + + /* Adjust internal indexes: + */ + nb->notebook.activeIndex = -1; + if (current == srcIndex) { + nb->notebook.currentIndex = destIndex; + } else if (destIndex <= current && current < srcIndex) { + ++nb->notebook.currentIndex; + } else if (srcIndex < current && current <= destIndex) { + --nb->notebook.currentIndex; + } + + TtkRedisplayWidget(&nb->core); + + return TCL_OK; +} + +/* $nb forget $tab -- + * Removes the specified tab. + */ +static int NotebookForgetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tab"); + return TCL_ERROR; + } + + if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + + Ttk_ForgetSlave(nb->notebook.mgr, index); + TtkRedisplayWidget(&nb->core); + + return TCL_OK; +} + +/* $nb hide $tab -- + * Hides the specified tab. + */ +static int NotebookHideCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + int index; + Tab *tab; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tab"); + return TCL_ERROR; + } + + if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + + tab = Ttk_SlaveData(nb->notebook.mgr, index); + tab->state = TAB_STATE_HIDDEN; + if (index == nb->notebook.currentIndex) { + SelectNearestTab(nb); + } + + TtkRedisplayWidget(&nb->core); + + return TCL_OK; +} + +/* $nb identify $x $y -- + * Returns name of tab element at $x,$y; empty string if none. + */ +static int NotebookIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + static const char *whatTable[] = { "element", "tab", NULL }; + enum { IDENTIFY_ELEMENT, IDENTIFY_TAB }; + int what = IDENTIFY_ELEMENT; + Notebook *nb = recordPtr; + Ttk_Element element = NULL; + int x, y, tabIndex; + + if (objc < 4 || objc > 5) { + Tcl_WrongNumArgs(interp, 2,objv, "?what? x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK + || (objc == 5 && + Tcl_GetIndexFromObj(interp, objv[2], whatTable, "option", 0, &what) + != TCL_OK) + ) { + return TCL_ERROR; + } + + tabIndex = IdentifyTab(nb, x, y); + if (tabIndex >= 0) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, tabIndex); + Ttk_State state = TabState(nb, tabIndex); + Ttk_Layout tabLayout = nb->notebook.tabLayout; + + Ttk_RebindSublayout(tabLayout, tab); + Ttk_PlaceLayout(tabLayout, state, tab->parcel); + + element = Ttk_IdentifyElement(tabLayout, x, y); + } + + switch (what) { + case IDENTIFY_ELEMENT: + if (element) { + const char *elementName = Ttk_ElementName(element); + Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1)); + } + break; + case IDENTIFY_TAB: + if (tabIndex >= 0) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(tabIndex)); + } + break; + } + return TCL_OK; +} + +/* $nb index $item -- + * Returns the integer index of the tab specified by $item, + * the empty string if $item does not identify a tab. + * See above for valid item formats. + */ +static int NotebookIndexCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + int index, status; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tab"); + return TCL_ERROR; + } + + /* + * Special-case for "end": + */ + if (!strcmp("end", Tcl_GetString(objv[2]))) { + int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr); + Tcl_SetObjResult(interp, Tcl_NewIntObj(nSlaves)); + return TCL_OK; + } + + status = FindTabIndex(interp, nb, objv[2], &index); + if (status == TCL_OK && index >= 0) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + } + + return status; +} + +/* $nb select ?$item? -- + * Select the specified tab, or return the widget path of + * the currently-selected pane. + */ +static int NotebookSelectCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + + if (objc == 2) { + if (nb->notebook.currentIndex >= 0) { + Tk_Window pane = Ttk_SlaveWindow( + nb->notebook.mgr, nb->notebook.currentIndex); + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(pane), -1)); + } + return TCL_OK; + } else if (objc == 3) { + int index, status = GetTabIndex(interp, nb, objv[2], &index); + if (status == TCL_OK) { + SelectTab(nb, index); + } + return status; + } /*else*/ + Tcl_WrongNumArgs(interp, 2, objv, "?tab?"); + return TCL_ERROR; +} + +/* $nb tabs -- + * Return list of tabs. + */ +static int NotebookTabsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + Ttk_Manager *mgr = nb->notebook.mgr; + Tcl_Obj *result; + int i; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + result = Tcl_NewListObj(0, NULL); + for (i = 0; i < Ttk_NumberSlaves(mgr); ++i) { + const char *pathName = Tk_PathName(Ttk_SlaveWindow(mgr,i)); + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(pathName,-1)); + } + Tcl_SetObjResult(interp, result); + + return TCL_OK; +} + +/* $nb tab $tab ?-option ?value -option value...?? + */ +static int NotebookTabCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Notebook *nb = recordPtr; + Ttk_Manager *mgr = nb->notebook.mgr; + int index; + Tk_Window slaveWindow; + Tab *tab; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tab ?-option ?value??..."); + return TCL_ERROR; + } + + if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + + tab = Ttk_SlaveData(mgr, index); + slaveWindow = Ttk_SlaveWindow(mgr, index); + + if (objc == 3) { + return TtkEnumerateOptions(interp, tab, + PaneOptionSpecs, nb->notebook.paneOptionTable, slaveWindow); + } else if (objc == 4) { + return TtkGetOptionValue(interp, tab, objv[3], + nb->notebook.paneOptionTable, slaveWindow); + } /* else */ + + if (ConfigureTab(interp, nb, tab, slaveWindow, objc-3,objv+3) != TCL_OK) { + return TCL_ERROR; + } + + /* If the current tab has become disabled or hidden, + * select the next nondisabled, unhidden one: + */ + if (index == nb->notebook.currentIndex && tab->state != TAB_STATE_NORMAL) { + SelectNearestTab(nb); + } + + return TCL_OK; +} + +/* Subcommand table: + */ +static const Ttk_Ensemble NotebookCommands[] = { + { "add", NotebookAddCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "forget", NotebookForgetCommand,0 }, + { "hide", NotebookHideCommand,0 }, + { "identify", NotebookIdentifyCommand,0 }, + { "index", NotebookIndexCommand,0 }, + { "insert", NotebookInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "select", NotebookSelectCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "tab", NotebookTabCommand,0 }, + { "tabs", NotebookTabsCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget class hooks. + */ + +static void NotebookInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Notebook *nb = recordPtr; + + nb->notebook.mgr = Ttk_CreateManager( + &NotebookManagerSpec, recordPtr, nb->core.tkwin); + + nb->notebook.tabOptionTable = Tk_CreateOptionTable(interp,TabOptionSpecs); + nb->notebook.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs); + + nb->notebook.currentIndex = -1; + nb->notebook.activeIndex = -1; + nb->notebook.tabLayout = 0; + + nb->notebook.clientArea = Ttk_MakeBox(0,0,1,1); + + Tk_CreateEventHandler( + nb->core.tkwin, NotebookEventMask, NotebookEventHandler, recordPtr); +} + +static void NotebookCleanup(void *recordPtr) +{ + Notebook *nb = recordPtr; + + Ttk_DeleteManager(nb->notebook.mgr); + if (nb->notebook.tabLayout) + Ttk_FreeLayout(nb->notebook.tabLayout); +} + +static int NotebookConfigure(Tcl_Interp *interp, void *clientData, int mask) +{ + Notebook *nb = clientData; + + /* + * Error-checks: + */ + if (nb->notebook.paddingObj) { + /* Check for valid -padding: */ + Ttk_Padding unused; + if (Ttk_GetPaddingFromObj( + interp, nb->core.tkwin, nb->notebook.paddingObj, &unused) + != TCL_OK) { + return TCL_ERROR; + } + } + + return TtkCoreConfigure(interp, clientData, mask); +} + +/* NotebookGetLayout -- + * GetLayout widget hook. + */ +static Ttk_Layout NotebookGetLayout( + Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Notebook *nb = recordPtr; + Ttk_Layout notebookLayout = TtkWidgetGetLayout(interp, theme, recordPtr); + Ttk_Layout tabLayout; + + if (!notebookLayout) { + return NULL; + } + + tabLayout = Ttk_CreateSublayout( + interp, theme, notebookLayout, ".Tab", nb->notebook.tabOptionTable); + + if (tabLayout) { + if (nb->notebook.tabLayout) { + Ttk_FreeLayout(nb->notebook.tabLayout); + } + nb->notebook.tabLayout = tabLayout; + } + + return notebookLayout; +} + +/*------------------------------------------------------------------------ + * +++ Display routines. + */ + +static void DisplayTab(Notebook *nb, int index, Drawable d) +{ + Ttk_Layout tabLayout = nb->notebook.tabLayout; + Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index); + Ttk_State state = TabState(nb, index); + + if (tab->state != TAB_STATE_HIDDEN) { + Ttk_RebindSublayout(tabLayout, tab); + Ttk_PlaceLayout(tabLayout, state, tab->parcel); + Ttk_DrawLayout(tabLayout, state, d); + } +} + +static void NotebookDisplay(void *clientData, Drawable d) +{ + Notebook *nb = clientData; + int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr); + int index; + + /* Draw notebook background (base layout): + */ + Ttk_DrawLayout(nb->core.layout, nb->core.state, d); + + /* Draw tabs from left to right, but draw the current tab last + * so it will overwrite its neighbors. + */ + for (index = 0; index < nSlaves; ++index) { + if (index != nb->notebook.currentIndex) { + DisplayTab(nb, index, d); + } + } + if (nb->notebook.currentIndex >= 0) { + DisplayTab(nb, nb->notebook.currentIndex, d); + } +} + +/*------------------------------------------------------------------------ + * +++ Widget specification and layout definitions. + */ + +static WidgetSpec NotebookWidgetSpec = +{ + "TNotebook", /* className */ + sizeof(Notebook), /* recordSize */ + NotebookOptionSpecs, /* optionSpecs */ + NotebookCommands, /* subcommands */ + NotebookInitialize, /* initializeProc */ + NotebookCleanup, /* cleanupProc */ + NotebookConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + NotebookGetLayout, /* getLayoutProc */ + NotebookSize, /* geometryProc */ + NotebookDoLayout, /* layoutProc */ + NotebookDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(NotebookLayout) + TTK_NODE("Notebook.client", TTK_FILL_BOTH) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(TabLayout) + TTK_GROUP("Notebook.tab", TTK_FILL_BOTH, + TTK_GROUP("Notebook.padding", TTK_PACK_TOP|TTK_FILL_BOTH, + TTK_GROUP("Notebook.focus", TTK_PACK_TOP|TTK_FILL_BOTH, + TTK_NODE("Notebook.label", TTK_PACK_TOP)))) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkNotebook_Init(Tcl_Interp *interp) +{ + Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(themePtr, "Tab", TabLayout); + Ttk_RegisterLayout(themePtr, "TNotebook", NotebookLayout); + + RegisterWidget(interp, "ttk::notebook", &NotebookWidgetSpec); +} + +/*EOF*/ diff --git a/generic/ttk/ttkPanedwindow.c b/generic/ttk/ttkPanedwindow.c new file mode 100644 index 0000000..b301372 --- /dev/null +++ b/generic/ttk/ttkPanedwindow.c @@ -0,0 +1,975 @@ +/* + * Copyright (c) 2005, Joe English. Freely redistributable. + * + * ttk::panedwindow widget implementation. + * + * TODO: track active/pressed sash. + */ + +#include <string.h> +#include <tk.h> +#include "ttkManager.h" +#include "ttkTheme.h" +#include "ttkWidget.h" + +/*------------------------------------------------------------------------ + * +++ Layout algorithm. + * + * (pos=x/y, size=width/height, depending on -orient=horizontal/vertical) + * + * Each pane carries two pieces of state: the request size and the + * position of the following sash. (The final pane has no sash, + * its sash position is used as a sentinel value). + * + * Pane geometry is determined by the sash positions. + * When resizing, sash positions are computed from the request sizes, + * the available space, and pane weights (see PlaceSashes()). + * This ensures continuous resize behavior (that is: changing + * the size by X pixels then changing the size by Y pixels + * gives the same result as changing the size by X+Y pixels + * in one step). + * + * The request size is initially set to the slave window's requested size. + * When the user drags a sash, each pane's request size is set to its + * actual size. This ensures that panes "stay put" on the next resize. + * + * If reqSize == 0, use 0 for the weight as well. This ensures that + * "collapsed" panes stay collapsed during a resize, regardless of + * their nominal -weight. + * + * +++ Invariants. + * + * #sash = #pane - 1 + * pos(pane[0]) = 0 + * pos(sash[i]) = pos(pane[i]) + size(pane[i]), 0 <= i <= #sash + * pos(pane[i+1]) = pos(sash[i]) + size(sash[i]), 0 <= i < #sash + * pos(sash[#sash]) = size(pw) // sentinel value, constraint + * + * size(pw) = sum(size(pane(0..#pane))) + sum(size(sash(0..#sash))) + * size(pane[i]) >= 0, for 0 <= i < #pane + * size(sash[i]) >= 0, for 0 <= i < #sash + * ==> pos(pane[i]) <= pos(sash[i]) <= pos(pane[i+1]), for 0 <= i < #sash + * + * Assumption: all sashes are the same size. + */ + +/*------------------------------------------------------------------------ + * +++ Widget record. + */ + +typedef struct { + Tcl_Obj *orientObj; + int orient; + int width; + int height; + Ttk_Manager *mgr; + Tk_OptionTable paneOptionTable; + Ttk_Layout sashLayout; + int sashThickness; +} PanedPart; + +typedef struct { + WidgetCore core; + PanedPart paned; +} Paned; + +/* @@@ NOTE: -orient is readonly 'cause dynamic oriention changes NYI + */ +static Tk_OptionSpec PanedOptionSpecs[] = { + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical", + Tk_Offset(Paned,paned.orientObj), Tk_Offset(Paned,paned.orient), + 0,(ClientData)ttkOrientStrings,READONLY_OPTION|STYLE_CHANGED }, + {TK_OPTION_INT, "-width", "width", "Width", "0", + -1,Tk_Offset(Paned,paned.width), + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-height", "height", "Height", "0", + -1,Tk_Offset(Paned,paned.height), + 0,0,GEOMETRY_CHANGED }, + + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Slave pane record. + */ +typedef struct { + int reqSize; /* Pane request size */ + int sashPos; /* Folowing sash position */ + int weight; /* Pane -weight, for resizing */ +} Pane; + +static Tk_OptionSpec PaneOptionSpecs[] = { + {TK_OPTION_INT, "-weight", "weight", "Weight", "0", + -1,Tk_Offset(Pane,weight), 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/* CreatePane -- + * Create a new pane record. + */ +static Pane *CreatePane(Tcl_Interp *interp, Paned *pw, Tk_Window slaveWindow) +{ + Tk_OptionTable optionTable = pw->paned.paneOptionTable; + void *record = ckalloc(sizeof(Pane)); + Pane *pane = record; + + memset(record, 0, sizeof(Pane)); + if (Tk_InitOptions(interp, record, optionTable, slaveWindow) != TCL_OK) { + ckfree(record); + return NULL; + } + + pane->reqSize + = pw->paned.orient == TTK_ORIENT_HORIZONTAL + ? Tk_ReqWidth(slaveWindow) : Tk_ReqHeight(slaveWindow); + + return pane; +} + +/* DestroyPane -- + * Free pane record. + */ +static void DestroyPane(Paned *pw, Pane *pane) +{ + void *record = pane; + Tk_FreeConfigOptions(record, pw->paned.paneOptionTable, pw->core.tkwin); + ckfree(record); +} + +/* ConfigurePane -- + * Set pane options. + */ +static int ConfigurePane( + Tcl_Interp *interp, Paned *pw, Pane *pane, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Ttk_Manager *mgr = pw->paned.mgr; + Tk_SavedOptions savedOptions; + int mask = 0; + + if (Tk_SetOptions(interp, (void*)pane, pw->paned.paneOptionTable, + objc, objv, slaveWindow, &savedOptions, &mask) != TCL_OK) + { + return TCL_ERROR; + } + + /* Sanity-check: + */ + if (pane->weight < 0) { + Tcl_AppendResult(interp, "-weight must be nonnegative", NULL); + goto error; + } + + /* Done. + */ + Tk_FreeSavedOptions(&savedOptions); + Ttk_ManagerSizeChanged(mgr); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + + +/*------------------------------------------------------------------------ + * +++ Sash adjustment. + */ + +/* ShoveUp -- + * Place sash i at specified position, recursively shoving + * previous sashes upwards as needed, until hitting the top + * of the window. If that happens, shove back down. + * + * Returns: final position of sash i. + */ + +static int ShoveUp(Paned *pw, int i, int pos) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr, i); + int sashThickness = pw->paned.sashThickness; + + if (i == 0) { + if (pos < 0) + pos = 0; + } else { + Pane *prevPane = Ttk_SlaveData(pw->paned.mgr, i-1); + if (pos < prevPane->sashPos + sashThickness) + pos = ShoveUp(pw, i-1, pos - sashThickness) + sashThickness; + } + return pane->sashPos = pos; +} + +/* ShoveDown -- + * Same as ShoveUp, but going in the opposite direction + * and stopping at the sentinel sash. + */ +static int ShoveDown(Paned *pw, int i, int pos) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr,i); + int sashThickness = pw->paned.sashThickness; + + if (i == Ttk_NumberSlaves(pw->paned.mgr) - 1) { + pos = pane->sashPos; /* Sentinel value == master window size */ + } else { + Pane *nextPane = Ttk_SlaveData(pw->paned.mgr,i+1); + if (pos + sashThickness > nextPane->sashPos) + pos = ShoveDown(pw, i+1, pos + sashThickness) - sashThickness; + } + return pane->sashPos = pos; +} + +/* PanedSize -- + * Compute the requested size of the paned widget + * from the individual pane request sizes. + * + * Used as the WidgetSpec sizeProc and the ManagerSpec sizeProc. + */ +static int PanedSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Paned *pw = recordPtr; + int nPanes = Ttk_NumberSlaves(pw->paned.mgr); + int nSashes = nPanes - 1; + int sashThickness = pw->paned.sashThickness; + int width = 0, height = 0; + int index; + + if (pw->paned.orient == TTK_ORIENT_HORIZONTAL) { + for (index = 0; index < nPanes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + + if (height < Tk_ReqHeight(slaveWindow)) + height = Tk_ReqHeight(slaveWindow); + width += pane->reqSize; + } + width += nSashes * sashThickness; + } else { + for (index = 0; index < nPanes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + + if (width < Tk_ReqWidth(slaveWindow)) + width = Tk_ReqWidth(slaveWindow); + height += pane->reqSize; + } + height += nSashes * sashThickness; + } + + *widthPtr = pw->paned.width > 0 ? pw->paned.width : width; + *heightPtr = pw->paned.height > 0 ? pw->paned.height : height; + return 1; +} + +/* AdjustPanes -- + * Set pane request sizes from sash positions. + * + * NOTE: + * AdjustPanes followed by PlaceSashes (called during relayout) + * will leave the sashes in the same place, as long as available size + * remains contant. + */ +static void AdjustPanes(Paned *pw) +{ + int sashThickness = pw->paned.sashThickness; + int pos = 0; + int index; + + for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int size = pane->sashPos - pos; + pane->reqSize = size >= 0 ? size : 0; + pos = pane->sashPos + sashThickness; + } +} + +/* PlaceSashes -- + * Set sash positions from pane request sizes and available space. + * The sentinel sash position is set to the available space. + * + * Allocate pane->reqSize pixels to each pane, and distribute + * the difference = available size - requested size according + * to pane->weight. + * + * If there's still some left over, squeeze panes from the bottom up + * (This can happen if all weights are zero, or if one or more panes + * are too small to absorb the required shrinkage). + * + * Notes: + * This doesn't distribute the remainder pixels as evenly as it could + * when more than one pane has weight > 1. + */ +static void PlaceSashes(Paned *pw, int width, int height) +{ + Ttk_Manager *mgr = pw->paned.mgr; + int nPanes = Ttk_NumberSlaves(mgr); + int sashThickness = pw->paned.sashThickness; + int available = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? width : height; + int reqSize = 0, totalWeight = 0; + int difference, delta, remainder, pos, i; + + if (nPanes == 0) + return; + + /* Compute total required size and total available weight: + */ + for (i = 0; i < nPanes; ++i) { + Pane *pane = Ttk_SlaveData(mgr, i); + reqSize += pane->reqSize; + totalWeight += pane->weight * (pane->reqSize != 0); + } + + /* Compute difference to be redistributed: + */ + difference = available - reqSize - sashThickness*(nPanes-1); + if (totalWeight != 0) { + delta = difference / totalWeight; + remainder = difference % totalWeight; + if (remainder < 0) { + --delta; + remainder += totalWeight; + } + } else { + delta = remainder = 0; + } + /* ASSERT: 0 <= remainder < totalWeight */ + + /* Place sashes: + */ + pos = 0; + for (i = 0; i < nPanes; ++i) { + Pane *pane = Ttk_SlaveData(mgr, i); + int weight = pane->weight * (pane->reqSize != 0); + int size = pane->reqSize + delta * weight; + + if (weight > remainder) + weight = remainder; + remainder -= weight; + size += weight; + + if (size < 0) + size = 0; + + pane->sashPos = (pos += size); + pos += sashThickness; + } + + /* Handle emergency shrink/emergency stretch: + * Set sentinel sash position to end of widget, + * shove preceding sashes up. + */ + ShoveUp(pw, nPanes - 1, available); +} + +/* PlacePanes -- + * Places slave panes based on sash positions. + */ +static void PlacePanes(Paned *pw) +{ + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + int width = Tk_Width(pw->core.tkwin), height = Tk_Height(pw->core.tkwin); + int sashThickness = pw->paned.sashThickness; + int pos = 0; + int index; + + for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int size = pane->sashPos - pos; + + if (size > 0) { + if (horizontal) { + Ttk_PlaceSlave(pw->paned.mgr, index, pos, 0, size, height); + } else { + Ttk_PlaceSlave(pw->paned.mgr, index, 0, pos, width, size); + } + } else { + Ttk_UnmapSlave(pw->paned.mgr, index); + } + + pos = pane->sashPos + sashThickness; + } +} + +/*------------------------------------------------------------------------ + * +++ Manager specification. + */ + +static void PanedPlaceSlaves(void *managerData) +{ + Paned *pw = managerData; + PlaceSashes(pw, Tk_Width(pw->core.tkwin), Tk_Height(pw->core.tkwin)); + PlacePanes(pw); +} + +static void PaneRemoved(void *managerData, int index) +{ + Paned *pw = managerData; + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + DestroyPane(pw, pane); +} + +static int AddPane( + Tcl_Interp *interp, Paned *pw, + int destIndex, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Pane *pane; + if (!Ttk_Maintainable(interp, slaveWindow, pw->core.tkwin)) { + return TCL_ERROR; + } + if (Ttk_SlaveIndex(pw->paned.mgr, slaveWindow) >= 0) { + Tcl_AppendResult(interp, + Tk_PathName(slaveWindow), " already added", + NULL); + return TCL_ERROR; + } + + pane = CreatePane(interp, pw, slaveWindow); + if (!pane) { + return TCL_ERROR; + } + if (ConfigurePane(interp, pw, pane, slaveWindow, objc, objv) != TCL_OK) { + DestroyPane(pw, pane); + return TCL_ERROR; + } + + Ttk_InsertSlave(pw->paned.mgr, destIndex, slaveWindow, pane); + return TCL_OK; +} + +/* PaneRequest -- + * Only update pane request size if slave is currently unmapped. + * Geometry requests from mapped slaves are not directly honored + * in order to avoid unexpected pane resizes (esp. while the + * user is dragging a sash [#1325286]). + */ +static int PaneRequest(void *managerData, int index, int width, int height) +{ + Paned *pw = managerData; + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + + if (!Tk_IsMapped(slaveWindow)) { + pane->reqSize = horizontal ? width : height; + } + return 1; +} + +static Ttk_ManagerSpec PanedManagerSpec = { + { "panedwindow", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + PanedSize, + PanedPlaceSlaves, + PaneRequest, + PaneRemoved +}; + +/*------------------------------------------------------------------------ + * +++ Event handler. + * + * <<NOTE-PW-LEAVE-NOTIFYINFERIOR>> + * Tk does not execute binding scripts for <Leave> events when + * the pointer crosses from a parent to a child. This widget + * needs to know when that happens, though, so it can reset + * the cursor. + * + * This event handler generates an <<EnteredChild>> virtual event + * on LeaveNotify/NotifyInferior. + */ + +static const unsigned PanedEventMask = LeaveWindowMask; +static void PanedEventProc(ClientData clientData, XEvent *eventPtr) +{ + WidgetCore *corePtr = clientData; + if ( eventPtr->type == LeaveNotify + && eventPtr->xcrossing.detail == NotifyInferior) + { + TtkSendVirtualEvent(corePtr->tkwin, "EnteredChild"); + } +} + +/*------------------------------------------------------------------------ + * +++ Initialization and cleanup hooks. + */ + +static void PanedInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Paned *pw = recordPtr; + + Tk_CreateEventHandler(pw->core.tkwin, + PanedEventMask, PanedEventProc, recordPtr); + pw->paned.mgr = Ttk_CreateManager(&PanedManagerSpec, pw, pw->core.tkwin); + pw->paned.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs); + pw->paned.sashLayout = 0; + pw->paned.sashThickness = 1; +} + +static void PanedCleanup(void *recordPtr) +{ + Paned *pw = recordPtr; + + if (pw->paned.sashLayout) + Ttk_FreeLayout(pw->paned.sashLayout); + Tk_DeleteEventHandler(pw->core.tkwin, + PanedEventMask, PanedEventProc, recordPtr); + Ttk_DeleteManager(pw->paned.mgr); +} + +/* Post-configuration hook. + */ +static int PanedPostConfigure(Tcl_Interp *interp, void *clientData, int mask) +{ + Paned *pw = clientData; + + if (mask & GEOMETRY_CHANGED) { + /* User has changed -width or -height. + * Recalculate sash positions based on requested size. + */ + Tk_Window tkwin = pw->core.tkwin; + PlaceSashes(pw, + pw->paned.width > 0 ? pw->paned.width : Tk_Width(tkwin), + pw->paned.height > 0 ? pw->paned.height : Tk_Height(tkwin)); + } + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Layout management hooks. + */ +static Ttk_Layout PanedGetLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) +{ + Paned *pw = recordPtr; + Ttk_Layout panedLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr); + + if (panedLayout) { + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + const char *layoutName = + horizontal ? ".Vertical.Sash" : ".Horizontal.Sash"; + Ttk_Layout sashLayout = Ttk_CreateSublayout( + interp, themePtr, panedLayout, layoutName, pw->core.optionTable); + + if (sashLayout) { + int sashWidth, sashHeight; + + Ttk_LayoutSize(sashLayout, 0, &sashWidth, &sashHeight); + pw->paned.sashThickness = horizontal ? sashWidth : sashHeight; + + if (pw->paned.sashLayout) + Ttk_FreeLayout(pw->paned.sashLayout); + pw->paned.sashLayout = sashLayout; + } else { + Ttk_FreeLayout(panedLayout); + return 0; + } + } + + return panedLayout; +} + +/*------------------------------------------------------------------------ + * +++ Drawing routines. + */ + +/* SashLayout -- + * Place the sash sublayout after the specified pane, + * in preparation for drawing. + */ +static Ttk_Layout SashLayout(Paned *pw, int index) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int thickness = pw->paned.sashThickness, + height = Tk_Height(pw->core.tkwin), + width = Tk_Width(pw->core.tkwin), + sashPos = pane->sashPos; + + Ttk_PlaceLayout( + pw->paned.sashLayout, pw->core.state, + pw->paned.orient == TTK_ORIENT_HORIZONTAL + ? Ttk_MakeBox(sashPos, 0, thickness, height) + : Ttk_MakeBox(0, sashPos, width, thickness)); + + return pw->paned.sashLayout; +} + +static void DrawSash(Paned *pw, int index, Drawable d) +{ + Ttk_DrawLayout(SashLayout(pw, index), pw->core.state, d); +} + +static void PanedDisplay(void *recordPtr, Drawable d) +{ + Paned *pw = recordPtr; + int i, nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; + + TtkWidgetDisplay(recordPtr, d); + for (i = 0; i < nSashes; ++i) { + DrawSash(pw, i, d); + } +} + +/*------------------------------------------------------------------------ + * +++ Widget commands. + */ + +/* $pw add window [ options ... ] + */ +static int PanedAddCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + Tk_Window slaveWindow; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "window"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow( + interp, Tcl_GetString(objv[2]), pw->core.tkwin); + + if (!slaveWindow) { + return TCL_ERROR; + } + + return AddPane(interp, pw, Ttk_NumberSlaves(pw->paned.mgr), slaveWindow, + objc - 3, objv + 3); +} + +/* $pw insert $index $slave ?-option value ...? + * Insert new slave, or move existing one. + */ +static int PanedInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int nSlaves = Ttk_NumberSlaves(pw->paned.mgr); + int srcIndex, destIndex; + Tk_Window slaveWindow; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow( + interp, Tcl_GetString(objv[3]), pw->core.tkwin); + if (!slaveWindow) { + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(objv[2]), "end")) { + destIndex = Ttk_NumberSlaves(pw->paned.mgr); + } else if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp,pw->paned.mgr,objv[2],&destIndex)) + { + return TCL_ERROR; + } + + srcIndex = Ttk_SlaveIndex(pw->paned.mgr, slaveWindow); + if (srcIndex < 0) { /* New slave: */ + return AddPane(interp, pw, destIndex, slaveWindow, objc-4, objv+4); + } /* else -- move existing slave: */ + + if (destIndex >= nSlaves) + destIndex = nSlaves - 1; + Ttk_ReorderSlave(pw->paned.mgr, srcIndex, destIndex); + + return objc == 4 ? TCL_OK : + ConfigurePane(interp, pw, + Ttk_SlaveData(pw->paned.mgr, destIndex), + Ttk_SlaveWindow(pw->paned.mgr, destIndex), + objc-4,objv+4); +} + +/* $pw forget $pane + */ +static int PanedForgetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int paneIndex; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2,objv, "pane"); + return TCL_ERROR; + } + + if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp, pw->paned.mgr, objv[2], &paneIndex)) + { + return TCL_ERROR; + } + Ttk_ForgetSlave(pw->paned.mgr, paneIndex); + + return TCL_OK; +} + +/* $pw identify ?what? $x $y -- + * Return index of sash at $x,$y + */ +static int PanedIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + static const char *whatTable[] = { "element", "sash", NULL }; + enum { IDENTIFY_ELEMENT, IDENTIFY_SASH }; + int what = IDENTIFY_SASH; + Paned *pw = recordPtr; + int sashThickness = pw->paned.sashThickness; + int nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; + int x, y, pos; + int index; + + if (objc < 4 || objc > 5) { + Tcl_WrongNumArgs(interp, 2,objv, "?what? x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK + || (objc == 5 && + Tcl_GetIndexFromObj(interp, objv[2], whatTable, "option", 0, &what) + != TCL_OK) + ) { + return TCL_ERROR; + } + + pos = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? x : y; + for (index = 0; index < nSashes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + if (pane->sashPos <= pos && pos <= pane->sashPos + sashThickness) { + /* Found it. */ + switch (what) { + case IDENTIFY_SASH: + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + return TCL_OK; + case IDENTIFY_ELEMENT: + { + Ttk_Element element = + Ttk_IdentifyElement(SashLayout(pw, index), x, y); + if (element) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj(Ttk_ElementName(element), -1)); + } + return TCL_OK; + } + } + } + } + + return TCL_OK; /* nothing found - return empty string */ +} + +/* $pw pane $pane ?-option ?value -option value ...?? + * Query/modify pane options. + */ +static int PanedPaneCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int paneIndex; + Tk_Window slaveWindow; + Pane *pane; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2,objv, "pane ?-option value ...?"); + return TCL_ERROR; + } + + if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp,pw->paned.mgr,objv[2],&paneIndex)) + { + return TCL_ERROR; + } + + pane = Ttk_SlaveData(pw->paned.mgr, paneIndex); + slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, paneIndex); + + switch (objc) { + case 3: + return TtkEnumerateOptions(interp, pane, PaneOptionSpecs, + pw->paned.paneOptionTable, slaveWindow); + case 4: + return TtkGetOptionValue(interp, pane, objv[3], + pw->paned.paneOptionTable, slaveWindow); + default: + return ConfigurePane(interp, pw, pane, slaveWindow, objc-3,objv+3); + } +} + +/* $pw panes -- + * Return list of managed panes. + */ +static int PanedPanesCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + Ttk_Manager *mgr = pw->paned.mgr; + Tcl_Obj *panes; + int i; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + panes = Tcl_NewListObj(0, NULL); + for (i = 0; i < Ttk_NumberSlaves(mgr); ++i) { + const char *pathName = Tk_PathName(Ttk_SlaveWindow(mgr,i)); + Tcl_ListObjAppendElement(interp, panes, Tcl_NewStringObj(pathName,-1)); + } + Tcl_SetObjResult(interp, panes); + + return TCL_OK; +} + + +/* $pw sashpos $index ?$newpos? + * Query or modify sash position. + */ +static int PanedSashposCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int sashIndex, position = -1; + Pane *pane; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index ?newpos?"); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[2], &sashIndex) != TCL_OK) { + return TCL_ERROR; + } + if (sashIndex < 0 || sashIndex >= Ttk_NumberSlaves(pw->paned.mgr) - 1) { + Tcl_AppendResult(interp, + "sash index ", Tcl_GetString(objv[2]), " out of range", + NULL); + return TCL_ERROR; + } + + pane = Ttk_SlaveData(pw->paned.mgr, sashIndex); + + if (objc == 3) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); + return TCL_OK; + } + /* else -- set new sash position */ + + if (Tcl_GetIntFromObj(interp, objv[3], &position) != TCL_OK) { + return TCL_ERROR; + } + + if (position < pane->sashPos) { + ShoveUp(pw, sashIndex, position); + } else { + ShoveDown(pw, sashIndex, position); + } + + AdjustPanes(pw); + Ttk_ManagerLayoutChanged(pw->paned.mgr); + + Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); + return TCL_OK; +} + +static const Ttk_Ensemble PanedCommands[] = { + { "add", PanedAddCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "forget", PanedForgetCommand,0 }, + { "identify", PanedIdentifyCommand,0 }, + { "insert", PanedInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "pane", PanedPaneCommand,0 }, + { "panes", PanedPanesCommand,0 }, + { "sashpos", PanedSashposCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget specification. + */ + +static WidgetSpec PanedWidgetSpec = +{ + "TPanedwindow", /* className */ + sizeof(Paned), /* recordSize */ + PanedOptionSpecs, /* optionSpecs */ + PanedCommands, /* subcommands */ + PanedInitialize, /* initializeProc */ + PanedCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + PanedPostConfigure, /* postConfigureProc */ + PanedGetLayout, /* getLayoutProc */ + PanedSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + PanedDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Elements and layouts. + */ + +static const int DEFAULT_SASH_THICKNESS = 5; + +typedef struct { + Tcl_Obj *thicknessObj; +} SashElement; + +static Ttk_ElementOptionSpec SashElementOptions[] = { + { "-sashthickness", TK_OPTION_INT, + Tk_Offset(SashElement,thicknessObj), "5" }, + { NULL, 0, 0, NULL } +}; + +static void SashElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SashElement *sash = elementRecord; + int thickness = DEFAULT_SASH_THICKNESS; + Tcl_GetIntFromObj(NULL, sash->thicknessObj, &thickness); + *widthPtr = *heightPtr = thickness; +} + +static Ttk_ElementSpec SashElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SashElement), + SashElementOptions, + SashElementSize, + TtkNullElementDraw +}; + +TTK_BEGIN_LAYOUT(PanedLayout) + TTK_NODE("Panedwindow.background", 0)/* @@@ BUG: empty layouts don't work */ +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(HorizontalSashLayout) + TTK_NODE("Sash.hsash", TTK_FILL_X) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(VerticalSashLayout) + TTK_NODE("Sash.vsash", TTK_FILL_Y) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Registration routine. + */ +MODULE_SCOPE +void TtkPanedwindow_Init(Tcl_Interp *interp) +{ + Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); + RegisterWidget(interp, "ttk::panedwindow", &PanedWidgetSpec); + + Ttk_RegisterElement(interp, themePtr, "hsash", &SashElementSpec, 0); + Ttk_RegisterElement(interp, themePtr, "vsash", &SashElementSpec, 0); + + Ttk_RegisterLayout(themePtr, "TPanedwindow", PanedLayout); + Ttk_RegisterLayout(themePtr, "Horizontal.Sash", HorizontalSashLayout); + Ttk_RegisterLayout(themePtr, "Vertical.Sash", VerticalSashLayout); +} + diff --git a/generic/ttk/ttkProgress.c b/generic/ttk/ttkProgress.c new file mode 100644 index 0000000..4dc50a2 --- /dev/null +++ b/generic/ttk/ttkProgress.c @@ -0,0 +1,545 @@ +/* + * Copyright (c) Joe English, Pat Thoyts, Michael Kirkham + * + * ttk::progressbar widget. + */ + +#include <math.h> +#include <tk.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, (ClientData)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, (ClientData)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 = clientData; + + pb->progress.timer = 0; + + if (AnimationEnabled(pb)) { + int phase = 0; + Tcl_GetIntFromObj(NULL, pb->progress.phaseObj, &phase); + + /* + * Update -phase: + */ + ++phase; + if (pb->progress.maxPhase) + phase %= pb->progress.maxPhase; + 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, (ClientData)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 = 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 *interp, void *recordPtr) +{ + Progressbar *pb = recordPtr; + pb->progress.variableTrace = 0; + pb->progress.timer = 0; +} + +static void ProgressbarCleanup(void *recordPtr) +{ + Progressbar *pb = 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 = 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 *interp, void *recordPtr, int mask) +{ + Progressbar *pb = recordPtr; + int status = TCL_OK; + + 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 = recordPtr; + int length = 100, 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 = 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 = 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 = 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); + + TtkRedisplayWidget(&pb->core); + + /* Update value by setting the linked -variable, if there is one: + */ + if (pb->progress.variableTrace) { + return Tcl_ObjSetVar2( + interp, pb->progress.variableObj, 0, newValueObj, + TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG) + ? TCL_OK : TCL_ERROR; + } + + /* Otherwise, change the -value directly: + */ + Tcl_IncrRefCount(newValueObj); + 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[]) +{ + return ProgressbarStartStopCommand( + interp, "::ttk::progressbar::start", objc, objv); +} + +static int ProgressbarStopCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + 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*/ diff --git a/generic/ttk/ttkScale.c b/generic/ttk/ttkScale.c new file mode 100644 index 0000000..69753d1 --- /dev/null +++ b/generic/ttk/ttkScale.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net> + * + * ttk::scale widget. + */ + +#include <tk.h> +#include <string.h> +#include <stdio.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEF_SCALE_LENGTH "100" + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* + * Scale widget record + */ +typedef struct +{ + /* slider element options */ + Tcl_Obj *fromObj; /* minimum value */ + Tcl_Obj *toObj; /* maximum value */ + Tcl_Obj *valueObj; /* current value */ + Tcl_Obj *lengthObj; /* length of the long axis of the scale */ + Tcl_Obj *orientObj; /* widget orientation */ + int orient; + + /* widget options */ + Tcl_Obj *commandObj; + Tcl_Obj *variableObj; + + /* internal state */ + Ttk_TraceHandle *variableTrace; + +} ScalePart; + +typedef struct +{ + WidgetCore core; + ScalePart scale; +} Scale; + +static Tk_OptionSpec ScaleOptionSpecs[] = +{ + {TK_OPTION_STRING, "-command", "command", "Command", "", + Tk_Offset(Scale,scale.commandObj), -1, + TK_OPTION_NULL_OK,0,0}, + {TK_OPTION_STRING, "-variable", "variable", "Variable", "", + Tk_Offset(Scale,scale.variableObj), -1, + 0,0,0}, + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal", + Tk_Offset(Scale,scale.orientObj), + Tk_Offset(Scale,scale.orient), 0, + (ClientData)ttkOrientStrings, STYLE_CHANGED }, + + {TK_OPTION_DOUBLE, "-from", "from", "From", "0", + Tk_Offset(Scale,scale.fromObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-to", "to", "To", "1.0", + Tk_Offset(Scale,scale.toObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-value", "value", "Value", "0", + Tk_Offset(Scale,scale.valueObj), -1, 0, 0, 0}, + {TK_OPTION_PIXELS, "-length", "length", "Length", + DEF_SCALE_LENGTH, Tk_Offset(Scale,scale.lengthObj), -1, 0, 0, + GEOMETRY_CHANGED}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +static XPoint ValueToPoint(Scale *scalePtr, double value); +static double PointToValue(Scale *scalePtr, int x, int y); + +/* ScaleVariableChanged -- + * Variable trace procedure for scale -variable; + * Updates the scale's value. + * If the linked variable is not a valid double, + * sets the 'invalid' state. + */ +static void ScaleVariableChanged(void *recordPtr, const char *value) +{ + Scale *scale = recordPtr; + double v; + + if (value == NULL || Tcl_GetDouble(0, value, &v) != TCL_OK) { + TtkWidgetChangeState(&scale->core, TTK_STATE_INVALID, 0); + } else { + Tcl_Obj *valueObj = Tcl_NewDoubleObj(v); + Tcl_IncrRefCount(valueObj); + Tcl_DecrRefCount(scale->scale.valueObj); + scale->scale.valueObj = valueObj; + TtkWidgetChangeState(&scale->core, 0, TTK_STATE_INVALID); + } + TtkRedisplayWidget(&scale->core); +} + +/* ScaleInitialize -- + * Scale widget initialization hook. + */ +static void ScaleInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Scale *scalePtr = recordPtr; + TtkTrackElementState(&scalePtr->core); +} + +static void ScaleCleanup(void *recordPtr) +{ + Scale *scale = recordPtr; + + if (scale->scale.variableTrace) { + Ttk_UntraceVariable(scale->scale.variableTrace); + scale->scale.variableTrace = 0; + } +} + +/* ScaleConfigure -- + * Configuration hook. + */ +static int ScaleConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Scale *scale = recordPtr; + Tcl_Obj *varName = scale->scale.variableObj; + Ttk_TraceHandle *vt = 0; + + if (varName != NULL && *Tcl_GetString(varName) != '\0') { + vt = Ttk_TraceVariable(interp,varName, ScaleVariableChanged,recordPtr); + if (!vt) return TCL_ERROR; + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { + if (vt) Ttk_UntraceVariable(vt); + return TCL_ERROR; + } + + if (scale->scale.variableTrace) { + Ttk_UntraceVariable(scale->scale.variableTrace); + } + scale->scale.variableTrace = vt; + + return TCL_OK; +} + +/* ScalePostConfigure -- + * Post-configuration hook. + */ +static int ScalePostConfigure( + Tcl_Interp *interp, void *recordPtr, int mask) +{ + Scale *scale = recordPtr; + int status = TCL_OK; + + if (scale->scale.variableTrace) { + status = Ttk_FireTrace(scale->scale.variableTrace); + if (WidgetDestroyed(&scale->core)) { + return TCL_ERROR; + } + if (status != TCL_OK) { + /* Unset -variable: */ + Ttk_UntraceVariable(scale->scale.variableTrace); + Tcl_DecrRefCount(scale->scale.variableObj); + scale->scale.variableTrace = 0; + scale->scale.variableObj = NULL; + status = TCL_ERROR; + } + } + + return status; +} + +/* ScaleGetLayout -- + * getLayout hook. + */ +static Ttk_Layout +ScaleGetLayout(Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Scale *scalePtr = recordPtr; + return TtkWidgetGetOrientedLayout( + interp, theme, recordPtr, scalePtr->scale.orientObj); +} + +/* + * TroughBox -- + * Returns the inner area of the trough element. + */ +static Ttk_Box TroughBox(Scale *scalePtr) +{ + return Ttk_ClientRegion(scalePtr->core.layout, "trough"); +} + +/* + * TroughRange -- + * Return the value area of the trough element, adjusted + * for slider size. + */ +static Ttk_Box TroughRange(Scale *scalePtr) +{ + Ttk_Box troughBox = TroughBox(scalePtr); + Ttk_Element slider = Ttk_FindElement(scalePtr->core.layout,"slider"); + + /* + * If this is a scale widget, adjust range for slider: + */ + if (slider) { + Ttk_Box sliderBox = Ttk_ElementParcel(slider); + if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { + troughBox.x += sliderBox.width / 2; + troughBox.width -= sliderBox.width; + } else { + troughBox.y += sliderBox.height / 2; + troughBox.height -= sliderBox.height; + } + } + + return troughBox; +} + +/* + * ScaleFraction -- + */ +static double ScaleFraction(Scale *scalePtr, double value) +{ + double from = 0, to = 1, fraction; + + Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from); + Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to); + + if (from == to) { + return 1.0; + } + + fraction = (value - from) / (to - from); + + return fraction < 0 ? 0 : fraction > 1 ? 1 : fraction; +} + +/* $scale get ?x y? -- + * Returns the current value of the scale widget, or if $x and + * $y are specified, the value represented by point @x,y. + */ +static int +ScaleGetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scale *scalePtr = recordPtr; + int x, y, r = TCL_OK; + double value = 0; + + if ((objc != 2) && (objc != 4)) { + Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?"); + return TCL_ERROR; + } + if (objc == 2) { + Tcl_SetObjResult(interp, scalePtr->scale.valueObj); + } else { + r = Tcl_GetIntFromObj(interp, objv[2], &x); + if (r == TCL_OK) + r = Tcl_GetIntFromObj(interp, objv[3], &y); + if (r == TCL_OK) { + value = PointToValue(scalePtr, x, y); + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(value)); + } + } + return r; +} + +/* $scale set $newValue + */ +static int +ScaleSetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scale *scalePtr = recordPtr; + double from = 0.0, to = 1.0, value; + int result = TCL_OK; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "set value"); + return TCL_ERROR; + } + + if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) { + return TCL_ERROR; + } + + if (scalePtr->core.state & TTK_STATE_DISABLED) { + return TCL_OK; + } + + /* ASSERT: fromObj and toObj are valid doubles. + */ + Tcl_GetDoubleFromObj(interp, scalePtr->scale.fromObj, &from); + Tcl_GetDoubleFromObj(interp, scalePtr->scale.toObj, &to); + + /* Limit new value to between 'from' and 'to': + */ + if (from < to) { + value = value < from ? from : value > to ? to : value; + } else { + value = value < to ? to : value > from ? from : value; + } + + /* + * Set value: + */ + Tcl_DecrRefCount(scalePtr->scale.valueObj); + scalePtr->scale.valueObj = Tcl_NewDoubleObj(value); + Tcl_IncrRefCount(scalePtr->scale.valueObj); + TtkRedisplayWidget(&scalePtr->core); + + /* + * Set attached variable, if any: + */ + if (scalePtr->scale.variableObj != NULL) { + Tcl_ObjSetVar2(interp, scalePtr->scale.variableObj, NULL, + scalePtr->scale.valueObj, TCL_GLOBAL_ONLY); + } + if (WidgetDestroyed(&scalePtr->core)) { + return TCL_ERROR; + } + + /* + * Invoke -command, if any: + */ + if (scalePtr->scale.commandObj != NULL) { + Tcl_Obj *cmdObj = Tcl_DuplicateObj(scalePtr->scale.commandObj); + Tcl_IncrRefCount(cmdObj); + Tcl_AppendToObj(cmdObj, " ", 1); + Tcl_AppendObjToObj(cmdObj, scalePtr->scale.valueObj); + result = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(cmdObj); + } + + return result; +} + +static int +ScaleCoordsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scale *scalePtr = recordPtr; + double value; + int r = TCL_OK; + + if (objc < 2 || objc > 3) { + Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?"); + return TCL_ERROR; + } + + if (objc == 3) { + r = Tcl_GetDoubleFromObj(interp, objv[2], &value); + } else { + r = Tcl_GetDoubleFromObj(interp, scalePtr->scale.valueObj, &value); + } + + if (r == TCL_OK) { + Tcl_Obj *point[2]; + XPoint pt = ValueToPoint(scalePtr, value); + point[0] = Tcl_NewIntObj(pt.x); + point[1] = Tcl_NewIntObj(pt.y); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); + } + return r; +} + +static void ScaleDoLayout(void *clientData) +{ + WidgetCore *corePtr = clientData; + Ttk_Element slider = Ttk_FindElement(corePtr->layout, "slider"); + + Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); + + /* Adjust the slider position: + */ + if (slider) { + Scale *scalePtr = clientData; + Ttk_Box troughBox = TroughBox(scalePtr); + Ttk_Box sliderBox = Ttk_ElementParcel(slider); + double value = 0.0; + double fraction; + int range; + + Tcl_GetDoubleFromObj(NULL, scalePtr->scale.valueObj, &value); + fraction = ScaleFraction(scalePtr, value); + + if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { + range = troughBox.width - sliderBox.width; + sliderBox.x += (int)(fraction * range); + } else { + range = troughBox.height - sliderBox.height; + sliderBox.y += (int)(fraction * range); + } + Ttk_PlaceElement(corePtr->layout, slider, sliderBox); + } +} + +/* + * ScaleSize -- + * Compute requested size of scale. + */ +static int ScaleSize(void *clientData, int *widthPtr, int *heightPtr) +{ + WidgetCore *corePtr = clientData; + Scale *scalePtr = clientData; + int length; + + Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr); + + /* Assert the -length configuration option */ + Tk_GetPixelsFromObj(NULL, corePtr->tkwin, + scalePtr->scale.lengthObj, &length); + if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) { + *heightPtr = MAX(*heightPtr, length); + } else { + *widthPtr = MAX(*widthPtr, length); + } + + return 1; +} + +static double +PointToValue(Scale *scalePtr, int x, int y) +{ + Ttk_Box troughBox = TroughRange(scalePtr); + double from = 0, to = 1, fraction; + + Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from); + Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to); + + if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { + fraction = (double)(x - troughBox.x) / (double)troughBox.width; + } else { + fraction = (double)(y - troughBox.y) / (double)troughBox.height; + } + + fraction = fraction < 0 ? 0 : fraction > 1 ? 1 : fraction; + + return from + fraction * (to-from); +} + +/* + * Return the center point in the widget corresponding to the given + * value. This point can be used to center the slider. + */ + +static XPoint +ValueToPoint(Scale *scalePtr, double value) +{ + Ttk_Box troughBox = TroughRange(scalePtr); + double fraction = ScaleFraction(scalePtr, value); + XPoint pt = {0, 0}; + + if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { + pt.x = troughBox.x + (int)(fraction * troughBox.width); + pt.y = troughBox.y + troughBox.height / 2; + } else { + pt.x = troughBox.x + troughBox.width / 2; + pt.y = troughBox.y + (int)(fraction * troughBox.height); + } + return pt; +} + +static const Ttk_Ensemble ScaleCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "set", ScaleSetCommand,0 }, + { "get", ScaleGetCommand,0 }, + { "coords", ScaleCoordsCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec ScaleWidgetSpec = +{ + "TScale", /* Class name */ + sizeof(Scale), /* record size */ + ScaleOptionSpecs, /* option specs */ + ScaleCommands, /* widget commands */ + ScaleInitialize, /* initialization proc */ + ScaleCleanup, /* cleanup proc */ + ScaleConfigure, /* configure proc */ + ScalePostConfigure, /* postConfigure */ + ScaleGetLayout, /* getLayoutProc */ + ScaleSize, /* sizeProc */ + ScaleDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(VerticalScaleLayout) + TTK_GROUP("Vertical.Scale.trough", TTK_FILL_BOTH, + TTK_NODE("Vertical.Scale.slider", TTK_PACK_TOP) ) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(HorizontalScaleLayout) + TTK_GROUP("Horizontal.Scale.trough", TTK_FILL_BOTH, + TTK_NODE("Horizontal.Scale.slider", TTK_PACK_LEFT) ) +TTK_END_LAYOUT + +/* + * Initialization. + */ +MODULE_SCOPE +void TtkScale_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "Vertical.TScale", VerticalScaleLayout); + Ttk_RegisterLayout(theme, "Horizontal.TScale", HorizontalScaleLayout); + + RegisterWidget(interp, "ttk::scale", &ScaleWidgetSpec); +} + diff --git a/generic/ttk/ttkScroll.c b/generic/ttk/ttkScroll.c new file mode 100644 index 0000000..defe05a --- /dev/null +++ b/generic/ttk/ttkScroll.c @@ -0,0 +1,252 @@ +/* + * Copyright 2004, Joe English + * + * Support routines for scrollable widgets. + * + * (This is sort of half-baked; needs some work) + * + * Scrollable interface: + * + * + 'first' is controlled by [xy]view widget command + * and other scrolling commands like 'see'; + * + 'total' depends on widget contents; + * + 'last' depends on first, total, and widget size. + * + * Choreography (typical usage): + * + * 1. User adjusts scrollbar, scrollbar widget calls its -command + * 2. Scrollbar -command invokes the scrollee [xy]view widget method + * 3. TtkScrollviewCommand calls TtkScrollTo(), which updates + * 'first' and schedules a redisplay. + * 4. Once the scrollee knows 'total' and 'last' (typically in + * the LayoutProc), call TtkScrolled(h,first,last,total) to + * synchronize the scrollbar. + * 5. The scrollee -[xy]scrollcommand is called (in an idle callback) + * 6. Which calls the scrollbar 'set' method and redisplays the scrollbar. + * + * If the scrollee has internal scrolling (e.g., a 'see' method), + * it should TtkScrollTo() directly (step 2). + * + * If the widget value changes, it should call TtkScrolled() (step 4). + * (This usually happens automatically when the widget is redisplayed). + * + * If the scrollee's -[xy]scrollcommand changes, it should call + * TtkScrollbarUpdateRequired, which will invoke step (5) (@@@ Fix this) + */ + +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +/* Private data: + */ +#define SCROLL_UPDATE_PENDING (0x1) +#define SCROLL_UPDATE_REQUIRED (0x2) + +struct ScrollHandleRec +{ + unsigned flags; + WidgetCore *corePtr; + Scrollable *scrollPtr; +}; + +/* TtkCreateScrollHandle -- + * Initialize scroll handle. + */ +ScrollHandle TtkCreateScrollHandle(WidgetCore *corePtr, Scrollable *scrollPtr) +{ + ScrollHandle h = (ScrollHandle)ckalloc(sizeof(*h)); + + h->flags = 0; + h->corePtr = corePtr; + h->scrollPtr = scrollPtr; + + scrollPtr->first = 0; + scrollPtr->last = 1; + scrollPtr->total = 1; + return h; +} + +/* UpdateScrollbar -- + * Call the -scrollcommand callback to sync the scrollbar. + * Returns: Whatever the -scrollcommand does. + */ +static int UpdateScrollbar(Tcl_Interp *interp, ScrollHandle h) +{ + Scrollable *s = h->scrollPtr; + WidgetCore *corePtr = h->corePtr; + char arg1[TCL_DOUBLE_SPACE + 2]; + char arg2[TCL_DOUBLE_SPACE + 2]; + int code; + + h->flags &= ~SCROLL_UPDATE_REQUIRED; + + if (s->scrollCmd == NULL) { + return TCL_OK; + } + + arg1[0] = arg2[0] = ' '; + Tcl_PrintDouble(interp, (double)s->first / s->total, arg1+1); + Tcl_PrintDouble(interp, (double)s->last / s->total, arg2+1); + + Tcl_Preserve(corePtr); + code = Tcl_VarEval(interp, s->scrollCmd, arg1, arg2, NULL); + if (WidgetDestroyed(corePtr)) { + Tcl_Release(corePtr); + return TCL_ERROR; + } + Tcl_Release(corePtr); + + if (code != TCL_OK && !Tcl_InterpDeleted(interp)) { + /* Disable the -scrollcommand, add to stack trace: + */ + ckfree(s->scrollCmd); + s->scrollCmd = 0; + + Tcl_AddErrorInfo(interp, /* @@@ "horizontal" / "vertical" */ + "\n (scrolling command executed by "); + Tcl_AddErrorInfo(interp, Tk_PathName(h->corePtr->tkwin)); + Tcl_AddErrorInfo(interp, ")"); + } + return code; +} + +/* UpdateScrollbarBG -- + * Idle handler to update the scrollbar. + */ +static void UpdateScrollbarBG(ClientData clientData) +{ + ScrollHandle h = (ScrollHandle)clientData; + Tcl_Interp *interp = h->corePtr->interp; + int code; + + h->flags &= ~SCROLL_UPDATE_PENDING; + Tcl_Preserve((ClientData) interp); + code = UpdateScrollbar(interp, h); + if (code == TCL_ERROR && !Tcl_InterpDeleted(interp)) { + Tcl_BackgroundError(interp); + } + Tcl_Release((ClientData) interp); +} + +/* TtkScrolled -- + * Update scroll info, schedule scrollbar update. + */ +void TtkScrolled(ScrollHandle h, int first, int last, int total) +{ + Scrollable *s = h->scrollPtr; + + /* Sanity-check inputs: + */ + if (total <= 0) { + first = 0; + last = 1; + total = 1; + } + + if (last > total) { + first -= (last - total); + if (first < 0) first = 0; + last = total; + } + + if (s->first != first || s->last != last || s->total != total + || (h->flags & SCROLL_UPDATE_REQUIRED)) + { + s->first = first; + s->last = last; + s->total = total; + + if (!(h->flags & SCROLL_UPDATE_PENDING)) { + Tcl_DoWhenIdle(UpdateScrollbarBG, (ClientData)h); + h->flags |= SCROLL_UPDATE_PENDING; + } + } +} + +/* TtkScrollbarUpdateRequired -- + * Force a scrollbar update at the next call to TtkScrolled(), + * even if scroll parameters haven't changed (e.g., if + * -yscrollcommand has changed). + */ + +void TtkScrollbarUpdateRequired(ScrollHandle h) +{ + h->flags |= SCROLL_UPDATE_REQUIRED; +} + +/* TtkScrollviewCommand -- + * Widget [xy]view command implementation. + * + * $w [xy]view -- return current view region + * $w [xy]view $index -- set topmost item + * $w [xy]view moveto $fraction + * $w [xy]view scroll $number $what -- scrollbar interface + */ +int TtkScrollviewCommand( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle h) +{ + Scrollable *s = h->scrollPtr; + int newFirst = s->first; + + if (objc == 2) { + Tcl_Obj *result[2]; + result[0] = Tcl_NewDoubleObj((double)s->first / s->total); + result[1] = Tcl_NewDoubleObj((double)s->last / s->total); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); + return TCL_OK; + } else if (objc == 3) { + if (Tcl_GetIntFromObj(interp, objv[2], &newFirst) != TCL_OK) { + return TCL_ERROR; + } + } else { + double fraction; + int count; + + switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count)) { + case TK_SCROLL_ERROR: + return TCL_ERROR; + case TK_SCROLL_MOVETO: + newFirst = (int) ((fraction * s->total) + 0.5); + break; + case TK_SCROLL_UNITS: + newFirst = s->first + count; + break; + case TK_SCROLL_PAGES: { + int perPage = s->last - s->first; /* @@@ */ + newFirst = s->first + count * perPage; + break; + } + } + } + + TtkScrollTo(h, newFirst); + + return TCL_OK; +} + +void TtkScrollTo(ScrollHandle h, int newFirst) +{ + Scrollable *s = h->scrollPtr; + + if (newFirst >= s->total) + newFirst = s->total - 1; + if (newFirst > s->first && s->last >= s->total) /* don't scroll past end */ + newFirst = s->first; + if (newFirst < 0) + newFirst = 0; + + if (newFirst != s->first) { + s->first = newFirst; + TtkRedisplayWidget(h->corePtr); + } +} + +void TtkFreeScrollHandle(ScrollHandle h) +{ + if (h->flags & SCROLL_UPDATE_PENDING) { + Tcl_CancelIdleCall(UpdateScrollbarBG, (ClientData)h); + } + ckfree((ClientData)h); +} + diff --git a/generic/ttk/ttkScrollbar.c b/generic/ttk/ttkScrollbar.c new file mode 100644 index 0000000..5b0c212 --- /dev/null +++ b/generic/ttk/ttkScrollbar.c @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2003, Joe English + * + * ttk::scrollbar widget. + */ + +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" + +/*------------------------------------------------------------------------ + * +++ Scrollbar widget record. + */ +typedef struct +{ + Tcl_Obj *commandObj; + + int orient; + Tcl_Obj *orientObj; + + double first; /* top fraction */ + double last; /* bottom fraction */ + + Ttk_Box troughBox; /* trough parcel */ + int minSize; /* minimum size of thumb */ +} ScrollbarPart; + +typedef struct +{ + WidgetCore core; + ScrollbarPart scrollbar; +} Scrollbar; + +static Tk_OptionSpec ScrollbarOptionSpecs[] = +{ + {TK_OPTION_STRING, "-command", "command", "Command", "", + Tk_Offset(Scrollbar,scrollbar.commandObj), -1, 0,0,0}, + + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical", + Tk_Offset(Scrollbar,scrollbar.orientObj), + Tk_Offset(Scrollbar,scrollbar.orient), + 0,(ClientData)ttkOrientStrings,STYLE_CHANGED }, + + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Widget hooks. + */ + +static void +ScrollbarInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Scrollbar *sb = recordPtr; + sb->scrollbar.first = 0.0; + sb->scrollbar.last = 1.0; + + TtkTrackElementState(&sb->core); +} + +static Ttk_Layout ScrollbarGetLayout( + Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Scrollbar *sb = recordPtr; + return TtkWidgetGetOrientedLayout( + interp, theme, recordPtr, sb->scrollbar.orientObj); +} + +/* + * ScrollbarDoLayout -- + * Layout hook. Adjusts the position of the scrollbar thumb. + * + * Side effects: + * Sets sb->troughBox and sb->minSize. + */ +static void ScrollbarDoLayout(void *recordPtr) +{ + Scrollbar *sb = recordPtr; + WidgetCore *corePtr = &sb->core; + Ttk_Element thumb; + Ttk_Box thumbBox; + int thumbWidth, thumbHeight; + double first, last, size; + int minSize; + + /* + * Use generic layout manager to compute initial layout: + */ + Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); + + /* + * Locate thumb element, extract parcel and requested minimum size: + */ + thumb = Ttk_FindElement(corePtr->layout, "thumb"); + if (!thumb) /* Something has gone wrong -- bail */ + return; + + sb->scrollbar.troughBox = thumbBox = Ttk_ElementParcel(thumb); + Ttk_LayoutNodeReqSize( + corePtr->layout, thumb, &thumbWidth,&thumbHeight); + + /* + * Adjust thumb element parcel: + */ + first = sb->scrollbar.first; + last = sb->scrollbar.last; + + if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) { + minSize = thumbHeight; + size = thumbBox.height - minSize; + thumbBox.y += (int)(size * first); + thumbBox.height = (int)(size * last) + minSize - (int)(size * first); + } else { + minSize = thumbWidth; + size = thumbBox.width - minSize; + thumbBox.x += (int)(size * first); + thumbBox.width = (int)(size * last) + minSize - (int)(size * first); + } + sb->scrollbar.minSize = minSize; + Ttk_PlaceElement(corePtr->layout, thumb, thumbBox); +} + +/*------------------------------------------------------------------------ + * +++ Widget commands. + */ + +/* $sb set $first $last -- + * Set the position of the scrollbar. + */ +static int +ScrollbarSetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scrollbar *scrollbar = recordPtr; + Tcl_Obj *firstObj, *lastObj; + double first, last; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "first last"); + return TCL_ERROR; + } + + firstObj = objv[2]; + lastObj = objv[3]; + if (Tcl_GetDoubleFromObj(interp, firstObj, &first) != TCL_OK + || Tcl_GetDoubleFromObj(interp, lastObj, &last) != TCL_OK) + return TCL_ERROR; + + /* Range-checks: + */ + if (first < 0.0) { + first = 0.0; + } else if (first > 1.0) { + first = 1.0; + } + + if (last < first) { + last = first; + } else if (last > 1.0) { + last = 1.0; + } + + /* ASSERT: 0.0 <= first <= last <= 1.0 */ + + scrollbar->scrollbar.first = first; + scrollbar->scrollbar.last = last; + if (first <= 0.0 && last >= 1.0) { + scrollbar->core.state |= TTK_STATE_DISABLED; + } else { + scrollbar->core.state &= ~TTK_STATE_DISABLED; + } + + TtkRedisplayWidget(&scrollbar->core); + + return TCL_OK; +} + +/* $sb get -- + * Returns the last thing passed to 'set'. + */ +static int +ScrollbarGetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scrollbar *scrollbar = recordPtr; + Tcl_Obj *result[2]; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + result[0] = Tcl_NewDoubleObj(scrollbar->scrollbar.first); + result[1] = Tcl_NewDoubleObj(scrollbar->scrollbar.last); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); + + return TCL_OK; +} + +/* $sb delta $dx $dy -- + * Returns the percentage change corresponding to a mouse movement + * of $dx, $dy. + */ +static int +ScrollbarDeltaCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scrollbar *sb = recordPtr; + double dx, dy; + double delta = 0.0; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "dx dy"); + return TCL_ERROR; + } + + if (Tcl_GetDoubleFromObj(interp, objv[2], &dx) != TCL_OK + || Tcl_GetDoubleFromObj(interp, objv[3], &dy) != TCL_OK) + { + return TCL_ERROR; + } + + delta = 0.0; + if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) { + int size = sb->scrollbar.troughBox.height - sb->scrollbar.minSize; + if (size > 0) { + delta = (double)dy / (double)size; + } + } else { + int size = sb->scrollbar.troughBox.width - sb->scrollbar.minSize; + if (size > 0) { + delta = (double)dx / (double)size; + } + } + + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(delta)); + return TCL_OK; +} + +/* $sb fraction $x $y -- + * Returns a real number between 0 and 1 indicating where the + * point given by x and y lies in the trough area of the scrollbar. + */ +static int +ScrollbarFractionCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Scrollbar *sb = recordPtr; + Ttk_Box b = sb->scrollbar.troughBox; + int minSize = sb->scrollbar.minSize; + double x, y; + double fraction = 0.0; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "x y"); + return TCL_ERROR; + } + + if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK + || Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) + { + return TCL_ERROR; + } + + fraction = 0.0; + if (sb->scrollbar.orient == TTK_ORIENT_VERTICAL) { + if (b.height > minSize) { + fraction = (double)(y - b.y) / (double)(b.height - minSize); + } + } else { + if (b.width > minSize) { + fraction = (double)(x - b.x) / (double)(b.width - minSize); + } + } + + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(fraction)); + return TCL_OK; +} + +static const Ttk_Ensemble ScrollbarCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "delta", ScrollbarDeltaCommand,0 }, + { "fraction", ScrollbarFractionCommand,0 }, + { "get", ScrollbarGetCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "set", ScrollbarSetCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget specification. + */ +static WidgetSpec ScrollbarWidgetSpec = +{ + "TScrollbar", /* className */ + sizeof(Scrollbar), /* recordSize */ + ScrollbarOptionSpecs, /* optionSpecs */ + ScrollbarCommands, /* subcommands */ + ScrollbarInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + ScrollbarGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + ScrollbarDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(VerticalScrollbarLayout) + TTK_GROUP("Vertical.Scrollbar.trough", TTK_FILL_Y, + TTK_NODE("Vertical.Scrollbar.uparrow", TTK_PACK_TOP) + TTK_NODE("Vertical.Scrollbar.downarrow", TTK_PACK_BOTTOM) + TTK_NODE( + "Vertical.Scrollbar.thumb", TTK_PACK_TOP|TTK_EXPAND|TTK_FILL_BOTH)) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(HorizontalScrollbarLayout) + TTK_GROUP("Horizontal.Scrollbar.trough", TTK_FILL_X, + TTK_NODE("Horizontal.Scrollbar.leftarrow", TTK_PACK_LEFT) + TTK_NODE("Horizontal.Scrollbar.rightarrow", TTK_PACK_RIGHT) + TTK_NODE( + "Horizontal.Scrollbar.thumb", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_BOTH)) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Initialization. + */ + +MODULE_SCOPE +void TtkScrollbar_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme,"Vertical.TScrollbar",VerticalScrollbarLayout); + Ttk_RegisterLayout(theme,"Horizontal.TScrollbar",HorizontalScrollbarLayout); + + RegisterWidget(interp, "ttk::scrollbar", &ScrollbarWidgetSpec); +} + +/*EOF*/ diff --git a/generic/ttk/ttkSeparator.c b/generic/ttk/ttkSeparator.c new file mode 100644 index 0000000..b52e6f4 --- /dev/null +++ b/generic/ttk/ttkSeparator.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2004, Joe English + * + * ttk::separator and ttk::sizegrip widgets. + */ + +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" + +/* +++ Separator widget record: + */ +typedef struct +{ + Tcl_Obj *orientObj; + int orient; +} SeparatorPart; + +typedef struct +{ + WidgetCore core; + SeparatorPart separator; +} Separator; + +static Tk_OptionSpec SeparatorOptionSpecs[] = { + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal", + Tk_Offset(Separator,separator.orientObj), + Tk_Offset(Separator,separator.orient), + 0,(ClientData)ttkOrientStrings,STYLE_CHANGED }, + + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/* + * GetLayout hook -- + * Choose layout based on -orient option. + */ +static Ttk_Layout SeparatorGetLayout( + Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Separator *sep = recordPtr; + return TtkWidgetGetOrientedLayout( + interp, theme, recordPtr, sep->separator.orientObj); +} + +/* + * Widget commands: + */ +static const Ttk_Ensemble SeparatorCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +/* + * Widget specification: + */ +static WidgetSpec SeparatorWidgetSpec = +{ + "TSeparator", /* className */ + sizeof(Separator), /* recordSize */ + SeparatorOptionSpecs, /* optionSpecs */ + SeparatorCommands, /* subcommands */ + TtkNullInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + SeparatorGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(SeparatorLayout) + TTK_NODE("Separator.separator", TTK_FILL_BOTH) +TTK_END_LAYOUT + +/* +++ Sizegrip widget: + * Has no options or methods other than the standard ones. + */ + +static Tk_OptionSpec SizegripOptionSpecs[] = { + WIDGET_TAKEFOCUS_FALSE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +static const Ttk_Ensemble SizegripCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec SizegripWidgetSpec = +{ + "TSizegrip", /* className */ + sizeof(WidgetCore), /* recordSize */ + SizegripOptionSpecs, /* optionSpecs */ + SizegripCommands, /* subcommands */ + TtkNullInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(SizegripLayout) + TTK_NODE("Sizegrip.sizegrip", TTK_PACK_BOTTOM|TTK_STICK_S|TTK_STICK_E) +TTK_END_LAYOUT + +/* +++ Initialization: + */ + +MODULE_SCOPE +void TtkSeparator_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "TSeparator", SeparatorLayout); + Ttk_RegisterLayout(theme, "TSizegrip", SizegripLayout); + + RegisterWidget(interp, "ttk::separator", &SeparatorWidgetSpec); + RegisterWidget(interp, "ttk::sizegrip", &SizegripWidgetSpec); +} + +/*EOF*/ diff --git a/generic/ttk/ttkSquare.c b/generic/ttk/ttkSquare.c new file mode 100644 index 0000000..d002f2f --- /dev/null +++ b/generic/ttk/ttkSquare.c @@ -0,0 +1,301 @@ +/* square.c - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net> + * + * Minimal sample ttk widget. + */ + +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#if defined(TTK_SQUARE_WIDGET) || 1 + +#ifndef DEFAULT_BORDERWIDTH +#define DEFAULT_BORDERWIDTH "2" +#endif + +/* + * First, we setup the widget record. The Ttk package provides a structure + * that contains standard widget data so it is only necessary to define + * a structure that holds the data required for our widget. We do this by + * defining a widget part and then specifying the widget record as the + * concatenation of the two structures. + */ + +typedef struct +{ + Tcl_Obj *widthObj; + Tcl_Obj *heightObj; + Tcl_Obj *reliefObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *paddingObj; + Tcl_Obj *anchorObj; +} SquarePart; + +typedef struct +{ + WidgetCore core; + SquarePart square; +} Square; + +/* + * Widget options. + * + * This structure is the same as the option specification structure used + * for Tk widgets. For each option we provide the type, name and options + * database name and class name and the position in the structure and + * default values. At the bottom we bring in the standard widget option + * defined for all widgets. + */ + +static Tk_OptionSpec SquareOptionSpecs[] = +{ + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + DEFAULT_BORDERWIDTH, Tk_Offset(Square,square.borderWidthObj), -1, + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_BORDER, "-foreground", "foreground", "Foreground", + DEFAULT_BACKGROUND, Tk_Offset(Square,square.foregroundObj), + -1, 0, 0, 0}, + + {TK_OPTION_PIXELS, "-width", "width", "Width", + "50", Tk_Offset(Square,square.widthObj), -1, 0, 0, + GEOMETRY_CHANGED}, + {TK_OPTION_PIXELS, "-height", "height", "Height", + "50", Tk_Offset(Square,square.heightObj), -1, 0, 0, + GEOMETRY_CHANGED}, + + {TK_OPTION_STRING, "-padding", "padding", "Pad", NULL, + Tk_Offset(Square,square.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", + NULL, Tk_Offset(Square,square.reliefObj), -1, TK_OPTION_NULL_OK, 0, 0}, + + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + NULL, Tk_Offset(Square,square.anchorObj), -1, TK_OPTION_NULL_OK, 0, 0}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/* + * Almost all of the widget functionality is handled by the default Ttk + * widget code and the contained element. The one thing that we must handle + * is the -anchor option which positions the square element within the parcel + * of space available for the widget. + * To do this we must find out the layout preferences for the square + * element and adjust its position within our region. + * + * Note that if we do not have a "square" elememt then just the default + * layout will be done. So if someone places a label element into the + * widget layout it will still be handled but the -anchor option will be + * passed onto the label element instead of handled here. + */ + +static void +SquareDoLayout(void *clientData) +{ + WidgetCore *corePtr = (WidgetCore *)clientData; + Ttk_Box winBox; + Ttk_Element squareNode; + + squareNode = Ttk_FindElement(corePtr->layout, "square"); + winBox = Ttk_WinBox(corePtr->tkwin); + Ttk_PlaceLayout(corePtr->layout, corePtr->state, winBox); + + /* + * Adjust the position of the square element within the widget according + * to the -anchor option. + */ + + if (squareNode) { + Square *squarePtr = clientData; + Tk_Anchor anchor = TK_ANCHOR_CENTER; + Ttk_Box b; + + b = Ttk_ElementParcel(squareNode); + if (squarePtr->square.anchorObj != NULL) + Tk_GetAnchorFromObj(NULL, squarePtr->square.anchorObj, &anchor); + b = Ttk_AnchorBox(winBox, b.width, b.height, anchor); + + Ttk_PlaceElement(corePtr->layout, squareNode, b); + } +} + +/* + * Widget commands. A widget is impelemented as an ensemble and the + * subcommands are listed here. Ttk provides default implementations + * that are sufficient for our needs. + */ + +static const Ttk_Ensemble SquareCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +/* + * The Widget specification structure holds all the implementation + * information about this widget and this is what must be registered + * with Tk in the package initialization code (see bottom). + */ + +static WidgetSpec SquareWidgetSpec = +{ + "TSquare", /* className */ + sizeof(Square), /* recordSize */ + SquareOptionSpecs, /* optionSpecs */ + SquareCommands, /* subcommands */ + TtkNullInitialize, /* initializeProc */ + TtkNullCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + SquareDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +/* ---------------------------------------------------------------------- + * Square element + * + * In this section we demonstrate what is required to create a new themed + * element. + */ + +typedef struct +{ + Tcl_Obj *borderObj; + Tcl_Obj *foregroundObj; + Tcl_Obj *borderWidthObj; + Tcl_Obj *reliefObj; + Tcl_Obj *widthObj; + Tcl_Obj *heightObj; +} SquareElement; + +static Ttk_ElementOptionSpec SquareElementOptions[] = +{ + { "-background", TK_OPTION_BORDER, Tk_Offset(SquareElement,borderObj), + DEFAULT_BACKGROUND }, + { "-foreground", TK_OPTION_BORDER, Tk_Offset(SquareElement,foregroundObj), + DEFAULT_BACKGROUND }, + { "-borderwidth", TK_OPTION_PIXELS, Tk_Offset(SquareElement,borderWidthObj), + DEFAULT_BORDERWIDTH }, + { "-relief", TK_OPTION_RELIEF, Tk_Offset(SquareElement,reliefObj), + "raised" }, + { "-width", TK_OPTION_PIXELS, Tk_Offset(SquareElement,widthObj), "20"}, + { "-height", TK_OPTION_PIXELS, Tk_Offset(SquareElement,heightObj), "20"}, + { NULL, 0, 0, NULL } +}; + +/* + * The element geometry function is called when the layout code wishes to + * find out how big this element wants to be. We must return our preferred + * size and padding information + */ + +static void SquareElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SquareElement *square = elementRecord; + int borderWidth = 0; + + Tcl_GetIntFromObj(NULL, square->borderWidthObj, &borderWidth); + *paddingPtr = Ttk_UniformPadding((short)borderWidth); + Tk_GetPixelsFromObj(NULL, tkwin, square->widthObj, widthPtr); + Tk_GetPixelsFromObj(NULL, tkwin, square->heightObj, heightPtr); +} + +/* + * Draw the element in the box provided. + */ + +static void SquareElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + SquareElement *square = elementRecord; + Tk_3DBorder foreground = NULL; + int borderWidth = 1, relief = TK_RELIEF_FLAT; + + foreground = Tk_Get3DBorderFromObj(tkwin, square->foregroundObj); + Tcl_GetIntFromObj(NULL, square->borderWidthObj, &borderWidth); + Tk_GetReliefFromObj(NULL, square->reliefObj, &relief); + + Tk_Fill3DRectangle(tkwin, d, foreground, + b.x, b.y, b.width, b.height, borderWidth, relief); +} + +static Ttk_ElementSpec SquareElementSpec = +{ + TK_STYLE_VERSION_2, + sizeof(SquareElement), + SquareElementOptions, + SquareElementSize, + SquareElementDraw +}; + +/* ---------------------------------------------------------------------- + * + * Layout section. + * + * Every widget class needs a layout style that specifies which elements + * are part of the widget and how they should be placed. The element layout + * engine is similar to the Tk pack geometry manager. Read the documentation + * for the details. In this example we just need to have the square element + * that has been defined for this widget placed on a background. We will + * also need some padding to keep it away from the edges. + */ + +TTK_BEGIN_LAYOUT(SquareLayout) + TTK_NODE("Square.background", TTK_FILL_BOTH) + TTK_GROUP("Square.padding", TTK_FILL_BOTH, + TTK_NODE("Square.square", 0)) +TTK_END_LAYOUT + +/* ---------------------------------------------------------------------- + * + * Widget initialization. + * + * This file defines a new element and a new widget. We need to register + * the element with the themes that will need it. In this case we will + * register with the default theme that is the root of the theme inheritance + * tree. This means all themes will find this element. + * We then need to register the widget class style. This is the layout + * specification. If a different theme requires an alternative layout, we + * could register that here. For instance, in some themes the scrollbars have + * one uparrow, in other themes there are two uparrow elements. + * Finally we register the widget itself. This step creates a tcl command so + * that we can actually create an instance of this class. The widget is + * linked to a particular style by the widget class name. This is important + * to realise as the programmer may change the classname when creating a + * new instance. If this is done, a new layout will need to be created (which + * can be done at script level). Some widgets may require particular elements + * to be present but we try to avoid this where possible. In this widget's C + * code, no reference is made to any particular elements. The programmer is + * free to specify a new style using completely different elements. + */ + +/* public */ MODULE_SCOPE int +TtkSquareWidget_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + /* register the new elements for this theme engine */ + Ttk_RegisterElement(interp, theme, "square", &SquareElementSpec, NULL); + + /* register the layout for this theme */ + Ttk_RegisterLayout(theme, "TSquare", SquareLayout); + + /* register the widget */ + RegisterWidget(interp, "ttk::square", &SquareWidgetSpec); + + return TCL_OK; +} + +#endif /* TTK_SQUARE_WIDGET */ + diff --git a/generic/ttk/ttkState.c b/generic/ttk/ttkState.c new file mode 100644 index 0000000..a71ae21 --- /dev/null +++ b/generic/ttk/ttkState.c @@ -0,0 +1,273 @@ +/* + * Tk widget state utilities. + * + * Copyright (c) 2003 Joe English. Freely redistributable. + * + */ + +#include <string.h> + +#include <tk.h> +#include "ttkTheme.h" + +/* + * Table of state names. Must be kept in sync with TTK_STATE_* + * #defines in ttkTheme.h. + */ +static const char *const stateNames[] = +{ + "active", /* Mouse cursor is over widget or element */ + "disabled", /* Widget is disabled */ + "focus", /* Widget has keyboard focus */ + "pressed", /* Pressed or "armed" */ + "selected", /* "on", "true", "current", etc. */ + "background", /* Top-level window lost focus (Mac,Win "inactive") */ + "alternate", /* Widget-specific alternate display style */ + "invalid", /* Bad value */ + "readonly", /* Editing/modification disabled */ + "hover", /* Mouse cursor is over widget */ + "reserved1", /* Reserved for future extension */ + "reserved2", /* Reserved for future extension */ + "reserved3", /* Reserved for future extension */ + "user3", /* User-definable state */ + "user2", /* User-definable state */ + "user1", /* User-definable state */ + NULL +}; + +/*------------------------------------------------------------------------ + * +++ StateSpec object type: + * + * The string representation consists of a list of state names, + * each optionally prefixed by an exclamation point (!). + * + * The internal representation uses the upper half of the longValue + * to store the on bits and the lower half to store the off bits. + * If we ever get more than 16 states, this will need to be reconsidered... + */ + +static int StateSpecSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj); +/* static void StateSpecFreeIntRep(Tcl_Obj *); */ +#define StateSpecFreeIntRep 0 /* not needed */ +static void StateSpecDupIntRep(Tcl_Obj *, Tcl_Obj *); +static void StateSpecUpdateString(Tcl_Obj *); + +static +struct Tcl_ObjType StateSpecObjType = +{ + "StateSpec", + StateSpecFreeIntRep, + StateSpecDupIntRep, + StateSpecUpdateString, + StateSpecSetFromAny +}; + +static void StateSpecDupIntRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) +{ + copyPtr->internalRep.longValue = srcPtr->internalRep.longValue; + copyPtr->typePtr = &StateSpecObjType; +} + +static int StateSpecSetFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr) +{ + int status; + int objc; + Tcl_Obj **objv; + int i; + unsigned int onbits = 0, offbits = 0; + + status = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv); + if (status != TCL_OK) + return status; + + for (i = 0; i < objc; ++i) { + const char *stateName = Tcl_GetString(objv[i]); + int on, j; + + if (*stateName == '!') { + ++stateName; + on = 0; + } else { + on = 1; + } + + for (j = 0; stateNames[j] != 0; ++j) { + if (strcmp(stateName, stateNames[j]) == 0) + break; + } + + if (stateNames[j] == 0) { + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Invalid state name ", stateName,NULL); + } + return TCL_ERROR; + } + + if (on) { + onbits |= (1<<j); + } else { + offbits |= (1<<j); + } + } + + /* Invalidate old intrep: + */ + if (objPtr->typePtr && objPtr->typePtr->freeIntRepProc) { + objPtr->typePtr->freeIntRepProc(objPtr); + } + + objPtr->typePtr = &StateSpecObjType; + objPtr->internalRep.longValue = (onbits << 16) | offbits; + + return TCL_OK; +} + +static void StateSpecUpdateString(Tcl_Obj *objPtr) +{ + unsigned int onbits = (objPtr->internalRep.longValue & 0xFFFF0000) >> 16; + unsigned int offbits = objPtr->internalRep.longValue & 0x0000FFFF; + unsigned int mask = onbits | offbits; + Tcl_DString result; + int i, len; + + Tcl_DStringInit(&result); + + for (i=0; stateNames[i] != NULL; ++i) { + if (mask & (1<<i)) { + if (offbits & (1<<i)) + Tcl_DStringAppend(&result, "!", 1); + Tcl_DStringAppend(&result, stateNames[i], -1); + Tcl_DStringAppend(&result, " ", 1); + } + } + + len = Tcl_DStringLength(&result); + if (len) { + /* 'len' includes extra trailing ' ' */ + objPtr->bytes = Tcl_Alloc((unsigned)len); + objPtr->length = len-1; + strncpy(objPtr->bytes, Tcl_DStringValue(&result), (size_t)len-1); + objPtr->bytes[len-1] = '\0'; + } else { + /* empty string */ + objPtr->length = 0; + objPtr->bytes = Tcl_Alloc(1); + *objPtr->bytes = '\0'; + } + + Tcl_DStringFree(&result); +} + +Tcl_Obj *Ttk_NewStateSpecObj(unsigned int onbits, unsigned int offbits) +{ + Tcl_Obj *objPtr = Tcl_NewObj(); + + Tcl_InvalidateStringRep(objPtr); + objPtr->typePtr = &StateSpecObjType; + objPtr->internalRep.longValue = (onbits << 16) | offbits; + + return objPtr; +} + +int Ttk_GetStateSpecFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + Ttk_StateSpec *spec) +{ + if (objPtr->typePtr != &StateSpecObjType) { + int status = StateSpecSetFromAny(interp, objPtr); + if (status != TCL_OK) + return status; + } + + spec->onbits = (objPtr->internalRep.longValue & 0xFFFF0000) >> 16; + spec->offbits = objPtr->internalRep.longValue & 0x0000FFFF; + return TCL_OK; +} + + +/* + * Tk_StateMapLookup -- + * + * A state map is a paired list of StateSpec / value pairs. + * Returns the value corresponding to the first matching state + * specification, or NULL if not found or an error occurs. + */ +Tcl_Obj *Ttk_StateMapLookup( + Tcl_Interp *interp, /* Where to leave error messages; may be NULL */ + Ttk_StateMap map, /* State map */ + Ttk_State state) /* State to look up */ +{ + Tcl_Obj **specs; + int nSpecs; + int j, status; + + status = Tcl_ListObjGetElements(interp, map, &nSpecs, &specs); + if (status != TCL_OK) + return NULL; + + for (j = 0; j < nSpecs; j += 2) { + Ttk_StateSpec spec; + status = Ttk_GetStateSpecFromObj(interp, specs[j], &spec); + if (status != TCL_OK) + return NULL; + if (Ttk_StateMatches(state, &spec)) + return specs[j+1]; + } + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "No match in state map", NULL); + } + return NULL; +} + +/* Ttk_GetStateMapFromObj -- + * Returns a Ttk_StateMap from a Tcl_Obj*. + * Since a Ttk_StateMap is just a specially-formatted Tcl_Obj, + * this basically just checks for errors. + */ +Ttk_StateMap Ttk_GetStateMapFromObj( + Tcl_Interp *interp, /* Where to leave error messages; may be NULL */ + Tcl_Obj *mapObj) /* State map */ +{ + Tcl_Obj **specs; + int nSpecs; + int j, status; + + status = Tcl_ListObjGetElements(interp, mapObj, &nSpecs, &specs); + if (status != TCL_OK) + return NULL; + + if (nSpecs % 2 != 0) { + if (interp) + Tcl_SetResult(interp, + "State map must have an even number of elements", + TCL_STATIC); + return 0; + } + + for (j = 0; j < nSpecs; j += 2) { + Ttk_StateSpec spec; + if (Ttk_GetStateSpecFromObj(interp, specs[j], &spec) != TCL_OK) + return NULL; + } + + return mapObj; +} + +/* + * Ttk_StateTableLooup -- + * Look up an index from a statically allocated state table. + */ +int Ttk_StateTableLookup(Ttk_StateTable *map, unsigned int state) +{ + while ((state & map->onBits) != map->onBits + || (~state & map->offBits) != map->offBits) + { + ++map; + } + return map->index; +} + +/*EOF*/ diff --git a/generic/ttk/ttkStubInit.c b/generic/ttk/ttkStubInit.c new file mode 100644 index 0000000..87b33dc --- /dev/null +++ b/generic/ttk/ttkStubInit.c @@ -0,0 +1,61 @@ +/* + * This file is (mostly) automatically generated from ttk.decls. + * It is compiled and linked in with the ttk package proper. + */ + +#include "tk.h" +#include "ttkTheme.h" + +MODULE_SCOPE const TtkStubs ttkStubs; + +/* !BEGIN!: Do not edit below this line. */ + +const TtkStubs ttkStubs = { + TCL_STUB_MAGIC, + TTK_STUBS_EPOCH, + TTK_STUBS_REVISION, + 0, + Ttk_GetTheme, /* 0 */ + Ttk_GetDefaultTheme, /* 1 */ + Ttk_GetCurrentTheme, /* 2 */ + Ttk_CreateTheme, /* 3 */ + Ttk_RegisterCleanup, /* 4 */ + Ttk_RegisterElementSpec, /* 5 */ + Ttk_RegisterElement, /* 6 */ + Ttk_RegisterElementFactory, /* 7 */ + Ttk_RegisterLayout, /* 8 */ + 0, /* 9 */ + Ttk_GetStateSpecFromObj, /* 10 */ + Ttk_NewStateSpecObj, /* 11 */ + Ttk_GetStateMapFromObj, /* 12 */ + Ttk_StateMapLookup, /* 13 */ + Ttk_StateTableLookup, /* 14 */ + 0, /* 15 */ + 0, /* 16 */ + 0, /* 17 */ + 0, /* 18 */ + 0, /* 19 */ + Ttk_GetPaddingFromObj, /* 20 */ + Ttk_GetBorderFromObj, /* 21 */ + Ttk_GetStickyFromObj, /* 22 */ + Ttk_MakePadding, /* 23 */ + Ttk_UniformPadding, /* 24 */ + Ttk_AddPadding, /* 25 */ + Ttk_RelievePadding, /* 26 */ + Ttk_MakeBox, /* 27 */ + Ttk_BoxContains, /* 28 */ + Ttk_PackBox, /* 29 */ + Ttk_StickBox, /* 30 */ + Ttk_AnchorBox, /* 31 */ + Ttk_PadBox, /* 32 */ + Ttk_ExpandBox, /* 33 */ + Ttk_PlaceBox, /* 34 */ + Ttk_NewBoxObj, /* 35 */ + 0, /* 36 */ + 0, /* 37 */ + 0, /* 38 */ + 0, /* 39 */ + Ttk_GetOrientFromObj, /* 40 */ +}; + +/* !END!: Do not edit above this line. */ diff --git a/generic/ttk/ttkStubLib.c b/generic/ttk/ttkStubLib.c new file mode 100644 index 0000000..2c07b9d --- /dev/null +++ b/generic/ttk/ttkStubLib.c @@ -0,0 +1,74 @@ +/* + * We need to ensure that we use the tcl stub macros so that this file + * contains no references to any of the tcl stub functions. + */ + +#undef USE_TCL_STUBS +#define USE_TCL_STUBS + +#include "tk.h" + +#define USE_TTK_STUBS 1 +#include "ttkTheme.h" + +MODULE_SCOPE const TtkStubs *ttkStubsPtr; +const TtkStubs *ttkStubsPtr = NULL; + +/* + *---------------------------------------------------------------------- + * + * TtkInitializeStubs -- + * Load the Ttk package, initialize stub table pointer. + * Do not call this function directly, use Ttk_InitStubs() macro instead. + * + * Results: + * The actual version of the package that satisfies the request, or + * NULL to indicate that an error occurred. + * + * Side effects: + * Sets the stub table pointer. + * + */ +MODULE_SCOPE const char * +TtkInitializeStubs( + Tcl_Interp *interp, const char *version, int epoch, int revision) +{ + int exact = 0; + const char *packageName = "Ttk"; + const char *errMsg = NULL; + ClientData pkgClientData = NULL; + const char *actualVersion = Tcl_PkgRequireEx( + interp, packageName, version, exact, &pkgClientData); + const TtkStubs *stubsPtr = pkgClientData; + + if (!actualVersion) { + return NULL; + } + + if (!stubsPtr) { + errMsg = "missing stub table pointer"; + goto error; + } + if (stubsPtr->epoch != epoch) { + errMsg = "epoch number mismatch"; + goto error; + } + if (stubsPtr->revision < revision) { + errMsg = "require later revision"; + goto error; + } + + ttkStubsPtr = stubsPtr; + return actualVersion; + +error: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Error loading ", packageName, " package", + " (requested version '", version, + "', loaded version '", actualVersion, "'): ", + errMsg, + NULL); + return NULL; +} + diff --git a/generic/ttk/ttkTagSet.c b/generic/ttk/ttkTagSet.c new file mode 100644 index 0000000..9f2a87b --- /dev/null +++ b/generic/ttk/ttkTagSet.c @@ -0,0 +1,306 @@ +/* + * Tag tables. 3/4-baked, work in progress. + * + * Copyright (C) 2005, Joe English. Freely redistributable. + */ + +#include <string.h> /* for memset() */ +#include <tcl.h> +#include <tk.h> + +#include "ttkTheme.h" +#include "ttkWidget.h" + +/*------------------------------------------------------------------------ + * +++ Internal data structures. + */ +struct TtkTag { + int priority; /* 1=>highest */ + const char *tagName; /* Back-pointer to hash table entry */ + void *tagRecord; /* User data */ +}; + +struct TtkTagTable { + Tk_Window tkwin; /* owner window */ + Tk_OptionSpec *optionSpecs; /* ... */ + Tk_OptionTable optionTable; /* ... */ + int recordSize; /* size of tag record */ + int nTags; /* #tags defined so far */ + Tcl_HashTable tags; /* defined tags */ +}; + +/*------------------------------------------------------------------------ + * +++ Tags. + */ +static Ttk_Tag NewTag(Ttk_TagTable tagTable, const char *tagName) +{ + Ttk_Tag tag = (Ttk_Tag)ckalloc(sizeof(*tag)); + tag->tagRecord = ckalloc(tagTable->recordSize); + memset(tag->tagRecord, 0, tagTable->recordSize); + /* Don't need Tk_InitOptions() here, all defaults should be NULL. */ + tag->priority = ++tagTable->nTags; + tag->tagName = tagName; + return tag; +} + +static void DeleteTag(Ttk_TagTable tagTable, Ttk_Tag tag) +{ + Tk_FreeConfigOptions(tag->tagRecord,tagTable->optionTable,tagTable->tkwin); + ckfree(tag->tagRecord); + ckfree((void*)tag); +} + +/*------------------------------------------------------------------------ + * +++ Tag tables. + */ + +Ttk_TagTable Ttk_CreateTagTable( + Tcl_Interp *interp, Tk_Window tkwin, + Tk_OptionSpec optionSpecs[], int recordSize) +{ + Ttk_TagTable tagTable = (Ttk_TagTable)ckalloc(sizeof(*tagTable)); + tagTable->tkwin = tkwin; + tagTable->optionSpecs = optionSpecs; + tagTable->optionTable = Tk_CreateOptionTable(interp, optionSpecs); + tagTable->recordSize = recordSize; + tagTable->nTags = 0; + Tcl_InitHashTable(&tagTable->tags, TCL_STRING_KEYS); + return tagTable; +} + +void Ttk_DeleteTagTable(Ttk_TagTable tagTable) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + + entryPtr = Tcl_FirstHashEntry(&tagTable->tags, &search); + while (entryPtr != NULL) { + DeleteTag(tagTable, Tcl_GetHashValue(entryPtr)); + entryPtr = Tcl_NextHashEntry(&search); + } + + Tcl_DeleteHashTable(&tagTable->tags); + ckfree((void*)tagTable); +} + +Ttk_Tag Ttk_GetTag(Ttk_TagTable tagTable, const char *tagName) +{ + int isNew = 0; + Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry( + &tagTable->tags, tagName, &isNew); + + if (isNew) { + tagName = Tcl_GetHashKey(&tagTable->tags, entryPtr); + Tcl_SetHashValue(entryPtr, NewTag(tagTable,tagName)); + } + return Tcl_GetHashValue(entryPtr); +} + +Ttk_Tag Ttk_GetTagFromObj(Ttk_TagTable tagTable, Tcl_Obj *objPtr) +{ + return Ttk_GetTag(tagTable, Tcl_GetString(objPtr)); +} + +/*------------------------------------------------------------------------ + * +++ Tag sets. + */ + +/* Ttk_GetTagSetFromObj -- + * Extract an array of pointers to Ttk_Tags from a Tcl_Obj. + * objPtr may be NULL, in which case a new empty tag set is returned. + * + * Returns NULL and leaves an error message in interp->result on error. + * + * Non-NULL results must be passed to Ttk_FreeTagSet(). + */ +Ttk_TagSet Ttk_GetTagSetFromObj( + Tcl_Interp *interp, Ttk_TagTable tagTable, Tcl_Obj *objPtr) +{ + Ttk_TagSet tagset = (Ttk_TagSet)(ckalloc(sizeof *tagset)); + Tcl_Obj **objv; + int i, objc; + + if (objPtr == NULL) { + tagset->tags = NULL; + tagset->nTags = 0; + return tagset; + } + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + ckfree((ClientData)tagset); + return NULL; + } + + tagset->tags = (Ttk_Tag*)ckalloc((objc+1) * sizeof(Ttk_Tag)); + for (i=0; i<objc; ++i) { + tagset->tags[i] = Ttk_GetTagFromObj(tagTable, objv[i]); + } + tagset->tags[i] = NULL; + tagset->nTags = objc; + + return tagset; +} + +/* Ttk_NewTagSetObj -- + * Construct a fresh Tcl_Obj * from a tag set. + */ +Tcl_Obj *Ttk_NewTagSetObj(Ttk_TagSet tagset) +{ + Tcl_Obj *result = Tcl_NewListObj(0,0); + int i; + + for (i = 0; i < tagset->nTags; ++i) { + Tcl_ListObjAppendElement( + NULL, result, Tcl_NewStringObj(tagset->tags[i]->tagName, -1)); + } + return result; +} + +void Ttk_FreeTagSet(Ttk_TagSet tagset) +{ + ckfree((ClientData)tagset->tags); + ckfree((ClientData)tagset); +} + +/* Ttk_TagSetContains -- test if tag set contains a tag. + */ +int Ttk_TagSetContains(Ttk_TagSet tagset, Ttk_Tag tag) +{ + int i; + for (i = 0; i < tagset->nTags; ++i) { + if (tagset->tags[i] == tag) { + return 1; + } + } + return 0; +} + +/* Ttk_TagSetAdd -- add a tag to a tag set. + * + * Returns: 0 if tagset already contained tag, + * 1 if tagset was modified. + */ +int Ttk_TagSetAdd(Ttk_TagSet tagset, Ttk_Tag tag) +{ + int i; + for (i = 0; i < tagset->nTags; ++i) { + if (tagset->tags[i] == tag) { + return 0; + } + } + tagset->tags = (void*)ckrealloc((void*)tagset->tags, + (tagset->nTags+1)*sizeof(tagset->tags[0])); + tagset->tags[tagset->nTags++] = tag; + return 1; +} + +/* Ttk_TagSetRemove -- remove a tag from a tag set. + * + * Returns: 0 if tagset did not contain tag, + * 1 if tagset was modified. + */ +int Ttk_TagSetRemove(Ttk_TagSet tagset, Ttk_Tag tag) +{ + int i = 0, j = 0; + while (i < tagset->nTags) { + if ((tagset->tags[j] = tagset->tags[i]) != tag) { + ++j; + } + ++i; + } + tagset->nTags = j; + return j != i; +} + +/*------------------------------------------------------------------------ + * +++ Utilities for widget commands. + */ + +/* Ttk_EnumerateTags -- implements [$w tag names] + */ +int Ttk_EnumerateTags( + Tcl_Interp *interp, Ttk_TagTable tagTable) +{ + return TtkEnumerateHashTable(interp, &tagTable->tags); +} + +/* Ttk_EnumerateTagOptions -- implements [$w tag configure $tag] + */ +int Ttk_EnumerateTagOptions( + Tcl_Interp *interp, Ttk_TagTable tagTable, Ttk_Tag tag) +{ + return TtkEnumerateOptions(interp, tag->tagRecord, + tagTable->optionSpecs, tagTable->optionTable, tagTable->tkwin); +} + +/* Ttk_TagOptionValue -- implements [$w tag configure $tag -option] + */ +Tcl_Obj *Ttk_TagOptionValue( + Tcl_Interp *interp, + Ttk_TagTable tagTable, + Ttk_Tag tag, + Tcl_Obj *optionName) +{ + return Tk_GetOptionValue(interp, + tag->tagRecord, tagTable->optionTable, optionName, tagTable->tkwin); +} + +/* Ttk_ConfigureTag -- implements [$w tag configure $tag -option value...] + */ +int Ttk_ConfigureTag( + Tcl_Interp *interp, + Ttk_TagTable tagTable, + Ttk_Tag tag, + int objc, Tcl_Obj *const objv[]) +{ + return Tk_SetOptions( + interp, tag->tagRecord, tagTable->optionTable, + objc, objv, tagTable->tkwin, NULL/*savedOptions*/, NULL/*mask*/); +} + +/*------------------------------------------------------------------------ + * +++ Tag values. + */ + +#define OBJ_AT(record, offset) (*(Tcl_Obj**)(((char*)record)+offset)) + +void Ttk_TagSetValues(Ttk_TagTable tagTable, Ttk_TagSet tagSet, void *record) +{ + const int LOWEST_PRIORITY = 0x7FFFFFFF; + int i, j; + + memset(record, 0, tagTable->recordSize); + + for (i = 0; tagTable->optionSpecs[i].type != TK_OPTION_END; ++i) { + Tk_OptionSpec *optionSpec = tagTable->optionSpecs + i; + int offset = optionSpec->objOffset; + int prio = LOWEST_PRIORITY; + + for (j = 0; j < tagSet->nTags; ++j) { + Ttk_Tag tag = tagSet->tags[j]; + if (OBJ_AT(tag->tagRecord, offset) != 0 && tag->priority < prio) { + OBJ_AT(record, offset) = OBJ_AT(tag->tagRecord, offset); + prio = tag->priority; + } + } + } +} + +void Ttk_TagSetApplyStyle( + Ttk_TagTable tagTable, Ttk_Style style, Ttk_State state, void *record) +{ + Tk_OptionSpec *optionSpec = tagTable->optionSpecs; + + while (optionSpec->type != TK_OPTION_END) { + int offset = optionSpec->objOffset; + const char *optionName = optionSpec->optionName; + Tcl_Obj *val = Ttk_StyleMap(style, optionName, state); + if (val) { + OBJ_AT(record, offset) = val; + } else if (OBJ_AT(record, offset) == 0) { + OBJ_AT(record, offset) = Ttk_StyleDefault(style, optionName); + } + ++optionSpec; + } +} + diff --git a/generic/ttk/ttkTheme.c b/generic/ttk/ttkTheme.c new file mode 100644 index 0000000..5095487 --- /dev/null +++ b/generic/ttk/ttkTheme.c @@ -0,0 +1,1737 @@ +/* + * ttkTheme.c -- + * + * This file implements the widget styles and themes support. + * + * Copyright (c) 2002 Frederic Bonnet + * Copyright (c) 2003 Joe English + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include <stdlib.h> +#include <string.h> +#include <tk.h> +#include <tkInt.h> +#include "ttkThemeInt.h" + +#define PKG_ASSOC_KEY "Ttk" + +/*------------------------------------------------------------------------ + * +++ Styles. + * + * Invariants: + * If styleName contains a dot, parentStyle->styleName is everything + * after the first dot; otherwise, parentStyle is the theme's root + * style ".". The root style's parentStyle is NULL. + * + */ + +typedef struct Ttk_Style_ +{ + const char *styleName; /* points to hash table key */ + Tcl_HashTable settingsTable; /* KEY: string; VALUE: StateMap */ + Tcl_HashTable defaultsTable; /* KEY: string; VALUE: resource */ + Ttk_LayoutTemplate layoutTemplate; /* Layout template for style, or NULL */ + Ttk_Style parentStyle; /* Previous style in chain */ + Ttk_ResourceCache cache; /* Back-pointer to resource cache */ +} Style; + +static Style *NewStyle() +{ + Style *stylePtr = (Style*)ckalloc(sizeof(Style)); + + stylePtr->styleName = NULL; + stylePtr->parentStyle = NULL; + stylePtr->layoutTemplate = NULL; + stylePtr->cache = NULL; + Tcl_InitHashTable(&stylePtr->settingsTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&stylePtr->defaultsTable, TCL_STRING_KEYS); + + return stylePtr; +} + +static void FreeStyle(Style *stylePtr) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + + entryPtr = Tcl_FirstHashEntry(&stylePtr->settingsTable, &search); + while (entryPtr != NULL) { + Ttk_StateMap stateMap = Tcl_GetHashValue(entryPtr); + Tcl_DecrRefCount(stateMap); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&stylePtr->settingsTable); + + entryPtr = Tcl_FirstHashEntry(&stylePtr->defaultsTable, &search); + while (entryPtr != NULL) { + Tcl_Obj *defaultValue = Tcl_GetHashValue(entryPtr); + Tcl_DecrRefCount(defaultValue); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&stylePtr->defaultsTable); + + Ttk_FreeLayoutTemplate(stylePtr->layoutTemplate); + + ckfree((ClientData)stylePtr); +} + +/* + * Ttk_StyleMap -- + * Look up state-specific option value from specified style. + */ +Tcl_Obj *Ttk_StyleMap(Ttk_Style style, const char *optionName, Ttk_State state) +{ + while (style) { + Tcl_HashEntry *entryPtr = + Tcl_FindHashEntry(&style->settingsTable, optionName); + if (entryPtr) { + Ttk_StateMap stateMap = Tcl_GetHashValue(entryPtr); + return Ttk_StateMapLookup(NULL, stateMap, state); + } + style = style->parentStyle; + } + return 0; +} + +/* + * Ttk_StyleDefault -- + * Look up default resource setting the in the specified style. + */ +Tcl_Obj *Ttk_StyleDefault(Ttk_Style style, const char *optionName) +{ + while (style) { + Tcl_HashEntry *entryPtr = + Tcl_FindHashEntry(&style->defaultsTable, optionName); + if (entryPtr) + return Tcl_GetHashValue(entryPtr); + style= style->parentStyle; + } + return 0; +} + +/*------------------------------------------------------------------------ + * +++ Elements. + */ +typedef const Tk_OptionSpec **OptionMap; + /* array of Tk_OptionSpecs mapping widget options to element options */ + +struct Ttk_ElementClass_ { + const char *name; /* Points to hash table key */ + Ttk_ElementSpec *specPtr; /* Template provided during registration. */ + void *clientData; /* Client data passed in at registration time */ + void *elementRecord; /* Scratch buffer for element record storage */ + int nResources; /* #Element options */ + Tcl_Obj **defaultValues; /* Array of option default values */ + Tcl_HashTable optMapCache; /* Map: Tk_OptionTable * -> OptionMap */ +}; + +/* TTKGetOptionSpec -- + * Look up a Tk_OptionSpec by name from a Tk_OptionTable, + * and verify that it's compatible with the specified Tk_OptionType, + * along with other constraints (see below). + */ +static const Tk_OptionSpec *TTKGetOptionSpec( + const char *optionName, + Tk_OptionTable optionTable, + Tk_OptionType optionType) +{ + const Tk_OptionSpec *optionSpec = TkGetOptionSpec(optionName, optionTable); + + if (!optionSpec) + return 0; + + /* Make sure widget option has a Tcl_Obj* entry: + */ + if (optionSpec->objOffset < 0) { + return 0; + } + + /* Grrr. Ignore accidental mismatches caused by prefix-matching: + */ + if (strcmp(optionSpec->optionName, optionName)) { + return 0; + } + + /* Ensure that the widget option type is compatible with + * the element option type. + * + * TK_OPTION_STRING element options are compatible with anything. + * As a workaround for the workaround for Bug #967209, + * TK_OPTION_STRING widget options are also compatible with anything + * (see <<NOTE-NULLOPTIONS>>). + */ + if ( optionType != TK_OPTION_STRING + && optionSpec->type != TK_OPTION_STRING + && optionType != optionSpec->type) + { + return 0; + } + + return optionSpec; +} + +/* BuildOptionMap -- + * Construct the mapping from element options to widget options. + */ +static OptionMap +BuildOptionMap(Ttk_ElementClass *elementClass, Tk_OptionTable optionTable) +{ + OptionMap optionMap = (OptionMap)ckalloc( + sizeof(const Tk_OptionSpec) * elementClass->nResources + 1); + int i; + + for (i = 0; i < elementClass->nResources; ++i) { + Ttk_ElementOptionSpec *e = elementClass->specPtr->options+i; + optionMap[i] = TTKGetOptionSpec(e->optionName, optionTable, e->type); + } + + return optionMap; +} + +/* GetOptionMap -- + * Return a cached OptionMap matching the specified optionTable + * for the specified element, creating it if necessary. + */ +static OptionMap +GetOptionMap(Ttk_ElementClass *elementClass, Tk_OptionTable optionTable) +{ + OptionMap optionMap; + int isNew; + Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry( + &elementClass->optMapCache, (void*)optionTable, &isNew); + + if (isNew) { + optionMap = BuildOptionMap(elementClass, optionTable); + Tcl_SetHashValue(entryPtr, optionMap); + } else { + optionMap = Tcl_GetHashValue(entryPtr); + } + + return optionMap; +} + +/* + * NewElementClass -- + * Allocate and initialize an element class record + * from the specified element specification. + */ +static Ttk_ElementClass * +NewElementClass(const char *name, Ttk_ElementSpec *specPtr,void *clientData) +{ + Ttk_ElementClass *elementClass = + (Ttk_ElementClass*)ckalloc(sizeof(Ttk_ElementClass)); + int i; + + elementClass->name = name; + elementClass->specPtr = specPtr; + elementClass->clientData = clientData; + elementClass->elementRecord = ckalloc(specPtr->elementSize); + + /* Count #element resources: + */ + for (i = 0; specPtr->options[i].optionName != 0; ++i) + continue; + elementClass->nResources = i; + + /* Initialize default values: + */ + elementClass->defaultValues = (Tcl_Obj**) + ckalloc(elementClass->nResources * sizeof(Tcl_Obj *) + 1); + for (i=0; i < elementClass->nResources; ++i) { + const char *defaultValue = specPtr->options[i].defaultValue; + if (defaultValue) { + elementClass->defaultValues[i] = Tcl_NewStringObj(defaultValue,-1); + Tcl_IncrRefCount(elementClass->defaultValues[i]); + } else { + elementClass->defaultValues[i] = 0; + } + } + + /* Initialize option map cache: + */ + Tcl_InitHashTable(&elementClass->optMapCache, TCL_ONE_WORD_KEYS); + + return elementClass; +} + +/* + * FreeElementClass -- + * Release resources associated with an element class record. + */ +static void FreeElementClass(Ttk_ElementClass *elementClass) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + int i; + + /* + * Free default values: + */ + for (i = 0; i < elementClass->nResources; ++i) { + if (elementClass->defaultValues[i]) { + Tcl_DecrRefCount(elementClass->defaultValues[i]); + } + } + ckfree((ClientData)elementClass->defaultValues); + + /* + * Free option map cache: + */ + entryPtr = Tcl_FirstHashEntry(&elementClass->optMapCache, &search); + while (entryPtr != NULL) { + ckfree(Tcl_GetHashValue(entryPtr)); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&elementClass->optMapCache); + + ckfree(elementClass->elementRecord); + ckfree((ClientData)elementClass); +} + +/*------------------------------------------------------------------------ + * +++ Themes. + */ + +static int ThemeEnabled(Ttk_Theme theme, void *clientData) { return 1; } + /* Default ThemeEnabledProc -- always return true */ + +typedef struct Ttk_Theme_ +{ + Ttk_Theme parentPtr; /* Parent theme. */ + Tcl_HashTable elementTable; /* Map element names to class records */ + Tcl_HashTable styleTable; /* Map style names to Styles */ + Ttk_Style rootStyle; /* "." style, root of chain */ + Ttk_ThemeEnabledProc *enabledProc; /* Function called by SetTheme */ + void *enabledData; /* ClientData for enabledProc */ + Ttk_ResourceCache cache; /* Back-pointer to resource cache */ +} Theme; + +static Theme *NewTheme(Ttk_ResourceCache cache, Ttk_Theme parent) +{ + Theme *themePtr = (Theme*)ckalloc(sizeof(Theme)); + Tcl_HashEntry *entryPtr; + int unused; + + themePtr->parentPtr = parent; + themePtr->enabledProc = ThemeEnabled; + themePtr->enabledData = NULL; + themePtr->cache = cache; + Tcl_InitHashTable(&themePtr->elementTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&themePtr->styleTable, TCL_STRING_KEYS); + + /* + * Create root style "." + */ + entryPtr = Tcl_CreateHashEntry(&themePtr->styleTable, ".", &unused); + themePtr->rootStyle = NewStyle(); + themePtr->rootStyle->styleName = + Tcl_GetHashKey(&themePtr->styleTable, entryPtr); + themePtr->rootStyle->cache = themePtr->cache; + Tcl_SetHashValue(entryPtr, themePtr->rootStyle); + + return themePtr; +} + +static void FreeTheme(Theme *themePtr) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + + /* + * Free element table: + */ + entryPtr = Tcl_FirstHashEntry(&themePtr->elementTable, &search); + while (entryPtr != NULL) { + Ttk_ElementClass *elementClass = Tcl_GetHashValue(entryPtr); + FreeElementClass(elementClass); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&themePtr->elementTable); + + /* + * Free style table: + */ + entryPtr = Tcl_FirstHashEntry(&themePtr->styleTable, &search); + while (entryPtr != NULL) { + Style *stylePtr = Tcl_GetHashValue(entryPtr); + FreeStyle(stylePtr); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&themePtr->styleTable); + + /* + * Free theme record: + */ + ckfree((ClientData)themePtr); + + return; +} + +/* + * Element constructors. + */ +typedef struct { + Ttk_ElementFactory factory; + void *clientData; +} FactoryRec; + +/* + * Cleanup records: + */ +typedef struct CleanupStruct { + void *clientData; + Ttk_CleanupProc *cleanupProc; + struct CleanupStruct *next; +} Cleanup; + +/*------------------------------------------------------------------------ + * +++ Master style package data structure. + */ +typedef struct +{ + Tcl_Interp *interp; /* Owner interp */ + Tcl_HashTable themeTable; /* KEY: name; VALUE: Theme pointer */ + Tcl_HashTable factoryTable; /* KEY: name; VALUE: FactoryRec ptr */ + Theme *defaultTheme; /* Default theme; global fallback*/ + Theme *currentTheme; /* Currently-selected theme */ + Cleanup *cleanupList; /* Cleanup records */ + Ttk_ResourceCache cache; /* Resource cache */ + int themeChangePending; /* scheduled ThemeChangedProc call? */ +} StylePackageData; + +static void ThemeChangedProc(ClientData); /* Forward */ + +/* Ttk_StylePkgFree -- + * Cleanup procedure for StylePackageData. + */ +static void Ttk_StylePkgFree(ClientData clientData, Tcl_Interp *interp) +{ + StylePackageData *pkgPtr = clientData; + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr; + Cleanup *cleanup; + + /* + * Cancel any pending ThemeChanged calls: + */ + if (pkgPtr->themeChangePending) { + Tcl_CancelIdleCall(ThemeChangedProc, pkgPtr); + } + + /* + * Free themes. + */ + entryPtr = Tcl_FirstHashEntry(&pkgPtr->themeTable, &search); + while (entryPtr != NULL) { + Theme *themePtr = Tcl_GetHashValue(entryPtr); + FreeTheme(themePtr); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&pkgPtr->themeTable); + + /* + * Free element constructor table: + */ + entryPtr = Tcl_FirstHashEntry(&pkgPtr->factoryTable, &search); + while (entryPtr != NULL) { + ckfree(Tcl_GetHashValue(entryPtr)); + entryPtr = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&pkgPtr->factoryTable); + + /* + * Release cache: + */ + Ttk_FreeResourceCache(pkgPtr->cache); + + /* + * Call all registered cleanup procedures: + */ + cleanup = pkgPtr->cleanupList; + while (cleanup) { + Cleanup *next = cleanup->next; + cleanup->cleanupProc(cleanup->clientData); + ckfree((ClientData)cleanup); + cleanup = next; + } + + ckfree((ClientData)pkgPtr); +} + +/* + * GetStylePackageData -- + * Look up the package data registered with the interp. + */ + +static StylePackageData *GetStylePackageData(Tcl_Interp *interp) +{ + return Tcl_GetAssocData(interp, PKG_ASSOC_KEY, NULL); +} + +/* + * Ttk_RegisterCleanup -- + * + * Register a function to be called when a theme engine is deleted. + * (This only happens when the main interp is destroyed). The cleanup + * function is called with the current Tcl interpreter and the client + * data provided here. + * + */ +void Ttk_RegisterCleanup( + Tcl_Interp *interp, ClientData clientData, Ttk_CleanupProc *cleanupProc) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + Cleanup *cleanup = (Cleanup*)ckalloc(sizeof(*cleanup)); + + cleanup->clientData = clientData; + cleanup->cleanupProc = cleanupProc; + cleanup->next = pkgPtr->cleanupList; + pkgPtr->cleanupList = cleanup; +} + +/* ThemeChangedProc -- + * Notify all widgets that the theme has been changed. + * Scheduled as an idle callback; clientData is a StylePackageData *. + * + * Sends a <<ThemeChanged>> event to every widget in the hierarchy. + * Widgets respond to this by calling the WorldChanged class proc, + * which in turn recreates the layout. + * + * The Tk C API doesn't doesn't provide an easy way to traverse + * the widget hierarchy, so this is done by evaluating a Tcl script. + */ + +static void ThemeChangedProc(ClientData clientData) +{ + static char ThemeChangedScript[] = "ttk::ThemeChanged"; + StylePackageData *pkgPtr = clientData; + + if (Tcl_GlobalEval(pkgPtr->interp, ThemeChangedScript) != TCL_OK) { + Tcl_BackgroundError(pkgPtr->interp); + } + pkgPtr->themeChangePending = 0; +} + +/* + * ThemeChanged -- + * Schedule a call to ThemeChanged if one is not already pending. + */ +static void ThemeChanged(StylePackageData *pkgPtr) +{ + if (!pkgPtr->themeChangePending) { + Tcl_DoWhenIdle(ThemeChangedProc, pkgPtr); + pkgPtr->themeChangePending = 1; + } +} + +/* + * Ttk_CreateTheme -- + * Create a new theme and register it in the global theme table. + * + * Returns: + * Pointer to new Theme structure; NULL if named theme already exists. + * Leaves an error message in interp's result on error. + */ + +Ttk_Theme +Ttk_CreateTheme( + Tcl_Interp *interp, /* Interpreter in which to create theme */ + const char *name, /* Name of the theme to create. */ + Ttk_Theme parent) /* Parent/fallback theme, NULL for default */ +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + Tcl_HashEntry *entryPtr; + int newEntry; + Theme *themePtr; + + entryPtr = Tcl_CreateHashEntry(&pkgPtr->themeTable, name, &newEntry); + if (!newEntry) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Theme ", name, " already exists", NULL); + return NULL; + } + + /* + * Initialize new theme: + */ + if (!parent) parent = pkgPtr->defaultTheme; + + themePtr = NewTheme(pkgPtr->cache, parent); + Tcl_SetHashValue(entryPtr, themePtr); + + return themePtr; +} + +/* + * Ttk_SetThemeEnabledProc -- + * Sets a procedure that is used to check that this theme is available. + */ + +void Ttk_SetThemeEnabledProc( + Ttk_Theme theme, Ttk_ThemeEnabledProc enabledProc, void *enabledData) +{ + theme->enabledProc = enabledProc; + theme->enabledData = enabledData; +} + +/* + * LookupTheme -- + * Retrieve a registered theme by name. If not found, + * returns NULL and leaves an error message in interp's result. + */ + +static Ttk_Theme LookupTheme( + Tcl_Interp *interp, /* where to leave error messages */ + StylePackageData *pkgPtr, /* style package master record */ + const char *name) /* theme name */ +{ + Tcl_HashEntry *entryPtr; + + entryPtr = Tcl_FindHashEntry(&pkgPtr->themeTable, name); + if (!entryPtr) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "theme \"", name, "\" doesn't exist", NULL); + return NULL; + } + + return Tcl_GetHashValue(entryPtr); +} + +/* + * Ttk_GetTheme -- + * Public interface to LookupTheme. + */ +Ttk_Theme Ttk_GetTheme(Tcl_Interp *interp, const char *themeName) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + + return LookupTheme(interp, pkgPtr, themeName); +} + +Ttk_Theme Ttk_GetCurrentTheme(Tcl_Interp *interp) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + return pkgPtr->currentTheme; +} + +Ttk_Theme Ttk_GetDefaultTheme(Tcl_Interp *interp) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + return pkgPtr->defaultTheme; +} + +/* + * Ttk_UseTheme -- + * Set the current theme, notify all widgets that the theme has changed. + */ +int Ttk_UseTheme(Tcl_Interp *interp, Ttk_Theme theme) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + + /* + * Check if selected theme is enabled: + */ + while (theme && !theme->enabledProc(theme, theme->enabledData)) { + theme = theme->parentPtr; + } + if (!theme) { + /* This shouldn't happen -- default theme should always work */ + Tcl_Panic("No themes available?"); + return TCL_ERROR; + } + + pkgPtr->currentTheme = theme; + ThemeChanged(pkgPtr); + return TCL_OK; +} + +/* + * Ttk_GetResourceCache -- + * Return the resource cache associated with 'interp' + */ +Ttk_ResourceCache +Ttk_GetResourceCache(Tcl_Interp *interp) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + return pkgPtr->cache; +} + +/* + * Register a new layout specification with a style. + * @@@ TODO: Make sure layoutName is not ".", root style must not have a layout + */ +MODULE_SCOPE +void Ttk_RegisterLayoutTemplate( + Ttk_Theme theme, /* Target theme */ + const char *layoutName, /* Name of new layout */ + Ttk_LayoutTemplate layoutTemplate) /* Template */ +{ + Ttk_Style style = Ttk_GetStyle(theme, layoutName); + if (style->layoutTemplate) { + Ttk_FreeLayoutTemplate(style->layoutTemplate); + } + style->layoutTemplate = layoutTemplate; +} + +void Ttk_RegisterLayout( + Ttk_Theme themePtr, /* Target theme */ + const char *layoutName, /* Name of new layout */ + Ttk_LayoutSpec specPtr) /* Static layout information */ +{ + Ttk_LayoutTemplate layoutTemplate = Ttk_BuildLayoutTemplate(specPtr); + Ttk_RegisterLayoutTemplate(themePtr, layoutName, layoutTemplate); +} + +/* + * Ttk_GetStyle -- + * Look up a Style from a Theme, create new style if not found. + */ +Ttk_Style Ttk_GetStyle(Ttk_Theme themePtr, const char *styleName) +{ + Tcl_HashEntry *entryPtr; + int newStyle; + + entryPtr = Tcl_CreateHashEntry(&themePtr->styleTable, styleName, &newStyle); + if (newStyle) { + Ttk_Style stylePtr = NewStyle(); + const char *dot = strchr(styleName, '.'); + + if (dot) { + stylePtr->parentStyle = Ttk_GetStyle(themePtr, dot + 1); + } else { + stylePtr->parentStyle = themePtr->rootStyle; + } + + stylePtr->styleName = Tcl_GetHashKey(&themePtr->styleTable, entryPtr); + stylePtr->cache = stylePtr->parentStyle->cache; + Tcl_SetHashValue(entryPtr, stylePtr); + return stylePtr; + } + return Tcl_GetHashValue(entryPtr); +} + +/* FindLayoutTemplate -- + * Locate a layout template in the layout table, checking + * generic names to specific names first, then looking for + * the full name in the parent theme. + */ +Ttk_LayoutTemplate +Ttk_FindLayoutTemplate(Ttk_Theme themePtr, const char *layoutName) +{ + while (themePtr) { + Ttk_Style stylePtr = Ttk_GetStyle(themePtr, layoutName); + while (stylePtr) { + if (stylePtr->layoutTemplate) { + return stylePtr->layoutTemplate; + } + stylePtr = stylePtr->parentStyle; + } + themePtr = themePtr->parentPtr; + } + return NULL; +} + +const char *Ttk_StyleName(Ttk_Style stylePtr) +{ + return stylePtr->styleName; +} + +/* + * Ttk_GetElement -- + * Look up an element class by name in a given theme. + * If not found, try generic element names in this theme, then + * repeat the lookups in the parent theme. + * If not found, return the null element. + */ +Ttk_ElementClass *Ttk_GetElement(Ttk_Theme themePtr, const char *elementName) +{ + Tcl_HashEntry *entryPtr; + const char *dot = elementName; + + /* + * Check if element has already been registered: + */ + entryPtr = Tcl_FindHashEntry(&themePtr->elementTable, elementName); + if (entryPtr) { + return Tcl_GetHashValue(entryPtr); + } + + /* + * Check generic names: + */ + while (!entryPtr && ((dot = strchr(dot, '.')) != NULL)) { + dot++; + entryPtr = Tcl_FindHashEntry(&themePtr->elementTable, dot); + } + if (entryPtr) { + return Tcl_GetHashValue(entryPtr); + } + + /* + * Check parent theme: + */ + if (themePtr->parentPtr) { + return Ttk_GetElement(themePtr->parentPtr, elementName); + } + + /* + * Not found, and this is the root theme; return null element, "". + * (@@@ SHOULD: signal a background error) + */ + entryPtr = Tcl_FindHashEntry(&themePtr->elementTable, ""); + /* ASSERT: entryPtr != 0 */ + return Tcl_GetHashValue(entryPtr); +} + +const char *Ttk_ElementClassName(Ttk_ElementClass *elementClass) +{ + return elementClass->name; +} + +/* + * Ttk_RegisterElementFactory -- + * Register a new element factory. + */ +int Ttk_RegisterElementFactory( + Tcl_Interp *interp, const char *name, + Ttk_ElementFactory factory, void *clientData) +{ + StylePackageData *pkgPtr = GetStylePackageData(interp); + FactoryRec *recPtr = (FactoryRec*)ckalloc(sizeof(*recPtr)); + Tcl_HashEntry *entryPtr; + int newEntry; + + recPtr->factory = factory; + recPtr->clientData = clientData; + + entryPtr = Tcl_CreateHashEntry(&pkgPtr->factoryTable, name, &newEntry); + if (!newEntry) { + /* Free old factory: */ + ckfree(Tcl_GetHashValue(entryPtr)); + } + Tcl_SetHashValue(entryPtr, recPtr); + + return TCL_OK; +} + +/* Ttk_CloneElement -- element factory procedure. + * (style element create $name) "from" $theme ?$element? + */ +static int Ttk_CloneElement( + Tcl_Interp *interp, void *clientData, + Ttk_Theme theme, const char *elementName, + int objc, Tcl_Obj *const objv[]) +{ + Ttk_Theme fromTheme; + Ttk_ElementClass *fromElement; + + if (objc <= 0 || objc > 2) { + Tcl_WrongNumArgs(interp, 0, objv, "theme ?element?"); + return TCL_ERROR; + } + + fromTheme = Ttk_GetTheme(interp, Tcl_GetString(objv[0])); + if (!fromTheme) { + return TCL_ERROR; + } + + if (objc == 2) { + fromElement = Ttk_GetElement(fromTheme, Tcl_GetString(objv[1])); + } else { + fromElement = Ttk_GetElement(fromTheme, elementName); + } + if (!fromElement) { + return TCL_ERROR; + } + + if (Ttk_RegisterElement(interp, theme, elementName, + fromElement->specPtr, fromElement->clientData) == NULL) + { + return TCL_ERROR; + } + return TCL_OK; +} + +/* Ttk_RegisterElement-- + * Register an element in the given theme. + * Returns: Element handle if successful, NULL otherwise. + * On failure, leaves an error message in interp's result + * if interp is non-NULL. + */ + +Ttk_ElementClass *Ttk_RegisterElement( + Tcl_Interp *interp, /* Where to leave error messages */ + Ttk_Theme theme, /* Style engine providing the implementation. */ + const char *name, /* Name of new element */ + Ttk_ElementSpec *specPtr, /* Static template information */ + void *clientData) /* application-specific data */ +{ + Ttk_ElementClass *elementClass; + Tcl_HashEntry *entryPtr; + int newEntry; + + if (specPtr->version != TK_STYLE_VERSION_2) { + /* Version mismatch */ + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Internal error: Ttk_RegisterElement (", + name, "): invalid version", + NULL); + } + return 0; + } + + entryPtr = Tcl_CreateHashEntry(&theme->elementTable, name, &newEntry); + if (!newEntry) { + if (interp) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Duplicate element ", name, NULL); + } + return 0; + } + + name = Tcl_GetHashKey(&theme->elementTable, entryPtr); + elementClass = NewElementClass(name, specPtr, clientData); + Tcl_SetHashValue(entryPtr, elementClass); + + return elementClass; +} + +/* Ttk_RegisterElementSpec (deprecated) -- + * Register a new element. + */ +int Ttk_RegisterElementSpec(Ttk_Theme theme, + const char *name, Ttk_ElementSpec *specPtr, void *clientData) +{ + return Ttk_RegisterElement(NULL, theme, name, specPtr, clientData) + ? TCL_OK : TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Element record initialization. + */ + +/* + * AllocateResource -- + * Extra initialization for element options like TK_OPTION_COLOR, etc. + * + * Returns: 1 if OK, 0 on failure. + * + * Note: if resource allocation fails at this point (just prior + * to drawing an element), there's really no good place to + * report the error. Instead we just silently fail. + */ + +static int AllocateResource( + Ttk_ResourceCache cache, + Tk_Window tkwin, + Tcl_Obj **destPtr, + int optionType) +{ + Tcl_Obj *resource = *destPtr; + + switch (optionType) + { + case TK_OPTION_FONT: + return (*destPtr = Ttk_UseFont(cache, tkwin, resource)) != NULL; + case TK_OPTION_COLOR: + return (*destPtr = Ttk_UseColor(cache, tkwin, resource)) != NULL; + case TK_OPTION_BORDER: + return (*destPtr = Ttk_UseBorder(cache, tkwin, resource)) != NULL; + default: + /* no-op; always succeeds */ + return 1; + } +} + +/* + * InitializeElementRecord -- + * + * Fill in the element record based on the element's option table. + * Resources are initialized from: + * the corresponding widget option if present and non-NULL, + * otherwise the dynamic state map if specified, + * otherwise from the corresponding widget resource if present, + * otherwise the default value specified at registration time. + * + * Returns: + * 1 if OK, 0 if an error is detected. + * + * NOTES: + * Tcl_Obj * reference counts are _NOT_ adjusted. + */ + +static +int InitializeElementRecord( + Ttk_ElementClass *eclass, /* Element instance to initialize */ + Ttk_Style style, /* Style table */ + char *widgetRecord, /* Source of widget option values */ + Tk_OptionTable optionTable, /* Option table describing widget record */ + Tk_Window tkwin, /* Corresponding window */ + Ttk_State state) /* Widget or element state */ +{ + char *elementRecord = eclass->elementRecord; + OptionMap optionMap = GetOptionMap(eclass,optionTable); + int nResources = eclass->nResources; + Ttk_ResourceCache cache = style->cache; + Ttk_ElementOptionSpec *elementOption = eclass->specPtr->options; + + int i; + for (i=0; i<nResources; ++i, ++elementOption) { + Tcl_Obj **dest = (Tcl_Obj **) + (elementRecord + elementOption->offset); + const char *optionName = elementOption->optionName; + Tcl_Obj *dynamicSetting = Ttk_StyleMap(style, optionName, state); + Tcl_Obj *widgetValue = 0; + Tcl_Obj *elementDefault = eclass->defaultValues[i]; + + if (optionMap[i]) { + widgetValue = *(Tcl_Obj **) + (widgetRecord + optionMap[i]->objOffset); + } + + if (widgetValue) { + *dest = widgetValue; + } else if (dynamicSetting) { + *dest = dynamicSetting; + } else { + Tcl_Obj *styleDefault = Ttk_StyleDefault(style, optionName); + *dest = styleDefault ? styleDefault : elementDefault; + } + + if (!AllocateResource(cache, tkwin, dest, elementOption->type)) { + return 0; + } + } + + return 1; +} + +/*------------------------------------------------------------------------ + * +++ Public API. + */ + +/* + * Ttk_QueryStyle -- + * Look up a style option based on the current state. + */ +Tcl_Obj *Ttk_QueryStyle( + Ttk_Style style, /* Style to query */ + void *recordPtr, /* Widget record */ + Tk_OptionTable optionTable, /* Option table describing widget record */ + const char *optionName, /* Option name */ + Ttk_State state) /* Current state */ +{ + const Tk_OptionSpec *optionSpec; + Tcl_Obj *result; + + /* + * Check widget record: + */ + optionSpec = TTKGetOptionSpec(optionName, optionTable, TK_OPTION_ANY); + if (optionSpec) { + result = *(Tcl_Obj**)(((char*)recordPtr) + optionSpec->objOffset); + if (result) { + return result; + } + } + + /* + * Check dynamic settings: + */ + result = Ttk_StyleMap(style, optionName, state); + if (result) { + return result; + } + + /* + * Use style default: + */ + return Ttk_StyleDefault(style, optionName); +} + +/* + * Ttk_ElementSize -- + * Compute the requested size of the given element. + */ + +void +Ttk_ElementSize( + Ttk_ElementClass *eclass, /* Element to query */ + Ttk_Style style, /* Style settings */ + char *recordPtr, /* The widget record. */ + Tk_OptionTable optionTable, /* Description of widget record */ + Tk_Window tkwin, /* The widget window. */ + Ttk_State state, /* Current widget state */ + int *widthPtr, /* Requested width */ + int *heightPtr, /* Reqested height */ + Ttk_Padding *paddingPtr) /* Requested inner border */ +{ + paddingPtr->left = paddingPtr->right = paddingPtr->top = paddingPtr->bottom + = *widthPtr = *heightPtr = 0; + + if (!InitializeElementRecord( + eclass, style, recordPtr, optionTable, tkwin, state)) + { + return; + } + eclass->specPtr->size( + eclass->clientData, eclass->elementRecord, + tkwin, widthPtr, heightPtr, paddingPtr); +} + +/* + * Ttk_DrawElement -- + * Draw the given widget element in a given drawable area. + */ + +void +Ttk_DrawElement( + Ttk_ElementClass *eclass, /* Element instance */ + Ttk_Style style, /* Style settings */ + char *recordPtr, /* The widget record. */ + Tk_OptionTable optionTable, /* Description of option table */ + Tk_Window tkwin, /* The widget window. */ + Drawable d, /* Where to draw element. */ + Ttk_Box b, /* Element area */ + Ttk_State state) /* Widget or element state flags. */ +{ + if (b.width <= 0 || b.height <= 0) + return; + if (!InitializeElementRecord( + eclass, style, recordPtr, optionTable, tkwin, state)) + { + return; + } + eclass->specPtr->draw( + eclass->clientData, eclass->elementRecord, + tkwin, d, b, state); +} + +/*------------------------------------------------------------------------ + * +++ 'style' command ensemble procedures. + */ + +/* + * TtkEnumerateHashTable -- + * Helper routine. Sets interp's result to the list of all keys + * in the hash table. + * + * Returns: TCL_OK. + * Side effects: Sets interp's result. + */ + +MODULE_SCOPE +int TtkEnumerateHashTable(Tcl_Interp *interp, Tcl_HashTable *ht) +{ + Tcl_HashSearch search; + Tcl_Obj *result = Tcl_NewListObj(0, NULL); + Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(ht, &search); + + while (entryPtr != NULL) { + Tcl_Obj *nameObj = Tcl_NewStringObj(Tcl_GetHashKey(ht, entryPtr),-1); + Tcl_ListObjAppendElement(interp, result, nameObj); + entryPtr = Tcl_NextHashEntry(&search); + } + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* HashTableToDict -- + * Helper routine. Converts a TCL_STRING_KEYS Tcl_HashTable + * with Tcl_Obj * entries into a dictionary. + */ +static Tcl_Obj* HashTableToDict(Tcl_HashTable *ht) +{ + Tcl_HashSearch search; + Tcl_Obj *result = Tcl_NewListObj(0, NULL); + Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(ht, &search); + + while (entryPtr != NULL) { + Tcl_Obj *nameObj = Tcl_NewStringObj(Tcl_GetHashKey(ht, entryPtr),-1); + Tcl_Obj *valueObj = Tcl_GetHashValue(entryPtr); + Tcl_ListObjAppendElement(NULL, result, nameObj); + Tcl_ListObjAppendElement(NULL, result, valueObj); + entryPtr = Tcl_NextHashEntry(&search); + } + + return result; +} + +/* + style map $style ? -resource statemap ... ? + * + * Note that resource names are unconstrained; the Style + * doesn't know what resources individual elements may use. + */ +static int +StyleMapCmd( + ClientData clientData, /* Master StylePackageData pointer */ + Tcl_Interp *interp, /* Current interpreter */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument objects */ +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + const char *styleName; + Style *stylePtr; + int i; + + if (objc < 3) { +usage: + Tcl_WrongNumArgs(interp,2,objv,"style ?-option ?value...??"); + return TCL_ERROR; + } + + styleName = Tcl_GetString(objv[2]); + stylePtr = Ttk_GetStyle(theme, styleName); + + /* NOTE: StateMaps are actually Tcl_Obj *s, so HashTableToDict works + * for settingsTable. + */ + if (objc == 3) { /* style map $styleName */ + Tcl_SetObjResult(interp, HashTableToDict(&stylePtr->settingsTable)); + return TCL_OK; + } else if (objc == 4) { /* style map $styleName -option */ + const char *optionName = Tcl_GetString(objv[3]); + Tcl_HashEntry *entryPtr = + Tcl_FindHashEntry(&stylePtr->settingsTable, optionName); + if (entryPtr) { + Tcl_SetObjResult(interp, (Tcl_Obj*)Tcl_GetHashValue(entryPtr)); + } + return TCL_OK; + } else if (objc % 2 != 1) { + goto usage; + } + + for (i = 3; i < objc; i += 2) { + const char *optionName = Tcl_GetString(objv[i]); + Tcl_Obj *stateMap = objv[i+1]; + Tcl_HashEntry *entryPtr; + int newEntry; + + /* Make sure 'stateMap' is legal: + * (@@@ SHOULD: check for valid resource values as well, + * but we don't know what types they should be at this level.) + */ + if (!Ttk_GetStateMapFromObj(interp, stateMap)) + return TCL_ERROR; + + entryPtr = Tcl_CreateHashEntry( + &stylePtr->settingsTable,optionName,&newEntry); + + Tcl_IncrRefCount(stateMap); + if (!newEntry) { + Tcl_DecrRefCount((Tcl_Obj*)Tcl_GetHashValue(entryPtr)); + } + Tcl_SetHashValue(entryPtr, stateMap); + } + ThemeChanged(pkgPtr); + return TCL_OK; +} + +/* + style configure $style -option ?value... + */ +static int StyleConfigureCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + const char *styleName; + Style *stylePtr; + int i; + + if (objc < 3) { +usage: + Tcl_WrongNumArgs(interp,2,objv,"style ?-option ?value...??"); + return TCL_ERROR; + } + + styleName = Tcl_GetString(objv[2]); + stylePtr = Ttk_GetStyle(theme, styleName); + + if (objc == 3) { /* style default $styleName */ + Tcl_SetObjResult(interp, HashTableToDict(&stylePtr->defaultsTable)); + return TCL_OK; + } else if (objc == 4) { /* style default $styleName -option */ + const char *optionName = Tcl_GetString(objv[3]); + Tcl_HashEntry *entryPtr = + Tcl_FindHashEntry(&stylePtr->defaultsTable, optionName); + if (entryPtr) { + Tcl_SetObjResult(interp, (Tcl_Obj*)Tcl_GetHashValue(entryPtr)); + } + return TCL_OK; + } else if (objc % 2 != 1) { + goto usage; + } + + for (i = 3; i < objc; i += 2) { + const char *optionName = Tcl_GetString(objv[i]); + Tcl_Obj *value = objv[i+1]; + Tcl_HashEntry *entryPtr; + int newEntry; + + entryPtr = Tcl_CreateHashEntry( + &stylePtr->defaultsTable,optionName,&newEntry); + + Tcl_IncrRefCount(value); + if (!newEntry) { + Tcl_DecrRefCount((Tcl_Obj*)Tcl_GetHashValue(entryPtr)); + } + Tcl_SetHashValue(entryPtr, value); + } + + ThemeChanged(pkgPtr); + return TCL_OK; +} + +/* + style lookup $style -option ?statespec? ?defaultValue? + */ +static int StyleLookupCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + Ttk_Style style = NULL; + const char *optionName; + Ttk_State state = 0ul; + Tcl_Obj *result; + + if (objc < 4 || objc > 6) { + Tcl_WrongNumArgs(interp, 2, objv, "style -option ?state? ?default?"); + return TCL_ERROR; + } + + style = Ttk_GetStyle(theme, Tcl_GetString(objv[2])); + if (!style) { + return TCL_ERROR; + } + optionName = Tcl_GetString(objv[3]); + + if (objc >= 5) { + Ttk_StateSpec stateSpec; + /* @@@ SB: Ttk_GetStateFromObj(); 'offbits' spec is ignored */ + if (Ttk_GetStateSpecFromObj(interp, objv[4], &stateSpec) != TCL_OK) { + return TCL_ERROR; + } + state = stateSpec.onbits; + } + + result = Ttk_QueryStyle(style, NULL,NULL, optionName, state); + if (result == NULL && objc >= 6) { /* Use caller-supplied fallback */ + result = objv[5]; + } + + if (result) { + Tcl_SetObjResult(interp, result); + } + + return TCL_OK; +} + +static int StyleThemeCurrentCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr = NULL; + const char *name = NULL; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, ""); + return TCL_ERROR; + } + + entryPtr = Tcl_FirstHashEntry(&pkgPtr->themeTable, &search); + while (entryPtr != NULL) { + Theme *ptr = Tcl_GetHashValue(entryPtr); + if (ptr == pkgPtr->currentTheme) { + name = Tcl_GetHashKey(&pkgPtr->themeTable, entryPtr); + break; + } + entryPtr = Tcl_NextHashEntry(&search); + } + + if (name == NULL) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("error: failed to get theme name", -1)); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1)); + return TCL_OK; +} + +/* + style theme create name ?-parent $theme? ?-settings { script }? + */ +static int StyleThemeCreateCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + static const char *optStrings[] = + { "-parent", "-settings", NULL }; + enum { OP_PARENT, OP_SETTINGS }; + Ttk_Theme parentTheme = pkgPtr->defaultTheme, newTheme; + Tcl_Obj *settingsScript = NULL; + const char *themeName; + int i; + + if (objc < 4 || objc % 2 != 0) { + Tcl_WrongNumArgs(interp, 3, objv, "name ?-option value ...?"); + return TCL_ERROR; + } + + themeName = Tcl_GetString(objv[3]); + + for (i=4; i < objc; i +=2) { + int option; + if (Tcl_GetIndexFromObj( + interp, objv[i], optStrings, "option", 0, &option) != TCL_OK) + { + return TCL_ERROR; + } + + switch (option) { + case OP_PARENT: + parentTheme = LookupTheme( + interp, pkgPtr, Tcl_GetString(objv[i+1])); + if (!parentTheme) + return TCL_ERROR; + break; + case OP_SETTINGS: + settingsScript = objv[i+1]; + break; + } + } + + newTheme = Ttk_CreateTheme(interp, themeName, parentTheme); + if (!newTheme) { + return TCL_ERROR; + } + + /* + * Evaluate the -settings script, if supplied: + */ + if (settingsScript) { + Ttk_Theme oldTheme = pkgPtr->currentTheme; + int status; + + pkgPtr->currentTheme = newTheme; + status = Tcl_EvalObjEx(interp, settingsScript, 0); + pkgPtr->currentTheme = oldTheme; + return status; + } else { + return TCL_OK; + } +} + +/* + style theme names -- + * Return list of registered themes. + */ +static int StyleThemeNamesCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + return TtkEnumerateHashTable(interp, &pkgPtr->themeTable); +} + +/* + style theme settings $theme $script + * + * Temporarily sets the current theme to $themeName, + * evaluates $script, then restores the old theme. + */ +static int +StyleThemeSettingsCmd( + ClientData clientData, /* Master StylePackageData pointer */ + Tcl_Interp *interp, /* Current interpreter */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument objects */ +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme oldTheme = pkgPtr->currentTheme; + Ttk_Theme newTheme; + int status; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "theme script"); + return TCL_ERROR; + } + + newTheme = LookupTheme(interp, pkgPtr, Tcl_GetString(objv[3])); + if (!newTheme) + return TCL_ERROR; + + pkgPtr->currentTheme = newTheme; + status = Tcl_EvalObjEx(interp, objv[4], 0); + pkgPtr->currentTheme = oldTheme; + + return status; +} + +/* + style element create name type ? ...args ? + */ +static int StyleElementCreateCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + const char *elementName, *factoryName; + Tcl_HashEntry *entryPtr; + FactoryRec *recPtr; + + if (objc < 5) { + Tcl_WrongNumArgs(interp, 3, objv, "name type ?-option value ...?"); + return TCL_ERROR; + } + + elementName = Tcl_GetString(objv[3]); + factoryName = Tcl_GetString(objv[4]); + + entryPtr = Tcl_FindHashEntry(&pkgPtr->factoryTable, factoryName); + if (!entryPtr) { + Tcl_AppendResult(interp, "No such element type ", factoryName, NULL); + return TCL_ERROR; + } + + recPtr = Tcl_GetHashValue(entryPtr); + + return recPtr->factory(interp, recPtr->clientData, + theme, elementName, objc - 5, objv + 5); +} + +/* + style element names -- + * Return a list of elements defined in the current theme. + */ +static int StyleElementNamesCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + return TCL_ERROR; + } + return TtkEnumerateHashTable(interp, &theme->elementTable); +} + +/* + style element options $element -- + * Return list of element options for specified element + */ +static int StyleElementOptionsCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + const char *elementName; + Ttk_ElementClass *elementClass; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "element"); + return TCL_ERROR; + } + + elementName = Tcl_GetString(objv[3]); + elementClass = Ttk_GetElement(theme, elementName); + if (elementClass) { + Ttk_ElementSpec *specPtr = elementClass->specPtr; + Ttk_ElementOptionSpec *option = specPtr->options; + Tcl_Obj *result = Tcl_NewListObj(0,0); + + while (option->optionName) { + Tcl_ListObjAppendElement( + interp, result, Tcl_NewStringObj(option->optionName,-1)); + ++option; + } + + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + + Tcl_AppendResult(interp, "element ", elementName, " not found", NULL); + return TCL_ERROR; +} + +/* + style layout name ?spec? + */ +static int StyleLayoutCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme = pkgPtr->currentTheme; + const char *layoutName; + Ttk_LayoutTemplate layoutTemplate; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "name ?spec?"); + return TCL_ERROR; + } + + layoutName = Tcl_GetString(objv[2]); + + if (objc == 3) { + layoutTemplate = Ttk_FindLayoutTemplate(theme, layoutName); + if (!layoutTemplate) { + Tcl_AppendResult(interp, "Layout ", layoutName, " not found", NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Ttk_UnparseLayoutTemplate(layoutTemplate)); + } else { + layoutTemplate = Ttk_ParseLayoutTemplate(interp, objv[3]); + if (!layoutTemplate) { + return TCL_ERROR; + } + Ttk_RegisterLayoutTemplate(theme, layoutName, layoutTemplate); + ThemeChanged(pkgPtr); + } + return TCL_OK; +} + +/* + style theme use $theme -- + * Sets the current theme to $theme + */ +static int +StyleThemeUseCmd( + ClientData clientData, /* Master StylePackageData pointer */ + Tcl_Interp *interp, /* Current interpreter */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument objects */ +{ + StylePackageData *pkgPtr = clientData; + Ttk_Theme theme; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 3, objv, "?theme?"); + return TCL_ERROR; + } + + if (objc == 3) { + return StyleThemeCurrentCmd(clientData, interp, objc, objv); + } + + theme = LookupTheme(interp, pkgPtr, Tcl_GetString(objv[3])); + if (!theme) { + return TCL_ERROR; + } + + return Ttk_UseTheme(interp, theme); +} + +/* + * StyleObjCmd -- + * Implementation of the [style] command. + */ + +static const Ttk_Ensemble StyleThemeEnsemble[] = { + { "create", StyleThemeCreateCmd, 0 }, + { "names", StyleThemeNamesCmd, 0 }, + { "settings", StyleThemeSettingsCmd, 0 }, + { "use", StyleThemeUseCmd, 0 }, + { NULL, 0, 0 } +}; + +static const Ttk_Ensemble StyleElementEnsemble[] = { + { "create", StyleElementCreateCmd, 0 }, + { "names", StyleElementNamesCmd, 0 }, + { "options", StyleElementOptionsCmd, 0 }, + { NULL, 0, 0 } +}; + +static const Ttk_Ensemble StyleEnsemble[] = { + { "configure", StyleConfigureCmd, 0 }, + { "map", StyleMapCmd, 0 }, + { "lookup", StyleLookupCmd, 0 }, + { "layout", StyleLayoutCmd, 0 }, + { "theme", 0, StyleThemeEnsemble }, + { "element", 0, StyleElementEnsemble }, + { NULL, 0, 0 } +}; + +static int +StyleObjCmd( + ClientData clientData, /* Master StylePackageData pointer */ + Tcl_Interp *interp, /* Current interpreter */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument objects */ +{ + return Ttk_InvokeEnsemble(StyleEnsemble, 1, clientData,interp,objc,objv); +} + +MODULE_SCOPE +int Ttk_InvokeEnsemble( /* Run an ensemble command */ + const Ttk_Ensemble *ensemble, int cmdIndex, + void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + while (cmdIndex < objc) { + int index; + if (Tcl_GetIndexFromObjStruct(interp, + objv[cmdIndex], ensemble, sizeof(ensemble[0]), + "command", 0, &index) + != TCL_OK) + { + return TCL_ERROR; + } + + if (ensemble[index].command) { + return ensemble[index].command(clientData, interp, objc, objv); + } + ensemble = ensemble[index].ensemble; + ++cmdIndex; + } + Tcl_WrongNumArgs(interp, cmdIndex, objv, "option ?arg ...?"); + return TCL_ERROR; +} + +/* + * Ttk_StylePkgInit -- + * Initializes all the structures that are used by the style + * package on a per-interp basis. + */ + +void Ttk_StylePkgInit(Tcl_Interp *interp) +{ + Tcl_Namespace *nsPtr; + + StylePackageData *pkgPtr = (StylePackageData *) + ckalloc(sizeof(StylePackageData)); + + pkgPtr->interp = interp; + Tcl_InitHashTable(&pkgPtr->themeTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&pkgPtr->factoryTable, TCL_STRING_KEYS); + pkgPtr->cleanupList = NULL; + pkgPtr->cache = Ttk_CreateResourceCache(interp); + pkgPtr->themeChangePending = 0; + + Tcl_SetAssocData(interp, PKG_ASSOC_KEY, Ttk_StylePkgFree, pkgPtr); + + /* + * Create the default system theme: + * + * pkgPtr->defaultTheme must be initialized to 0 before + * calling Ttk_CreateTheme for the first time, since it's used + * as the parent theme. + */ + pkgPtr->defaultTheme = 0; + pkgPtr->defaultTheme = pkgPtr->currentTheme = + Ttk_CreateTheme(interp, "default", NULL); + + /* + * Register null element, used as a last-resort fallback: + */ + Ttk_RegisterElement(interp, pkgPtr->defaultTheme, "", &ttkNullElementSpec, 0); + + /* + * Register commands: + */ + Tcl_CreateObjCommand(interp, "::ttk::style", StyleObjCmd, pkgPtr, 0); + + nsPtr = Tcl_FindNamespace(interp, "::ttk", NULL, TCL_LEAVE_ERR_MSG); + Tcl_Export(interp, nsPtr, "style", 0 /* dontResetList */); + + Ttk_RegisterElementFactory(interp, "from", Ttk_CloneElement, 0); +} + +/*EOF*/ diff --git a/generic/ttk/ttkTheme.h b/generic/ttk/ttkTheme.h new file mode 100644 index 0000000..7bf2a7f --- /dev/null +++ b/generic/ttk/ttkTheme.h @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2003 Joe English. Freely redistributable. + * + * Declarations for Tk theme engine. + */ + +#ifndef _TTKTHEME +#define _TTKTHEME + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MODULE_SCOPE +# ifdef __cplusplus +# define MODULE_SCOPE extern "C" +# else +# define MODULE_SCOPE extern +# endif +#endif + +#define TTKAPI MODULE_SCOPE + +/* Ttk syncs to the Tk version & patchlevel */ +#define TTK_VERSION TK_VERSION +#define TTK_PATCH_LEVEL TK_PATCH_LEVEL + +/*------------------------------------------------------------------------ + * +++ Defaults for element option specifications. + */ +#define DEFAULT_FONT "TkDefaultFont" +#define DEFAULT_BACKGROUND "#d9d9d9" +#define DEFAULT_FOREGROUND "black" + +/*------------------------------------------------------------------------ + * +++ Widget states. + * Keep in sync with stateNames[] in tkstate.c. + */ + +typedef unsigned int Ttk_State; + +#define TTK_STATE_ACTIVE (1<<0) +#define TTK_STATE_DISABLED (1<<1) +#define TTK_STATE_FOCUS (1<<2) +#define TTK_STATE_PRESSED (1<<3) +#define TTK_STATE_SELECTED (1<<4) +#define TTK_STATE_BACKGROUND (1<<5) +#define TTK_STATE_ALTERNATE (1<<6) +#define TTK_STATE_INVALID (1<<7) +#define TTK_STATE_READONLY (1<<8) +#define TTK_STATE_HOVER (1<<9) +#define TTK_STATE_USER6 (1<<10) +#define TTK_STATE_USER5 (1<<11) +#define TTK_STATE_USER4 (1<<12) +#define TTK_STATE_USER3 (1<<13) +#define TTK_STATE_USER2 (1<<14) +#define TTK_STATE_USER1 (1<<15) + +/* Maintenance note: if you get all the way to "USER1", + * see tkstate.c + */ +typedef struct +{ + unsigned int onbits; /* bits to turn on */ + unsigned int offbits; /* bits to turn off */ +} Ttk_StateSpec; + +#define Ttk_StateMatches(state, spec) \ + (((state) & ((spec)->onbits|(spec)->offbits)) == (spec)->onbits) + +#define Ttk_ModifyState(state, spec) \ + (((state) & ~(spec)->offbits) | (spec)->onbits) + +TTKAPI int Ttk_GetStateSpecFromObj(Tcl_Interp *, Tcl_Obj *, Ttk_StateSpec *); +TTKAPI Tcl_Obj *Ttk_NewStateSpecObj(unsigned int onbits,unsigned int offbits); + +/*------------------------------------------------------------------------ + * +++ State maps and state tables. + */ +typedef Tcl_Obj *Ttk_StateMap; + +TTKAPI Ttk_StateMap Ttk_GetStateMapFromObj(Tcl_Interp *, Tcl_Obj *); +TTKAPI Tcl_Obj *Ttk_StateMapLookup(Tcl_Interp*, Ttk_StateMap, Ttk_State); + +/* + * Table for looking up an integer index based on widget state: + */ +typedef struct +{ + int index; /* Value to return if this entry matches */ + unsigned int onBits; /* Bits which must be set */ + unsigned int offBits; /* Bits which must be cleared */ +} Ttk_StateTable; + +TTKAPI int Ttk_StateTableLookup(Ttk_StateTable map[], Ttk_State); + +/*------------------------------------------------------------------------ + * +++ Padding. + * Used to represent internal padding and borders. + */ +typedef struct +{ + short left; + short top; + short right; + short bottom; +} Ttk_Padding; + +TTKAPI int Ttk_GetPaddingFromObj(Tcl_Interp*,Tk_Window,Tcl_Obj*,Ttk_Padding*); +TTKAPI int Ttk_GetBorderFromObj(Tcl_Interp*,Tcl_Obj*,Ttk_Padding*); + +TTKAPI Ttk_Padding Ttk_MakePadding(short l, short t, short r, short b); +TTKAPI Ttk_Padding Ttk_UniformPadding(short borderWidth); +TTKAPI Ttk_Padding Ttk_AddPadding(Ttk_Padding, Ttk_Padding); +TTKAPI Ttk_Padding Ttk_RelievePadding(Ttk_Padding, int relief, int n); + +#define Ttk_PaddingWidth(p) ((p).left + (p).right) +#define Ttk_PaddingHeight(p) ((p).top + (p).bottom) + +#define Ttk_SetMargins(tkwin, pad) \ + Tk_SetInternalBorderEx(tkwin, pad.left, pad.right, pad.top, pad.bottom) + +/*------------------------------------------------------------------------ + * +++ Boxes. + * Used to represent rectangular regions + */ +typedef struct /* Hey, this is an XRectangle! */ +{ + int x; + int y; + int width; + int height; +} Ttk_Box; + +TTKAPI Ttk_Box Ttk_MakeBox(int x, int y, int width, int height); +TTKAPI int Ttk_BoxContains(Ttk_Box, int x, int y); + +#define Ttk_WinBox(tkwin) Ttk_MakeBox(0,0,Tk_Width(tkwin),Tk_Height(tkwin)) + +/*------------------------------------------------------------------------ + * +++ Layout utilities. + */ +typedef enum { + TTK_SIDE_LEFT, TTK_SIDE_TOP, TTK_SIDE_RIGHT, TTK_SIDE_BOTTOM +} Ttk_Side; + +typedef unsigned int Ttk_Sticky; + +/* + * -sticky bits for Ttk_StickBox: + */ +#define TTK_STICK_W (0x1) +#define TTK_STICK_E (0x2) +#define TTK_STICK_N (0x4) +#define TTK_STICK_S (0x8) + +/* + * Aliases and useful combinations: + */ +#define TTK_FILL_X (0x3) /* -sticky ew */ +#define TTK_FILL_Y (0xC) /* -sticky ns */ +#define TTK_FILL_BOTH (0xF) /* -sticky nswe */ + +TTKAPI int Ttk_GetStickyFromObj(Tcl_Interp *, Tcl_Obj *, Ttk_Sticky *); +TTKAPI Tcl_Obj *Ttk_NewStickyObj(Ttk_Sticky); + +/* + * Extra bits for position specifications (combine -side and -sticky) + */ + +typedef unsigned int Ttk_PositionSpec; /* See below */ + +#define TTK_PACK_LEFT (0x10) /* pack at left of current parcel */ +#define TTK_PACK_RIGHT (0x20) /* pack at right of current parcel */ +#define TTK_PACK_TOP (0x40) /* pack at top of current parcel */ +#define TTK_PACK_BOTTOM (0x80) /* pack at bottom of current parcel */ +#define TTK_EXPAND (0x100) /* use entire parcel */ +#define TTK_BORDER (0x200) /* draw this element after children */ +#define TTK_UNIT (0x400) /* treat descendants as a part of element */ + +/* + * Extra bits for layout specifications + */ +#define _TTK_CHILDREN (0x1000)/* for LayoutSpecs -- children follow */ +#define _TTK_LAYOUT_END (0x2000)/* for LayoutSpecs -- end of child list */ +#define _TTK_LAYOUT (0x4000)/* for LayoutSpec tables -- define layout */ + +#define _TTK_MASK_STICK (0x0F) /* See Ttk_UnparseLayout() */ +#define _TTK_MASK_PACK (0xF0) /* See Ttk_UnparseLayout(), packStrings */ + +TTKAPI Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int w, int h, Ttk_Side side); +TTKAPI Ttk_Box Ttk_StickBox(Ttk_Box parcel, int w, int h, Ttk_Sticky sticky); +TTKAPI Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int w, int h, Tk_Anchor anchor); +TTKAPI Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p); +TTKAPI Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p); +TTKAPI Ttk_Box Ttk_PlaceBox(Ttk_Box *cavity, int w,int h, Ttk_Side,Ttk_Sticky); +TTKAPI Ttk_Box Ttk_PositionBox(Ttk_Box *cavity, int w, int h, Ttk_PositionSpec); + +/*------------------------------------------------------------------------ + * +++ Themes. + */ +MODULE_SCOPE void Ttk_StylePkgInit(Tcl_Interp *); + +typedef struct Ttk_Theme_ *Ttk_Theme; +typedef struct Ttk_ElementClass_ Ttk_ElementClass; +typedef struct Ttk_Layout_ *Ttk_Layout; +typedef struct Ttk_LayoutNode_ *Ttk_Element; +typedef struct Ttk_Style_ *Ttk_Style; + +TTKAPI Ttk_Theme Ttk_GetTheme(Tcl_Interp *interp, const char *name); +TTKAPI Ttk_Theme Ttk_GetDefaultTheme(Tcl_Interp *interp); +TTKAPI Ttk_Theme Ttk_GetCurrentTheme(Tcl_Interp *interp); + +TTKAPI Ttk_Theme Ttk_CreateTheme( + Tcl_Interp *interp, const char *name, Ttk_Theme parent); + +typedef int (Ttk_ThemeEnabledProc)(Ttk_Theme theme, void *clientData); +MODULE_SCOPE void Ttk_SetThemeEnabledProc(Ttk_Theme, Ttk_ThemeEnabledProc, void *); + +MODULE_SCOPE int Ttk_UseTheme(Tcl_Interp *, Ttk_Theme); + +typedef void (Ttk_CleanupProc)(void *clientData); +TTKAPI void Ttk_RegisterCleanup( + Tcl_Interp *interp, void *deleteData, Ttk_CleanupProc *cleanupProc); + +/*------------------------------------------------------------------------ + * +++ Elements. + */ + +enum TTKStyleVersion2 { TK_STYLE_VERSION_2 = 2 }; + +typedef void (Ttk_ElementSizeProc)(void *clientData, void *elementRecord, + Tk_Window tkwin, int *widthPtr, int *heightPtr, Ttk_Padding*); +typedef void (Ttk_ElementDrawProc)(void *clientData, void *elementRecord, + Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state); + +typedef struct Ttk_ElementOptionSpec +{ + const char *optionName; /* Command-line name of the widget option */ + Tk_OptionType type; /* Accepted option types */ + int offset; /* Offset of Tcl_Obj* field in element record */ + const char *defaultValue; /* Default value to used if resource missing */ +} Ttk_ElementOptionSpec; + +#define TK_OPTION_ANY TK_OPTION_STRING + +typedef struct Ttk_ElementSpec { + enum TTKStyleVersion2 version; /* Version of the style support. */ + size_t elementSize; /* Size of element record */ + Ttk_ElementOptionSpec *options; /* List of options, NULL-terminated */ + Ttk_ElementSizeProc *size; /* Compute min size and padding */ + Ttk_ElementDrawProc *draw; /* Draw the element */ +} Ttk_ElementSpec; + +TTKAPI Ttk_ElementClass *Ttk_RegisterElement( + Tcl_Interp *interp, Ttk_Theme theme, const char *elementName, + Ttk_ElementSpec *, void *clientData); + +typedef int (*Ttk_ElementFactory) + (Tcl_Interp *, void *clientData, + Ttk_Theme, const char *elementName, int objc, Tcl_Obj *const objv[]); + +TTKAPI int Ttk_RegisterElementFactory( + Tcl_Interp *, const char *name, Ttk_ElementFactory, void *clientData); + +/* + * Null element implementation: + * has no geometry or layout; may be used as a stub or placeholder. + */ + +typedef struct { + Tcl_Obj *unused; +} NullElement; + +MODULE_SCOPE void TtkNullElementSize + (void *, void *, Tk_Window, int *, int *, Ttk_Padding *); +MODULE_SCOPE void TtkNullElementDraw + (void *, void *, Tk_Window, Drawable, Ttk_Box, Ttk_State); +MODULE_SCOPE Ttk_ElementOptionSpec TtkNullElementOptions[]; +MODULE_SCOPE Ttk_ElementSpec ttkNullElementSpec; + +/*------------------------------------------------------------------------ + * +++ Layout templates. + */ +typedef struct { + const char * elementName; + unsigned opcode; +} TTKLayoutInstruction, *Ttk_LayoutSpec; + +#define TTK_BEGIN_LAYOUT_TABLE(name) \ + static TTKLayoutInstruction name[] = { +#define TTK_LAYOUT(name, content) \ + { name, _TTK_CHILDREN|_TTK_LAYOUT }, \ + content \ + { 0, _TTK_LAYOUT_END }, +#define TTK_GROUP(name, flags, children) \ + { name, flags | _TTK_CHILDREN }, \ + children \ + { 0, _TTK_LAYOUT_END }, +#define TTK_NODE(name, flags) { name, flags }, +#define TTK_END_LAYOUT_TABLE { 0, _TTK_LAYOUT | _TTK_LAYOUT_END } }; + +#define TTK_BEGIN_LAYOUT(name) static TTKLayoutInstruction name[] = { +#define TTK_END_LAYOUT { 0, _TTK_LAYOUT_END } }; + +TTKAPI void Ttk_RegisterLayout( + Ttk_Theme theme, const char *className, Ttk_LayoutSpec layoutSpec); + +TTKAPI void Ttk_RegisterLayouts( + Ttk_Theme theme, Ttk_LayoutSpec layoutTable); + +/*------------------------------------------------------------------------ + * +++ Layout instances. + */ + +MODULE_SCOPE Ttk_Layout Ttk_CreateLayout( + Tcl_Interp *, Ttk_Theme, const char *name, + void *recordPtr, Tk_OptionTable, Tk_Window tkwin); + +MODULE_SCOPE Ttk_Layout Ttk_CreateSublayout( + Tcl_Interp *, Ttk_Theme, Ttk_Layout, const char *name, Tk_OptionTable); + +MODULE_SCOPE void Ttk_FreeLayout(Ttk_Layout); + +MODULE_SCOPE void Ttk_LayoutSize(Ttk_Layout,Ttk_State,int *widthPtr,int *heightPtr); +MODULE_SCOPE void Ttk_PlaceLayout(Ttk_Layout, Ttk_State, Ttk_Box); +MODULE_SCOPE void Ttk_DrawLayout(Ttk_Layout, Ttk_State, Drawable); + +MODULE_SCOPE void Ttk_RebindSublayout(Ttk_Layout, void *recordPtr); + +MODULE_SCOPE Ttk_Element Ttk_IdentifyElement(Ttk_Layout, int x, int y); +MODULE_SCOPE Ttk_Element Ttk_FindElement(Ttk_Layout, const char *nodeName); + +MODULE_SCOPE const char *Ttk_ElementName(Ttk_Element); +MODULE_SCOPE Ttk_Box Ttk_ElementParcel(Ttk_Element); + +MODULE_SCOPE Ttk_Box Ttk_ClientRegion(Ttk_Layout, const char *elementName); + +MODULE_SCOPE Ttk_Box Ttk_LayoutNodeInternalParcel(Ttk_Layout,Ttk_Element); +MODULE_SCOPE Ttk_Padding Ttk_LayoutNodeInternalPadding(Ttk_Layout,Ttk_Element); +MODULE_SCOPE void Ttk_LayoutNodeReqSize(Ttk_Layout, Ttk_Element, int *w, int *h); + +MODULE_SCOPE void Ttk_PlaceElement(Ttk_Layout, Ttk_Element, Ttk_Box); +MODULE_SCOPE void Ttk_ChangeElementState(Ttk_Element,unsigned set,unsigned clr); + +MODULE_SCOPE Tcl_Obj *Ttk_QueryOption(Ttk_Layout, const char *, Ttk_State); + +TTKAPI Ttk_Style Ttk_LayoutStyle(Ttk_Layout); +TTKAPI Tcl_Obj *Ttk_StyleDefault(Ttk_Style, const char *optionName); +TTKAPI Tcl_Obj *Ttk_StyleMap(Ttk_Style, const char *optionName, Ttk_State); + +/*------------------------------------------------------------------------ + * +++ Resource cache. + * See resource.c for explanation. + */ + +typedef struct Ttk_ResourceCache_ *Ttk_ResourceCache; +MODULE_SCOPE Ttk_ResourceCache Ttk_CreateResourceCache(Tcl_Interp *); +MODULE_SCOPE void Ttk_FreeResourceCache(Ttk_ResourceCache); + +MODULE_SCOPE Ttk_ResourceCache Ttk_GetResourceCache(Tcl_Interp*); +MODULE_SCOPE Tcl_Obj *Ttk_UseFont(Ttk_ResourceCache, Tk_Window, Tcl_Obj *); +MODULE_SCOPE Tcl_Obj *Ttk_UseColor(Ttk_ResourceCache, Tk_Window, Tcl_Obj *); +MODULE_SCOPE Tcl_Obj *Ttk_UseBorder(Ttk_ResourceCache, Tk_Window, Tcl_Obj *); +MODULE_SCOPE Tk_Image Ttk_UseImage(Ttk_ResourceCache, Tk_Window, Tcl_Obj *); + +MODULE_SCOPE void Ttk_RegisterNamedColor(Ttk_ResourceCache, const char *, XColor *); + +/*------------------------------------------------------------------------ + * +++ Image specifications. + */ + +typedef struct TtkImageSpec Ttk_ImageSpec; +TTKAPI Ttk_ImageSpec *TtkGetImageSpec(Tcl_Interp *, Tk_Window, Tcl_Obj *); +TTKAPI void TtkFreeImageSpec(Ttk_ImageSpec *); +TTKAPI Tk_Image TtkSelectImage(Ttk_ImageSpec *, Ttk_State); + +/*------------------------------------------------------------------------ + * +++ Miscellaneous enumerations. + * Other stuff that element implementations need to know about. + */ +typedef enum /* -default option values */ +{ + TTK_BUTTON_DEFAULT_NORMAL, /* widget defaultable */ + TTK_BUTTON_DEFAULT_ACTIVE, /* currently the default widget */ + TTK_BUTTON_DEFAULT_DISABLED /* not defaultable */ +} Ttk_ButtonDefaultState; + +TTKAPI int Ttk_GetButtonDefaultStateFromObj(Tcl_Interp *, Tcl_Obj *, int *); + +typedef enum /* -compound option values */ +{ + TTK_COMPOUND_NONE, /* image if specified, otherwise text */ + TTK_COMPOUND_TEXT, /* text only */ + TTK_COMPOUND_IMAGE, /* image only */ + TTK_COMPOUND_CENTER, /* text overlays image */ + TTK_COMPOUND_TOP, /* image above text */ + TTK_COMPOUND_BOTTOM, /* image below text */ + TTK_COMPOUND_LEFT, /* image to left of text */ + TTK_COMPOUND_RIGHT /* image to right of text */ +} Ttk_Compound; + +TTKAPI int Ttk_GetCompoundFromObj(Tcl_Interp *, Tcl_Obj *, int *); + +typedef enum { /* -orient option values */ + TTK_ORIENT_HORIZONTAL, + TTK_ORIENT_VERTICAL +} Ttk_Orient; + +/*------------------------------------------------------------------------ + * +++ Utilities. + */ + +typedef struct TtkEnsemble { + const char *name; /* subcommand name */ + Tcl_ObjCmdProc *command; /* subcommand implementation, OR: */ + const struct TtkEnsemble *ensemble; /* subcommand ensemble */ +} Ttk_Ensemble; + +MODULE_SCOPE int Ttk_InvokeEnsemble( /* Run an ensemble command */ + const Ttk_Ensemble *commands, int cmdIndex, + void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); + +MODULE_SCOPE int TtkEnumerateHashTable(Tcl_Interp *, Tcl_HashTable *); + +/*------------------------------------------------------------------------ + * +++ Stub table declarations. + */ + +#include "ttkDecls.h" + +/* + * Drawing utilities for theme code: + * (@@@ find a better home for this) + */ +typedef enum { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT } ArrowDirection; +MODULE_SCOPE void TtkArrowSize(int h, ArrowDirection, int *widthPtr, int *heightPtr); +MODULE_SCOPE void TtkDrawArrow(Display *, Drawable, GC, Ttk_Box, ArrowDirection); +MODULE_SCOPE void TtkFillArrow(Display *, Drawable, GC, Ttk_Box, ArrowDirection); + +#ifdef __cplusplus +} +#endif +#endif /* _TTKTHEME */ diff --git a/generic/ttk/ttkThemeInt.h b/generic/ttk/ttkThemeInt.h new file mode 100644 index 0000000..3aaada8 --- /dev/null +++ b/generic/ttk/ttkThemeInt.h @@ -0,0 +1,42 @@ +/* + * Theme engine: private definitions. + * + * Copyright (c) 2004 Joe English. Freely redistributable. + */ + +#ifndef _TTKTHEMEINT +#define _TTKTHEMEINT + +#include "ttkTheme.h" + +typedef struct Ttk_TemplateNode_ Ttk_TemplateNode, *Ttk_LayoutTemplate; + +MODULE_SCOPE Ttk_ElementClass *Ttk_GetElement(Ttk_Theme, const char *name); +MODULE_SCOPE const char *Ttk_ElementClassName(Ttk_ElementClass *); + +MODULE_SCOPE void Ttk_ElementSize( + Ttk_ElementClass *, Ttk_Style, char *recordPtr, Tk_OptionTable, + Tk_Window tkwin, Ttk_State state, + int *widthPtr, int *heightPtr, Ttk_Padding*); +MODULE_SCOPE void Ttk_DrawElement( + Ttk_ElementClass *, Ttk_Style, char *recordPtr, Tk_OptionTable, + Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state); + +MODULE_SCOPE Tcl_Obj *Ttk_QueryStyle( + Ttk_Style, void *, Tk_OptionTable, const char *, Ttk_State state); + +MODULE_SCOPE Ttk_LayoutTemplate Ttk_ParseLayoutTemplate( + Tcl_Interp *, Tcl_Obj *); +MODULE_SCOPE Tcl_Obj *Ttk_UnparseLayoutTemplate(Ttk_LayoutTemplate); +MODULE_SCOPE Ttk_LayoutTemplate Ttk_BuildLayoutTemplate(Ttk_LayoutSpec); +MODULE_SCOPE void Ttk_FreeLayoutTemplate(Ttk_LayoutTemplate); +MODULE_SCOPE void Ttk_RegisterLayoutTemplate( + Ttk_Theme theme, const char *layoutName, Ttk_LayoutTemplate); + +MODULE_SCOPE Ttk_Style Ttk_GetStyle(Ttk_Theme themePtr, const char *styleName); +MODULE_SCOPE Ttk_LayoutTemplate Ttk_FindLayoutTemplate( + Ttk_Theme themePtr, const char *layoutName); + +MODULE_SCOPE const char *Ttk_StyleName(Ttk_Style); + +#endif /* _TTKTHEMEINT */ diff --git a/generic/ttk/ttkTrace.c b/generic/ttk/ttkTrace.c new file mode 100644 index 0000000..8bc8519 --- /dev/null +++ b/generic/ttk/ttkTrace.c @@ -0,0 +1,190 @@ +/* + * Copyright 2003, Joe English + * + * Simplified interface to Tcl_TraceVariable. + * + * PROBLEM: Can't distinguish "variable does not exist" (which is OK) + * from other errors (which are not). + */ + +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +struct TtkTraceHandle_ +{ + Tcl_Interp *interp; /* Containing interpreter */ + Tcl_Obj *varnameObj; /* Name of variable being traced */ + Ttk_TraceProc callback; /* Callback procedure */ + void *clientData; /* Data to pass to callback */ +}; + +/* + * Tcl_VarTraceProc for trace handles. + */ +static char * +VarTraceProc( + ClientData clientData, /* Widget record pointer */ + Tcl_Interp *interp, /* Interpreter containing variable. */ + const char *name1, /* (unused) */ + const char *name2, /* (unused) */ + int flags) /* Information about what happened. */ +{ + Ttk_TraceHandle *tracePtr = clientData; + const char *name, *value; + Tcl_Obj *valuePtr; + + if (flags & TCL_INTERP_DESTROYED) { + return NULL; + } + + name = Tcl_GetString(tracePtr->varnameObj); + + /* + * If the variable is being unset, then re-establish the trace: + */ + if (flags & TCL_TRACE_DESTROYED) { + /* + * If a prior call to Ttk_UntraceVariable() left behind an + * indicator that we wanted this handler to be deleted (see below), + * cleanup the ClientData bits and exit. + */ + if (tracePtr->interp == NULL) { + Tcl_DecrRefCount(tracePtr->varnameObj); + ckfree((ClientData)tracePtr); + return NULL; + } + Tcl_TraceVar(interp, name, + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + VarTraceProc, clientData); + tracePtr->callback(tracePtr->clientData, NULL); + return NULL; + } + + /* + * Call the callback: + */ + valuePtr = Tcl_GetVar2Ex(interp, name, NULL, TCL_GLOBAL_ONLY); + value = valuePtr ? Tcl_GetString(valuePtr) : NULL; + tracePtr->callback(tracePtr->clientData, value); + + return NULL; +} + +/* Ttk_TraceVariable(interp, varNameObj, callback, clientdata) -- + * Attach a write trace to the specified variable, + * which will pass the variable's value to 'callback' + * whenever the variable is set. + * + * When the variable is unset, passes NULL to the callback + * and reattaches the trace. + */ +Ttk_TraceHandle *Ttk_TraceVariable( + Tcl_Interp *interp, + Tcl_Obj *varnameObj, + Ttk_TraceProc callback, + void *clientData) +{ + Ttk_TraceHandle *h = (Ttk_TraceHandle*)ckalloc(sizeof(*h)); + int status; + + h->interp = interp; + h->varnameObj = Tcl_DuplicateObj(varnameObj); + Tcl_IncrRefCount(h->varnameObj); + h->clientData = clientData; + h->callback = callback; + + status = Tcl_TraceVar(interp, Tcl_GetString(varnameObj), + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + VarTraceProc, (ClientData)h); + + if (status != TCL_OK) { + Tcl_DecrRefCount(h->varnameObj); + ckfree((ClientData)h); + return NULL; + } + + return h; +} + +/* + * Ttk_UntraceVariable -- + * Remove previously-registered trace and free the handle. + */ +void Ttk_UntraceVariable(Ttk_TraceHandle *h) +{ + if (h) { + ClientData cd = NULL; + + /* + * Workaround for Tcl Bug 3062331. The trace design problem is + * that when variable unset traces fire, Tcl documents that the + * traced variable has already been unset. It's already gone. + * So from within an unset trace, if you try to call + * Tcl_UntraceVar() on that variable, it will do nothing, because + * the variable by that name can no longer be found. It's gone. + * This means callers of Tcl_UntraceVar() that might be running + * in response to an unset trace have to handle the possibility + * that their Tcl_UntraceVar() call will do nothing. In this case, + * we have to support the possibility that Tcl_UntraceVar() will + * leave the trace in place, so we need to leave the ClientData + * untouched so when that trace does fire it will not crash. + */ + + /* + * Search the traces on the variable to see if the one we are tasked + * with removing is present. + */ + while ((cd = Tcl_VarTraceInfo(h->interp, Tcl_GetString(h->varnameObj), + TCL_GLOBAL_ONLY, VarTraceProc, cd)) != NULL) { + if (cd == (ClientData) h) { + break; + } + } + /* + * If the trace we wish to delete is not visible, Tcl_UntraceVar + * will do nothing, so don't try to call it. Instead set an + * indicator in the Ttk_TraceHandle that we need to cleanup later. + */ + if (cd == NULL) { + h->interp = NULL; + return; + } + Tcl_UntraceVar(h->interp, Tcl_GetString(h->varnameObj), + TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + VarTraceProc, (ClientData)h); + Tcl_DecrRefCount(h->varnameObj); + ckfree((ClientData)h); + } +} + +/* + * Ttk_FireTrace -- + * Executes a trace handle as if the variable has been written. + * + * Note: may reenter the interpreter. + */ +int Ttk_FireTrace(Ttk_TraceHandle *tracePtr) +{ + Tcl_Interp *interp = tracePtr->interp; + void *clientData = tracePtr->clientData; + const char *name = Tcl_GetString(tracePtr->varnameObj); + Ttk_TraceProc callback = tracePtr->callback; + Tcl_Obj *valuePtr; + const char *value; + + /* Read the variable. + * Note that this can reenter the interpreter, and anything can happen -- + * including the current trace handle being freed! + */ + valuePtr = Tcl_GetVar2Ex(interp, name, NULL, TCL_GLOBAL_ONLY); + value = valuePtr ? Tcl_GetString(valuePtr) : NULL; + + /* Call callback. + */ + callback(clientData, value); + + return TCL_OK; +} + +/*EOF*/ diff --git a/generic/ttk/ttkTrack.c b/generic/ttk/ttkTrack.c new file mode 100644 index 0000000..9cf8267 --- /dev/null +++ b/generic/ttk/ttkTrack.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2004, Joe English + * + * TtkTrackElementState() -- helper routine for widgets + * like scrollbars in which individual elements may + * be active or pressed instead of the widget as a whole. + * + * Usage: + * TtkTrackElementState(&recordPtr->core); + * + * Registers an event handler on the widget that tracks pointer + * events and updates the state of the element under the + * mouse cursor. + * + * The "active" element is the one under the mouse cursor, + * and is normally set to the ACTIVE state unless another element + * is currently being pressed. + * + * The active element becomes "pressed" on <ButtonPress> events, + * and remains "active" and "pressed" until the corresponding + * <ButtonRelease> event. + * + * TODO: Handle "chords" properly (e.g., <B1-ButtonPress-2>) + */ + +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +typedef struct { + WidgetCore *corePtr; /* widget to track */ + Ttk_Layout tracking; /* current layout being tracked */ + Ttk_Element activeElement; /* element under the mouse cursor */ + Ttk_Element pressedElement; /* currently pressed element */ +} ElementStateTracker; + +/* + * ActivateElement(es, node) -- + * Make 'node' the active element if non-NULL. + * Deactivates the currently active element if different. + * + * The active element has TTK_STATE_ACTIVE set _unless_ + * another element is 'pressed' + */ +static void ActivateElement(ElementStateTracker *es, Ttk_Element element) +{ + if (es->activeElement == element) { + /* No change */ + return; + } + + if (!es->pressedElement) { + if (es->activeElement) { + /* Deactivate old element */ + Ttk_ChangeElementState(es->activeElement, 0,TTK_STATE_ACTIVE); + } + if (element) { + /* Activate new element */ + Ttk_ChangeElementState(element, TTK_STATE_ACTIVE,0); + } + TtkRedisplayWidget(es->corePtr); + } + + es->activeElement = element; +} + +/* ReleaseElement -- + * Releases the currently pressed element, if any. + */ +static void ReleaseElement(ElementStateTracker *es) +{ + if (!es->pressedElement) + return; + + Ttk_ChangeElementState( + es->pressedElement, 0,TTK_STATE_PRESSED|TTK_STATE_ACTIVE); + es->pressedElement = 0; + + /* Reactivate element under the mouse cursor: + */ + if (es->activeElement) + Ttk_ChangeElementState(es->activeElement, TTK_STATE_ACTIVE,0); + + TtkRedisplayWidget(es->corePtr); +} + +/* PressElement -- + * Presses the specified element. + */ +static void PressElement(ElementStateTracker *es, Ttk_Element element) +{ + if (es->pressedElement) { + ReleaseElement(es); + } + + if (element) { + Ttk_ChangeElementState( + element, TTK_STATE_PRESSED|TTK_STATE_ACTIVE, 0); + } + + es->pressedElement = element; + TtkRedisplayWidget(es->corePtr); +} + +/* ElementStateEventProc -- + * Event handler for tracking element states. + */ + +static const unsigned ElementStateMask = + ButtonPressMask + | ButtonReleaseMask + | PointerMotionMask + | LeaveWindowMask + | EnterWindowMask + | StructureNotifyMask + ; + +static void +ElementStateEventProc(ClientData clientData, XEvent *ev) +{ + ElementStateTracker *es = clientData; + Ttk_Layout layout = es->corePtr->layout; + Ttk_Element element; + + /* Guard against dangling pointers [#2431428] + */ + if (es->tracking != layout) { + es->pressedElement = es->activeElement = 0; + es->tracking = layout; + } + + switch (ev->type) + { + case MotionNotify : + element = Ttk_IdentifyElement( + layout, ev->xmotion.x, ev->xmotion.y); + ActivateElement(es, element); + break; + case LeaveNotify: + ActivateElement(es, 0); + if (ev->xcrossing.mode == NotifyGrab) + PressElement(es, 0); + break; + case EnterNotify: + element = Ttk_IdentifyElement( + layout, ev->xcrossing.x, ev->xcrossing.y); + ActivateElement(es, element); + break; + case ButtonPress: + element = Ttk_IdentifyElement( + layout, ev->xbutton.x, ev->xbutton.y); + if (element) + PressElement(es, element); + break; + case ButtonRelease: + ReleaseElement(es); + break; + case DestroyNotify: + /* Unregister this event handler and free client data. + */ + Tk_DeleteEventHandler(es->corePtr->tkwin, + ElementStateMask, ElementStateEventProc, es); + ckfree(clientData); + break; + } +} + +/* + * TtkTrackElementState -- + * Register an event handler to manage the 'pressed' + * and 'active' states of individual widget elements. + */ + +void TtkTrackElementState(WidgetCore *corePtr) +{ + ElementStateTracker *es = (ElementStateTracker*)ckalloc(sizeof(*es)); + es->corePtr = corePtr; + es->tracking = 0; + es->activeElement = es->pressedElement = 0; + Tk_CreateEventHandler(corePtr->tkwin, + ElementStateMask,ElementStateEventProc,es); +} + diff --git a/generic/ttk/ttkTreeview.c b/generic/ttk/ttkTreeview.c new file mode 100644 index 0000000..862c7f6 --- /dev/null +++ b/generic/ttk/ttkTreeview.c @@ -0,0 +1,3442 @@ +/* + * Copyright (c) 2004, Joe English + * + * ttk::treeview widget implementation. + */ + +#include <string.h> +#include <stdio.h> +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEF_TREE_ROWS "10" +#define DEF_COLWIDTH "200" +#define DEF_MINWIDTH "20" + +static const int DEFAULT_ROWHEIGHT = 20; +static const int DEFAULT_INDENT = 20; +static const int HALO = 4; /* separator */ + +#define TTK_STATE_OPEN TTK_STATE_USER1 +#define TTK_STATE_LEAF TTK_STATE_USER2 + +#define STATE_CHANGED (0x100) /* item state option changed */ + +/*------------------------------------------------------------------------ + * +++ Tree items. + * + * INVARIANTS: + * item->children ==> item->children->parent == item + * item->next ==> item->next->parent == item->parent + * item->next ==> item->next->prev == item + * item->prev ==> item->prev->next == item + */ + +typedef struct TreeItemRec TreeItem; +struct TreeItemRec { + Tcl_HashEntry *entryPtr; /* Back-pointer to hash table entry */ + TreeItem *parent; /* Parent item */ + TreeItem *children; /* Linked list of child items */ + TreeItem *next; /* Next sibling */ + TreeItem *prev; /* Previous sibling */ + + /* + * Options and instance data: + */ + Ttk_State state; + Tcl_Obj *textObj; + Tcl_Obj *imageObj; + Tcl_Obj *valuesObj; + Tcl_Obj *openObj; + Tcl_Obj *tagsObj; + + /* + * Derived resources: + */ + Ttk_TagSet tagset; + Ttk_ImageSpec *imagespec; +}; + +#define ITEM_OPTION_TAGS_CHANGED 0x100 +#define ITEM_OPTION_IMAGE_CHANGED 0x200 + +static Tk_OptionSpec ItemOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + "", Tk_Offset(TreeItem,textObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + NULL, Tk_Offset(TreeItem,imageObj), -1, + TK_OPTION_NULL_OK,0,ITEM_OPTION_IMAGE_CHANGED }, + {TK_OPTION_STRING, "-values", "values", "Values", + NULL, Tk_Offset(TreeItem,valuesObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_BOOLEAN, "-open", "open", "Open", + "0", Tk_Offset(TreeItem,openObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-tags", "tags", "Tags", + NULL, Tk_Offset(TreeItem,tagsObj), -1, + TK_OPTION_NULL_OK,0,ITEM_OPTION_TAGS_CHANGED }, + + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/* + NewItem -- + * Allocate a new, uninitialized, unlinked item + */ +static TreeItem *NewItem(void) +{ + TreeItem *item = (TreeItem*)ckalloc(sizeof(*item)); + + item->entryPtr = 0; + item->parent = item->children = item->next = item->prev = NULL; + + item->state = 0ul; + item->textObj = NULL; + item->imageObj = NULL; + item->valuesObj = NULL; + item->openObj = NULL; + item->tagsObj = NULL; + + item->tagset = NULL; + item->imagespec = NULL; + + return item; +} + +/* + FreeItem -- + * Destroy an item + */ +static void FreeItem(TreeItem *item) +{ + if (item->textObj) { Tcl_DecrRefCount(item->textObj); } + if (item->imageObj) { Tcl_DecrRefCount(item->imageObj); } + if (item->valuesObj) { Tcl_DecrRefCount(item->valuesObj); } + if (item->openObj) { Tcl_DecrRefCount(item->openObj); } + if (item->tagsObj) { Tcl_DecrRefCount(item->tagsObj); } + + if (item->tagset) { Ttk_FreeTagSet(item->tagset); } + if (item->imagespec) { TtkFreeImageSpec(item->imagespec); } + + ckfree((ClientData)item); +} + +static void FreeItemCB(void *clientData) { FreeItem(clientData); } + +/* + DetachItem -- + * Unlink an item from the tree. + */ +static void DetachItem(TreeItem *item) +{ + if (item->parent && item->parent->children == item) + item->parent->children = item->next; + if (item->prev) + item->prev->next = item->next; + if (item->next) + item->next->prev = item->prev; + item->next = item->prev = item->parent = NULL; +} + +/* + InsertItem -- + * Insert an item into the tree after the specified item. + * + * Preconditions: + * + item is currently detached + * + prev != NULL ==> prev->parent == parent. + */ +static void InsertItem(TreeItem *parent, TreeItem *prev, TreeItem *item) +{ + item->parent = parent; + item->prev = prev; + if (prev) { + item->next = prev->next; + prev->next = item; + } else { + item->next = parent->children; + parent->children = item; + } + if (item->next) { + item->next->prev = item; + } +} + +/* + NextPreorder -- + * Return the next item in preorder traversal order. + */ + +static TreeItem *NextPreorder(TreeItem *item) +{ + if (item->children) + return item->children; + while (!item->next) { + item = item->parent; + if (!item) + return 0; + } + return item->next; +} + +/*------------------------------------------------------------------------ + * +++ Display items and tag options. + */ + +typedef struct { + Tcl_Obj *textObj; /* taken from item / data cell */ + Tcl_Obj *imageObj; /* taken from item */ + Tcl_Obj *anchorObj; /* from column <<NOTE-ANCHOR>> */ + Tcl_Obj *backgroundObj; /* remainder from tag */ + Tcl_Obj *foregroundObj; + Tcl_Obj *fontObj; +} DisplayItem; + +static Tk_OptionSpec TagOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + NULL, Tk_Offset(DisplayItem,textObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + NULL, Tk_Offset(DisplayItem,imageObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + NULL, Tk_Offset(DisplayItem,anchorObj), -1, + TK_OPTION_NULL_OK, 0, GEOMETRY_CHANGED}, /* <<NOTE-ANCHOR>> */ + {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor", + NULL, Tk_Offset(DisplayItem,backgroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor", + NULL, Tk_Offset(DisplayItem,foregroundObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_FONT, "-font", "font", "Font", + NULL, Tk_Offset(DisplayItem,fontObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/*------------------------------------------------------------------------ + * +++ Columns. + * + * There are separate option tables associated with the column record: + * ColumnOptionSpecs is for configuring the column, + * and HeadingOptionSpecs is for drawing headings. + */ +typedef struct { + int width; /* Column width, in pixels */ + int minWidth; /* Minimum column width, in pixels */ + int stretch; /* Should column stretch while resizing? */ + Tcl_Obj *idObj; /* Column identifier, from -columns option */ + + Tcl_Obj *anchorObj; /* -anchor for cell data <<NOTE-ANCHOR>> */ + + /* Column heading data: + */ + Tcl_Obj *headingObj; /* Heading label */ + Tcl_Obj *headingImageObj; /* Heading image */ + Tcl_Obj *headingAnchorObj; /* -anchor for heading label */ + Tcl_Obj *headingCommandObj; /* Command to execute */ + Tcl_Obj *headingStateObj; /* @@@ testing ... */ + Ttk_State headingState; /* ... */ + + /* Temporary storage for cell data + */ + Tcl_Obj *data; +} TreeColumn; + +static void InitColumn(TreeColumn *column) +{ + column->width = 200; + column->minWidth = 20; + column->stretch = 1; + column->idObj = 0; + column->anchorObj = 0; + + column->headingState = 0; + column->headingObj = 0; + column->headingImageObj = 0; + column->headingAnchorObj = 0; + column->headingStateObj = 0; + column->headingCommandObj = 0; + + column->data = 0; +} + +static void FreeColumn(TreeColumn *column) +{ + if (column->idObj) { Tcl_DecrRefCount(column->idObj); } + if (column->anchorObj) { Tcl_DecrRefCount(column->anchorObj); } + + if (column->headingObj) { Tcl_DecrRefCount(column->headingObj); } + if (column->headingImageObj) { Tcl_DecrRefCount(column->headingImageObj); } + if (column->headingAnchorObj) { Tcl_DecrRefCount(column->headingAnchorObj); } + if (column->headingStateObj) { Tcl_DecrRefCount(column->headingStateObj); } + if (column->headingCommandObj) { Tcl_DecrRefCount(column->headingCommandObj); } + + /* Don't touch column->data, it's scratch storage */ +} + +static Tk_OptionSpec ColumnOptionSpecs[] = { + {TK_OPTION_INT, "-width", "width", "Width", + DEF_COLWIDTH, -1, Tk_Offset(TreeColumn,width), + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-minwidth", "minWidth", "MinWidth", + DEF_MINWIDTH, -1, Tk_Offset(TreeColumn,minWidth), + 0,0,0 }, + {TK_OPTION_BOOLEAN, "-stretch", "stretch", "Stretch", + "1", -1, Tk_Offset(TreeColumn,stretch), + 0,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + "w", Tk_Offset(TreeColumn,anchorObj), -1, /* <<NOTE-ANCHOR>> */ + 0,0,0 }, + {TK_OPTION_STRING, "-id", "id", "ID", + NULL, Tk_Offset(TreeColumn,idObj), -1, + TK_OPTION_NULL_OK,0,READONLY_OPTION }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +static Tk_OptionSpec HeadingOptionSpecs[] = { + {TK_OPTION_STRING, "-text", "text", "Text", + "", Tk_Offset(TreeColumn,headingObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-image", "image", "Image", + "", Tk_Offset(TreeColumn,headingImageObj), -1, + 0,0,0 }, + {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", + "center", Tk_Offset(TreeColumn,headingAnchorObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-command", "", "", + "", Tk_Offset(TreeColumn,headingCommandObj), -1, + TK_OPTION_NULL_OK,0,0 }, + {TK_OPTION_STRING, "state", "", "", + "", Tk_Offset(TreeColumn,headingStateObj), -1, + 0,0,STATE_CHANGED }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/*------------------------------------------------------------------------ + * +++ -show option: + * TODO: Implement SHOW_BRANCHES. + */ + +#define SHOW_TREE (0x1) /* Show tree column? */ +#define SHOW_HEADINGS (0x2) /* Show heading row? */ + +#define DEFAULT_SHOW "tree headings" + +static const char *showStrings[] = { + "tree", "headings", NULL +}; + +static int GetEnumSetFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + const char *table[], + unsigned *resultPtr) +{ + unsigned result = 0; + int i, objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) + return TCL_ERROR; + + for (i = 0; i < objc; ++i) { + int index; + if (TCL_OK != Tcl_GetIndexFromObj( + interp, objv[i], table, "value", TCL_EXACT, &index)) + { + return TCL_ERROR; + } + result |= (1 << index); + } + + *resultPtr = result; + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Treeview widget record. + * + * Dependencies: + * columns, columnNames: -columns + * displayColumns: -columns, -displaycolumns + * headingHeight: [layout] + * rowHeight, indent: style + */ +typedef struct { + /* Resources acquired at initialization-time: + */ + Tk_OptionTable itemOptionTable; + Tk_OptionTable columnOptionTable; + Tk_OptionTable headingOptionTable; + Tk_OptionTable tagOptionTable; + Tk_BindingTable bindingTable; + Ttk_TagTable tagTable; + + /* Acquired in GetLayout hook: + */ + Ttk_Layout itemLayout; + Ttk_Layout cellLayout; + Ttk_Layout headingLayout; + Ttk_Layout rowLayout; + + int headingHeight; /* Space for headings */ + int rowHeight; /* Height of each item */ + int indent; /* #pixels horizontal offset for child items */ + + /* Tree data: + */ + Tcl_HashTable items; /* Map: item name -> item */ + int serial; /* Next item # for autogenerated names */ + TreeItem *root; /* Root item */ + + TreeColumn column0; /* Column options for display column #0 */ + TreeColumn *columns; /* Array of column options for data columns */ + + TreeItem *focus; /* Current focus item */ + TreeItem *endPtr; /* See EndPosition() */ + + /* Widget options: + */ + Tcl_Obj *columnsObj; /* List of symbolic column names */ + Tcl_Obj *displayColumnsObj; /* List of columns to display */ + + Tcl_Obj *heightObj; /* height (rows) */ + Tcl_Obj *paddingObj; /* internal padding */ + + Tcl_Obj *showObj; /* -show list */ + Tcl_Obj *selectModeObj; /* -selectmode option */ + + Scrollable xscroll; + ScrollHandle xscrollHandle; + Scrollable yscroll; + ScrollHandle yscrollHandle; + + /* Derived resources: + */ + Tcl_HashTable columnNames; /* Map: column name -> column table entry */ + int nColumns; /* #columns */ + unsigned showFlags; /* bitmask of subparts to display */ + + TreeColumn **displayColumns; /* List of columns for display (incl tree) */ + int nDisplayColumns; /* #display columns */ + Ttk_Box headingArea; /* Display area for column headings */ + Ttk_Box treeArea; /* Display area for tree */ + int slack; /* Slack space (see Resizing section) */ + +} TreePart; + +typedef struct { + WidgetCore core; + TreePart tree; +} Treeview; + +#define USER_MASK 0x0100 +#define COLUMNS_CHANGED (USER_MASK) +#define DCOLUMNS_CHANGED (USER_MASK<<1) +#define SCROLLCMD_CHANGED (USER_MASK<<2) +#define SHOW_CHANGED (USER_MASK<<3) + +static const char *SelectModeStrings[] = { "none", "browse", "extended", NULL }; + +static Tk_OptionSpec TreeviewOptionSpecs[] = { + {TK_OPTION_STRING, "-columns", "columns", "Columns", + "", Tk_Offset(Treeview,tree.columnsObj), -1, + 0,0,COLUMNS_CHANGED | GEOMETRY_CHANGED /*| READONLY_OPTION*/ }, + {TK_OPTION_STRING, "-displaycolumns","displayColumns","DisplayColumns", + "#all", Tk_Offset(Treeview,tree.displayColumnsObj), -1, + 0,0,DCOLUMNS_CHANGED | GEOMETRY_CHANGED }, + {TK_OPTION_STRING, "-show", "show", "Show", + DEFAULT_SHOW, Tk_Offset(Treeview,tree.showObj), -1, + 0,0,SHOW_CHANGED | GEOMETRY_CHANGED }, + + {TK_OPTION_STRING_TABLE, "-selectmode", "selectMode", "SelectMode", + "extended", Tk_Offset(Treeview,tree.selectModeObj), -1, + 0,(ClientData)SelectModeStrings,0 }, + + {TK_OPTION_PIXELS, "-height", "height", "Height", + DEF_TREE_ROWS, Tk_Offset(Treeview,tree.heightObj), -1, + 0,0,GEOMETRY_CHANGED}, + {TK_OPTION_STRING, "-padding", "padding", "Pad", + NULL, Tk_Offset(Treeview,tree.paddingObj), -1, + TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED }, + + {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", + NULL, -1, Tk_Offset(Treeview, tree.xscroll.scrollCmd), + TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, + {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", + NULL, -1, Tk_Offset(Treeview, tree.yscroll.scrollCmd), + TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED}, + + WIDGET_TAKEFOCUS_TRUE, + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Utilities. + */ +typedef void (*HashEntryIterator)(void *hashValue); + +static void foreachHashEntry(Tcl_HashTable *ht, HashEntryIterator func) +{ + Tcl_HashSearch search; + Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(ht, &search); + while (entryPtr != NULL) { + func(Tcl_GetHashValue(entryPtr)); + entryPtr = Tcl_NextHashEntry(&search); + } +} + +/* + unshareObj(objPtr) -- + * Ensure that a Tcl_Obj * has refcount 1 -- either return objPtr + * itself, or a duplicated copy. + */ +static Tcl_Obj *unshareObj(Tcl_Obj *objPtr) +{ + if (Tcl_IsShared(objPtr)) { + Tcl_Obj *newObj = Tcl_DuplicateObj(objPtr); + Tcl_DecrRefCount(objPtr); + Tcl_IncrRefCount(newObj); + return newObj; + } + return objPtr; +} + +/* DisplayLayout -- + * Rebind, place, and draw a layout + object combination. + */ +static void DisplayLayout( + Ttk_Layout layout, void *recordPtr, Ttk_State state, Ttk_Box b, Drawable d) +{ + Ttk_RebindSublayout(layout, recordPtr); + Ttk_PlaceLayout(layout, state, b); + Ttk_DrawLayout(layout, state, d); +} + +/* + GetColumn -- + * Look up column by name or number. + * Returns: pointer to column table entry, NULL if not found. + * Leaves an error message in interp->result on error. + */ +static TreeColumn *GetColumn( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj) +{ + Tcl_HashEntry *entryPtr; + int columnIndex; + + /* Check for named column: + */ + entryPtr = Tcl_FindHashEntry( + &tv->tree.columnNames, Tcl_GetString(columnIDObj)); + if (entryPtr) { + return Tcl_GetHashValue(entryPtr); + } + + /* Check for number: + */ + if (Tcl_GetIntFromObj(NULL, columnIDObj, &columnIndex) == TCL_OK) { + if (columnIndex < 0 || columnIndex >= tv->tree.nColumns) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Column index ", + Tcl_GetString(columnIDObj), + " out of bounds", + NULL); + return NULL; + } + + return tv->tree.columns + columnIndex; + } + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Invalid column index ", Tcl_GetString(columnIDObj), + NULL); + return NULL; +} + +/* + FindColumn -- + * Look up column by name, number, or display index. + */ +static TreeColumn *FindColumn( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *columnIDObj) +{ + int colno; + + if (sscanf(Tcl_GetString(columnIDObj), "#%d", &colno) == 1) + { /* Display column specification, #n */ + if (colno >= 0 && colno < tv->tree.nDisplayColumns) { + return tv->tree.displayColumns[colno]; + } + /* else */ + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Column ", Tcl_GetString(columnIDObj), " out of range", + NULL); + return NULL; + } + + return GetColumn(interp, tv, columnIDObj); +} + +/* + FindItem -- + * Locates the item with the specified identifier in the tree. + * If there is no such item, leaves an error message in interp. + */ +static TreeItem *FindItem( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *itemNameObj) +{ + const char *itemName = Tcl_GetString(itemNameObj); + Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&tv->tree.items, itemName); + + if (!entryPtr) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Item ", itemName, " not found", NULL); + return 0; + } + return Tcl_GetHashValue(entryPtr); +} + +/* + GetItemListFromObj -- + * Parse a Tcl_Obj * as a list of items. + * Returns a NULL-terminated array of items; result must + * be ckfree()d. On error, returns NULL and leaves an error + * message in interp. + */ + +static TreeItem **GetItemListFromObj( + Tcl_Interp *interp, Treeview *tv, Tcl_Obj *objPtr) +{ + TreeItem **items; + Tcl_Obj **elements; + int i, nElements; + + if (Tcl_ListObjGetElements(interp,objPtr,&nElements,&elements) != TCL_OK) { + return NULL; + } + + items = (TreeItem**)ckalloc((nElements + 1)*sizeof(TreeItem*)); + for (i = 0; i < nElements; ++i) { + items[i] = FindItem(interp, tv, elements[i]); + if (!items[i]) { + ckfree((ClientData)items); + return NULL; + } + } + items[i] = NULL; + return items; +} + +/* + ItemName -- + * Returns the item's ID. + */ +static const char *ItemName(Treeview *tv, TreeItem *item) +{ + return Tcl_GetHashKey(&tv->tree.items, item->entryPtr); +} + +/* + ItemID -- + * Returns a fresh Tcl_Obj * (refcount 0) holding the + * item identifier of the specified item. + */ +static Tcl_Obj *ItemID(Treeview *tv, TreeItem *item) +{ + return Tcl_NewStringObj(ItemName(tv, item), -1); +} + +/*------------------------------------------------------------------------ + * +++ Column configuration. + */ + +/* + TreeviewFreeColumns -- + * Free column data. + */ +static void TreeviewFreeColumns(Treeview *tv) +{ + int i; + + Tcl_DeleteHashTable(&tv->tree.columnNames); + Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS); + + if (tv->tree.columns) { + for (i = 0; i < tv->tree.nColumns; ++i) + FreeColumn(tv->tree.columns + i); + ckfree((ClientData)tv->tree.columns); + tv->tree.columns = 0; + } +} + +/* + TreeviewInitColumns -- + * Initialize column data when -columns changes. + * Returns: TCL_OK or TCL_ERROR; + */ +static int TreeviewInitColumns(Tcl_Interp *interp, Treeview *tv) +{ + Tcl_Obj **columns; + int i, ncols; + + if (Tcl_ListObjGetElements( + interp, tv->tree.columnsObj, &ncols, &columns) != TCL_OK) + { + return TCL_ERROR; + } + + /* + * Free old values: + */ + TreeviewFreeColumns(tv); + + /* + * Initialize columns array and columnNames hash table: + */ + tv->tree.nColumns = ncols; + tv->tree.columns = + (TreeColumn*)ckalloc(tv->tree.nColumns * sizeof(TreeColumn)); + + for (i = 0; i < ncols; ++i) { + int isNew; + Tcl_Obj *columnName = Tcl_DuplicateObj(columns[i]); + + Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry( + &tv->tree.columnNames, Tcl_GetString(columnName), &isNew); + Tcl_SetHashValue(entryPtr, tv->tree.columns + i); + + InitColumn(tv->tree.columns + i); + Tk_InitOptions( + interp, (ClientData)(tv->tree.columns + i), + tv->tree.columnOptionTable, tv->core.tkwin); + Tk_InitOptions( + interp, (ClientData)(tv->tree.columns + i), + tv->tree.headingOptionTable, tv->core.tkwin); + Tcl_IncrRefCount(columnName); + tv->tree.columns[i].idObj = columnName; + } + + return TCL_OK; +} + +/* + TreeviewInitDisplayColumns -- + * Initializes the 'displayColumns' array. + * + * Note that displayColumns[0] is always the tree column, + * even when SHOW_TREE is not set. + * + * @@@ TODO: disallow duplicated columns + */ +static int TreeviewInitDisplayColumns(Tcl_Interp *interp, Treeview *tv) +{ + Tcl_Obj **dcolumns; + int index, ndcols; + TreeColumn **displayColumns = 0; + + if (Tcl_ListObjGetElements(interp, + tv->tree.displayColumnsObj, &ndcols, &dcolumns) != TCL_OK) { + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(tv->tree.displayColumnsObj), "#all")) { + ndcols = tv->tree.nColumns; + displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*)); + for (index = 0; index < ndcols; ++index) { + displayColumns[index+1] = tv->tree.columns + index; + } + } else { + displayColumns = (TreeColumn**)ckalloc((ndcols+1)*sizeof(TreeColumn*)); + for (index = 0; index < ndcols; ++index) { + displayColumns[index+1] = GetColumn(interp, tv, dcolumns[index]); + if (!displayColumns[index+1]) { + ckfree((ClientData)displayColumns); + return TCL_ERROR; + } + } + } + displayColumns[0] = &tv->tree.column0; + + if (tv->tree.displayColumns) + ckfree((ClientData)tv->tree.displayColumns); + tv->tree.displayColumns = displayColumns; + tv->tree.nDisplayColumns = ndcols + 1; + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Resizing. + * slack invariant: TreeWidth(tree) + slack = treeArea.width + */ + +#define FirstColumn(tv) ((tv->tree.showFlags&SHOW_TREE) ? 0 : 1) + +/* + TreeWidth -- + * Compute the requested tree width from the sum of visible column widths. + */ +static int TreeWidth(Treeview *tv) +{ + int i = FirstColumn(tv); + int width = 0; + + while (i < tv->tree.nDisplayColumns) { + width += tv->tree.displayColumns[i++]->width; + } + return width; +} + +/* + RecomputeSlack -- + */ +static void RecomputeSlack(Treeview *tv) +{ + tv->tree.slack = tv->tree.treeArea.width - TreeWidth(tv); +} + +/* + PickupSlack/DepositSlack -- + * When resizing columns, distribute extra space to 'slack' first, + * and only adjust column widths if 'slack' goes to zero. + * That is, don't bother changing column widths if the tree + * is already scrolled or short. + */ +static int PickupSlack(Treeview *tv, int extra) +{ + int newSlack = tv->tree.slack + extra; + + if ( (newSlack < 0 && 0 <= tv->tree.slack) + || (newSlack > 0 && 0 >= tv->tree.slack)) + { + tv->tree.slack = 0; + return newSlack; + } else { + tv->tree.slack = newSlack; + return 0; + } +} + +static void DepositSlack(Treeview *tv, int extra) +{ + tv->tree.slack += extra; +} + +/* + Stretch -- + * Adjust width of column by N pixels, down to minimum width. + * Returns: #pixels actually moved. + */ +static int Stretch(TreeColumn *c, int n) +{ + int newWidth = n + c->width; + if (newWidth < c->minWidth) { + n = c->minWidth - c->width; + c->width = c->minWidth; + } else { + c->width = newWidth; + } + return n; +} + +/* + ShoveLeft -- + * Adjust width of (stretchable) columns to the left by N pixels. + * Returns: leftover slack. + */ +static int ShoveLeft(Treeview *tv, int i, int n) +{ + int first = FirstColumn(tv); + while (n != 0 && i >= first) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, n); + } + --i; + } + return n; +} + +/* + ShoveRight -- + * Adjust width of (stretchable) columns to the right by N pixels. + * Returns: leftover slack. + */ +static int ShoveRight(Treeview *tv, int i, int n) +{ + while (n != 0 && i < tv->tree.nDisplayColumns) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, n); + } + ++i; + } + return n; +} + +/* + DistributeWidth -- + * Distribute n pixels evenly across all stretchable display columns. + * Returns: leftover slack. + * Notes: + * The "((++w % m) < r)" term is there so that the remainder r = n % m + * is distributed round-robin. + */ +static int DistributeWidth(Treeview *tv, int n) +{ + int w = TreeWidth(tv); + int m = 0; + int i, d, r; + + for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) { + if (tv->tree.displayColumns[i]->stretch) { + ++m; + } + } + if (m == 0) { + return n; + } + + d = n / m; + r = n % m; + if (r < 0) { r += m; --d; } + + for (i = FirstColumn(tv); i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *c = tv->tree.displayColumns[i]; + if (c->stretch) { + n -= Stretch(c, d + ((++w % m) < r)); + } + } + return n; +} + +/* + ResizeColumns -- + * Recompute column widths based on available width. + * Pick up slack first; + * Distribute the remainder evenly across stretchable columns; + * If any is still left over due to minwidth constraints, shove left. + */ +static void ResizeColumns(Treeview *tv, int newWidth) +{ + int delta = newWidth - (TreeWidth(tv) + tv->tree.slack); + DepositSlack(tv, + ShoveLeft(tv, tv->tree.nDisplayColumns - 1, + DistributeWidth(tv, PickupSlack(tv, delta)))); +} + +/* + DragColumn -- + * Move the separator to the right of specified column, + * adjusting other column widths as necessary. + */ +static void DragColumn(Treeview *tv, int i, int delta) +{ + TreeColumn *c = tv->tree.displayColumns[i]; + int dl = delta - ShoveLeft(tv, i-1, delta - Stretch(c, delta)); + int dr = ShoveRight(tv, i+1, PickupSlack(tv, -dl)); + DepositSlack(tv, dr); +} + +/*------------------------------------------------------------------------ + * +++ Event handlers. + */ + +static TreeItem *IdentifyItem(Treeview *tv, int y); /*forward*/ + +static const unsigned int TreeviewBindEventMask = + KeyPressMask|KeyReleaseMask + | ButtonPressMask|ButtonReleaseMask + | PointerMotionMask|ButtonMotionMask + | VirtualEventMask + ; + +static void TreeviewBindEventProc(void *clientData, XEvent *event) +{ + Treeview *tv = clientData; + TreeItem *item = NULL; + Ttk_TagSet tagset; + + /* + * Figure out where to deliver the event. + */ + switch (event->type) + { + case KeyPress: + case KeyRelease: + case VirtualEvent: + item = tv->tree.focus; + break; + case ButtonPress: + case ButtonRelease: + item = IdentifyItem(tv, event->xbutton.y); + break; + case MotionNotify: + item = IdentifyItem(tv, event->xmotion.y); + break; + default: + break; + } + + if (!item) { + return; + } + + /* ASSERT: Ttk_GetTagSetFromObj succeeds. + * NB: must use a local copy of the tagset, + * in case a binding script stomps on -tags. + */ + tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, item->tagsObj); + + /* + * Fire binding: + */ + Tcl_Preserve(clientData); + Tk_BindEvent(tv->tree.bindingTable, event, tv->core.tkwin, + tagset->nTags, (void **)tagset->tags); + Tcl_Release(clientData); + + Ttk_FreeTagSet(tagset); +} + +/*------------------------------------------------------------------------ + * +++ Initialization and cleanup. + */ + +static void TreeviewInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Treeview *tv = recordPtr; + int unused; + + tv->tree.itemOptionTable = + Tk_CreateOptionTable(interp, ItemOptionSpecs); + tv->tree.columnOptionTable = + Tk_CreateOptionTable(interp, ColumnOptionSpecs); + tv->tree.headingOptionTable = + Tk_CreateOptionTable(interp, HeadingOptionSpecs); + tv->tree.tagOptionTable = + Tk_CreateOptionTable(interp, TagOptionSpecs); + + tv->tree.tagTable = Ttk_CreateTagTable( + interp, tv->core.tkwin, TagOptionSpecs, sizeof(DisplayItem)); + tv->tree.bindingTable = Tk_CreateBindingTable(interp); + Tk_CreateEventHandler(tv->core.tkwin, + TreeviewBindEventMask, TreeviewBindEventProc, tv); + + tv->tree.itemLayout + = tv->tree.cellLayout + = tv->tree.headingLayout + = tv->tree.rowLayout + = 0; + tv->tree.headingHeight = tv->tree.rowHeight = DEFAULT_ROWHEIGHT; + tv->tree.indent = DEFAULT_INDENT; + + Tcl_InitHashTable(&tv->tree.columnNames, TCL_STRING_KEYS); + tv->tree.nColumns = tv->tree.nDisplayColumns = 0; + tv->tree.columns = NULL; + tv->tree.displayColumns = NULL; + tv->tree.showFlags = ~0; + + InitColumn(&tv->tree.column0); + Tk_InitOptions( + interp, (ClientData)(&tv->tree.column0), + tv->tree.columnOptionTable, tv->core.tkwin); + Tk_InitOptions( + interp, (ClientData)(&tv->tree.column0), + tv->tree.headingOptionTable, tv->core.tkwin); + + Tcl_InitHashTable(&tv->tree.items, TCL_STRING_KEYS); + tv->tree.serial = 0; + + tv->tree.focus = tv->tree.endPtr = 0; + + /* Create root item "": + */ + tv->tree.root = NewItem(); + Tk_InitOptions(interp, (ClientData)tv->tree.root, + tv->tree.itemOptionTable, tv->core.tkwin); + tv->tree.root->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL); + tv->tree.root->entryPtr = Tcl_CreateHashEntry(&tv->tree.items, "", &unused); + Tcl_SetHashValue(tv->tree.root->entryPtr, tv->tree.root); + + /* Scroll handles: + */ + tv->tree.xscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.xscroll); + tv->tree.yscrollHandle = TtkCreateScrollHandle(&tv->core,&tv->tree.yscroll); + + /* Size parameters: + */ + tv->tree.treeArea = tv->tree.headingArea = Ttk_MakeBox(0,0,0,0); + tv->tree.slack = 0; +} + +static void TreeviewCleanup(void *recordPtr) +{ + Treeview *tv = recordPtr; + + Tk_DeleteEventHandler(tv->core.tkwin, + TreeviewBindEventMask, TreeviewBindEventProc, tv); + Tk_DeleteBindingTable(tv->tree.bindingTable); + Ttk_DeleteTagTable(tv->tree.tagTable); + + if (tv->tree.itemLayout) Ttk_FreeLayout(tv->tree.itemLayout); + if (tv->tree.cellLayout) Ttk_FreeLayout(tv->tree.cellLayout); + if (tv->tree.headingLayout) Ttk_FreeLayout(tv->tree.headingLayout); + if (tv->tree.rowLayout) Ttk_FreeLayout(tv->tree.rowLayout); + + TreeviewFreeColumns(tv); + + if (tv->tree.displayColumns) + Tcl_Free((ClientData)tv->tree.displayColumns); + + foreachHashEntry(&tv->tree.items, FreeItemCB); + Tcl_DeleteHashTable(&tv->tree.items); + + TtkFreeScrollHandle(tv->tree.xscrollHandle); + TtkFreeScrollHandle(tv->tree.yscrollHandle); +} + +/* + TreeviewConfigure -- + * Configuration widget hook. + * + * BUG: If user sets -columns and -displaycolumns, but -displaycolumns + * has an error, the widget is left in an inconsistent state. + */ +static int +TreeviewConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Treeview *tv = recordPtr; + unsigned showFlags = tv->tree.showFlags; + + if (mask & COLUMNS_CHANGED) { + if (TreeviewInitColumns(interp, tv) != TCL_OK) + return TCL_ERROR; + mask |= DCOLUMNS_CHANGED; + } + if (mask & DCOLUMNS_CHANGED) { + if (TreeviewInitDisplayColumns(interp, tv) != TCL_OK) + return TCL_ERROR; + } + if (mask & SCROLLCMD_CHANGED) { + TtkScrollbarUpdateRequired(tv->tree.xscrollHandle); + TtkScrollbarUpdateRequired(tv->tree.yscrollHandle); + } + if ( (mask & SHOW_CHANGED) + && GetEnumSetFromObj( + interp,tv->tree.showObj,showStrings,&showFlags) != TCL_OK) + { + return TCL_ERROR; + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { + return TCL_ERROR; + } + + tv->tree.showFlags = showFlags; + + if (mask & (SHOW_CHANGED | DCOLUMNS_CHANGED)) { + RecomputeSlack(tv); + } + return TCL_OK; +} + +/* + ConfigureItem -- + * Set item options. + */ +static int ConfigureItem( + Tcl_Interp *interp, Treeview *tv, TreeItem *item, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + Ttk_ImageSpec *newImageSpec = NULL; + Ttk_TagSet newTagSet = NULL; + + if (Tk_SetOptions(interp, (ClientData)item, tv->tree.itemOptionTable, + objc, objv, tv->core.tkwin, &savedOptions, &mask) + != TCL_OK) + { + return TCL_ERROR; + } + + /* Make sure that -values is a valid list: + */ + if (item->valuesObj) { + int unused; + if (Tcl_ListObjLength(interp, item->valuesObj, &unused) != TCL_OK) + goto error; + } + + /* Check -image. + */ + if ((mask & ITEM_OPTION_IMAGE_CHANGED) && item->imageObj) { + newImageSpec = TtkGetImageSpec(interp, tv->core.tkwin, item->imageObj); + if (!newImageSpec) { + goto error; + } + } + + /* Check -tags. + * Side effect: may create new tags. + */ + if (mask & ITEM_OPTION_TAGS_CHANGED) { + newTagSet = Ttk_GetTagSetFromObj( + interp, tv->tree.tagTable, item->tagsObj); + if (!newTagSet) { + goto error; + } + } + + /* Keep TTK_STATE_OPEN flag in sync with item->openObj. + * We use both a state flag and a Tcl_Obj* resource so elements + * can access the value in either way. + */ + if (item->openObj) { + int isOpen; + if (Tcl_GetBooleanFromObj(interp, item->openObj, &isOpen) != TCL_OK) + goto error; + if (isOpen) + item->state |= TTK_STATE_OPEN; + else + item->state &= ~TTK_STATE_OPEN; + } + + /* All OK. + */ + Tk_FreeSavedOptions(&savedOptions); + if (mask & ITEM_OPTION_TAGS_CHANGED) { + if (item->tagset) { Ttk_FreeTagSet(item->tagset); } + item->tagset = newTagSet; + } + if (mask & ITEM_OPTION_IMAGE_CHANGED) { + if (item->imagespec) { TtkFreeImageSpec(item->imagespec); } + item->imagespec = newImageSpec; + } + TtkRedisplayWidget(&tv->core); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + if (newTagSet) { Ttk_FreeTagSet(newTagSet); } + if (newImageSpec) { TtkFreeImageSpec(newImageSpec); } + return TCL_ERROR; +} + +/* + ConfigureColumn -- + * Set column options. + */ +static int ConfigureColumn( + Tcl_Interp *interp, Treeview *tv, TreeColumn *column, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + + if (Tk_SetOptions(interp, (ClientData)column, + tv->tree.columnOptionTable, objc, objv, tv->core.tkwin, + &savedOptions,&mask) != TCL_OK) + { + return TCL_ERROR; + } + + if (mask & READONLY_OPTION) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "Attempt to change read-only option", NULL); + goto error; + } + + /* Propagate column width changes to overall widget request width, + * but only if the widget is currently unmapped, in order to prevent + * geometry jumping during interactive column resize. + */ + if (mask & GEOMETRY_CHANGED) { + if (!Tk_IsMapped(tv->core.tkwin)) { + TtkResizeWidget(&tv->core); + } + RecomputeSlack(tv); + } + TtkRedisplayWidget(&tv->core); + + /* ASSERT: SLACKINVARIANT */ + + Tk_FreeSavedOptions(&savedOptions); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + +/* + ConfigureHeading -- + * Set heading options. + */ +static int ConfigureHeading( + Tcl_Interp *interp, Treeview *tv, TreeColumn *column, + int objc, Tcl_Obj *const objv[]) +{ + Tk_SavedOptions savedOptions; + int mask; + + if (Tk_SetOptions(interp, (ClientData)column, + tv->tree.headingOptionTable, objc, objv, tv->core.tkwin, + &savedOptions,&mask) != TCL_OK) + { + return TCL_ERROR; + } + + /* @@@ testing ... */ + if ((mask & STATE_CHANGED) && column->headingStateObj) { + Ttk_StateSpec stateSpec; + if (Ttk_GetStateSpecFromObj( + interp, column->headingStateObj, &stateSpec) != TCL_OK) + { + goto error; + } + column->headingState = Ttk_ModifyState(column->headingState,&stateSpec); + Tcl_DecrRefCount(column->headingStateObj); + column->headingStateObj = Ttk_NewStateSpecObj(column->headingState,0); + Tcl_IncrRefCount(column->headingStateObj); + } + + TtkRedisplayWidget(&tv->core); + Tk_FreeSavedOptions(&savedOptions); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Geometry routines. + */ + +/* + CountRows -- + * Returns the number of viewable rows rooted at item + */ +static int CountRows(TreeItem *item) +{ + int rows = 1; + + if (item->state & TTK_STATE_OPEN) { + TreeItem *child = item->children; + while (child) { + rows += CountRows(child); + child = child->next; + } + } + return rows; +} + +/* + IdentifyRow -- + * Recursive search for item at specified y position. + * Main work routine for IdentifyItem() + */ +static TreeItem *IdentifyRow( + Treeview *tv, /* Widget record */ + TreeItem *item, /* Where to start search */ + int *ypos, /* Scan position */ + int y) /* Target y coordinate */ +{ + while (item) { + int next_ypos = *ypos + tv->tree.rowHeight; + if (*ypos <= y && y <= next_ypos) { + return item; + } + *ypos = next_ypos; + if (item->state & TTK_STATE_OPEN) { + TreeItem *subitem = IdentifyRow(tv, item->children, ypos, y); + if (subitem) { + return subitem; + } + } + item = item->next; + } + return 0; +} + +/* + IdentifyItem -- + * Locate the item at the specified y position, if any. + */ +static TreeItem *IdentifyItem(Treeview *tv, int y) +{ + int rowHeight = tv->tree.rowHeight; + int ypos = tv->tree.treeArea.y - rowHeight * tv->tree.yscroll.first; + return IdentifyRow(tv, tv->tree.root->children, &ypos, y); +} + +/* + IdentifyDisplayColumn -- + * Returns the display column number at the specified x position, + * or -1 if x is outside any columns. + */ +static int IdentifyDisplayColumn(Treeview *tv, int x, int *x1) +{ + int colno = FirstColumn(tv); + int xpos = tv->tree.treeArea.x - tv->tree.xscroll.first; + + while (colno < tv->tree.nDisplayColumns) { + TreeColumn *column = tv->tree.displayColumns[colno]; + int next_xpos = xpos + column->width; + if (xpos <= x && x <= next_xpos + HALO) { + *x1 = next_xpos; + return colno; + } + ++colno; + xpos = next_xpos; + } + + return -1; +} + +/* + RowNumber -- + * Calculate which row the specified item appears on; + * returns -1 if the item is not viewable. + * Xref: DrawForest, IdentifyItem. + */ +static int RowNumber(Treeview *tv, TreeItem *item) +{ + TreeItem *p = tv->tree.root->children; + int n = 0; + + while (p) { + if (p == item) + return n; + + ++n; + + /* Find next viewable item in preorder traversal order + */ + if (p->children && (p->state & TTK_STATE_OPEN)) { + p = p->children; + } else { + while (!p->next && p && p->parent) + p = p->parent; + if (p) + p = p->next; + } + } + + return -1; +} + +/* + ItemDepth -- return the depth of a tree item. + * The depth of an item is equal to the number of proper ancestors, + * not counting the root node. + */ +static int ItemDepth(TreeItem *item) +{ + int depth = 0; + while (item->parent) { + ++depth; + item = item->parent; + } + return depth-1; +} + +/* + ItemRow -- + * Returns row number of specified item relative to root, + * -1 if item is not viewable. + */ +static int ItemRow(Treeview *tv, TreeItem *p) +{ + TreeItem *root = tv->tree.root; + int rowNumber = 0; + + for (;;) { + if (p->prev) { + p = p->prev; + rowNumber += CountRows(p); + } else { + p = p->parent; + if (!(p && (p->state & TTK_STATE_OPEN))) { + /* detached or closed ancestor */ + return -1; + } + if (p == root) { + return rowNumber; + } + ++rowNumber; + } + } +} + +/* + BoundingBox -- + * Compute the parcel of the specified column of the specified item, + * (or the entire item if column is NULL) + * Returns: 0 if item or column is not viewable, 1 otherwise. + */ +static int BoundingBox( + Treeview *tv, /* treeview widget */ + TreeItem *item, /* desired item */ + TreeColumn *column, /* desired column */ + Ttk_Box *bbox_rtn) /* bounding box of item */ +{ + int row = ItemRow(tv, item); + Ttk_Box bbox = tv->tree.treeArea; + + if (row < tv->tree.yscroll.first || row > tv->tree.yscroll.last) { + /* not viewable, or off-screen */ + return 0; + } + + bbox.y += (row - tv->tree.yscroll.first) * tv->tree.rowHeight; + bbox.height = tv->tree.rowHeight; + + bbox.x -= tv->tree.xscroll.first; + bbox.width = TreeWidth(tv); + + if (column) { + int xpos = 0, i = FirstColumn(tv); + while (i < tv->tree.nDisplayColumns) { + if (tv->tree.displayColumns[i] == column) { + break; + } + xpos += tv->tree.displayColumns[i]->width; + ++i; + } + if (i == tv->tree.nDisplayColumns) { /* specified column unviewable */ + return 0; + } + bbox.x += xpos; + bbox.width = column->width; + + /* Account for indentation in tree column: + */ + if (column == &tv->tree.column0) { + int indent = tv->tree.indent * ItemDepth(item); + bbox.x += indent; + bbox.width -= indent; + } + } + *bbox_rtn = bbox; + return 1; +} + +/* + IdentifyRegion -- + */ + +typedef enum { + REGION_NOTHING = 0, + REGION_HEADING, + REGION_SEPARATOR, + REGION_TREE, + REGION_CELL +} TreeRegion; + +static const char *regionStrings[] = { + "nothing", "heading", "separator", "tree", "cell", 0 +}; + +static TreeRegion IdentifyRegion(Treeview *tv, int x, int y) +{ + int x1 = 0, colno; + + colno = IdentifyDisplayColumn(tv, x, &x1); + if (Ttk_BoxContains(tv->tree.headingArea, x, y)) { + if (colno < 0) { + return REGION_NOTHING; + } else if (-HALO <= x1 - x && x1 - x <= HALO) { + return REGION_SEPARATOR; + } else { + return REGION_HEADING; + } + } else if (Ttk_BoxContains(tv->tree.treeArea, x, y)) { + TreeItem *item = IdentifyItem(tv, y); + if (item && colno > 0) { + return REGION_CELL; + } else if (item) { + return REGION_TREE; + } + } + return REGION_NOTHING; +} + +/*------------------------------------------------------------------------ + * +++ Display routines. + */ + +/* + GetSublayout -- + * Utility routine; acquires a sublayout for items, cells, etc. + */ +static Ttk_Layout GetSublayout( + Tcl_Interp *interp, + Ttk_Theme themePtr, + Ttk_Layout parentLayout, + const char *layoutName, + Tk_OptionTable optionTable, + Ttk_Layout *layoutPtr) +{ + Ttk_Layout newLayout = Ttk_CreateSublayout( + interp, themePtr, parentLayout, layoutName, optionTable); + + if (newLayout) { + if (*layoutPtr) + Ttk_FreeLayout(*layoutPtr); + *layoutPtr = newLayout; + } + return newLayout; +} + +/* + TreeviewGetLayout -- + * GetLayout() widget hook. + */ +static Ttk_Layout TreeviewGetLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) +{ + Treeview *tv = recordPtr; + Ttk_Layout treeLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr); + Tcl_Obj *objPtr; + int unused; + + if (!( + treeLayout + && GetSublayout(interp, themePtr, treeLayout, ".Item", + tv->tree.tagOptionTable, &tv->tree.itemLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Cell", + tv->tree.tagOptionTable, &tv->tree.cellLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Heading", + tv->tree.headingOptionTable, &tv->tree.headingLayout) + && GetSublayout(interp, themePtr, treeLayout, ".Row", + tv->tree.tagOptionTable, &tv->tree.rowLayout) + )) { + return 0; + } + + /* Compute heading height. + */ + Ttk_RebindSublayout(tv->tree.headingLayout, &tv->tree.column0); + Ttk_LayoutSize(tv->tree.headingLayout, 0, &unused, &tv->tree.headingHeight); + + /* Get item height, indent from style: + * @@@ TODO: sanity-check. + */ + tv->tree.rowHeight = DEFAULT_ROWHEIGHT; + tv->tree.indent = DEFAULT_INDENT; + if ((objPtr = Ttk_QueryOption(treeLayout, "-rowheight", 0))) { + (void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.rowHeight); + } + if ((objPtr = Ttk_QueryOption(treeLayout, "-indent", 0))) { + (void)Tcl_GetIntFromObj(NULL, objPtr, &tv->tree.indent); + } + + return treeLayout; +} + +/* + TreeviewDoLayout -- + * DoLayout() widget hook. Computes widget layout. + * + * Side effects: + * Computes headingArea and treeArea. + * Computes subtree height. + * Invokes scroll callbacks. + */ +static void TreeviewDoLayout(void *clientData) +{ + Treeview *tv = clientData; + int visibleRows; + + /* ASSERT: SLACKINVARIANT */ + + Ttk_PlaceLayout(tv->core.layout,tv->core.state,Ttk_WinBox(tv->core.tkwin)); + tv->tree.treeArea = Ttk_ClientRegion(tv->core.layout, "treearea"); + + ResizeColumns(tv, tv->tree.treeArea.width); + /* ASSERT: SLACKINVARIANT */ + + TtkScrolled(tv->tree.xscrollHandle, + tv->tree.xscroll.first, + tv->tree.xscroll.first + tv->tree.treeArea.width, + TreeWidth(tv)); + + if (tv->tree.showFlags & SHOW_HEADINGS) { + tv->tree.headingArea = Ttk_PackBox( + &tv->tree.treeArea, 1, tv->tree.headingHeight, TTK_SIDE_TOP); + } else { + tv->tree.headingArea = Ttk_MakeBox(0,0,0,0); + } + + visibleRows = tv->tree.treeArea.height / tv->tree.rowHeight; + tv->tree.root->state |= TTK_STATE_OPEN; + TtkScrolled(tv->tree.yscrollHandle, + tv->tree.yscroll.first, + tv->tree.yscroll.first + visibleRows, + CountRows(tv->tree.root) - 1); +} + +/* + TreeviewSize -- + * SizeProc() widget hook. Size is determined by + * -height option and column widths. + */ +static int TreeviewSize(void *clientData, int *widthPtr, int *heightPtr) +{ + Treeview *tv = clientData; + int nRows, padHeight, padWidth; + + Ttk_LayoutSize(tv->core.layout, tv->core.state, &padWidth, &padHeight); + Tcl_GetIntFromObj(NULL, tv->tree.heightObj, &nRows); + + *widthPtr = padWidth + TreeWidth(tv); + *heightPtr = padHeight + tv->tree.rowHeight * nRows; + + if (tv->tree.showFlags & SHOW_HEADINGS) { + *heightPtr += tv->tree.headingHeight; + } + + return 1; +} + +/* + ItemState -- + * Returns the state of the specified item, based + * on widget state, item state, and other information. + */ +static Ttk_State ItemState(Treeview *tv, TreeItem *item) +{ + Ttk_State state = tv->core.state | item->state; + if (!item->children) + state |= TTK_STATE_LEAF; + if (item != tv->tree.focus) + state &= ~TTK_STATE_FOCUS; + return state; +} + +/* + DrawHeadings -- + * Draw tree headings. + */ +static void DrawHeadings(Treeview *tv, Drawable d) +{ + const int x0 = tv->tree.headingArea.x - tv->tree.xscroll.first; + const int y0 = tv->tree.headingArea.y; + const int h0 = tv->tree.headingArea.height; + int i = FirstColumn(tv); + int x = 0; + + while (i < tv->tree.nDisplayColumns) { + TreeColumn *column = tv->tree.displayColumns[i]; + Ttk_Box parcel = Ttk_MakeBox(x0+x, y0, column->width, h0); + DisplayLayout(tv->tree.headingLayout, + column, column->headingState, parcel, d); + x += column->width; + ++i; + } +} + +/* + PrepareItem -- + * Fill in a displayItem record. + */ +static void PrepareItem( + Treeview *tv, TreeItem *item, DisplayItem *displayItem) +{ + Ttk_Style style = Ttk_LayoutStyle(tv->core.layout); + Ttk_State state = ItemState(tv, item); + + Ttk_TagSetValues(tv->tree.tagTable, item->tagset, displayItem); + Ttk_TagSetApplyStyle(tv->tree.tagTable, style, state, displayItem); +} + +/* + DrawCells -- + * Draw data cells for specified item. + */ +static void DrawCells( + Treeview *tv, TreeItem *item, DisplayItem *displayItem, + Drawable d, int x, int y) +{ + Ttk_Layout layout = tv->tree.cellLayout; + Ttk_State state = ItemState(tv, item); + Ttk_Padding cellPadding = {4, 0, 4, 0}; + int rowHeight = tv->tree.rowHeight; + int nValues = 0; + Tcl_Obj **values = 0; + int i; + + if (!item->valuesObj) { + return; + } + + Tcl_ListObjGetElements(NULL, item->valuesObj, &nValues, &values); + for (i = 0; i < tv->tree.nColumns; ++i) { + tv->tree.columns[i].data = (i < nValues) ? values[i] : 0; + } + + for (i = 1; i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *column = tv->tree.displayColumns[i]; + Ttk_Box parcel = Ttk_PadBox( + Ttk_MakeBox(x, y, column->width, rowHeight), cellPadding); + + displayItem->textObj = column->data; + displayItem->anchorObj = column->anchorObj; /* <<NOTE-ANCHOR>> */ + + DisplayLayout(layout, displayItem, state, parcel, d); + x += column->width; + } +} + +/* + DrawItem -- + * Draw an item (row background, tree label, and cells). + */ +static void DrawItem( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + Ttk_State state = ItemState(tv, item); + DisplayItem displayItem; + int rowHeight = tv->tree.rowHeight; + int x = tv->tree.treeArea.x - tv->tree.xscroll.first; + int y = tv->tree.treeArea.y + rowHeight * (row - tv->tree.yscroll.first); + + if (row % 2) state |= TTK_STATE_ALTERNATE; + + PrepareItem(tv, item, &displayItem); + + /* Draw row background: + */ + { + Ttk_Box rowBox = Ttk_MakeBox(x, y, TreeWidth(tv), rowHeight); + DisplayLayout(tv->tree.rowLayout, &displayItem, state, rowBox, d); + } + + /* Draw tree label: + */ + if (tv->tree.showFlags & SHOW_TREE) { + int indent = depth * tv->tree.indent; + int colwidth = tv->tree.column0.width; + Ttk_Box parcel = Ttk_MakeBox( + x+indent, y, colwidth-indent, rowHeight); + if (item->textObj) { displayItem.textObj = item->textObj; } + if (item->imageObj) { displayItem.imageObj = item->imageObj; } + /* ??? displayItem.anchorObj = 0; <<NOTE-ANCHOR>> */ + DisplayLayout(tv->tree.itemLayout, &displayItem, state, parcel, d); + x += colwidth; + } + + /* Draw data cells: + */ + DrawCells(tv, item, &displayItem, d, x, y); +} + +/* + DrawSubtree -- + * Draw an item and all of its (viewable) descendants. + * + * Returns: + * Row number of the last item drawn. + */ + +static int DrawForest( /* forward */ + Treeview *tv, TreeItem *item, Drawable d, int depth, int row); + +static int DrawSubtree( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + if (row >= tv->tree.yscroll.first) { + DrawItem(tv, item, d, depth, row); + } + + if (item->state & TTK_STATE_OPEN) { + return DrawForest(tv, item->children, d, depth + 1, row + 1); + } else { + return row + 1; + } +} + +/* + DrawForest -- + * Draw a sequence of items and their visible descendants. + * + * Returns: + * Row number of the last item drawn. + */ +static int DrawForest( + Treeview *tv, TreeItem *item, Drawable d, int depth, int row) +{ + while (item && row <= tv->tree.yscroll.last) { + row = DrawSubtree(tv, item, d, depth, row); + item = item->next; + } + return row; +} + +/* + TreeviewDisplay -- + * Display() widget hook. Draw the widget contents. + */ +static void TreeviewDisplay(void *clientData, Drawable d) +{ + Treeview *tv = clientData; + + Ttk_DrawLayout(tv->core.layout, tv->core.state, d); + if (tv->tree.showFlags & SHOW_HEADINGS) { + DrawHeadings(tv, d); + } + DrawForest(tv, tv->tree.root->children, d, 0,0); +} + +/*------------------------------------------------------------------------ + * +++ Utilities for widget commands + */ + +/* + InsertPosition -- + * Locate the previous sibling for [$tree insert]. + * + * Returns a pointer to the item just before the specified index, + * or 0 if the item is to be inserted at the beginning. + */ +static TreeItem *InsertPosition(TreeItem *parent, int index) +{ + TreeItem *prev = 0, *next = parent->children; + + while (next != 0 && index > 0) { + --index; + prev = next; + next = prev->next; + } + + return prev; +} + +/* + EndPosition -- + * Locate the last child of the specified node. + * + * To avoid quadratic-time behavior in the common cases + * where the treeview is populated in breadth-first or + * depth-first order using [$tv insert $parent end ...], + * we cache the result from the last call to EndPosition() + * and start the search from there on a cache hit. + * + */ +static TreeItem *EndPosition(Treeview *tv, TreeItem *parent) +{ + TreeItem *endPtr = tv->tree.endPtr; + + while (endPtr && endPtr->parent != parent) { + endPtr = endPtr->parent; + } + if (!endPtr) { + endPtr = parent->children; + } + + if (endPtr) { + while (endPtr->next) { + endPtr = endPtr->next; + } + tv->tree.endPtr = endPtr; + } + + return endPtr; +} + +/* + AncestryCheck -- + * Verify that specified item is not an ancestor of the specified parent; + * returns 1 if OK, 0 and leaves an error message in interp otherwise. + */ +static int AncestryCheck( + Tcl_Interp *interp, Treeview *tv, TreeItem *item, TreeItem *parent) +{ + TreeItem *p = parent; + while (p) { + if (p == item) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "Cannot insert ", ItemName(tv, item), + " as a descendant of ", ItemName(tv, parent), + NULL); + return 0; + } + p = p->parent; + } + return 1; +} + +/* + DeleteItems -- + * Remove an item and all of its descendants from the hash table + * and detach them from the tree; returns a linked list (chained + * along the ->next pointer) of deleted items. + */ +static TreeItem *DeleteItems(TreeItem *item, TreeItem *delq) +{ + if (item->entryPtr) { + DetachItem(item); + while (item->children) { + delq = DeleteItems(item->children, delq); + } + Tcl_DeleteHashEntry(item->entryPtr); + item->entryPtr = 0; + item->next = delq; + delq = item; + } /* else -- item has already been unlinked */ + return delq; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- item inquiry. + */ + +/* + $tv children $item ?newchildren? -- + * Return the list of children associated with $item + */ +static int TreeviewChildrenCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + Tcl_Obj *result; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?newchildren?"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (objc == 3) { + result = Tcl_NewListObj(0,0); + for (item = item->children; item; item = item->next) { + Tcl_ListObjAppendElement(interp, result, ItemID(tv, item)); + } + Tcl_SetObjResult(interp, result); + } else { + TreeItem **newChildren = GetItemListFromObj(interp, tv, objv[3]); + TreeItem *child; + int i; + + if (!newChildren) + return TCL_ERROR; + + /* Sanity-check: + */ + for (i=0; newChildren[i]; ++i) { + if (!AncestryCheck(interp, tv, newChildren[i], item)) { + ckfree((ClientData)newChildren); + return TCL_ERROR; + } + } + + /* Detach old children: + */ + child = item->children; + while (child) { + TreeItem *next = child->next; + DetachItem(child); + child = next; + } + + /* Detach new children from their current locations: + */ + for (i=0; newChildren[i]; ++i) { + DetachItem(newChildren[i]); + } + + /* Reinsert new children: + * Note: it is not an error for an item to be listed more than once, + * though it probably should be... + */ + child = 0; + for (i=0; newChildren[i]; ++i) { + if (newChildren[i]->parent) { + /* This is a duplicate element which has already been + * inserted. Ignore it. + */ + continue; + } + InsertItem(item, child, newChildren[i]); + child = newChildren[i]; + } + + ckfree((ClientData)newChildren); + TtkRedisplayWidget(&tv->core); + } + + return TCL_OK; +} + +/* + $tv parent $item -- + * Return the item ID of $item's parent. + */ +static int TreeviewParentCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->parent) { + Tcl_SetObjResult(interp, ItemID(tv, item->parent)); + } else { + /* This is the root item. @@@ Return an error? */ + Tcl_ResetResult(interp); + } + + return TCL_OK; +} + +/* + $tv next $item + * Return the ID of $item's next sibling. + */ +static int TreeviewNextCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->next) { + Tcl_SetObjResult(interp, ItemID(tv, item->next)); + } /* else -- leave interp-result empty */ + + return TCL_OK; +} + +/* + $tv prev $item + * Return the ID of $item's previous sibling. + */ +static int TreeviewPrevCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + if (item->prev) { + Tcl_SetObjResult(interp, ItemID(tv, item->prev)); + } /* else -- leave interp-result empty */ + + return TCL_OK; +} + +/* + $tv index $item -- + * Return the index of $item within its parent. + */ +static int TreeviewIndexCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + int index = 0; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + + while (item->prev) { + ++index; + item = item->prev; + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + return TCL_OK; +} + +/* + $tv exists $itemid -- + * Test if the specified item id is present in the tree. + */ +static int TreeviewExistsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Tcl_HashEntry *entryPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "itemid"); + return TCL_ERROR; + } + + entryPtr = Tcl_FindHashEntry(&tv->tree.items, Tcl_GetString(objv[2])); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(entryPtr != 0)); + return TCL_OK; +} + +/* + $tv bbox $itemid ?$column? -- + * Return bounding box [x y width height] of specified item. + */ +static int TreeviewBBoxCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item = 0; + TreeColumn *column = 0; + Ttk_Box bbox; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "itemid ?column"); + return TCL_ERROR; + } + + item = FindItem(interp, tv, objv[2]); + if (!item) { + return TCL_ERROR; + } + if (objc >=4 && (column = FindColumn(interp,tv,objv[3])) == NULL) { + return TCL_ERROR; + } + + if (BoundingBox(tv, item, column, &bbox)) { + Tcl_SetObjResult(interp, Ttk_NewBoxObj(bbox)); + } + + return TCL_OK; +} + +/* + $tv identify $x $y -- (obsolescent) + * Implements the old, horrible, 2-argument form of [$tv identify]. + * + * Returns: one of + * heading #n + * cell itemid #n + * item itemid element + * row itemid + */ +static int TreeviewHorribleIdentify( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], Treeview *tv) +{ + const char *what = "nothing", *detail = NULL; + TreeItem *item = 0; + Tcl_Obj *result; + int dColumnNumber; + char dcolbuf[16]; + int x, y, x1; + + /* ASSERT: objc == 4 */ + + if ( Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK + ) { + return TCL_ERROR; + } + + dColumnNumber = IdentifyDisplayColumn(tv, x, &x1); + if (dColumnNumber < 0) { + goto done; + } + sprintf(dcolbuf, "#%d", dColumnNumber); + + if (Ttk_BoxContains(tv->tree.headingArea,x,y)) { + if (-HALO <= x1 - x && x1 - x <= HALO) { + what = "separator"; + } else { + what = "heading"; + } + detail = dcolbuf; + } else if (Ttk_BoxContains(tv->tree.treeArea,x,y)) { + item = IdentifyItem(tv, y); + if (item && dColumnNumber > 0) { + what = "cell"; + detail = dcolbuf; + } else if (item) { + Ttk_Layout layout = tv->tree.itemLayout; + Ttk_Box itemBox; + DisplayItem displayItem; + Ttk_Element element; + + BoundingBox(tv, item, NULL, &itemBox); + PrepareItem(tv, item, &displayItem); /*@@@ FIX: -text, etc*/ + Ttk_RebindSublayout(layout, &displayItem); + Ttk_PlaceLayout(layout, ItemState(tv,item), itemBox); + element = Ttk_IdentifyElement(layout, x, y); + + if (element) { + what = "item"; + detail = Ttk_ElementName(element); + } else { + what = "row"; + } + } + } + +done: + result = Tcl_NewListObj(0,0); + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(what, -1)); + if (item) + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + if (detail) + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(detail, -1)); + + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* + $tv identify $component $x $y -- + * Identify the component at position x,y. + */ + +static int TreeviewIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + static const char *submethodStrings[] = + { "region", "item", "column", "row", "element", NULL }; + enum { I_REGION, I_ITEM, I_COLUMN, I_ROW, I_ELEMENT }; + + Treeview *tv = recordPtr; + int submethod; + int x, y; + + TreeRegion region; + Ttk_Box bbox; + TreeItem *item; + TreeColumn *column = 0; + int colno, x1; + + if (objc == 4) { /* Old form */ + return TreeviewHorribleIdentify(interp, objc, objv, tv); + } else if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "command x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIndexFromObj(interp, objv[2], + submethodStrings, "command", TCL_EXACT, &submethod) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK + ) { + return TCL_ERROR; + } + + region = IdentifyRegion(tv, x, y); + item = IdentifyItem(tv, y); + colno = IdentifyDisplayColumn(tv, x, &x1); + column = (colno >= 0) ? tv->tree.displayColumns[colno] : NULL; + + switch (submethod) + { + case I_REGION : + Tcl_SetObjResult(interp,Tcl_NewStringObj(regionStrings[region],-1)); + break; + + case I_ITEM : + case I_ROW : + if (item) { + Tcl_SetObjResult(interp, ItemID(tv, item)); + } + break; + + case I_COLUMN : + if (colno >= 0) { + char dcolbuf[16]; + sprintf(dcolbuf, "#%d", colno); + Tcl_SetObjResult(interp, Tcl_NewStringObj(dcolbuf, -1)); + } + break; + + case I_ELEMENT : + { + Ttk_Layout layout = 0; + DisplayItem displayItem; + Ttk_Element element; + + switch (region) { + case REGION_NOTHING: + layout = tv->core.layout; + return TCL_OK; /* @@@ NYI */ + case REGION_HEADING: + case REGION_SEPARATOR: + layout = tv->tree.headingLayout; + return TCL_OK; /* @@@ NYI */ + case REGION_TREE: + layout = tv->tree.itemLayout; + break; + case REGION_CELL: + layout = tv->tree.cellLayout; + break; + } + + if (!BoundingBox(tv, item, column, &bbox)) { + return TCL_OK; + } + + PrepareItem(tv, item, &displayItem); /*@@@ FIX: fill in -text,etc */ + Ttk_RebindSublayout(layout, &displayItem); + Ttk_PlaceLayout(layout, ItemState(tv,item), bbox); + element = Ttk_IdentifyElement(layout, x, y); + + if (element) { + const char *elementName = Ttk_ElementName(element); + Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1)); + } + break; + } + } + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- item and column configuration. + */ + +/* + $tv item $item ?options ....? + * Query or configure item options. + */ +static int TreeviewItemCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?option ?value??..."); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions(interp, item, ItemOptionSpecs, + tv->tree.itemOptionTable, tv->core.tkwin); + } else if (objc == 4) { + return TtkGetOptionValue(interp, item, objv[3], + tv->tree.itemOptionTable, tv->core.tkwin); + } else { + return ConfigureItem(interp, tv, item, objc-3, objv+3); + } +} + +/* + $tv column column ?options ....? + * Column data accessor + */ +static int TreeviewColumnCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeColumn *column; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "column -option value..."); + return TCL_ERROR; + } + if (!(column = FindColumn(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions(interp, column, ColumnOptionSpecs, + tv->tree.columnOptionTable, tv->core.tkwin); + } else if (objc == 4) { + return TtkGetOptionValue(interp, column, objv[3], + tv->tree.columnOptionTable, tv->core.tkwin); + } else { + return ConfigureColumn(interp, tv, column, objc-3, objv+3); + } +} + +/* + $tv heading column ?options ....? + * Heading data accessor + */ +static int TreeviewHeadingCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Tk_OptionTable optionTable = tv->tree.headingOptionTable; + Tk_Window tkwin = tv->core.tkwin; + TreeColumn *column; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "column -option value..."); + return TCL_ERROR; + } + if (!(column = FindColumn(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + if (objc == 3) { + return TtkEnumerateOptions( + interp, column, HeadingOptionSpecs, optionTable, tkwin); + } else if (objc == 4) { + return TtkGetOptionValue( + interp, column, objv[3], optionTable, tkwin); + } else { + return ConfigureHeading(interp, tv, column, objc-3,objv+3); + } +} + +/* + $tv set $item ?$column ?value?? + * Query or configure cell values + */ +static int TreeviewSetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item; + TreeColumn *column; + int columnNumber; + + if (objc < 3 || objc > 5) { + Tcl_WrongNumArgs(interp, 2, objv, "item ?column ?value??"); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) + return TCL_ERROR; + + /* Make sure -values exists: + */ + if (!item->valuesObj) { + item->valuesObj = Tcl_NewListObj(0,0); + Tcl_IncrRefCount(item->valuesObj); + } + + if (objc == 3) { + /* Return dictionary: + */ + Tcl_Obj *result = Tcl_NewListObj(0,0); + Tcl_Obj *value; + for (columnNumber=0; columnNumber<tv->tree.nColumns; ++columnNumber) { + Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &value); + if (value) { + Tcl_ListObjAppendElement(interp, result, + tv->tree.columns[columnNumber].idObj); + Tcl_ListObjAppendElement(interp, result, value); + } + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + + /* else -- get or set column + */ + if (!(column = FindColumn(interp, tv, objv[3]))) + return TCL_ERROR; + + if (column == &tv->tree.column0) { + /* @@@ Maybe set -text here instead? */ + Tcl_AppendResult(interp, "Display column #0 cannot be set", NULL); + return TCL_ERROR; + } + + /* Note: we don't do any error checking in the list operations, + * since item->valuesObj is guaranteed to be a list. + */ + columnNumber = column - tv->tree.columns; + + if (objc == 4) { /* get column */ + Tcl_Obj *result = 0; + Tcl_ListObjIndex(interp, item->valuesObj, columnNumber, &result); + if (!result) { + result = Tcl_NewStringObj("",0); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else { /* set column */ + int length; + + item->valuesObj = unshareObj(item->valuesObj); + + /* Make sure -values is fully populated: + */ + Tcl_ListObjLength(interp, item->valuesObj, &length); + while (length < tv->tree.nColumns) { + Tcl_Obj *empty = Tcl_NewStringObj("",0); + Tcl_ListObjAppendElement(interp, item->valuesObj, empty); + ++length; + } + + /* Set value: + */ + Tcl_ListObjReplace(interp,item->valuesObj,columnNumber,1,1,objv+4); + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- tree modification. + */ + +/* + $tv insert $parent $index ?-id id? ?-option value ...? + * Insert a new item. + */ +static int TreeviewInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *parent, *sibling, *newItem; + Tcl_HashEntry *entryPtr; + int isNew; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "parent index ?-id id? -options..."); + return TCL_ERROR; + } + + /* Get parent node: + */ + if ((parent = FindItem(interp, tv, objv[2])) == NULL) { + return TCL_ERROR; + } + + /* Locate previous sibling based on $index: + */ + if (!strcmp(Tcl_GetString(objv[3]), "end")) { + sibling = EndPosition(tv, parent); + } else { + int index; + if (Tcl_GetIntFromObj(interp, objv[3], &index) != TCL_OK) + return TCL_ERROR; + sibling = InsertPosition(parent, index); + } + + /* Get node name: + * If -id supplied and does not already exist, use that; + * Otherwise autogenerate new one. + */ + objc -= 4; objv += 4; + if (objc >= 2 && !strcmp("-id", Tcl_GetString(objv[0]))) { + const char *itemName = Tcl_GetString(objv[1]); + entryPtr = Tcl_CreateHashEntry(&tv->tree.items, itemName, &isNew); + if (!isNew) { + Tcl_AppendResult(interp, "Item ",itemName," already exists",NULL); + return TCL_ERROR; + } + objc -= 2; objv += 2; + } else { + char idbuf[16]; + do { + ++tv->tree.serial; + sprintf(idbuf, "I%03X", tv->tree.serial); + entryPtr = Tcl_CreateHashEntry(&tv->tree.items, idbuf, &isNew); + } while (!isNew); + } + + /* Create and configure new item: + */ + newItem = NewItem(); + Tk_InitOptions( + interp, (ClientData)newItem, tv->tree.itemOptionTable, tv->core.tkwin); + newItem->tagset = Ttk_GetTagSetFromObj(NULL, tv->tree.tagTable, NULL); + if (ConfigureItem(interp, tv, newItem, objc, objv) != TCL_OK) { + Tcl_DeleteHashEntry(entryPtr); + FreeItem(newItem); + return TCL_ERROR; + } + + /* Store in hash table, link into tree: + */ + Tcl_SetHashValue(entryPtr, newItem); + newItem->entryPtr = entryPtr; + InsertItem(parent, sibling, newItem); + TtkRedisplayWidget(&tv->core); + + Tcl_SetObjResult(interp, ItemID(tv, newItem)); + return TCL_OK; +} + +/* + $tv detach $item -- + * Unlink $item from the tree. + */ +static int TreeviewDetachCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem **items; + int i; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + if (!(items = GetItemListFromObj(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Sanity-check */ + for (i = 0; items[i]; ++i) { + if (items[i] == tv->tree.root) { + Tcl_AppendResult(interp, "Cannot detach root item", NULL); + ckfree((ClientData)items); + return TCL_ERROR; + } + } + + for (i = 0; items[i]; ++i) { + DetachItem(items[i]); + } + + TtkRedisplayWidget(&tv->core); + ckfree((ClientData)items); + return TCL_OK; +} + +/* + $tv delete $items -- + * Delete each item in $items. + * + * Do this in two passes: + * First detach the item and all its descendants and remove them + * from the hash table. Free the items themselves in a second pass. + * + * It's done this way because an item may appear more than once + * in the list of items to delete (either directly or as a descendant + * of a previously deleted item.) + */ + +static int TreeviewDeleteCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem **items, *delq; + int i; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "items"); + return TCL_ERROR; + } + + if (!(items = GetItemListFromObj(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Sanity-check: + */ + for (i=0; items[i]; ++i) { + if (items[i] == tv->tree.root) { + ckfree((ClientData)items); + Tcl_AppendResult(interp, "Cannot delete root item", NULL); + return TCL_ERROR; + } + } + + /* Remove items from hash table. + */ + delq = 0; + for (i=0; items[i]; ++i) { + delq = DeleteItems(items[i], delq); + } + + /* Free items: + */ + while (delq) { + TreeItem *next = delq->next; + if (tv->tree.focus == delq) + tv->tree.focus = 0; + if (tv->tree.endPtr == delq) + tv->tree.endPtr = 0; + FreeItem(delq); + delq = next; + } + + ckfree((ClientData)items); + TtkRedisplayWidget(&tv->core); + return TCL_OK; +} + +/* + $tv move $item $parent $index + * Move $item to the specified $index in $parent's child list. + */ +static int TreeviewMoveCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item, *parent; + TreeItem *sibling; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "item parent index"); + return TCL_ERROR; + } + if ( (item = FindItem(interp, tv, objv[2])) == 0 + || (parent = FindItem(interp, tv, objv[3])) == 0) + { + return TCL_ERROR; + } + + /* Locate previous sibling based on $index: + */ + if (!strcmp(Tcl_GetString(objv[4]), "end")) { + sibling = EndPosition(tv, parent); + } else { + TreeItem *p; + int index; + + if (Tcl_GetIntFromObj(interp, objv[4], &index) != TCL_OK) { + return TCL_ERROR; + } + + sibling = 0; + for (p = parent->children; p != NULL && index > 0; p = p->next) { + if (p != item) { + --index; + } /* else -- moving node forward, count index+1 nodes */ + sibling = p; + } + } + + /* Check ancestry: + */ + if (!AncestryCheck(interp, tv, item, parent)) { + return TCL_ERROR; + } + + /* Moving an item after itself is a no-op: + */ + if (item == sibling) { + return TCL_OK; + } + + /* Move item: + */ + DetachItem(item); + InsertItem(parent, sibling, item); + + TtkRedisplayWidget(&tv->core); + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- scrolling + */ + +static int TreeviewXViewCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + return TtkScrollviewCommand(interp, objc, objv, tv->tree.xscrollHandle); +} + +static int TreeviewYViewCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + return TtkScrollviewCommand(interp, objc, objv, tv->tree.yscrollHandle); +} + +/* $tree see $item -- + * Ensure that $item is visible. + */ +static int TreeviewSeeCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + TreeItem *item, *parent; + int rowNumber; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "item"); + return TCL_ERROR; + } + if (!(item = FindItem(interp, tv, objv[2]))) { + return TCL_ERROR; + } + + /* Make sure all ancestors are open: + */ + for (parent = item->parent; parent; parent = parent->parent) { + if (!(parent->state & TTK_STATE_OPEN)) { + parent->openObj = unshareObj(parent->openObj); + Tcl_SetBooleanObj(parent->openObj, 1); + parent->state |= TTK_STATE_OPEN; + TtkRedisplayWidget(&tv->core); + } + } + tv->tree.yscroll.total = CountRows(tv->tree.root) - 1; + + /* Make sure item is visible: + */ + rowNumber = RowNumber(tv, item); + if (rowNumber < tv->tree.yscroll.first) { + TtkScrollTo(tv->tree.yscrollHandle, rowNumber); + } else if (rowNumber >= tv->tree.yscroll.last) { + TtkScrollTo(tv->tree.yscrollHandle, + tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last)); + } + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- interactive column resize + */ + +/* + $tree drag $column $newX -- + * Set right edge of display column $column to x position $X + */ +static int TreeviewDragCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + int left = tv->tree.treeArea.x - tv->tree.xscroll.first; + int i = FirstColumn(tv); + TreeColumn *column; + int newx; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "column xposition"); + return TCL_ERROR; + } + + if ( (column = FindColumn(interp, tv, objv[2])) == 0 + || Tcl_GetIntFromObj(interp, objv[3], &newx) != TCL_OK) + { + return TCL_ERROR; + } + + for (;i < tv->tree.nDisplayColumns; ++i) { + TreeColumn *c = tv->tree.displayColumns[i]; + int right = left + c->width; + if (c == column) { + DragColumn(tv, i, newx - right); + /* ASSERT: SLACKINVARIANT */ + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } + left = right; + } + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, + "column ", Tcl_GetString(objv[2]), " is not displayed", + NULL); + return TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- focus and selection + */ + +/* + $tree focus ?item? + */ +static int TreeviewFocusCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc == 2) { + if (tv->tree.focus) { + Tcl_SetObjResult(interp, ItemID(tv, tv->tree.focus)); + } + return TCL_OK; + } else if (objc == 3) { + TreeItem *newFocus = FindItem(interp, tv, objv[2]); + if (!newFocus) + return TCL_ERROR; + tv->tree.focus = newFocus; + TtkRedisplayWidget(&tv->core); + return TCL_OK; + } else { + Tcl_WrongNumArgs(interp, 2, objv, "?newFocus?"); + return TCL_ERROR; + } +} + +/* + $tree selection ?add|remove|set|toggle $items? + */ +static int TreeviewSelectionCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + enum { + SELECTION_SET, SELECTION_ADD, SELECTION_REMOVE, SELECTION_TOGGLE + }; + static const char *selopStrings[] = { + "set", "add", "remove", "toggle", NULL + }; + + Treeview *tv = recordPtr; + int selop, i; + TreeItem *item, **items; + + if (objc == 2) { + Tcl_Obj *result = Tcl_NewListObj(0,0); + for (item = tv->tree.root->children; item; item=NextPreorder(item)) { + if (item->state & TTK_STATE_SELECTED) + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + } + Tcl_SetObjResult(interp, result); + return TCL_OK; + } + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "?add|remove|set|toggle items?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[2], selopStrings, + "selection operation", 0, &selop) != TCL_OK) + { + return TCL_ERROR; + } + + items = GetItemListFromObj(interp, tv, objv[3]); + if (!items) { + return TCL_ERROR; + } + + switch (selop) + { + case SELECTION_SET: + for (item=tv->tree.root; item; item=NextPreorder(item)) { + item->state &= ~TTK_STATE_SELECTED; + } + /*FALLTHRU*/ + case SELECTION_ADD: + for (i=0; items[i]; ++i) { + items[i]->state |= TTK_STATE_SELECTED; + } + break; + case SELECTION_REMOVE: + for (i=0; items[i]; ++i) { + items[i]->state &= ~TTK_STATE_SELECTED; + } + break; + case SELECTION_TOGGLE: + for (i=0; items[i]; ++i) { + items[i]->state ^= TTK_STATE_SELECTED; + } + break; + } + + ckfree((ClientData)items); + TtkSendVirtualEvent(tv->core.tkwin, "TreeviewSelect"); + TtkRedisplayWidget(&tv->core); + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Widget commands -- tags and bindings. + */ + +/* + $tv tag bind $tag ?$sequence ?$script?? + */ +static int TreeviewTagBindCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_TagTable tagTable = tv->tree.tagTable; + Tk_BindingTable bindingTable = tv->tree.bindingTable; + Ttk_Tag tag; + + if (objc < 4 || objc > 6) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?script?"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tagTable, objv[3]); + if (!tag) { return TCL_ERROR; } + + if (objc == 4) { /* $tv tag bind $tag */ + Tk_GetAllBindings(interp, bindingTable, tag); + } else if (objc == 5) { /* $tv tag bind $tag $sequence */ + /* TODO: distinguish "no such binding" (OK) from "bad pattern" (ERROR) + */ + const char *script = Tk_GetBinding(interp, + bindingTable, tag, Tcl_GetString(objv[4])); + if (script != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(script,-1)); + } + } else if (objc == 6) { /* $tv tag bind $tag $sequence $script */ + const char *sequence = Tcl_GetString(objv[4]); + const char *script = Tcl_GetString(objv[5]); + + if (!*script) { /* Delete existing binding */ + Tk_DeleteBinding(interp, bindingTable, tag, sequence); + } else { + unsigned long mask = Tk_CreateBinding(interp, + bindingTable, tag, sequence, script, 0); + + /* Test mask to make sure event is supported: + */ + if (mask & (~TreeviewBindEventMask)) { + Tk_DeleteBinding(interp, bindingTable, tag, sequence); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "unsupported event ", sequence, + "\nonly key, button, motion, and virtual events supported", + NULL); + return TCL_ERROR; + } + } + } + return TCL_OK; +} + +/* + $tv tag configure $tag ?-option ?value -option value...?? + */ +static int TreeviewTagConfigureCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_TagTable tagTable = tv->tree.tagTable; + Ttk_Tag tag; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?-option ?value ...??"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tagTable, objv[3]); + + if (objc == 4) { + return Ttk_EnumerateTagOptions(interp, tagTable, tag); + } else if (objc == 5) { + Tcl_Obj *result = Ttk_TagOptionValue(interp, tagTable, tag, objv[4]); + if (result) { + Tcl_SetObjResult(interp, result); + return TCL_OK; + } /* else */ + return TCL_ERROR; + } + /* else */ + TtkRedisplayWidget(&tv->core); + return Ttk_ConfigureTag(interp, tagTable, tag, objc - 4, objv + 4); +} + +/* + $tv tag has $tag ?$item? + */ +static int TreeviewTagHasCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc == 4) { /* Return list of all items with tag */ + Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + TreeItem *item = tv->tree.root; + Tcl_Obj *result = Tcl_NewListObj(0,0); + + while (item) { + if (Ttk_TagSetContains(item->tagset, tag)) { + Tcl_ListObjAppendElement(NULL, result, ItemID(tv, item)); + } + item = NextPreorder(item); + } + + Tcl_SetObjResult(interp, result); + return TCL_OK; + } else if (objc == 5) { /* Test if item has specified tag */ + Ttk_Tag tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + TreeItem *item = FindItem(interp, tv, objv[4]); + if (!item) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(Ttk_TagSetContains(item->tagset, tag))); + return TCL_OK; + } else { + Tcl_WrongNumArgs(interp, 3, objv, "tagName ?item?"); + return TCL_ERROR; + } +} + +/* + $tv tag names $tag + */ +static int TreeviewTagNamesCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, ""); + return TCL_ERROR; + } + + return Ttk_EnumerateTags(interp, tv->tree.tagTable); +} + +/* + $tv tag add $tag $items + */ +static void AddTag(TreeItem *item, Ttk_Tag tag) +{ + if (Ttk_TagSetAdd(item->tagset, tag)) { + if (item->tagsObj) Tcl_DecrRefCount(item->tagsObj); + item->tagsObj = Ttk_NewTagSetObj(item->tagset); + Tcl_IncrRefCount(item->tagsObj); + } +} + +static int TreeviewTagAddCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_Tag tag; + TreeItem **items; + int i; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName items"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + items = GetItemListFromObj(interp, tv, objv[4]); + + if (!items) { + return TCL_ERROR; + } + + for (i=0; items[i]; ++i) { + AddTag(items[i], tag); + } + + return TCL_OK; +} + +/* + $tv tag remove $tag ?$items? + */ +static void RemoveTag(TreeItem *item, Ttk_Tag tag) +{ + if (Ttk_TagSetRemove(item->tagset, tag)) { + if (item->tagsObj) Tcl_DecrRefCount(item->tagsObj); + item->tagsObj = Ttk_NewTagSetObj(item->tagset); + Tcl_IncrRefCount(item->tagsObj); + } +} + +static int TreeviewTagRemoveCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Treeview *tv = recordPtr; + Ttk_Tag tag; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "tagName items"); + return TCL_ERROR; + } + + tag = Ttk_GetTagFromObj(tv->tree.tagTable, objv[3]); + + if (objc == 5) { + TreeItem **items = GetItemListFromObj(interp, tv, objv[4]); + int i; + + if (!items) { + return TCL_ERROR; + } + for (i=0; items[i]; ++i) { + RemoveTag(items[i], tag); + } + } else if (objc == 4) { + TreeItem *item = tv->tree.root; + while (item) { + RemoveTag(item, tag); + item=NextPreorder(item); + } + } + return TCL_OK; +} + +static const Ttk_Ensemble TreeviewTagCommands[] = { + { "add", TreeviewTagAddCommand,0 }, + { "bind", TreeviewTagBindCommand,0 }, + { "configure", TreeviewTagConfigureCommand,0 }, + { "has", TreeviewTagHasCommand,0 }, + { "names", TreeviewTagNamesCommand,0 }, + { "remove", TreeviewTagRemoveCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget commands record. + */ +static const Ttk_Ensemble TreeviewCommands[] = { + { "bbox", TreeviewBBoxCommand,0 }, + { "children", TreeviewChildrenCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "column", TreeviewColumnCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "delete", TreeviewDeleteCommand,0 }, + { "detach", TreeviewDetachCommand,0 }, + { "drag", TreeviewDragCommand,0 }, + { "exists", TreeviewExistsCommand,0 }, + { "focus", TreeviewFocusCommand,0 }, + { "heading", TreeviewHeadingCommand,0 }, + { "identify", TreeviewIdentifyCommand,0 }, + { "index", TreeviewIndexCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "insert", TreeviewInsertCommand,0 }, + { "item", TreeviewItemCommand,0 }, + { "move", TreeviewMoveCommand,0 }, + { "next", TreeviewNextCommand,0 }, + { "parent", TreeviewParentCommand,0 }, + { "prev", TreeviewPrevCommand,0 }, + { "see", TreeviewSeeCommand,0 }, + { "selection" , TreeviewSelectionCommand,0 }, + { "set", TreeviewSetCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "tag", 0,TreeviewTagCommands }, + { "xview", TreeviewXViewCommand,0 }, + { "yview", TreeviewYViewCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget definition. + */ + +static WidgetSpec TreeviewWidgetSpec = { + "Treeview", /* className */ + sizeof(Treeview), /* recordSize */ + TreeviewOptionSpecs, /* optionSpecs */ + TreeviewCommands, /* subcommands */ + TreeviewInitialize, /* initializeProc */ + TreeviewCleanup, /* cleanupProc */ + TreeviewConfigure, /* configureProc */ + TtkNullPostConfigure, /* postConfigureProc */ + TreeviewGetLayout, /* getLayoutProc */ + TreeviewSize, /* sizeProc */ + TreeviewDoLayout, /* layoutProc */ + TreeviewDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Layout specifications. + */ + +TTK_BEGIN_LAYOUT_TABLE(LayoutTable) + +TTK_LAYOUT("Treeview", + TTK_GROUP("Treeview.field", TTK_FILL_BOTH|TTK_BORDER, + TTK_GROUP("Treeview.padding", TTK_FILL_BOTH, + TTK_NODE("Treeview.treearea", TTK_FILL_BOTH)))) + +TTK_LAYOUT("Item", + TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH, + TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT) + TTK_NODE("Treeitem.image", TTK_PACK_LEFT) + TTK_GROUP("Treeitem.focus", TTK_PACK_LEFT, + TTK_NODE("Treeitem.text", TTK_PACK_LEFT)))) + +TTK_LAYOUT("Cell", + TTK_GROUP("Treedata.padding", TTK_FILL_BOTH, + TTK_NODE("Treeitem.text", TTK_FILL_BOTH))) + +TTK_LAYOUT("Heading", + TTK_NODE("Treeheading.cell", TTK_FILL_BOTH) + TTK_GROUP("Treeheading.border", TTK_FILL_BOTH, + TTK_GROUP("Treeheading.padding", TTK_FILL_BOTH, + TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) + TTK_NODE("Treeheading.text", TTK_FILL_X)))) + +TTK_LAYOUT("Row", + TTK_NODE("Treeitem.row", TTK_FILL_BOTH)) + +TTK_END_LAYOUT_TABLE + +/*------------------------------------------------------------------------ + * +++ Tree indicator element. + */ + +typedef struct { + Tcl_Obj *colorObj; + Tcl_Obj *sizeObj; + Tcl_Obj *marginsObj; +} TreeitemIndicator; + +static Ttk_ElementOptionSpec TreeitemIndicatorOptions[] = { + { "-foreground", TK_OPTION_COLOR, + Tk_Offset(TreeitemIndicator,colorObj), DEFAULT_FOREGROUND }, + { "-indicatorsize", TK_OPTION_PIXELS, + Tk_Offset(TreeitemIndicator,sizeObj), "12" }, + { "-indicatormargins", TK_OPTION_STRING, + Tk_Offset(TreeitemIndicator,marginsObj), "2 2 4 2" }, + { NULL, 0, 0, NULL } +}; + +static void TreeitemIndicatorSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TreeitemIndicator *indicator = elementRecord; + Ttk_Padding margins; + int size = 0; + + Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginsObj, &margins); + Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size); + + *widthPtr = size + Ttk_PaddingWidth(margins); + *heightPtr = size + Ttk_PaddingHeight(margins); +} + +static void TreeitemIndicatorDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + TreeitemIndicator *indicator = elementRecord; + ArrowDirection direction = + (state & TTK_STATE_OPEN) ? ARROW_DOWN : ARROW_RIGHT; + Ttk_Padding margins; + XColor *borderColor = Tk_GetColorFromObj(tkwin, indicator->colorObj); + XGCValues gcvalues; GC gc; unsigned mask; + + if (state & TTK_STATE_LEAF) /* don't draw anything */ + return; + + Ttk_GetPaddingFromObj(NULL,tkwin,indicator->marginsObj,&margins); + b = Ttk_PadBox(b, margins); + + gcvalues.foreground = borderColor->pixel; + gcvalues.line_width = 1; + mask = GCForeground | GCLineWidth; + gc = Tk_GetGC(tkwin, mask, &gcvalues); + + TtkDrawArrow(Tk_Display(tkwin), d, gc, b, direction); + + Tk_FreeGC(Tk_Display(tkwin), gc); +} + +static Ttk_ElementSpec TreeitemIndicatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TreeitemIndicator), + TreeitemIndicatorOptions, + TreeitemIndicatorSize, + TreeitemIndicatorDraw +}; + +/*------------------------------------------------------------------------ + * +++ Row element. + */ + +typedef struct { + Tcl_Obj *backgroundObj; + Tcl_Obj *rowNumberObj; +} RowElement; + +static Ttk_ElementOptionSpec RowElementOptions[] = { + { "-background", TK_OPTION_COLOR, + Tk_Offset(RowElement,backgroundObj), DEFAULT_BACKGROUND }, + { "-rownumber", TK_OPTION_INT, + Tk_Offset(RowElement,rowNumberObj), "0" }, + { NULL, 0, 0, NULL } +}; + +static void RowElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + RowElement *row = elementRecord; + XColor *color = Tk_GetColorFromObj(tkwin, row->backgroundObj); + GC gc = Tk_GCForColor(color, d); + XFillRectangle(Tk_Display(tkwin), d, gc, + b.x, b.y, b.width, b.height); +} + +static Ttk_ElementSpec RowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(RowElement), + RowElementOptions, + TtkNullElementSize, + RowElementDraw +}; + +/*------------------------------------------------------------------------ + * +++ Initialisation. + */ + +MODULE_SCOPE +void TtkTreeview_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + RegisterWidget(interp, "ttk::treeview", &TreeviewWidgetSpec); + + Ttk_RegisterElement(interp, theme, "Treeitem.indicator", + &TreeitemIndicatorElementSpec, 0); + Ttk_RegisterElement(interp, theme, "Treeitem.row", &RowElementSpec, 0); + Ttk_RegisterElement(interp, theme, "Treeheading.cell", &RowElementSpec, 0); + Ttk_RegisterElement(interp, theme, "treearea", &ttkNullElementSpec, 0); + + Ttk_RegisterLayouts(theme, LayoutTable); +} + +/*EOF*/ diff --git a/generic/ttk/ttkWidget.c b/generic/ttk/ttkWidget.c new file mode 100644 index 0000000..d5e0484 --- /dev/null +++ b/generic/ttk/ttkWidget.c @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2003, Joe English + * + * Core widget utilities. + */ + +#include <string.h> +#include <tk.h> +#include "ttkTheme.h" +#include "ttkWidget.h" + +#ifdef MAC_OSX_TK +#define TK_NO_DOUBLE_BUFFERING 1 +#endif + +/*------------------------------------------------------------------------ + * +++ Internal helper routines. + */ + +/* UpdateLayout -- + * Call the widget's get-layout hook to recompute corePtr->layout. + * Returns TCL_OK if successful, returns TCL_ERROR and leaves + * the layout unchanged otherwise. + */ +static int UpdateLayout(Tcl_Interp *interp, WidgetCore *corePtr) +{ + Ttk_Theme themePtr = Ttk_GetCurrentTheme(interp); + Ttk_Layout newLayout = + corePtr->widgetSpec->getLayoutProc(interp, themePtr,corePtr); + + if (newLayout) { + if (corePtr->layout) { + Ttk_FreeLayout(corePtr->layout); + } + corePtr->layout = newLayout; + return TCL_OK; + } + return TCL_ERROR; +} + +/* SizeChanged -- + * Call the widget's sizeProc to compute new requested size + * and pass it to the geometry manager. + */ +static void SizeChanged(WidgetCore *corePtr) +{ + int reqWidth = 1, reqHeight = 1; + + if (corePtr->widgetSpec->sizeProc(corePtr,&reqWidth,&reqHeight)) { + Tk_GeometryRequest(corePtr->tkwin, reqWidth, reqHeight); + } +} + +#ifndef TK_NO_DOUBLE_BUFFERING + +/* BeginDrawing -- + * Returns a Drawable for drawing the widget contents. + * This is normally an off-screen Pixmap, copied to + * the window by EndDrawing(). + */ +static Drawable BeginDrawing(Tk_Window tkwin) +{ + return Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), + Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin)); +} + +/* EndDrawing -- + * Copy the drawable contents to the screen and release resources. + */ +static void EndDrawing(Tk_Window tkwin, Drawable d) +{ + XGCValues gcValues; + GC gc; + + gcValues.function = GXcopy; + gcValues.graphics_exposures = False; + gc = Tk_GetGC(tkwin, GCFunction|GCGraphicsExposures, &gcValues); + + XCopyArea(Tk_Display(tkwin), d, Tk_WindowId(tkwin), gc, + 0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), + 0, 0); + + Tk_FreePixmap(Tk_Display(tkwin), d); + Tk_FreeGC(Tk_Display(tkwin), gc); +} +#else +/* No double-buffering: draw directly into the window. */ +static Drawable BeginDrawing(Tk_Window tkwin) { return Tk_WindowId(tkwin); } +static void EndDrawing(Tk_Window tkwin, Drawable d) { } +#endif + +/* DrawWidget -- + * Redraw a widget. Called as an idle handler. + */ +static void DrawWidget(ClientData recordPtr) +{ + WidgetCore *corePtr = recordPtr; + + corePtr->flags &= ~REDISPLAY_PENDING; + if (Tk_IsMapped(corePtr->tkwin)) { + Drawable d = BeginDrawing(corePtr->tkwin); + corePtr->widgetSpec->layoutProc(recordPtr); + corePtr->widgetSpec->displayProc(recordPtr, d); + EndDrawing(corePtr->tkwin, d); + } +} + +/* TtkRedisplayWidget -- + * Schedule redisplay as an idle handler. + */ +void TtkRedisplayWidget(WidgetCore *corePtr) +{ + if (corePtr->flags & WIDGET_DESTROYED) { + return; + } + + if (!(corePtr->flags & REDISPLAY_PENDING)) { + Tcl_DoWhenIdle(DrawWidget, corePtr); + corePtr->flags |= REDISPLAY_PENDING; + } +} + +/* TtkResizeWidget -- + * Recompute widget size, schedule geometry propagation and redisplay. + */ +void TtkResizeWidget(WidgetCore *corePtr) +{ + if (corePtr->flags & WIDGET_DESTROYED) { + return; + } + + SizeChanged(corePtr); + TtkRedisplayWidget(corePtr); +} + +/* TtkWidgetChangeState -- + * Set / clear the specified bits in the 'state' flag, + */ +void TtkWidgetChangeState(WidgetCore *corePtr, + unsigned int setBits, unsigned int clearBits) +{ + Ttk_State oldState = corePtr->state; + corePtr->state = (oldState & ~clearBits) | setBits; + if (corePtr->state ^ oldState) { + TtkRedisplayWidget(corePtr); + } +} + +/* WidgetInstanceObjCmd -- + * Widget instance command implementation. + */ +static int +WidgetInstanceObjCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = clientData; + const Ttk_Ensemble *commands = corePtr->widgetSpec->commands; + int status; + + Tcl_Preserve(clientData); + status = Ttk_InvokeEnsemble(commands,1, clientData,interp,objc,objv); + Tcl_Release(clientData); + + return status; +} + +/*------------------------------------------------------------------------ + * +++ Widget destruction. + * + * A widget can be destroyed when the application explicitly + * destroys the window or one of its ancestors via [destroy] + * or Tk_DestroyWindow(); when the application deletes the widget + * instance command; when there is an error in the widget constructor; + * or when another application calls XDestroyWindow on the window ID. + * + * The window receives a <DestroyNotify> event in all cases, + * so we do the bulk of the cleanup there. See [#2207435] for + * further notes (esp. re: Tk_FreeConfigOptions). + * + * Widget code that reenters the interp should only do so + * when the widtget is Tcl_Preserve()d, and should check + * the WIDGET_DESTROYED flag bit upon return. + */ + +/* WidgetInstanceObjCmdDeleted -- + * Widget instance command deletion callback. + */ +static void +WidgetInstanceObjCmdDeleted(ClientData clientData) +{ + WidgetCore *corePtr = clientData; + corePtr->widgetCmd = NULL; + if (corePtr->tkwin != NULL) + Tk_DestroyWindow(corePtr->tkwin); +} + +/* FreeWidget -- + * Final cleanup for widget; called via Tcl_EventuallyFree(). + */ +static void +FreeWidget(char *memPtr) +{ + ckfree(memPtr); +} + +/* DestroyWidget -- + * Main widget destructor; called from <DestroyNotify> event handler. + */ +static void +DestroyWidget(WidgetCore *corePtr) +{ + corePtr->flags |= WIDGET_DESTROYED; + + corePtr->widgetSpec->cleanupProc(corePtr); + + Tk_FreeConfigOptions( + (ClientData)corePtr, corePtr->optionTable, corePtr->tkwin); + + if (corePtr->layout) { + Ttk_FreeLayout(corePtr->layout); + } + + if (corePtr->flags & REDISPLAY_PENDING) { + Tcl_CancelIdleCall(DrawWidget, corePtr); + } + + corePtr->tkwin = NULL; + if (corePtr->widgetCmd) { + Tcl_Command cmd = corePtr->widgetCmd; + corePtr->widgetCmd = 0; + /* NB: this can reenter the interpreter via a command traces */ + Tcl_DeleteCommandFromToken(corePtr->interp, cmd); + } + Tcl_EventuallyFree(corePtr, FreeWidget); +} + +/* + * CoreEventProc -- + * Event handler for basic events. + * Processes Expose, Configure, FocusIn/Out, and Destroy events. + * Also handles <<ThemeChanged>> virtual events. + * + * For Expose and Configure, simply schedule the widget for redisplay. + * For Destroy events, handle the cleanup process. + * + * For Focus events, set/clear the focus bit in the state field. + * It turns out this is impossible to do correctly in a binding script, + * because Tk filters out focus events with detail == NotifyInferior. + * + * For Deactivate/Activate pseudo-events, set/clear the background state + * flag. + */ + +static const unsigned CoreEventMask + = ExposureMask + | StructureNotifyMask + | FocusChangeMask + | VirtualEventMask + | ActivateMask + | EnterWindowMask + | LeaveWindowMask + ; + +static void CoreEventProc(ClientData clientData, XEvent *eventPtr) +{ + WidgetCore *corePtr = clientData; + + switch (eventPtr->type) + { + case ConfigureNotify : + TtkRedisplayWidget(corePtr); + break; + case Expose : + if (eventPtr->xexpose.count == 0) { + TtkRedisplayWidget(corePtr); + } + break; + case DestroyNotify : + Tk_DeleteEventHandler( + corePtr->tkwin, CoreEventMask,CoreEventProc,clientData); + DestroyWidget(corePtr); + break; + case FocusIn: + case FocusOut: + /* Don't process "virtual crossing" events */ + if ( eventPtr->xfocus.detail == NotifyInferior + || eventPtr->xfocus.detail == NotifyAncestor + || eventPtr->xfocus.detail == NotifyNonlinear) + { + if (eventPtr->type == FocusIn) + corePtr->state |= TTK_STATE_FOCUS; + else + corePtr->state &= ~TTK_STATE_FOCUS; + TtkRedisplayWidget(corePtr); + } + break; + case ActivateNotify: + corePtr->state &= ~TTK_STATE_BACKGROUND; + TtkRedisplayWidget(corePtr); + break; + case DeactivateNotify: + corePtr->state |= TTK_STATE_BACKGROUND; + TtkRedisplayWidget(corePtr); + break; + case LeaveNotify: + corePtr->state &= ~TTK_STATE_HOVER; + TtkRedisplayWidget(corePtr); + break; + case EnterNotify: + corePtr->state |= TTK_STATE_HOVER; + TtkRedisplayWidget(corePtr); + break; + case VirtualEvent: + if (!strcmp("ThemeChanged", ((XVirtualEvent *)(eventPtr))->name)) { + (void)UpdateLayout(corePtr->interp, corePtr); + SizeChanged(corePtr); + TtkRedisplayWidget(corePtr); + } + default: + /* can't happen... */ + break; + } +} + +/* + * WidgetWorldChanged -- + * Default Tk_ClassWorldChangedProc() for widgets. + * Invoked whenever fonts or other system resources are changed; + * recomputes geometry. + */ +static void WidgetWorldChanged(ClientData clientData) +{ + WidgetCore *corePtr = clientData; + SizeChanged(corePtr); + TtkRedisplayWidget(corePtr); +} + +static Tk_ClassProcs widgetClassProcs = { + sizeof(Tk_ClassProcs), /* size */ + WidgetWorldChanged, /* worldChangedProc */ + NULL, /* createProc */ + NULL /* modalProc */ +}; + +/* + * TtkWidgetConstructorObjCmd -- + * General-purpose widget constructor command implementation. + * ClientData is a WidgetSpec *. + */ +int TtkWidgetConstructorObjCmd( + ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetSpec *widgetSpec = clientData; + const char *className = widgetSpec->className; + Tk_OptionTable optionTable = + Tk_CreateOptionTable(interp, widgetSpec->optionSpecs); + Tk_Window tkwin; + void *recordPtr; + WidgetCore *corePtr; + Tk_SavedOptions savedOptions; + int i; + + if (objc < 2 || objc % 2 == 1) { + Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?"); + return TCL_ERROR; + } + + /* Check if a -class option has been specified. + * We have to do this before the InitOptions() call, + * since InitOptions() is affected by the widget class. + */ + for (i = 2; i < objc; i += 2) { + if (!strcmp(Tcl_GetString(objv[i]), "-class")) { + className = Tcl_GetString(objv[i+1]); + break; + } + } + + tkwin = Tk_CreateWindowFromPath( + interp, Tk_MainWindow(interp), Tcl_GetString(objv[1]), NULL); + if (tkwin == NULL) + return TCL_ERROR; + + /* + * Allocate and initialize the widget record. + */ + recordPtr = ckalloc(widgetSpec->recordSize); + memset(recordPtr, 0, widgetSpec->recordSize); + corePtr = recordPtr; + + corePtr->tkwin = tkwin; + corePtr->interp = interp; + corePtr->widgetSpec = widgetSpec; + corePtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(tkwin), + WidgetInstanceObjCmd, recordPtr, WidgetInstanceObjCmdDeleted); + corePtr->optionTable = optionTable; + corePtr->layout = NULL; + corePtr->flags = 0; + corePtr->state = 0; + + Tk_SetClass(tkwin, className); + Tk_SetClassProcs(tkwin, &widgetClassProcs, recordPtr); + Tk_SetWindowBackgroundPixmap(tkwin, ParentRelative); + + widgetSpec->initializeProc(interp, recordPtr); + + Tk_CreateEventHandler(tkwin, CoreEventMask, CoreEventProc, recordPtr); + + /* + * Initial configuration. + */ + + Tcl_Preserve(corePtr); + if (Tk_InitOptions(interp, recordPtr, optionTable, tkwin) != TCL_OK) { + goto error; + } + + if (Tk_SetOptions(interp, recordPtr, optionTable, + objc - 2, objv + 2, tkwin, &savedOptions, NULL) != TCL_OK) { + Tk_RestoreSavedOptions(&savedOptions); + goto error; + } else { + Tk_FreeSavedOptions(&savedOptions); + } + if (widgetSpec->configureProc(interp, recordPtr, ~0) != TCL_OK) + goto error; + if (widgetSpec->postConfigureProc(interp, recordPtr, ~0) != TCL_OK) + goto error; + + if (WidgetDestroyed(corePtr)) + goto error; + + Tcl_Release(corePtr); + + SizeChanged(corePtr); + Tk_MakeWindowExist(tkwin); + + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(tkwin), -1)); + return TCL_OK; + +error: + if (WidgetDestroyed(corePtr)) { + Tcl_SetResult(interp, "Widget has been destroyed", TCL_STATIC); + } else { + Tk_DestroyWindow(tkwin); + } + Tcl_Release(corePtr); + return TCL_ERROR; +} + +/*------------------------------------------------------------------------ + * +++ Default implementations for widget hook procedures. + */ + +/* TtkWidgetGetLayout -- + * Default getLayoutProc. + * Looks up the layout based on the -style resource (if specified), + * otherwise use the widget class. + */ +Ttk_Layout TtkWidgetGetLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) +{ + WidgetCore *corePtr = recordPtr; + const char *styleName = 0; + + if (corePtr->styleObj) + styleName = Tcl_GetString(corePtr->styleObj); + + if (!styleName || *styleName == '\0') + styleName = corePtr->widgetSpec->className; + + return Ttk_CreateLayout(interp, themePtr, styleName, + recordPtr, corePtr->optionTable, corePtr->tkwin); +} + +/* + * TtkWidgetGetOrientedLayout -- + * Helper routine. Same as TtkWidgetGetLayout, but prefixes + * "Horizontal." or "Vertical." to the style name, depending + * on the value of the 'orient' option. + */ +Ttk_Layout TtkWidgetGetOrientedLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr, Tcl_Obj *orientObj) +{ + WidgetCore *corePtr = recordPtr; + const char *baseStyleName = 0; + Tcl_DString styleName; + int orient = TTK_ORIENT_HORIZONTAL; + Ttk_Layout layout; + + Tcl_DStringInit(&styleName); + + /* Prefix: + */ + Ttk_GetOrientFromObj(NULL, orientObj, &orient); + if (orient == TTK_ORIENT_HORIZONTAL) + Tcl_DStringAppend(&styleName, "Horizontal.", -1); + else + Tcl_DStringAppend(&styleName, "Vertical.", -1); + + /* Add base style name: + */ + if (corePtr->styleObj) + baseStyleName = Tcl_GetString(corePtr->styleObj); + if (!baseStyleName || *baseStyleName == '\0') + baseStyleName = corePtr->widgetSpec->className; + + Tcl_DStringAppend(&styleName, baseStyleName, -1); + + /* Create layout: + */ + layout= Ttk_CreateLayout(interp, themePtr, Tcl_DStringValue(&styleName), + recordPtr, corePtr->optionTable, corePtr->tkwin); + + Tcl_DStringFree(&styleName); + + return layout; +} + +/* TtkNullInitialize -- + * Default widget initializeProc (no-op) + */ +void TtkNullInitialize(Tcl_Interp *interp, void *recordPtr) +{ +} + +/* TtkNullPostConfigure -- + * Default widget postConfigureProc (no-op) + */ +int TtkNullPostConfigure(Tcl_Interp *interp, void *clientData, int mask) +{ + return TCL_OK; +} + +/* TtkCoreConfigure -- + * Default widget configureProc. + * Handles -style option. + */ +int TtkCoreConfigure(Tcl_Interp *interp, void *clientData, int mask) +{ + WidgetCore *corePtr = clientData; + int status = TCL_OK; + + if (mask & STYLE_CHANGED) { + status = UpdateLayout(interp, corePtr); + } + + return status; +} + +/* TtkNullCleanup -- + * Default widget cleanupProc (no-op) + */ +void TtkNullCleanup(void *recordPtr) +{ + return; +} + +/* TtkWidgetDoLayout -- + * Default widget layoutProc. + */ +void TtkWidgetDoLayout(void *clientData) +{ + WidgetCore *corePtr = clientData; + Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); +} + +/* TtkWidgetDisplay -- + * Default widget displayProc. + */ +void TtkWidgetDisplay(void *recordPtr, Drawable d) +{ + WidgetCore *corePtr = recordPtr; + Ttk_DrawLayout(corePtr->layout, corePtr->state, d); +} + +/* TtkWidgetSize -- + * Default widget sizeProc() + */ +int TtkWidgetSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + WidgetCore *corePtr = recordPtr; + Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr); + return 1; +} + +/*------------------------------------------------------------------------ + * +++ Default implementations for widget subcommands. + */ + +/* $w cget -option + */ +int TtkWidgetCgetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = recordPtr; + Tcl_Obj *result; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option"); + return TCL_ERROR; + } + result = Tk_GetOptionValue(interp, recordPtr, + corePtr->optionTable, objv[2], corePtr->tkwin); + if (result == NULL) + return TCL_ERROR; + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* $w configure ?-option ?value ....?? + */ +int TtkWidgetConfigureCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = recordPtr; + Tcl_Obj *result; + + if (objc == 2) { + result = Tk_GetOptionInfo(interp, recordPtr, + corePtr->optionTable, NULL, corePtr->tkwin); + } else if (objc == 3) { + result = Tk_GetOptionInfo(interp, recordPtr, + corePtr->optionTable, objv[2], corePtr->tkwin); + } else { + Tk_SavedOptions savedOptions; + int status; + int mask = 0; + + status = Tk_SetOptions(interp, recordPtr, + corePtr->optionTable, objc - 2, objv + 2, + corePtr->tkwin, &savedOptions, &mask); + if (status != TCL_OK) + return status; + + if (mask & READONLY_OPTION) { + Tcl_SetResult(interp, + "Attempt to change read-only option", TCL_STATIC); + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; + } + + status = corePtr->widgetSpec->configureProc(interp, recordPtr, mask); + if (status != TCL_OK) { + Tk_RestoreSavedOptions(&savedOptions); + return status; + } + Tk_FreeSavedOptions(&savedOptions); + + status = corePtr->widgetSpec->postConfigureProc(interp,recordPtr,mask); + if (WidgetDestroyed(corePtr)) { + Tcl_SetResult(interp, "Widget has been destroyed", TCL_STATIC); + status = TCL_ERROR; + } + if (status != TCL_OK) { + return status; + } + + if (mask & (STYLE_CHANGED | GEOMETRY_CHANGED)) { + SizeChanged(corePtr); + } + + TtkRedisplayWidget(corePtr); + result = Tcl_NewObj(); + } + + if (result == 0) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + +/* $w state ? $stateSpec ? + * + * If $stateSpec is specified, modify the widget state accordingly, + * return a new stateSpec representing the changed bits. + * + * Otherwise, return a statespec matching all the currently-set bits. + */ + +int TtkWidgetStateCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = recordPtr; + Ttk_StateSpec spec; + int status; + Ttk_State oldState, changed; + + if (objc == 2) { + Tcl_SetObjResult(interp, + Ttk_NewStateSpecObj(corePtr->state, 0ul)); + return TCL_OK; + } + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "state-spec"); + return TCL_ERROR; + } + status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec); + if (status != TCL_OK) + return status; + + oldState = corePtr->state; + corePtr->state = Ttk_ModifyState(corePtr->state, &spec); + changed = corePtr->state ^ oldState; + + TtkRedisplayWidget(corePtr); + + Tcl_SetObjResult(interp, + Ttk_NewStateSpecObj(oldState & changed, ~oldState & changed)); + return status; +} + +/* $w instate $stateSpec ?$script? + * + * Tests if widget state matches $stateSpec. + * If $script is specified, execute script if state matches. + * Otherwise, return true/false + */ + +int TtkWidgetInstateCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = recordPtr; + Ttk_State state = corePtr->state; + Ttk_StateSpec spec; + int status = TCL_OK; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2, objv, "state-spec ?script?"); + return TCL_ERROR; + } + status = Ttk_GetStateSpecFromObj(interp, objv[2], &spec); + if (status != TCL_OK) + return status; + + if (objc == 3) { + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(Ttk_StateMatches(state,&spec))); + } else if (objc == 4) { + if (Ttk_StateMatches(state,&spec)) { + status = Tcl_EvalObjEx(interp, objv[3], 0); + } + } + return status; +} + +/* $w identify $x $y + * $w identify element $x $y + * Returns: name of element at $x, $y + */ +int TtkWidgetIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + WidgetCore *corePtr = recordPtr; + Ttk_Element element; + static const char *whatTable[] = { "element", NULL }; + int x, y, what; + + if (objc < 4 || objc > 5) { + Tcl_WrongNumArgs(interp, 2, objv, "?what? x y"); + return TCL_ERROR; + } + if (objc == 5) { + /* $w identify element $x $y */ + if (Tcl_GetIndexFromObj(interp,objv[2],whatTable,"option",0,&what) + != TCL_OK) + { + return TCL_ERROR; + } + } + + if ( Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK + ) { + return TCL_ERROR; + } + + element = Ttk_IdentifyElement(corePtr->layout, x, y); + if (element) { + const char *elementName = Ttk_ElementName(element); + Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1)); + } + + return TCL_OK; +} + +/*EOF*/ diff --git a/generic/ttk/ttkWidget.h b/generic/ttk/ttkWidget.h new file mode 100644 index 0000000..9e9ab69 --- /dev/null +++ b/generic/ttk/ttkWidget.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2003, Joe English + * Helper routines for widget implementations. + */ + +#ifndef _TTKWIDGET +#define _TTKWIDGET + +/* + * State flags for 'flags' field. + */ +#define WIDGET_DESTROYED 0x0001 +#define REDISPLAY_PENDING 0x0002 /* scheduled call to RedisplayWidget */ +#define CURSOR_ON 0x0020 /* See TtkBlinkCursor() */ +#define WIDGET_USER_FLAG 0x0100 /* 0x0100 - 0x8000 for user flags */ + +/* + * Bit fields for OptionSpec 'mask' field: + */ +#define READONLY_OPTION 0x1 +#define STYLE_CHANGED 0x2 +#define GEOMETRY_CHANGED 0x4 + +/* + * Core widget elements + */ +typedef struct WidgetSpec_ WidgetSpec; /* Forward */ + +typedef struct +{ + Tk_Window tkwin; /* Window associated with widget */ + Tcl_Interp *interp; /* Interpreter associated with widget. */ + WidgetSpec *widgetSpec; /* Widget class hooks */ + Tcl_Command widgetCmd; /* Token for widget command. */ + Tk_OptionTable optionTable; /* Option table */ + Ttk_Layout layout; /* Widget layout */ + + /* + * Storage for resources: + */ + Tcl_Obj *takeFocusPtr; /* Storage for -takefocus option */ + Tcl_Obj *cursorObj; /* Storage for -cursor option */ + Tcl_Obj *styleObj; /* Name of currently-applied style */ + Tcl_Obj *classObj; /* Class name (readonly option) */ + + Ttk_State state; /* Current widget state */ + unsigned int flags; /* internal flags, see above */ + +} WidgetCore; + +/* + * Widget specifications: + */ +struct WidgetSpec_ +{ + const char *className; /* Widget class name */ + size_t recordSize; /* #bytes in widget record */ + const Tk_OptionSpec *optionSpecs; /* Option specifications */ + const Ttk_Ensemble *commands; /* Widget instance subcommands */ + + /* + * Hooks: + */ + void (*initializeProc)(Tcl_Interp *, void *recordPtr); + void (*cleanupProc)(void *recordPtr); + int (*configureProc)(Tcl_Interp *, void *recordPtr, int flags); + int (*postConfigureProc)(Tcl_Interp *, void *recordPtr, int flags); + Ttk_Layout (*getLayoutProc)(Tcl_Interp *,Ttk_Theme, void *recordPtr); + int (*sizeProc)(void *recordPtr, int *widthPtr, int *heightPtr); + void (*layoutProc)(void *recordPtr); + void (*displayProc)(void *recordPtr, Drawable d); +}; + +/* + * Common factors for widget implementations: + */ +MODULE_SCOPE void TtkNullInitialize(Tcl_Interp *, void *); +MODULE_SCOPE int TtkNullPostConfigure(Tcl_Interp *, void *, int); +MODULE_SCOPE void TtkNullCleanup(void *recordPtr); +MODULE_SCOPE Ttk_Layout TtkWidgetGetLayout( + Tcl_Interp *, Ttk_Theme, void *recordPtr); +MODULE_SCOPE Ttk_Layout TtkWidgetGetOrientedLayout( + Tcl_Interp *, Ttk_Theme, void *recordPtr, Tcl_Obj *orientObj); +MODULE_SCOPE int TtkWidgetSize(void *recordPtr, int *w, int *h); +MODULE_SCOPE void TtkWidgetDoLayout(void *recordPtr); +MODULE_SCOPE void TtkWidgetDisplay(void *recordPtr, Drawable); + +MODULE_SCOPE int TtkCoreConfigure(Tcl_Interp*, void *, int mask); + +/* Common widget commands: + */ +MODULE_SCOPE int TtkWidgetConfigureCommand( + void *,Tcl_Interp *, int, Tcl_Obj*const[]); +MODULE_SCOPE int TtkWidgetCgetCommand( + void *,Tcl_Interp *, int, Tcl_Obj*const[]); +MODULE_SCOPE int TtkWidgetInstateCommand( + void *,Tcl_Interp *, int, Tcl_Obj*const[]); +MODULE_SCOPE int TtkWidgetStateCommand( + void *,Tcl_Interp *, int, Tcl_Obj*const[]); +MODULE_SCOPE int TtkWidgetIdentifyCommand( + void *,Tcl_Interp *, int, Tcl_Obj*const[]); + +/* Widget constructor: + */ +MODULE_SCOPE int TtkWidgetConstructorObjCmd( + ClientData, Tcl_Interp*, int, Tcl_Obj*const[]); + +#define RegisterWidget(interp, name, specPtr) \ + Tcl_CreateObjCommand(interp, name, \ + TtkWidgetConstructorObjCmd, (ClientData)specPtr,NULL) + +/* WIDGET_TAKEFOCUS_TRUE -- + * WIDGET_TAKEFOCUS_FALSE -- + * Add one or the other of these to each OptionSpecs table + * to indicate whether the widget should take focus + * during keyboard traversal. + */ +#define WIDGET_TAKEFOCUS_TRUE \ + {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", \ + "ttk::takefocus", Tk_Offset(WidgetCore, takeFocusPtr), -1, 0,0,0 } +#define WIDGET_TAKEFOCUS_FALSE \ + {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", \ + "", Tk_Offset(WidgetCore, takeFocusPtr), -1, 0,0,0 } + +/* WIDGET_INHERIT_OPTIONS(baseOptionSpecs) -- + * Add this at the end of an OptionSpecs table to inherit + * the options from 'baseOptionSpecs'. + */ +#define WIDGET_INHERIT_OPTIONS(baseOptionSpecs) \ + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0, (ClientData)baseOptionSpecs, 0} + +/* All widgets should inherit from ttkCoreOptionSpecs[]. + */ +MODULE_SCOPE Tk_OptionSpec ttkCoreOptionSpecs[]; + +/* + * Useful routines for use inside widget implementations: + */ +/* extern int WidgetDestroyed(WidgetCore *); */ +#define WidgetDestroyed(corePtr) ((corePtr)->flags & WIDGET_DESTROYED) + +MODULE_SCOPE void TtkWidgetChangeState(WidgetCore *, + unsigned int setBits, unsigned int clearBits); + +MODULE_SCOPE void TtkRedisplayWidget(WidgetCore *); +MODULE_SCOPE void TtkResizeWidget(WidgetCore *); + +MODULE_SCOPE void TtkTrackElementState(WidgetCore *); +MODULE_SCOPE void TtkBlinkCursor(WidgetCore *); + +/* + * -state option values (compatibility) + */ +MODULE_SCOPE void TtkCheckStateOption(WidgetCore *, Tcl_Obj *); + +/* + * Variable traces: + */ +typedef void (*Ttk_TraceProc)(void *recordPtr, const char *value); +typedef struct TtkTraceHandle_ Ttk_TraceHandle; + +MODULE_SCOPE Ttk_TraceHandle *Ttk_TraceVariable( + Tcl_Interp*, Tcl_Obj *varnameObj, Ttk_TraceProc callback, void *clientData); +MODULE_SCOPE void Ttk_UntraceVariable(Ttk_TraceHandle *); +MODULE_SCOPE int Ttk_FireTrace(Ttk_TraceHandle *); + +/* + * Virtual events: + */ +MODULE_SCOPE void TtkSendVirtualEvent(Tk_Window tgtWin, const char *eventName); + +/* + * Helper routines for data accessor commands: + */ +MODULE_SCOPE int TtkEnumerateOptions( + Tcl_Interp *, void *, const Tk_OptionSpec *, Tk_OptionTable, Tk_Window); +MODULE_SCOPE int TtkGetOptionValue( + Tcl_Interp *, void *, Tcl_Obj *optName, Tk_OptionTable, Tk_Window); + +/* + * Helper routines for scrolling widgets (see scroll.c). + */ +typedef struct { + int first; /* First visible item */ + int last; /* Last visible item */ + int total; /* Total #items */ + char *scrollCmd; /* Widget option */ +} Scrollable; + +typedef struct ScrollHandleRec *ScrollHandle; + +MODULE_SCOPE ScrollHandle TtkCreateScrollHandle(WidgetCore *, Scrollable *); +MODULE_SCOPE void TtkFreeScrollHandle(ScrollHandle); + +MODULE_SCOPE int TtkScrollviewCommand( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle); + +MODULE_SCOPE void TtkScrollTo(ScrollHandle, int newFirst); +MODULE_SCOPE void TtkScrolled(ScrollHandle, int first, int last, int total); +MODULE_SCOPE void TtkScrollbarUpdateRequired(ScrollHandle); + +/* + * Tag sets (work in progress, half-baked) + */ + +typedef struct TtkTag *Ttk_Tag; +typedef struct TtkTagTable *Ttk_TagTable; +typedef struct TtkTagSet { /* TODO: make opaque */ + Ttk_Tag *tags; + int nTags; +} *Ttk_TagSet; + +MODULE_SCOPE Ttk_TagTable Ttk_CreateTagTable( + Tcl_Interp *, Tk_Window tkwin, Tk_OptionSpec[], int recordSize); +MODULE_SCOPE void Ttk_DeleteTagTable(Ttk_TagTable); + +MODULE_SCOPE Ttk_Tag Ttk_GetTag(Ttk_TagTable, const char *tagName); +MODULE_SCOPE Ttk_Tag Ttk_GetTagFromObj(Ttk_TagTable, Tcl_Obj *); + +MODULE_SCOPE Tcl_Obj *Ttk_TagOptionValue( + Tcl_Interp *, Ttk_TagTable, Ttk_Tag, Tcl_Obj *optionName); + +MODULE_SCOPE int Ttk_EnumerateTagOptions( + Tcl_Interp *, Ttk_TagTable, Ttk_Tag); + +MODULE_SCOPE int Ttk_EnumerateTags(Tcl_Interp *, Ttk_TagTable); + +MODULE_SCOPE int Ttk_ConfigureTag( + Tcl_Interp *interp, Ttk_TagTable tagTable, Ttk_Tag tag, + int objc, Tcl_Obj *const objv[]); + +MODULE_SCOPE Ttk_TagSet Ttk_GetTagSetFromObj( + Tcl_Interp *interp, Ttk_TagTable, Tcl_Obj *objPtr); +MODULE_SCOPE Tcl_Obj *Ttk_NewTagSetObj(Ttk_TagSet); + +MODULE_SCOPE void Ttk_FreeTagSet(Ttk_TagSet); + +MODULE_SCOPE int Ttk_TagSetContains(Ttk_TagSet, Ttk_Tag tag); +MODULE_SCOPE int Ttk_TagSetAdd(Ttk_TagSet, Ttk_Tag tag); +MODULE_SCOPE int Ttk_TagSetRemove(Ttk_TagSet, Ttk_Tag tag); + +MODULE_SCOPE void Ttk_TagSetValues(Ttk_TagTable, Ttk_TagSet, void *record); +MODULE_SCOPE void Ttk_TagSetApplyStyle(Ttk_TagTable,Ttk_Style,Ttk_State,void*); + +/* + * String tables for widget resource specifications: + */ + +MODULE_SCOPE const char *ttkOrientStrings[]; +MODULE_SCOPE const char *ttkCompoundStrings[]; +MODULE_SCOPE const char *ttkDefaultStrings[]; + +/* + * ... other option types... + */ +MODULE_SCOPE int TtkGetLabelAnchorFromObj( + Tcl_Interp*, Tcl_Obj*, Ttk_PositionSpec *); + +/* + * Platform-specific initialization. + */ + +#if defined(__WIN32__) +#define Ttk_PlatformInit Ttk_WinPlatformInit +MODULE_SCOPE int Ttk_PlatformInit(Tcl_Interp *); +#elif defined(MAC_OSX_TK) +#define Ttk_PlatformInit Ttk_MacOSXPlatformInit +MODULE_SCOPE int Ttk_PlatformInit(Tcl_Interp *); +#else +#define Ttk_PlatformInit(interp) /* TTK_X11PlatformInit() */ +#endif + +#endif /* _TTKWIDGET */ |