/* $Id: notebook.c,v 1.67 2005/08/17 22:49:14 jenglish Exp $
* Copyright (c) 2004, Joe English
*
* TODO: Possibly: track <Map>, <Unmap> events on the master, map/unmap slaves.
*
* 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 "tkTheme.h"
#include "widget.h"
#include "manager.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 *stickyObj;
Ttk_Sticky sticky;
/* Label options:
*/
Tcl_Obj *textObj;
Tcl_Obj *imageObj;
Tcl_Obj *compoundObj;
Tcl_Obj *underlineObj;
} Tab;
static Tk_OptionSpec TabOptionSpecs[] =
{
{TK_OPTION_STRING_TABLE, "-state", "", "",
"normal", -1,Tk_Offset(Tab,state),
0,(ClientData)TabStateStrings,0 },
{TK_OPTION_STRING, "-sticky", "sticky", "Sticky", "nsew",
Tk_Offset(Tab,stickyObj), -1, 0,0,GEOMETRY_CHANGED },
{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}
};
/*------------------------------------------------------------------------
* +++ Notebook resources.
*/
typedef struct
{
Tcl_Obj *widthObj; /* Default width */
Tcl_Obj *heightObj; /* Default height */
Tcl_Obj *paddingObj; /* Internal padding */
Manager *mgr; /* Geometry manager */
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_Padding padding; /* External padding */
Ttk_Padding expandTab; /* Amount to expand selected tab */
int minTabWidth; /* Minimum tab width */
} NotebookStyle;
static void NotebookStyleOptions(Notebook *nb, NotebookStyle *nbstyle)
{
Tcl_Obj *objPtr;
nbstyle->expandTab = Ttk_UniformPadding(0);
if ((objPtr = Ttk_QueryOption(nb->core.layout, "-expandtab", 0)) != 0) {
Ttk_GetPaddingFromObj(NULL,nb->core.tkwin,objPtr,&nbstyle->expandTab);
}
nbstyle->padding = Ttk_UniformPadding(0);
if (nb->notebook.paddingObj) {
Ttk_GetPaddingFromObj(
NULL,nb->core.tkwin,nb->notebook.paddingObj,&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 < nb->notebook.mgr->nSlaves; ++index) {
Tab *tab = nb->notebook.mgr->slaves[index]->slaveData;
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;
WidgetChanged(&nb->core, REDISPLAY_REQUIRED);
}
}
/*
* 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 = nb->notebook.mgr->slaves[index]->slaveData;
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 == nb->notebook.mgr->nSlaves - 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.
*
* Side effects:
* Sets width and height fields for all tabs.
*
* Notes:
* Hidden tabs are included in the height computation, but not width.
*/
static void TabrowSize(Notebook *nb, int *widthPtr, int *heightPtr)
{
Ttk_Layout tabLayout = nb->notebook.tabLayout;
int tabrowWidth = 0, tabrowHeight = 0;
int i;
for (i = 0; i<nb->notebook.mgr->nSlaves; ++i) {
Tab *tab = nb->notebook.mgr->slaves[i]->slaveData;
Ttk_State tabState = TabState(nb,i);
Ttk_RebindSublayout(tabLayout, tab);
Ttk_LayoutSize(tabLayout,tabState,&tab->width,&tab->height);
tabrowHeight = MAX(tabrowHeight, tab->height);
if (tab->state != TAB_STATE_HIDDEN) {
tabrowWidth += tab->width;
}
}
*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 < nb->notebook.mgr->nSlaves; ++i) {
Tk_Window slaveWindow = nb->notebook.mgr->slaves[i]->slaveWindow;
clientWidth = MAX(clientWidth, Tk_ReqWidth(slaveWindow));
clientHeight = MAX(clientHeight, Tk_ReqHeight(slaveWindow));
}
/* 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, &tabrowWidth, &tabrowHeight);
tabrowHeight += nbstyle.expandTab.top;
/* 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.
*/
static void SqueezeTabs(
Notebook *nb, int desiredWidth, int availableWidth, int minTabWidth)
{
int nTabs = nb->notebook.mgr->nSlaves;
int shrinkage = desiredWidth - availableWidth;
int extra = 0;
int i;
for (i = 0; i < nTabs; ++i) {
Tab *tab = nb->notebook.mgr->slaves[i]->slaveData;
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;
}
}
/* 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;
int i;
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:
* Allow extra space on the top, left, and right (only)
* to account for expandTab.
*/
TabrowSize(nb, &tabrowWidth, &tabrowHeight);
tabrowBox = Ttk_PackBox(&cavity,
tabrowWidth, tabrowHeight+nbstyle.expandTab.top, TTK_SIDE_TOP);
tabrowBox.x += nbstyle.expandTab.left;
tabrowBox.width -= Ttk_PaddingWidth(nbstyle.expandTab);
if (tabrowWidth > tabrowBox.width) {
SqueezeTabs(nb, tabrowWidth, tabrowBox.width, nbstyle.minTabWidth);
}
for (i = 0; i<nb->notebook.mgr->nSlaves; ++i) {
Tab *tab = nb->notebook.mgr->slaves[i]->slaveData;
if (tab->state != TAB_STATE_HIDDEN) {
tab->parcel = Ttk_PlaceBox(&tabrowBox,
tab->width, tab->height, TTK_SIDE_LEFT, TTK_STICK_S);
if (TabState(nb, i) & TTK_STATE_SELECTED) {
tab->parcel = Ttk_ExpandBox(tab->parcel, nbstyle.expandTab);
}
}
}
/* 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's -sticky option:
*/
static void NotebookPlaceSlave(Notebook *nb, Slave *slave)
{
Tab *tab = slave->slaveData;
int slaveWidth = Tk_ReqWidth(slave->slaveWindow);
int slaveHeight = Tk_ReqHeight(slave->slaveWindow);
Ttk_Box slaveBox = Ttk_StickBox(
nb->notebook.clientArea, slaveWidth, slaveHeight, tab->sticky);
Tk_MoveResizeWindow(slave->slaveWindow,
slaveBox.x, slaveBox.y, slaveBox.width, slaveBox.height);
}
/* NotebookSlaveLayout --
* Geometry manager hook.
*/
static void NotebookSlaveLayout(void *recordPtr)
{
Notebook *nb = recordPtr;
int currentIndex = nb->notebook.currentIndex;
if (currentIndex >= 0) {
NotebookDoLayout(nb);
NotebookPlaceSlave(nb, nb->notebook.mgr->slaves[currentIndex]);
}
}
/*
* SelectTab(nb, index) --
* Change the currently-selected tab.
*/
static void SelectTab(Notebook *nb, int index)
{
Slave *slave = nb->notebook.mgr->slaves[index];
Tab *tab = slave->slaveData;
int currentIndex = nb->notebook.currentIndex;
if (index == currentIndex) {
return;
}
if (tab->state == TAB_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) {
Slave *oldSlave = nb->notebook.mgr->slaves[currentIndex];
Tk_UnmapWindow(oldSlave->slaveWindow);
}
NotebookPlaceSlave(nb, slave);
Tk_MapWindow(slave->slaveWindow);
nb->notebook.currentIndex = index;
WidgetChanged(&nb->core, REDISPLAY_REQUIRED);
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)
{
if (nb->notebook.mgr->nSlaves != 0) {
int nextIndex = index;
Tab *tab;
do {
nextIndex = (nextIndex + 1) % nb->notebook.mgr->nSlaves;
tab = nb->notebook.mgr->slaves[index]->slaveData;
if (tab->state == TAB_STATE_NORMAL) {
return nextIndex;
}
} while (nextIndex != index);
}
return -1;
}
/* SelectNextTab --
* Select the next tab after the specified index that is
* in the normal state. If all other tabs are disabled or hidden,
* does nothing.
*/
static void SelectNextTab(Notebook *nb, int index)
{
int nextIndex = NextTab(nb, index);
if (nextIndex != -1) {
SelectTab(nb, nextIndex);
}
}
/* TabAdded -- GM SlaveAdded hook.
*/
static void TabAdded(Manager *mgr, Slave *slave)
{ /* No-op */ }
/* TabRemoved -- GM SlaveRemoved hook.
*/
static void TabRemoved(Manager *mgr, int index, Slave *slave)
{
Notebook *nb = mgr->managerData;
if (index == nb->notebook.currentIndex) {
nb->notebook.currentIndex = -1;
SelectNextTab(nb, index);
}
if (index < nb->notebook.currentIndex) {
--nb->notebook.currentIndex;
}
WidgetChanged(&nb->core, REDISPLAY_REQUIRED);
}
/* TabConfigured -- GM slaveConfigured hook.
*/
static int TabConfigured(
Tcl_Interp *interp, Manager *mgr, Slave *slave, unsigned mask)
{
Tab *tab = slave->slaveData;
Ttk_Sticky sticky = tab->sticky;
/* Check options:
* @@@ TODO: validate -image option with GetImageList()
*/
if (Ttk_GetStickyFromObj(interp, tab->stickyObj, &sticky) != TCL_OK) {
return TCL_ERROR;
}
tab->sticky = sticky;
return TCL_OK;
}
static ManagerSpec NotebookManagerSpec =
{
{ "notebook", ManagerGeometryRequestProc, ManagerLostSlaveProc },
TabOptionSpecs, sizeof(Tab),
NotebookSize,
NotebookSlaveLayout,
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.
*/
/* GetTabIndex --
* Find the index of the specified tab.
* Tab identifiers are one of:
*
* + positional specifications @x,y,
* + "current",
* + numeric indices [0..nTabs],
* + slave window names
*
* Returns: TCL_OK or TCL_ERROR.
* Stores index of specified tab in *index_rtn, -1 if not found.
* Leaves an error message in interp in case of error.
*/
static int GetTabIndex(
Tcl_Interp *interp, /* Where to leave error messages */
Notebook *nb, /* Notebook widget record */
Tcl_Obj *objPtr, /* Tab name to look up */
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 (GetSlaveFromObj(interp, nb->notebook.mgr, objPtr, index_rtn) != NULL) {
return TCL_OK;
}
/* Nothing matched; GetSlaveFromObj will have left error message.
*/
return TCL_ERROR;
}
/*------------------------------------------------------------------------
* +++ 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;
}
if (!Manageable(interp, slaveWindow, nb->core.tkwin)) {
return TCL_ERROR;
}
/* Make sure slave is not already present:
*/
if (SlaveIndex(nb->notebook.mgr, slaveWindow) != -1) {
Tcl_AppendResult(interp,
Tk_PathName(slaveWindow), " already added",
NULL);
return TCL_ERROR;
}
/* Create and initialize new tab:
*/
if (AddSlave(interp, nb->notebook.mgr, slaveWindow, index, objc-3,objv+3)
!= TCL_OK) {
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);
}
WidgetChanged(&nb->core, REDISPLAY_REQUIRED|RELAYOUT_REQUIRED);
return TCL_OK;
}
/* $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, status;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "tab");
return TCL_ERROR;
}
status = GetTabIndex(interp, nb, objv[2], &index);
if (status == TCL_OK) {
ForgetSlave(nb->notebook.mgr, index);
}
return status;
}
/* $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 = GetTabIndex(interp, nb, objv[2], &index);
if (status == TCL_OK && index >= 0) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
}
return status;
}
/* $nb select $item --
* Selects the specified tab.
*/
static int NotebookSelectCommand(
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;
}
status = GetTabIndex(interp, nb, objv[2], &index);
if (status == TCL_OK) {
SelectTab(nb, index);
}
return status;
}
/* $nb tabs --
* Return list of tabs.
*/
static int NotebookTabsCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
Notebook *nb = recordPtr;
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(mgr->slaves[i]->slaveWindow);
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;
Manager *mgr = nb->notebook.mgr;
Tk_OptionTable tabOptionTable = mgr->slaveOptionTable;
int index;
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 = slave->slaveData;
if (objc == 3) {
return EnumerateOptions(interp, tab, TabOptionSpecs,
tabOptionTable, nb->core.tkwin);
} else if (objc == 4) {
return GetOptionValue(interp, tab, objv[3],
tabOptionTable, nb->core.tkwin);
} /* else */
if (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) {
SelectNextTab(nb, index);
}
WidgetChanged(&nb->core, RELAYOUT_REQUIRED);
return TCL_OK;
}
/* Subcommand table:
*/
static WidgetCommandSpec NotebookCommands[] =
{
{ "add", NotebookAddCommand },
{ "configure", WidgetConfigureCommand },
{ "cget", WidgetCgetCommand },
{ "forget", NotebookForgetCommand },
{ "index", NotebookIndexCommand },
{ "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 = CreateManager(
&NotebookManagerSpec, recordPtr, nb->core.tkwin);
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;
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: */
/* @@@ Make this a CustomOptionType */
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;
Tk_OptionTable tabOptionTable = nb->notebook.mgr->slaveOptionTable;
if (nb->notebook.tabLayout)
Ttk_FreeLayout(nb->notebook.tabLayout);
nb->notebook.tabLayout = Ttk_CreateSubLayout(
interp, theme, "TNotebook.Tab", 0, tabOptionTable, nb->core.tkwin);
return Ttk_CreateLayout(interp, theme, "TNotebook",
recordPtr, nb->core.optionTable, nb->core.tkwin);
}
/* +++ Display routines.
*/
static void DisplayTab(Notebook *nb, int index, Drawable d)
{
Ttk_Layout tabLayout = nb->notebook.tabLayout;
Tab *tab = nb->notebook.mgr->slaves[index]->slaveData;
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*/