/* $Id: notebook.c,v 1.36 2004/08/24 21:38:52 patthoyts Exp $
* Copyright (c) 2004, Joe English
*
* TODO: Possibly: track <Map>, <Unmap> events on the master, map/unmap slaves.
*
* NOTE-LOSTSLAVE: For some reason Tk_ManageGeometry(slave, NULL, 0)
* doesn't call our LostSlaveProc(), though it probably should.
* This requires extra care when removing a tab manually.
*
* 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"
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
typedef struct NotebookStruct Notebook; /* forward */
static void NotebookSlaveEventHandler(ClientData, XEvent *); /*forward*/
static Tk_GeomMgr NotebookGeometryManager; /* forward */
/* Resources for individual tabs:
*/
typedef struct
{
Notebook *master; /* manager widget */
TTK_Layout layout; /* Tab display layout */
/*
* Child window options:
*/
Tk_Window slave; /* slave window for this tab */
Tcl_Obj *stickyObj; /* -sticky option */
unsigned sticky;
/*
* Label resources:
*/
Tcl_Obj *textObj;
Tcl_Obj *imageObj;
Tcl_Obj *compoundObj;
Tcl_Obj *underlineObj;
} Tab;
static Tk_OptionSpec TabOptionSpecs[] =
{
{TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
"none", Tk_Offset(Tab,compoundObj), -1,
0,(ClientData)TTKCompoundStrings,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, "-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_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 */
int nTabs; /* #tabs */
Tab **tabs; /* array of tabs */
int currentIndex; /* index of currently selected tab */
int activeIndex; /* index of currently active tab */
Tk_OptionTable tabOptionTable;
TTK_Layout clientLayout; /* Display for client area */
TTK_Box clientArea; /* Where to pack slave widgets */
} NotebookPart;
struct NotebookStruct
{
WidgetCore core;
NotebookPart 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)
};
/*------------------------------------------------------------------------
* Tab management:
*/
static Tab *NewTab(Tcl_Interp *interp, Notebook *nb, Tk_Window slave)
{
Tab *tab = (Tab *)ckalloc(sizeof(*tab));
memset(tab,0,sizeof(*tab));
if (Tk_InitOptions(interp, (ClientData)tab,
nb->notebook.tabOptionTable, nb->core.tkwin) != TCL_OK)
{
ckfree((ClientData)tab);
return 0;
}
tab->slave = slave;
tab->master = nb;
tab->layout = TTK_CreateLayout(
interp, TTK_GetCurrentTheme(interp),
"Tab.TNotebook", nb->notebook.tabOptionTable);
return tab;
}
static void FreeTab(Tab *tab)
{
Notebook *nb = tab->master;
Tk_FreeConfigOptions((ClientData)tab,
nb->notebook.tabOptionTable, nb->core.tkwin);
if (tab->layout)
TTK_FreeLayout(tab->layout);
ckfree((ClientData)tab);
}
static int ConfigureTab(
Tcl_Interp *interp, Notebook *nb, Tab *tab, int objc, Tcl_Obj *CONST objv[])
{
if (Tk_SetOptions(interp, (ClientData)tab, nb->notebook.tabOptionTable,
objc, objv, nb->core.tkwin, 0, 0) != TCL_OK)
{
return TCL_ERROR;
}
/* Check options:
*/
if (TTK_GetStickyFromObj(interp, tab->stickyObj, &tab->sticky) != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
}
/* Add new tab to list:
*/
static void AddTab(Notebook *nb, Tab *tab)
{
int index = nb->notebook.nTabs;
++nb->notebook.nTabs;
nb->notebook.tabs = (Tab**)ckrealloc(
(ClientData)nb->notebook.tabs, nb->notebook.nTabs * sizeof(Tab *));
nb->notebook.tabs[index] = tab;
/* Register the notebook as the geometry manager of the slave:
*/
Tk_ManageGeometry(tab->slave, &NotebookGeometryManager, (ClientData)tab);
/* Register an event handler on the slave to track destruction:
* (??? why doesn't ManageGeometry do that for us ???)
*/
Tk_CreateEventHandler(tab->slave, StructureNotifyMask,
NotebookSlaveEventHandler, (ClientData)tab);
}
static void RemoveTab(Notebook *nb, int index)
{
Tab *tab = nb->notebook.tabs[index];
/* Remove tab from array:
* It's important to do this first, see NOTE-LOSTSLAVE.
*/
--nb->notebook.nTabs;
while (index < nb->notebook.nTabs) {
nb->notebook.tabs[index] = nb->notebook.tabs[index+1];
++index;
}
if (index == nb->notebook.currentIndex) {
nb->notebook.currentIndex = -1; /* @@@ SHOULD: raise something */
}
/* Free up tab resources, unmap slave window:
*/
Tk_DeleteEventHandler(tab->slave, StructureNotifyMask,
NotebookSlaveEventHandler, tab);
Tk_ManageGeometry(tab->slave, NULL, 0); /* NOTE-LOSTSLAVE */
Tk_UnmapWindow(tab->slave);
FreeTab(tab);
/* Redisplay the notebook:
*/
WidgetChanged(&nb->core, RELAYOUT_REQUIRED);
}
/*
* FindTab --
* Return the index of the specified slave window,
* or -1 if the window is not a slave of the notebook.
*/
static int FindTab(Notebook *nb, Tk_Window slave)
{
int index = 0;
while (index < nb->notebook.nTabs) {
if (nb->notebook.tabs[index]->slave == slave) {
return index;
}
++index;
}
return -1;
}
/*
* 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.nTabs; ++index) {
Tab *tab = nb->notebook.tabs[index];
if (TTK_BoxContains(TTK_LayoutNodeParcel(tab->layout),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, and activeIndex.
*/
static unsigned TabState(Notebook *nb, int index)
{
unsigned state = nb->core.state;
if (index == nb->notebook.currentIndex) {
state |= TTK_STATE_SELECTED;
} else {
state &= ~TTK_STATE_FOCUS;
}
if (index == nb->notebook.activeIndex) {
state |= TTK_STATE_ACTIVE;
}
return state;
}
/*------------------------------------------------------------------------
* Geometry management:
*/
static void NotebookDoLayout(void *recordPtr)
{
Notebook *nb = recordPtr;
int width = 0, height = 0;
int i;
Tk_Window nbwin = nb->core.tkwin;
TTK_Box cavity = TTK_MakeBox(0,0, Tk_Width(nbwin),Tk_Height(nbwin));
TTK_Box tabrowBox;
if (nb->notebook.paddingObj) {
TTK_Padding padding;
TTK_GetPaddingFromObj(
NULL,nb->core.tkwin,nb->notebook.paddingObj,&padding);
cavity = TTK_PadBox(cavity, padding);
}
/* Layout for notebook background (base layout):
*/
TTK_LayoutGeometry(nb->core.layout, recordPtr, nbwin, nb->core.state,
0,0, Tk_Width(nbwin), Tk_Height(nbwin));
/* Compute max height and total width of all tabs:
* @@@FACTOR THIS
*/
for (i = 0; i<nb->notebook.nTabs; ++i) {
int tabWidth, tabHeight;
Tab *tab = nb->notebook.tabs[i];
TTK_LayoutReqSize(tab->layout, (char*)tab, nb->core.tkwin,
TabState(nb,i), &tabWidth, &tabHeight);
if (tabHeight > height) height = tabHeight;
width += tabWidth;
}
/* Place tabs:
*/
tabrowBox = TTK_PackBox(&cavity, width, height, TTK_SIDE_TOP);
for (i = 0; i<nb->notebook.nTabs; ++i) {
Tab *tab = nb->notebook.tabs[i];
int tabWidth, tabHeight;
TTK_Box tabBox;
/* @@@ maybe cache this? */
TTK_LayoutReqSize(tab->layout, (char*)tab, nb->core.tkwin,
TabState(nb, i), &tabWidth, &tabHeight);
tabBox = TTK_StickBox(
TTK_PackBox(&tabrowBox, tabWidth, tabHeight, TTK_SIDE_LEFT),
tabWidth, tabHeight, TTK_STICK_S);
TTK_LayoutGeometry(
tab->layout, tab, nb->core.tkwin, TabState(nb, i),
tabBox.x, tabBox.y, tabBox.width, tabBox.height);
}
/* Layout for client area frame:
*/
TTK_LayoutGeometry(
nb->notebook.clientLayout, nb, nb->core.tkwin, nb->core.state,
cavity.x, cavity.y, cavity.width, cavity.height);
cavity = TTK_LayoutNodeInternalParcel(
nb->notebook.clientLayout, recordPtr, nb->core.tkwin);
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(Tab *tab)
{
Notebook *nb = tab->master;
int slaveWidth = Tk_ReqWidth(tab->slave);
int slaveHeight = Tk_ReqHeight(tab->slave);
TTK_Box slaveBox = TTK_StickBox(
nb->notebook.clientArea, slaveWidth, slaveHeight, tab->sticky);
Tk_MoveResizeWindow( /* %%%NOTE-CHILD */
tab->slave, slaveBox.x, slaveBox.y, slaveBox.width, slaveBox.height);
}
/*
* Notebook geometry manager procedures:
*/
static void
NotebookGeometryRequestProc(ClientData clientData, Tk_Window slave)
{
Tab *tab = clientData;
WidgetChanged(&tab->master->core, RELAYOUT_REQUIRED);
}
static void
NotebookGeometryLostSlaveProc(ClientData clientData, Tk_Window slave)
{
Tab *tab = (Tab *)clientData;
Notebook *nb = tab->master;
int index;
/* Locate slave in tab array, remove entry:
*/
for (index=0; index<nb->notebook.nTabs; ++index) {
if (nb->notebook.tabs[index] == tab) {
RemoveTab(nb, index);
return;
}
}
}
static Tk_GeomMgr NotebookGeometryManager = {
"notebook",
NotebookGeometryRequestProc,
NotebookGeometryLostSlaveProc
};
/* NotebookSlaveEventHandler --
* Notifies the master when a slave is destroyed.
*/
static void
NotebookSlaveEventHandler(ClientData clientData, XEvent *eventPtr)
{
Tab *tab = (Tab*)clientData;
if (eventPtr->type == DestroyNotify) {
NotebookGeometryLostSlaveProc(clientData, tab->slave);
}
}
/* NotebookEventHandler --
* Track <Configure> events, resize active child.
* Also tracks the active tab (we can't use TrackElementState()
* for this, since we manage tab layouts ourselves).
*/
static const int NotebookEventMask
= StructureNotifyMask
| PointerMotionMask
| LeaveWindowMask
;
static void NotebookEventHandler(ClientData clientData, XEvent *eventPtr)
{
Notebook *nb = (Notebook*)clientData;
if (eventPtr->type == DestroyNotify) {
/* Remove self */
Tk_DeleteEventHandler(nb->core.tkwin, NotebookEventMask,
NotebookEventHandler, clientData);
} else if (eventPtr->type == ConfigureNotify) {
/* Re-pack active slave: */
NotebookDoLayout(nb);
if (nb->notebook.currentIndex >= 0) {
NotebookPlaceSlave(nb->notebook.tabs[nb->notebook.currentIndex]);
}
} 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);
}
}
/* GetTabIndex --
* Find the index of the specified tab.
* Tab identifiers are one of:
*
* + numeric indices [0..nTabs],
* + slave window names
* + positional specifications @x,y
* + "current"
*
* Returns: index of specified tab, -1 if not found.
* If 'interp' is non-NULL, leaves an error message there.
*/
static int GetTabIndex(
Tcl_Interp *interp, /* Where to leave error message if non-NULL */
Notebook *nb, /* Notebook widget record */
Tcl_Obj *objPtr, /* Tab name to look up */
int *index_rtn)
{
const char *string = Tcl_GetString(objPtr);
int index, x, y;
*index_rtn = -1;
/*
* Check for integer indices:
*/
if (isdigit(string[0]) && Tcl_GetIntFromObj(NULL,objPtr,&index)==TCL_OK) {
if (index >= 0 && index < nb->notebook.nTabs) {
*index_rtn = index;
return TCL_OK;
} else {
if (interp)
Tcl_SetResult(interp, "Index out of range", TCL_STATIC);
return TCL_ERROR;
}
}
/*
* Or window path names...
*/
if (string[0] == '.') {
for (index = 0; index < nb->notebook.nTabs; ++index) {
Tab *tab = nb->notebook.tabs[index];
if (!strcmp(string, Tk_PathName(tab->slave))) {
*index_rtn = index;
return TCL_OK;
}
}
if (interp) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, string,
" is not managed by ", Tk_PathName(nb->core.tkwin),
NULL);
}
return TCL_ERROR;
}
/*
* Or @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;
}
/*
* Nothing matches.
*/
if (interp) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "Invalid tab index ", string, NULL);
}
return TCL_ERROR;
}
/*
* SelectTab --
* Select the specified tab.
*/
static void SelectTab(Notebook *nb, int index)
{
Tab *tab = nb->notebook.tabs[index];
int currentIndex = nb->notebook.currentIndex;
if (index == currentIndex)
return;
if (currentIndex >= 0)
Tk_UnmapWindow(nb->notebook.tabs[currentIndex]->slave);
NotebookPlaceSlave(tab);
Tk_MapWindow(tab->slave);
nb->notebook.currentIndex = index;
WidgetChanged(&nb->core, REDISPLAY_REQUIRED);
}
/*------------------------------------------------------------------------
* Widget instance commands
*/
/*
* $nb add window [ options ... ]
*/
static int NotebookAddCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
Notebook *nb = recordPtr;
Tk_Window slave;
Tab *tab = 0;
if (objc <= 2 || objc % 2 != 1) {
Tcl_WrongNumArgs(interp, 2, objv, "window ?options...?");
return TCL_ERROR;
}
slave = Tk_NameToWindow(interp, Tcl_GetString(objv[2]), nb->core.tkwin);
if (!slave)
return TCL_ERROR;
/*
* Only allow direct children of the notebook to be added as slaves.
* (%%%NOTE-CHILD-RESTRICTION might relax this later)
*/
if (Tk_Parent(slave) != nb->core.tkwin) {
Tcl_AppendResult(interp,
"can't add ", Tk_PathName(slave),
" to ", Tk_PathName(nb->core.tkwin),
NULL);
return TCL_ERROR;
}
/*
* Make sure slave is not already present:
*/
if (FindTab(nb, slave) != -1) {
Tcl_AppendResult(interp, Tk_PathName(slave), " already added", NULL);
return TCL_ERROR;
}
/* Create and initialize new tab:
*/
tab = NewTab(interp, nb, slave);
if (!tab) {
return TCL_ERROR;
}
if (ConfigureTab(interp, nb, tab, objc-3, objv+3) != TCL_OK) {
FreeTab(tab);
return TCL_ERROR;
}
AddTab(nb, tab);
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) {
RemoveTab(nb, index); /* NOTE-LOSTSLAVE */
}
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.nTabs));
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;
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 < nb->notebook.nTabs; ++i) {
Tcl_ListObjAppendElement(interp, result,
Tcl_NewStringObj(Tk_PathName(nb->notebook.tabs[i]->slave), -1));
}
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
/*
* $nb tabcget $item -$resource
*/
static int NotebookTabCgetCommand(
Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[], void *recordPtr)
{
Notebook *nb = recordPtr;
int index;
Tcl_Obj *result;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 2, objv, "tab option");
return TCL_ERROR;
}
if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
return TCL_ERROR;
}
result = Tk_GetOptionValue(interp, (ClientData)nb->notebook.tabs[index],
nb->notebook.tabOptionTable, objv[3], nb->core.tkwin);
if (result == NULL) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
static int NotebookTabConfigureCommand(
Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[], void *recordPtr)
{
Notebook *nb = recordPtr;
int index;
Tab *tab;
Tcl_Obj *result;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "tab configure ?option ?value??...");
return TCL_ERROR;
}
if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
return TCL_ERROR;
}
tab = nb->notebook.tabs[index];
if (objc == 3) {
result = Tk_GetOptionInfo(interp, (ClientData)tab,
nb->notebook.tabOptionTable, NULL, nb->core.tkwin);
} else if (objc == 4) {
result = Tk_GetOptionInfo(interp, (ClientData)tab,
nb->notebook.tabOptionTable, objv[3], nb->core.tkwin);
} else {
int status = ConfigureTab(interp, nb, tab, objc - 3,objv + 3);
if (status == TCL_OK) {
WidgetChanged(&nb->core, RELAYOUT_REQUIRED);
}
return status;
}
if (result == NULL) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
/* Subcommand table:
*/
static WidgetCommandSpec NotebookCommands[] =
{
{ "add", NotebookAddCommand },
{ "configure", WidgetConfigureCommand },
{ "cget", WidgetCgetCommand },
{ "forget", NotebookForgetCommand },
{ "index", NotebookIndexCommand },
{ "instate", WidgetInstateCommand },
{ "select", NotebookSelectCommand },
{ "state", WidgetStateCommand },
{ "tabcget", NotebookTabCgetCommand },
{ "tabconfigure", NotebookTabConfigureCommand },
{ "tabs", NotebookTabsCommand },
{ 0,0 }
};
/*------------------------------------------------------------------------
* Widget class hooks:
*/
static int NotebookInitialize(Tcl_Interp *interp, void *recordPtr)
{
Notebook *nb = recordPtr;
nb->notebook.nTabs = 0;
nb->notebook.tabs = NULL;
nb->notebook.currentIndex = -1;
nb->notebook.activeIndex = -1;
nb->notebook.tabOptionTable = Tk_CreateOptionTable(interp,TabOptionSpecs);
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;
if (nb->notebook.tabs) {
int i;
for (i=0; i<nb->notebook.nTabs; ++i)
FreeTab(nb->notebook.tabs[i]);
ckfree((ClientData)nb->notebook.tabs);
}
if (nb->notebook.clientLayout) {
TTK_FreeLayout(nb->notebook.clientLayout);
}
}
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 --
* @@@ TODO: error checks
*/
static TTK_Layout NotebookGetLayout(
Tcl_Interp *interp, TTK_Theme theme, void *recordPtr)
{
Notebook *nb = recordPtr;
int i;
if (nb->notebook.clientLayout) {
TTK_FreeLayout(nb->notebook.clientLayout);
}
nb->notebook.clientLayout = TTK_CreateLayout(
interp, theme, "Client.TNotebook", nb->core.optionTable);
for (i=0; i<nb->notebook.nTabs; ++i) {
Tab *tab = nb->notebook.tabs[i];
TTK_FreeLayout(tab->layout);
tab->layout = TTK_CreateLayout(
interp, theme, "Tab.TNotebook", nb->notebook.tabOptionTable);
}
return TTK_CreateLayout(interp, theme, "TNotebook", nb->core.optionTable);
}
/* NotebookSize --
* Client area determined by max size of slaves,
* overridden by -width and/or -height if nonzero.
*
* Total height is tab height + client area height.
* Total width is max(client width, tab width)
*/
static int NotebookSize(void *clientData, int *widthPtr, int *heightPtr)
{
Notebook *nb = clientData;
TTK_Padding padding = TTK_UniformPadding(0);
int clientWidth = 0, clientHeight = 0,
reqWidth = 0, reqHeight =0,
tabrowWidth = 0, tabrowHeight = 0;
int i;
/* Compute max requested size of all slaves:
*/
for (i = 0; i < nb->notebook.nTabs; ++i) {
Tk_Window slave = nb->notebook.tabs[i]->slave;
clientWidth = MAX(clientWidth, Tk_ReqWidth(slave));
clientHeight = MAX(clientHeight, Tk_ReqHeight(slave));
}
/* Client width/height overridable by resources:
*/
Tcl_GetIntFromObj(NULL, nb->notebook.widthObj,&reqWidth);
Tcl_GetIntFromObj(NULL, nb->notebook.heightObj,&reqHeight);
if (reqWidth > 0)
clientWidth = reqWidth;
if (reqHeight > 0)
clientHeight = reqHeight;
/* Compute max height and total width of all tabs:
* @@@FACTOR THIS
*/
for (i = 0; i<nb->notebook.nTabs; ++i) {
int tabWidth, tabHeight;
Tab *tab = nb->notebook.tabs[i];
TTK_LayoutReqSize(tab->layout, (char*)tab, nb->core.tkwin,
TabState(nb,i), &tabWidth, &tabHeight);
tabrowHeight = MAX(tabrowHeight, tabHeight);
tabrowWidth += tabWidth;
}
/* Account for padding:
*/
if (nb->notebook.paddingObj) {
TTK_GetPaddingFromObj(
NULL, nb->core.tkwin, nb->notebook.paddingObj, &padding);
}
*widthPtr = MAX(tabrowWidth, clientWidth) + TTK_PaddingWidth(padding);
*heightPtr = tabrowHeight + clientHeight + TTK_PaddingHeight(padding);
return 1;
}
static void DisplayTab(Notebook *nb, int index, Drawable d)
{
Tab *tab = nb->notebook.tabs[index];
unsigned state = TabState(nb, index);
TTK_LayoutDraw(tab->layout, (char*)tab, nb->core.tkwin, d, state);
}
static void NotebookDisplay(void *clientData, Drawable d)
{
Notebook *nb = clientData;
Tk_Window tkwin = nb->core.tkwin;
/* Draw notebook background (base layout):
*/
TTK_LayoutDraw(nb->core.layout, clientData, tkwin, d, nb->core.state);
/* Draw client area frame:
*/
TTK_LayoutDraw(nb->notebook.clientLayout, clientData,
tkwin, d, nb->core.state);
/* Now draw tabs, from outside in, drawing the current tab last:
*/
if (nb->notebook.currentIndex >= 0) {
int index = 0;
while (index < nb->notebook.currentIndex) {
DisplayTab(nb, index++, d);
}
index = nb->notebook.nTabs;
while (index > nb->notebook.currentIndex) {
DisplayTab(nb, --index, d);
}
DisplayTab(nb, index, d);
} else {
int index;
for (index = 0; index < nb->notebook.nTabs; ++index) {
DisplayTab(nb,index,d);
}
}
}
/* Widget specification:
*/
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 */
WIDGET_SPEC_END /* sentinel */
};
/*------------------------------------------------------------------------
* Default implementation of notebook elements:
*/
typedef struct {
Tcl_Obj *borderWidthObj;
Tcl_Obj *borderColorObj;
Tcl_Obj *backgroundObj;
} TabElement;
static TTK_ElementOptionSpec TabElementOptions[] = {
{ "-borderwidth", TK_OPTION_PIXELS,
Tk_Offset(TabElement,borderWidthObj),"1" },
{ "-bordercolor", TK_OPTION_COLOR,
Tk_Offset(TabElement,borderColorObj),"black" },
{ "-background", TK_OPTION_BORDER,
Tk_Offset(TabElement,backgroundObj), DEFAULT_BACKGROUND },
{0,0,0,0}
};
static void
TabElementGeometry(
void *clientData, void *elementRecord,
Tk_Window tkwin, int *widthPtr, int *heightPtr, TTK_Padding *paddingPtr)
{
TabElement *tab = elementRecord;
int borderWidth = 1;
Tk_GetPixelsFromObj(0, tkwin, tab->borderWidthObj, &borderWidth);
paddingPtr->top = paddingPtr->left = paddingPtr->right = borderWidth;
paddingPtr->bottom = 0;
}
static void
TabElementDraw(void *clientData, void *elementRecord,
Tk_Window tkwin, Drawable d, TTK_Box b, unsigned int state)
{
TabElement *tab = elementRecord;
Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, tab->backgroundObj);
XColor *color = Tk_GetColorFromObj(tkwin, tab->borderColorObj);
GC gc = Tk_GCForColor(color, d);
int borderWidth = 1;
XPoint pts[4];
Tcl_GetIntFromObj(NULL, tab->borderWidthObj, &borderWidth);
if (state & TTK_STATE_SELECTED) {
/*
* Draw slightly outside of the allocated parcel,
* to overwrite the client area border.
*/
b.height += borderWidth;
}
Tk_Fill3DRectangle(tkwin, d, border,
b.x, b.y, b.width, b.height, borderWidth, TK_RELIEF_FLAT);
pts[0].x = b.x; pts[0].y = b.y + b.height;
pts[1].x = b.x; pts[1].y = b.y;
pts[2].x = b.x + b.width; pts[2].y = b.y;
pts[3].x = b.x + b.width; pts[3].y = b.y + b.height;
#ifndef WIN32
/*
* Account for whether XDrawLines draws endpoints by platform
*/
--pts[0].y; --pts[5].y;
#endif
while (borderWidth--) {
XDrawLines(Tk_Display(tkwin), d, gc, pts, 4, CoordModeOrigin);
++pts[0].x; ++pts[1].x; --pts[2].x; --pts[3].x;
++pts[1].y; ++pts[2].y;
}
}
static TTK_ElementSpec TabElementSpec =
{
TK_STYLE_VERSION_2,
sizeof(TabElement),
TabElementOptions,
TabElementGeometry,
TabElementDraw
};
/*
* Client area element:
* Uses same resources as tab element.
*/
typedef TabElement ClientElement;
#define ClientElementOptions TabElementOptions
static void
ClientElementDraw(void *clientData, void *elementRecord,
Tk_Window tkwin, Drawable d, TTK_Box b, unsigned int state)
{
ClientElement *ce = elementRecord;
Tk_3DBorder border = Tk_Get3DBorderFromObj(tkwin, ce->backgroundObj);
XColor *color = Tk_GetColorFromObj(tkwin, ce->borderColorObj);
GC gc = Tk_GCForColor(color, d);
int borderWidth = 1;
Tcl_GetIntFromObj(NULL, ce->borderWidthObj, &borderWidth);
Tk_Fill3DRectangle(tkwin, d, border,
b.x, b.y, b.width, b.height, borderWidth,TK_RELIEF_FLAT);
while (borderWidth--) {
XDrawRectangle(Tk_Display(tkwin), d, gc, b.x,b.y,b.width-1,b.height-1);
++b.x; ++b.y; b.width -= 2; b.height -= 2;
}
}
static void
ClientElementGeometry(
void *clientData, void *elementRecord,
Tk_Window tkwin, int *widthPtr, int *heightPtr, TTK_Padding *paddingPtr)
{
ClientElement *ce = elementRecord;
int borderWidth = 1;
Tk_GetPixelsFromObj(0, tkwin, ce->borderWidthObj, &borderWidth);
*paddingPtr = TTK_UniformPadding((short)borderWidth);
}
static TTK_ElementSpec ClientElementSpec =
{
TK_STYLE_VERSION_2,
sizeof(ClientElement),
ClientElementOptions,
ClientElementGeometry,
ClientElementDraw
};
TTK_BEGIN_LAYOUT(NotebookLayout)
TTK_NODE("Notebook.background", TTK_STICK_ALL)
TTK_END_LAYOUT
TTK_BEGIN_LAYOUT(TabLayout)
TTK_NODE("Notebook.tab", TTK_STICK_ALL)
TTK_GROUP("Notebook.padding", TTK_EXPAND|TTK_STICK_ALL,
TTK_NODE("Notebook.label", TTK_EXPAND|TTK_STICK_ALL))
TTK_END_LAYOUT
TTK_BEGIN_LAYOUT(ClientLayout)
TTK_NODE("Notebook.client", TTK_EXPAND|TTK_STICK_ALL)
TTK_END_LAYOUT
int Notebook_Init(Tcl_Interp *interp)
{
TTK_Theme themePtr = TTK_GetDefaultTheme(interp);
TTK_RegisterElementSpec(themePtr, "tab", &TabElementSpec, NULL);
TTK_RegisterElementSpec(themePtr, "client", &ClientElementSpec, NULL);
TTK_RegisterLayout(themePtr, "Tab.TNotebook", TabLayout);
TTK_RegisterLayout(themePtr, "Client.TNotebook", ClientLayout);
TTK_RegisterLayout(themePtr, "TNotebook", NotebookLayout);
RegisterWidget(interp, "tnotebook", &NotebookWidgetSpec);
return TCL_OK;
}
/*EOF*/