Unnamed Fossil Project

Artifact [437c649cbc]
Login

Artifact [437c649cbc]

Artifact 437c649cbc1f2c345f3e3a7e6a125a456f9153fb9055ef4defab2501116b19ce:


/* $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*/