diff options
Diffstat (limited to 'generic/ttk/ttkNotebook.c')
-rw-r--r-- | generic/ttk/ttkNotebook.c | 1413 |
1 files changed, 1413 insertions, 0 deletions
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*/ |