1414 lines
38 KiB
C
1414 lines
38 KiB
C
/*
|
|
* 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-3,objv+3) != 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*/
|