diff options
Diffstat (limited to 'generic/ttk/ttkNotebook.c')
-rw-r--r-- | generic/ttk/ttkNotebook.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/generic/ttk/ttkNotebook.c b/generic/ttk/ttkNotebook.c new file mode 100644 index 0000000..e8b6640 --- /dev/null +++ b/generic/ttk/ttkNotebook.c @@ -0,0 +1,1264 @@ +/* $Id: ttkNotebook.c,v 1.1 2006/10/31 01:42:26 hobbs Exp $ + * Copyright (c) 2004, Joe English + * + * NOTE-ACTIVE: activeTabIndex is not always correct (it's + * more trouble than it's worth to track this 100%) + */ + +#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 *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} +}; + +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[] = +{ + WIDGET_TAKES_FOCUS, + + {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_INHERIT_OPTIONS(CoreOptionSpecs) +}; + +/* 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); + } + + /* compute tabPlacement and tabOrient as function of tabPosition: + */ + if (nbstyle->tabPosition & TTK_PACK_LEFT) { + nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_E; + nbstyle->tabOrient = TTK_ORIENT_VERTICAL; + } else if (nbstyle->tabPosition & TTK_PACK_RIGHT) { + nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_W; + nbstyle->tabOrient = TTK_ORIENT_VERTICAL; + } else if (nbstyle->tabPosition & TTK_PACK_BOTTOM) { + nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_N; + nbstyle->tabOrient = TTK_ORIENT_HORIZONTAL; + } else { /* Assume TTK_PACK_TOP */ + nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_S; + nbstyle->tabOrient = TTK_ORIENT_HORIZONTAL; + } + + 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. + */ + +/* + * 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_LayoutNode *clientNode = Ttk_LayoutFindNode(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); + } + + *widthPtr = MAX(tabrowWidth, clientWidth) + Ttk_PaddingWidth(padding); + *heightPtr = tabrowHeight + clientHeight + Ttk_PaddingHeight(padding); + + return 1; +} + +/*------------------------------------------------------------------------ + * +++ Geometry management - layout. + */ + +/* SqueezeTabs -- + * If the notebook is not wide enough to display all tabs, + * attempt to decrease tab widths to fit. + * + * All tabs are shrunk 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 tabs are truncated). + * + * 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 + */ + +static void SqueezeTabs( + Notebook *nb, int desiredWidth, int availableWidth, int minTabWidth) +{ + int nTabs = Ttk_NumberSlaves(nb->notebook.mgr); + int shrinkage = desiredWidth - availableWidth; + int extra = 0; + int i; + + for (i = 0; i < nTabs; ++i) { + Tab *tab = Ttk_SlaveData(nb->notebook.mgr,i); + int shrink = (shrinkage/nTabs) + (i < (shrinkage%nTabs)) + extra; + int shrinkability = MAX(0, tab->width - minTabWidth); + int delta = MIN(shrinkability, shrink); + tab->width -= delta; + extra = shrink - delta; + } +} + +/* 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_LayoutNode *clientNode = Ttk_LayoutFindNode(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); + + if (tabrowWidth > tabrowBox.width) { + SqueezeTabs(nb, tabrowWidth, tabrowBox.width, nbstyle.minTabWidth); + } + PlaceTabs(nb, tabrowBox, nbstyle.tabPlacement); + + /* Layout for client area frame: + */ + if (clientNode) { + Ttk_PlaceLayoutNode(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); + + SendVirtualEvent(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. Select the next available tab; or, if there is none, + * leaves all tabs unselected. + */ +static void SelectNearestTab(Notebook *nb) +{ + int nextIndex = NextTab(nb, nb->notebook.currentIndex); + + if (nextIndex >= 0) { + SelectTab(nb, nextIndex); + } else { + /* No available tabs -- unmap current one. + * ASSERT: this is safe to do even when the slave is being destroyed. + */ + if (nb->notebook.currentIndex >= 0) { + Ttk_UnmapSlave(nb->notebook.mgr, nb->notebook.currentIndex); + SendVirtualEvent(nb->core.tkwin, "NotebookTabChanged"); + } + nb->notebook.currentIndex = -1; + } +} + +/* TabAdded -- GM SlaveAdded hook. + */ +static void TabAdded(Ttk_Manager *mgr, int slaveIndex) { /* No-op */ } + +/* TabRemoved -- GM SlaveRemoved hook. + * Select the next tab if the current one is being removed. + * Adjust currentIndex to account for removed slave if needed. + */ +static void TabRemoved(Ttk_Manager *mgr, int index) +{ + Notebook *nb = mgr->managerData; + + if (index == nb->notebook.currentIndex) { + SelectNearestTab(nb); + } + + if (index < nb->notebook.currentIndex) { + --nb->notebook.currentIndex; + } + + TtkRedisplayWidget(&nb->core); +} + +/* TabConfigured -- GM slaveConfigured hook. + */ +static int TabConfigured( + Tcl_Interp *interp, Ttk_Manager *mgr, Ttk_Slave *slave, unsigned mask) +{ + Tab *tab = slave->slaveData; + Ttk_Sticky sticky = tab->sticky; + Tk_Window tkwin = mgr->masterWindow; + + /* Check options: + * @@@ TODO: validate -image option with GetImageList() + */ + if (Ttk_GetStickyFromObj(interp, tab->stickyObj, &sticky) != TCL_OK) { + return TCL_ERROR; + } + if (Ttk_GetPaddingFromObj(interp,tkwin,tab->paddingObj,&tab->padding) + != TCL_OK) + { + return TCL_ERROR; + } + + tab->sticky = sticky; + return TCL_OK; +} + +static Ttk_ManagerSpec NotebookManagerSpec = +{ + { "notebook", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + PaneOptionSpecs, sizeof(Tab), + + NotebookSize, + NotebookPlaceSlaves, + TabAdded, + TabRemoved, + TabConfigured +}; + +/*------------------------------------------------------------------------ + * +++ 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_GetSlaveFromObj( + interp, nb->notebook.mgr, objPtr, index_rtn) != NULL) + { + return TCL_OK; + } + + /* Nothing matched; Ttk_GetSlaveFromObj 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( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + Notebook *nb = recordPtr; + int index = nb->notebook.mgr->nSlaves; + Tk_Window slaveWindow; + + if (objc <= 2 || objc % 2 != 1) { + Tcl_WrongNumArgs(interp, 2, objv, "window ?options...?"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow(interp,Tcl_GetString(objv[2]),nb->core.tkwin); + if (!slaveWindow) { + return TCL_ERROR; + } + + /* Create and initialize new tab: + */ + if (TCL_OK != Ttk_AddSlave( + interp, nb->notebook.mgr, slaveWindow, index, objc-3,objv+3) ) + { + return TCL_ERROR; + } + + /* If no tab is currently selected (or if this is the first tab), + * select this one: + */ + if (nb->notebook.currentIndex < 0) { + SelectTab(nb, index); + } + + TtkResizeWidget(&nb->core); + + return TCL_OK; +} + +/* $nb insert $index $tab ?options...? + * Insert new tab, or move existing one. + */ +static int NotebookInsertCommand( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], void *recordPtr) +{ + Notebook *nb = recordPtr; + int current = nb->notebook.currentIndex; + int srcIndex, destIndex; + int status = TCL_OK; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index slave ?options...?"); + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(objv[2]), "end")) { + destIndex = Ttk_NumberSlaves(nb->notebook.mgr); + } else if (!Ttk_GetSlaveFromObj( + interp, nb->notebook.mgr, objv[2], &destIndex)) { + return TCL_ERROR; + } + + if (!Ttk_GetSlaveFromObj(interp, nb->notebook.mgr, objv[3], &srcIndex)) { + /* Try adding new slave: + */ + Tk_Window slaveWindow = + Tk_NameToWindow(interp,Tcl_GetString(objv[3]),nb->core.tkwin); + if (!slaveWindow) { + return TCL_ERROR; + } + + if (Ttk_AddSlave(interp, nb->notebook.mgr, slaveWindow, + destIndex, objc - 4, objv + 4) != TCL_OK) + { + return TCL_ERROR; + } + if (nb->notebook.currentIndex <= destIndex) { + ++nb->notebook.currentIndex; + } + return TCL_OK; + } + + /* else - move existing slave: */ + + if (destIndex >= nb->notebook.mgr->nSlaves) { + destIndex = nb->notebook.mgr->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; + } + + if (objc > 4) { + status = Ttk_ConfigureSlave(interp, nb->notebook.mgr, + nb->notebook.mgr->slaves[destIndex], objc-4,objv+4); + } + + TtkRedisplayWidget(&nb->core); + + return status; +} + +/* $nb forget $item -- + * Removes the selected tab. + */ +static int NotebookForgetCommand( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + 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); + + return TCL_OK; +} + +/* $nb identify $x $y -- + * Returns name of tab element at $x,$y; empty string if none. + */ +static int NotebookIdentifyCommand( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + Notebook *nb = recordPtr; + Ttk_LayoutNode *node = NULL; + int x, y, tabIndex; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[3], &y) != 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); + + node = Ttk_LayoutIdentify(tabLayout, x, y); + } + + if (node) { + const char *elementName = Ttk_LayoutNodeName(node); + Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1)); + } + + 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( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + 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]))) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(nb->notebook.mgr->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( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + 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( + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr) +{ + 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 < mgr->nSlaves; ++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( + Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[], void *recordPtr) +{ + Notebook *nb = recordPtr; + Ttk_Manager *mgr = nb->notebook.mgr; + int index; + Ttk_Slave *slave; + 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; + } + + slave = mgr->slaves[index]; + tab = Ttk_SlaveData(mgr, index); + + if (objc == 3) { + return EnumerateOptions(interp, tab, + PaneOptionSpecs, nb->notebook.paneOptionTable, nb->core.tkwin); + } else if (objc == 4) { + return GetOptionValue(interp, tab, objv[3], + nb->notebook.paneOptionTable, nb->core.tkwin); + } /* else */ + + if (Ttk_ConfigureSlave(interp, mgr, slave, 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); + } + + TtkResizeWidget(&nb->core); + return TCL_OK; +} + +/* Subcommand table: + */ +static WidgetCommandSpec NotebookCommands[] = +{ + { "add", NotebookAddCommand }, + { "configure", WidgetConfigureCommand }, + { "cget", WidgetCgetCommand }, + { "forget", NotebookForgetCommand }, + { "identify", NotebookIdentifyCommand }, + { "index", NotebookIndexCommand }, + { "insert", NotebookInsertCommand }, + { "instate", WidgetInstateCommand }, + { "select", NotebookSelectCommand }, + { "state", WidgetStateCommand }, + { "tab", NotebookTabCommand }, + { "tabs", NotebookTabsCommand }, + { 0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget class hooks. + */ + +static int 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); + + return TCL_OK; +} + +static void NotebookCleanup(void *recordPtr) +{ + Notebook *nb = recordPtr; + + Ttk_DeleteManager(nb->notebook.mgr); + Tk_DeleteOptionTable(nb->notebook.tabOptionTable); + Tk_DeleteOptionTable(nb->notebook.paneOptionTable); + + 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 CoreConfigure(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 = WidgetGetLayout(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 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 < nb->notebook.mgr->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 */ + NullPostConfigure, /* 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 + +int Notebook_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); + + return TCL_OK; +} + +/*EOF*/ |