1/* $Id$
2 * Copyright (c) 2004, Joe English
3 */
4
5#include <string.h>
6#include <ctype.h>
7#include <stdio.h>
8#include <tk.h>
9
10#include "ttkTheme.h"
11#include "ttkWidget.h"
12#include "ttkManager.h"
13
14#define MIN(a,b) ((a) < (b) ? (a) : (b))
15#define MAX(a,b) ((a) > (b) ? (a) : (b))
16
17/*------------------------------------------------------------------------
18 * +++ Tab resources.
19 */
20
21#define DEFAULT_MIN_TAB_WIDTH 24
22
23static const char *const TabStateStrings[] = { "normal", "disabled", "hidden", 0 };
24typedef enum {
25    TAB_STATE_NORMAL, TAB_STATE_DISABLED, TAB_STATE_HIDDEN
26} TAB_STATE;
27
28typedef struct
29{
30    /* Internal data:
31     */
32    int 	width, height;		/* Requested size of tab */
33    Ttk_Box	parcel;			/* Tab position */
34
35    /* Tab options:
36     */
37    TAB_STATE 	state;
38
39    /* Child window options:
40     */
41    Tcl_Obj	*paddingObj;		/* Padding inside pane */
42    Ttk_Padding	padding;
43    Tcl_Obj 	*stickyObj;
44    Ttk_Sticky	sticky;
45
46    /* Label options:
47     */
48    Tcl_Obj *textObj;
49    Tcl_Obj *imageObj;
50    Tcl_Obj *compoundObj;
51    Tcl_Obj *underlineObj;
52
53} Tab;
54
55/* Two different option tables are used for tabs:
56 * TabOptionSpecs is used to draw the tab, and only includes resources
57 * relevant to the tab.
58 *
59 * PaneOptionSpecs includes additional options for child window placement
60 * and is used to configure the slave.
61 */
62static Tk_OptionSpec TabOptionSpecs[] =
63{
64    {TK_OPTION_STRING_TABLE, "-state", "", "",
65	"normal", -1,Tk_Offset(Tab,state),
66	0,(ClientData)TabStateStrings,0 },
67    {TK_OPTION_STRING, "-text", "text", "Text", "",
68	Tk_Offset(Tab,textObj), -1, 0,0,GEOMETRY_CHANGED },
69    {TK_OPTION_STRING, "-image", "image", "Image", NULL/*default*/,
70	Tk_Offset(Tab,imageObj), -1, TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
71    {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
72	"none", Tk_Offset(Tab,compoundObj), -1,
73	0,(ClientData)ttkCompoundStrings,GEOMETRY_CHANGED },
74    {TK_OPTION_INT, "-underline", "underline", "Underline", "-1",
75	Tk_Offset(Tab,underlineObj), -1, 0,0,GEOMETRY_CHANGED },
76    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0 }
77};
78
79static Tk_OptionSpec PaneOptionSpecs[] =
80{
81    {TK_OPTION_STRING, "-padding", "padding", "Padding", "0",
82	Tk_Offset(Tab,paddingObj), -1, 0,0,GEOMETRY_CHANGED },
83    {TK_OPTION_STRING, "-sticky", "sticky", "Sticky", "nsew",
84	Tk_Offset(Tab,stickyObj), -1, 0,0,GEOMETRY_CHANGED },
85
86    WIDGET_INHERIT_OPTIONS(TabOptionSpecs)
87};
88
89/*------------------------------------------------------------------------
90 * +++ Notebook resources.
91 */
92typedef struct
93{
94    Tcl_Obj *widthObj;		/* Default width */
95    Tcl_Obj *heightObj;		/* Default height */
96    Tcl_Obj *paddingObj;	/* Padding around notebook */
97
98    Ttk_Manager *mgr;		/* Geometry manager */
99    Tk_OptionTable tabOptionTable;	/* Tab options */
100    Tk_OptionTable paneOptionTable;	/* Tab+pane options */
101    int currentIndex;		/* index of currently selected tab */
102    int activeIndex;		/* index of currently active tab */
103    Ttk_Layout tabLayout;	/* Sublayout for tabs */
104
105    Ttk_Box clientArea;		/* Where to pack slave widgets */
106} NotebookPart;
107
108typedef struct
109{
110    WidgetCore core;
111    NotebookPart notebook;
112} Notebook;
113
114static Tk_OptionSpec NotebookOptionSpecs[] =
115{
116    WIDGET_TAKES_FOCUS,
117
118    {TK_OPTION_INT, "-width", "width", "Width", "0",
119	Tk_Offset(Notebook,notebook.widthObj),-1,
120	0,0,GEOMETRY_CHANGED },
121    {TK_OPTION_INT, "-height", "height", "Height", "0",
122	Tk_Offset(Notebook,notebook.heightObj),-1,
123	0,0,GEOMETRY_CHANGED },
124    {TK_OPTION_STRING, "-padding", "padding", "Padding", NULL,
125	Tk_Offset(Notebook,notebook.paddingObj),-1,
126	TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
127
128    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
129};
130
131/* Notebook style options:
132 */
133typedef struct
134{
135    Ttk_PositionSpec	tabPosition;	/* Where to place tabs */
136    Ttk_Padding 	tabMargins;	/* Margins around tab row */
137    Ttk_PositionSpec 	tabPlacement;	/* How to pack tabs within tab row */
138    Ttk_Orient		tabOrient;	/* ... */
139    int 		minTabWidth;	/* Minimum tab width */
140    Ttk_Padding 	padding;	/* External padding */
141} NotebookStyle;
142
143static void NotebookStyleOptions(Notebook *nb, NotebookStyle *nbstyle)
144{
145    Tcl_Obj *objPtr;
146
147    nbstyle->tabPosition = TTK_PACK_TOP | TTK_STICK_W;
148    if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabposition", 0)) != 0) {
149	TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPosition);
150    }
151
152    /* Guess default tabPlacement as function of tabPosition:
153     */
154    if (nbstyle->tabPosition & TTK_PACK_LEFT) {
155	nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_E;
156    } else if (nbstyle->tabPosition & TTK_PACK_RIGHT) {
157	nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_W;
158    } else if (nbstyle->tabPosition & TTK_PACK_BOTTOM) {
159	nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_N;
160    } else { /* Assume TTK_PACK_TOP */
161	nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_S;
162    }
163    if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabplacement", 0)) != 0) {
164	TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPlacement);
165    }
166
167    /* Compute tabOrient as function of tabPlacement:
168     */
169    if (nbstyle->tabPlacement & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) {
170	nbstyle->tabOrient = TTK_ORIENT_HORIZONTAL;
171    } else {
172	nbstyle->tabOrient = TTK_ORIENT_VERTICAL;
173    }
174
175    nbstyle->tabMargins = Ttk_UniformPadding(0);
176    if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabmargins", 0)) != 0) {
177	Ttk_GetBorderFromObj(NULL, objPtr, &nbstyle->tabMargins);
178    }
179
180    nbstyle->padding = Ttk_UniformPadding(0);
181    if ((objPtr = Ttk_QueryOption(nb->core.layout, "-padding", 0)) != 0) {
182	Ttk_GetPaddingFromObj(NULL,nb->core.tkwin,objPtr,&nbstyle->padding);
183    }
184
185    nbstyle->minTabWidth = DEFAULT_MIN_TAB_WIDTH;
186    if ((objPtr = Ttk_QueryOption(nb->core.layout, "-mintabwidth", 0)) != 0) {
187	Tcl_GetIntFromObj(NULL, objPtr, &nbstyle->minTabWidth);
188    }
189}
190
191/*------------------------------------------------------------------------
192 * +++ Tab management.
193 */
194
195static Tab *CreateTab(Tcl_Interp *interp, Notebook *nb, Tk_Window slaveWindow)
196{
197    Tk_OptionTable optionTable = nb->notebook.paneOptionTable;
198    void *record = ckalloc(sizeof(Tab));
199    memset(record, 0, sizeof(Tab));
200
201    if (Tk_InitOptions(interp, record, optionTable, slaveWindow) != TCL_OK) {
202	ckfree(record);
203	return NULL;
204    }
205
206    return record;
207}
208
209static void DestroyTab(Notebook *nb, Tab *tab)
210{
211    void *record = tab;
212    Tk_FreeConfigOptions(record, nb->notebook.paneOptionTable, nb->core.tkwin);
213    ckfree(record);
214}
215
216static int ConfigureTab(
217    Tcl_Interp *interp, Notebook *nb, Tab *tab, Tk_Window slaveWindow,
218    int objc, Tcl_Obj *const objv[])
219{
220    Ttk_Sticky sticky = tab->sticky;
221    Ttk_Padding padding = tab->padding;
222    Tk_SavedOptions savedOptions;
223    int mask = 0;
224
225    if (Tk_SetOptions(interp, (ClientData)tab, nb->notebook.paneOptionTable,
226	    objc, objv, slaveWindow, &savedOptions, &mask) != TCL_OK)
227    {
228	return TCL_ERROR;
229    }
230
231    /* Check options:
232     * @@@ TODO: validate -image option.
233     */
234    if (Ttk_GetStickyFromObj(interp, tab->stickyObj, &sticky) != TCL_OK)
235    {
236	goto error;
237    }
238    if (Ttk_GetPaddingFromObj(interp, slaveWindow, tab->paddingObj, &padding)
239	    != TCL_OK)
240    {
241	goto error;
242    }
243
244    tab->sticky = sticky;
245    tab->padding = padding;
246
247    Tk_FreeSavedOptions(&savedOptions);
248    Ttk_ManagerSizeChanged(nb->notebook.mgr);
249    TtkRedisplayWidget(&nb->core);
250
251    return TCL_OK;
252error:
253    Tk_RestoreSavedOptions(&savedOptions);
254    return TCL_ERROR;
255}
256
257/*
258 * IdentifyTab --
259 * 	Return the index of the tab at point x,y,
260 * 	or -1 if no tab at that point.
261 */
262static int IdentifyTab(Notebook *nb, int x, int y)
263{
264    int index;
265    for (index = 0; index < Ttk_NumberSlaves(nb->notebook.mgr); ++index) {
266	Tab *tab = Ttk_SlaveData(nb->notebook.mgr,index);
267	if (	tab->state != TAB_STATE_HIDDEN
268	     && Ttk_BoxContains(tab->parcel, x,y))
269	{
270	    return index;
271	}
272    }
273    return -1;
274}
275
276/*
277 * ActivateTab --
278 * 	Set the active tab index, redisplay if necessary.
279 */
280static void ActivateTab(Notebook *nb, int index)
281{
282    if (index != nb->notebook.activeIndex) {
283	nb->notebook.activeIndex = index;
284	TtkRedisplayWidget(&nb->core);
285    }
286}
287
288/*
289 * TabState --
290 * 	Return the state of the specified tab, based on
291 * 	notebook state, currentIndex, activeIndex, and user-specified tab state.
292 *	The USER1 bit is set for the leftmost tab, and USER2
293 * 	is set for the rightmost tab.
294 */
295static Ttk_State TabState(Notebook *nb, int index)
296{
297    Ttk_State state = nb->core.state;
298    Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index);
299
300    if (index == nb->notebook.currentIndex) {
301	state |= TTK_STATE_SELECTED;
302    } else {
303	state &= ~TTK_STATE_FOCUS;
304    }
305
306    if (index == nb->notebook.activeIndex) {
307	state |= TTK_STATE_ACTIVE;
308    }
309    if (index == 0) {
310    	state |= TTK_STATE_USER1;
311    }
312    if (index == Ttk_NumberSlaves(nb->notebook.mgr) - 1) {
313    	state |= TTK_STATE_USER2;
314    }
315    if (tab->state == TAB_STATE_DISABLED) {
316	state |= TTK_STATE_DISABLED;
317    }
318
319    return state;
320}
321
322/*------------------------------------------------------------------------
323 * +++ Geometry management - size computation.
324 */
325
326/* TabrowSize --
327 *	Compute max height and total width of all tabs (horizontal layouts)
328 *	or total height and max width (vertical layouts).
329 *
330 * Side effects:
331 * 	Sets width and height fields for all tabs.
332 *
333 * Notes:
334 * 	Hidden tabs are included in the perpendicular computation
335 * 	(max height/width) but not parallel (total width/height).
336 */
337static void TabrowSize(
338    Notebook *nb, Ttk_Orient orient, int *widthPtr, int *heightPtr)
339{
340    Ttk_Layout tabLayout = nb->notebook.tabLayout;
341    int tabrowWidth = 0, tabrowHeight = 0;
342    int i;
343
344    for (i = 0; i < Ttk_NumberSlaves(nb->notebook.mgr); ++i) {
345	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i);
346	Ttk_State tabState = TabState(nb,i);
347
348	Ttk_RebindSublayout(tabLayout, tab);
349	Ttk_LayoutSize(tabLayout,tabState,&tab->width,&tab->height);
350
351	if (orient == TTK_ORIENT_HORIZONTAL) {
352	    tabrowHeight = MAX(tabrowHeight, tab->height);
353	    if (tab->state != TAB_STATE_HIDDEN) { tabrowWidth += tab->width; }
354	} else {
355	    tabrowWidth = MAX(tabrowWidth, tab->width);
356	    if (tab->state != TAB_STATE_HIDDEN) { tabrowHeight += tab->height; }
357	}
358    }
359
360    *widthPtr = tabrowWidth;
361    *heightPtr = tabrowHeight;
362}
363
364/* NotebookSize -- GM and widget size hook.
365 *
366 * Total height is tab height + client area height + pane internal padding
367 * Total width is max(client width, tab width) + pane internal padding
368 * Client area size determined by max size of slaves,
369 * overridden by -width and/or -height if nonzero.
370 */
371
372static int NotebookSize(void *clientData, int *widthPtr, int *heightPtr)
373{
374    Notebook *nb = clientData;
375    NotebookStyle nbstyle;
376    Ttk_Padding padding;
377    Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client");
378    int clientWidth = 0, clientHeight = 0,
379    	reqWidth = 0, reqHeight = 0,
380	tabrowWidth = 0, tabrowHeight = 0;
381    int i;
382
383    NotebookStyleOptions(nb, &nbstyle);
384
385    /* Compute max requested size of all slaves:
386     */
387    for (i = 0; i < Ttk_NumberSlaves(nb->notebook.mgr); ++i) {
388	Tk_Window slaveWindow = Ttk_SlaveWindow(nb->notebook.mgr, i);
389	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i);
390	int slaveWidth
391	    = Tk_ReqWidth(slaveWindow) + Ttk_PaddingWidth(tab->padding);
392	int slaveHeight
393	    = Tk_ReqHeight(slaveWindow) + Ttk_PaddingHeight(tab->padding);
394
395	clientWidth = MAX(clientWidth, slaveWidth);
396	clientHeight = MAX(clientHeight, slaveHeight);
397    }
398
399    /* Client width/height overridable by widget options:
400     */
401    Tcl_GetIntFromObj(NULL, nb->notebook.widthObj,&reqWidth);
402    Tcl_GetIntFromObj(NULL, nb->notebook.heightObj,&reqHeight);
403    if (reqWidth > 0)
404	clientWidth = reqWidth;
405    if (reqHeight > 0)
406	clientHeight = reqHeight;
407
408    /* Tab row:
409     */
410    TabrowSize(nb, nbstyle.tabOrient, &tabrowWidth, &tabrowHeight);
411    tabrowHeight += Ttk_PaddingHeight(nbstyle.tabMargins);
412    tabrowWidth += Ttk_PaddingWidth(nbstyle.tabMargins);
413
414    /* Account for exterior and interior padding:
415     */
416    padding = nbstyle.padding;
417    if (clientNode) {
418	Ttk_Padding ipad =
419	    Ttk_LayoutNodeInternalPadding(nb->core.layout, clientNode);
420	padding = Ttk_AddPadding(padding, ipad);
421    }
422
423    if (nbstyle.tabPosition & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) {
424	*widthPtr = MAX(tabrowWidth, clientWidth) + Ttk_PaddingWidth(padding);
425	*heightPtr = tabrowHeight + clientHeight + Ttk_PaddingHeight(padding);
426    } else {
427	*widthPtr = tabrowWidth + clientWidth + Ttk_PaddingWidth(padding);
428	*heightPtr = MAX(tabrowHeight,clientHeight) + Ttk_PaddingHeight(padding);
429    }
430
431    return 1;
432}
433
434/*------------------------------------------------------------------------
435 * +++ Geometry management - layout.
436 */
437
438/* SqueezeTabs --
439 *	Squeeze or stretch tabs to fit within the tab area parcel.
440 *
441 *	All tabs are adjusted by an equal amount, but will not be made
442 *	smaller than the minimum width.  (If all the tabs still do
443 *	not fit in the available space, the rightmost ones will
444 *	be further squozen by PlaceTabs()).
445 *
446 *	The algorithm does not always yield an optimal layout, but does
447 *	have the important property that decreasing the available width
448 *	by one pixel will cause at most one tab to shrink by one pixel;
449 *	this means that tabs resize "smoothly" when the window shrinks
450 *	and grows.
451 *
452 * @@@ <<NOTE-TABPOSITION>> bug: only works for horizontal orientations
453 * @@@ <<NOTE-SQUEEZE-HIDDEN>> does not account for hidden tabs.
454 */
455
456static void SqueezeTabs(
457    Notebook *nb, int needed, int available, int minTabWidth)
458{
459    int nTabs = Ttk_NumberSlaves(nb->notebook.mgr);
460
461    if (nTabs > 0) {
462	int difference = available - needed,
463	    delta = difference / nTabs,
464	    remainder = difference % nTabs,
465	    slack = 0;
466	int i;
467
468	if (remainder < 0) { remainder += nTabs; --delta; }
469
470	for (i = 0; i < nTabs; ++i) {
471	    Tab *tab = Ttk_SlaveData(nb->notebook.mgr,i);
472	    int adj = delta + (i < remainder) + slack;
473
474	    if (tab->width + adj >= minTabWidth) {
475		tab->width += adj;
476		slack = 0;
477	    } else {
478		slack = adj - (minTabWidth - tab->width);
479		tab->width = minTabWidth;
480	    }
481	}
482    }
483}
484
485/* PlaceTabs --
486 * 	Compute all tab parcels.
487 */
488static void PlaceTabs(
489    Notebook *nb, Ttk_Box tabrowBox, Ttk_PositionSpec tabPlacement)
490{
491    Ttk_Layout tabLayout = nb->notebook.tabLayout;
492    int nTabs = Ttk_NumberSlaves(nb->notebook.mgr);
493    int i;
494
495    for (i = 0; i < nTabs; ++i) {
496	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, i);
497	Ttk_State tabState = TabState(nb, i);
498
499	if (tab->state != TAB_STATE_HIDDEN) {
500	    Ttk_Padding expand = Ttk_UniformPadding(0);
501	    Tcl_Obj *expandObj = Ttk_QueryOption(tabLayout,"-expand",tabState);
502
503	    if (expandObj) {
504		Ttk_GetBorderFromObj(NULL, expandObj, &expand);
505	    }
506
507	    tab->parcel =
508		Ttk_ExpandBox(
509		    Ttk_PositionBox(&tabrowBox,
510			tab->width, tab->height, tabPlacement),
511		    expand);
512	}
513    }
514}
515
516/* NotebookDoLayout --
517 *	Computes notebook layout and places tabs.
518 *
519 * Side effects:
520 * 	Sets clientArea, used to place slave panes.
521 */
522static void NotebookDoLayout(void *recordPtr)
523{
524    Notebook *nb = recordPtr;
525    Tk_Window nbwin = nb->core.tkwin;
526    Ttk_Box cavity = Ttk_WinBox(nbwin);
527    int tabrowWidth = 0, tabrowHeight = 0;
528    Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client");
529    Ttk_Box tabrowBox;
530    NotebookStyle nbstyle;
531
532    NotebookStyleOptions(nb, &nbstyle);
533
534    /* Notebook internal padding:
535     */
536    cavity = Ttk_PadBox(cavity, nbstyle.padding);
537
538    /* Layout for notebook background (base layout):
539     */
540    Ttk_PlaceLayout(nb->core.layout, nb->core.state, Ttk_WinBox(nbwin));
541
542    /* Place tabs:
543     */
544    TabrowSize(nb, nbstyle.tabOrient, &tabrowWidth, &tabrowHeight);
545    tabrowBox = Ttk_PadBox(
546		    Ttk_PositionBox(&cavity,
547			tabrowWidth + Ttk_PaddingWidth(nbstyle.tabMargins),
548			tabrowHeight + Ttk_PaddingHeight(nbstyle.tabMargins),
549			nbstyle.tabPosition),
550		    nbstyle.tabMargins);
551
552    SqueezeTabs(nb, tabrowWidth, tabrowBox.width, nbstyle.minTabWidth);
553    PlaceTabs(nb, tabrowBox, nbstyle.tabPlacement);
554
555    /* Layout for client area frame:
556     */
557    if (clientNode) {
558	Ttk_PlaceElement(nb->core.layout, clientNode, cavity);
559	cavity = Ttk_LayoutNodeInternalParcel(nb->core.layout, clientNode);
560    }
561
562    if (cavity.height <= 0) cavity.height = 1;
563    if (cavity.width <= 0) cavity.width = 1;
564
565    nb->notebook.clientArea = cavity;
566}
567
568/*
569 * NotebookPlaceSlave --
570 * 	Set the position and size of a child widget
571 * 	based on the current client area and slave options:
572 */
573static void NotebookPlaceSlave(Notebook *nb, int slaveIndex)
574{
575    Tab *tab = Ttk_SlaveData(nb->notebook.mgr, slaveIndex);
576    Tk_Window slaveWindow = Ttk_SlaveWindow(nb->notebook.mgr, slaveIndex);
577    Ttk_Box slaveBox =
578	Ttk_StickBox(Ttk_PadBox(nb->notebook.clientArea, tab->padding),
579	    Tk_ReqWidth(slaveWindow), Tk_ReqHeight(slaveWindow),tab->sticky);
580
581    Ttk_PlaceSlave(nb->notebook.mgr, slaveIndex,
582	slaveBox.x, slaveBox.y, slaveBox.width, slaveBox.height);
583}
584
585/* NotebookPlaceSlaves --
586 * 	Geometry manager hook.
587 */
588static void NotebookPlaceSlaves(void *recordPtr)
589{
590    Notebook *nb = recordPtr;
591    int currentIndex = nb->notebook.currentIndex;
592    if (currentIndex >= 0) {
593	NotebookDoLayout(nb);
594	NotebookPlaceSlave(nb, currentIndex);
595    }
596}
597
598/*
599 * SelectTab(nb, index) --
600 * 	Change the currently-selected tab.
601 */
602static void SelectTab(Notebook *nb, int index)
603{
604    Tab *tab = Ttk_SlaveData(nb->notebook.mgr,index);
605    int currentIndex = nb->notebook.currentIndex;
606
607    if (index == currentIndex) {
608	return;
609    }
610
611    if (TabState(nb, index) & TTK_STATE_DISABLED) {
612	return;
613    }
614
615    /* Unhide the tab if it is currently hidden and being selected.
616     */
617    if (tab->state == TAB_STATE_HIDDEN) {
618	tab->state = TAB_STATE_NORMAL;
619    }
620
621    if (currentIndex >= 0) {
622	Ttk_UnmapSlave(nb->notebook.mgr, currentIndex);
623    }
624
625    NotebookPlaceSlave(nb, index);
626
627    nb->notebook.currentIndex = index;
628    TtkRedisplayWidget(&nb->core);
629
630    TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged");
631}
632
633/* NextTab --
634 * 	Returns the index of the next tab after the specified tab
635 * 	in the normal state (e.g., not hidden or disabled),
636 * 	or -1 if all tabs are disabled or hidden.
637 */
638static int NextTab(Notebook *nb, int index)
639{
640    int nTabs = Ttk_NumberSlaves(nb->notebook.mgr);
641    int nextIndex;
642
643    /* Scan forward for following usable tab:
644     */
645    for (nextIndex = index + 1; nextIndex < nTabs; ++nextIndex) {
646	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, nextIndex);
647	if (tab->state == TAB_STATE_NORMAL) {
648	    return nextIndex;
649	}
650    }
651
652    /* Not found -- scan backwards.
653     */
654    for (nextIndex = index - 1; nextIndex >= 0; --nextIndex) {
655	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, nextIndex);
656	if (tab->state == TAB_STATE_NORMAL) {
657	    return nextIndex;
658	}
659    }
660
661    /* Still nothing.  Give up.
662     */
663    return -1;
664}
665
666/* SelectNearestTab --
667 * 	Handles the case where the current tab is forgotten, hidden,
668 * 	or destroyed.
669 *
670 * 	Unmap the current tab and schedule the next available one
671 * 	to be mapped at the next GM update.
672 */
673static void SelectNearestTab(Notebook *nb)
674{
675    int currentIndex = nb->notebook.currentIndex;
676    int nextIndex = NextTab(nb, currentIndex);
677
678    if (currentIndex >= 0) {
679	Ttk_UnmapSlave(nb->notebook.mgr, currentIndex);
680    }
681    if (currentIndex != nextIndex) {
682	TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged");
683    }
684
685    nb->notebook.currentIndex = nextIndex;
686    Ttk_ManagerLayoutChanged(nb->notebook.mgr);
687    TtkRedisplayWidget(&nb->core);
688}
689
690/* TabRemoved -- GM SlaveRemoved hook.
691 * 	Select the next tab if the current one is being removed.
692 * 	Adjust currentIndex to account for removed slave.
693 */
694static void TabRemoved(void *managerData, int index)
695{
696    Notebook *nb = managerData;
697    Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index);
698
699    if (index == nb->notebook.currentIndex) {
700	SelectNearestTab(nb);
701    }
702
703    if (index < nb->notebook.currentIndex) {
704	--nb->notebook.currentIndex;
705    }
706
707    DestroyTab(nb, tab);
708
709    TtkRedisplayWidget(&nb->core);
710}
711
712static int TabRequest(void *managerData, int index, int width, int height)
713{
714    return 1;
715}
716
717/* AddTab --
718 * 	Add new tab at specified index.
719 */
720static int AddTab(
721    Tcl_Interp *interp, Notebook *nb,
722    int destIndex, Tk_Window slaveWindow,
723    int objc, Tcl_Obj *const objv[])
724{
725    Tab *tab;
726    if (!Ttk_Maintainable(interp, slaveWindow, nb->core.tkwin)) {
727	return TCL_ERROR;
728    }
729#if 0 /* can't happen */
730    if (Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow) >= 0) {
731	Tcl_AppendResult(interp,
732	    Tk_PathName(slaveWindow), " already added",
733	    NULL);
734	return TCL_ERROR;
735    }
736#endif
737
738    /* Create and insert tab.
739     */
740    tab = CreateTab(interp, nb, slaveWindow);
741    if (!tab) {
742	return TCL_ERROR;
743    }
744    if (ConfigureTab(interp, nb, tab, slaveWindow, objc, objv) != TCL_OK) {
745	DestroyTab(nb, tab);
746	return TCL_ERROR;
747    }
748
749    Ttk_InsertSlave(nb->notebook.mgr, destIndex, slaveWindow, tab);
750
751    /* Adjust indices and/or autoselect first tab:
752     */
753    if (nb->notebook.currentIndex < 0) {
754	SelectTab(nb, destIndex);
755    } else if (nb->notebook.currentIndex >= destIndex) {
756	++nb->notebook.currentIndex;
757    }
758
759    return TCL_OK;
760}
761
762static Ttk_ManagerSpec NotebookManagerSpec = {
763    { "notebook", Ttk_GeometryRequestProc, Ttk_LostSlaveProc },
764    NotebookSize,
765    NotebookPlaceSlaves,
766    TabRequest,
767    TabRemoved
768};
769
770/*------------------------------------------------------------------------
771 * +++ Event handlers.
772 */
773
774/* NotebookEventHandler --
775 * 	Tracks the active tab.
776 */
777static const int NotebookEventMask
778    = StructureNotifyMask
779    | PointerMotionMask
780    | LeaveWindowMask
781    ;
782static void NotebookEventHandler(ClientData clientData, XEvent *eventPtr)
783{
784    Notebook *nb = clientData;
785
786    if (eventPtr->type == DestroyNotify) { /* Remove self */
787	Tk_DeleteEventHandler(nb->core.tkwin,
788	    NotebookEventMask, NotebookEventHandler, clientData);
789    } else if (eventPtr->type == MotionNotify) {
790	int index = IdentifyTab(nb, eventPtr->xmotion.x, eventPtr->xmotion.y);
791	ActivateTab(nb, index);
792    } else if (eventPtr->type == LeaveNotify) {
793	ActivateTab(nb, -1);
794    }
795}
796
797/*------------------------------------------------------------------------
798 * +++ Utilities.
799 */
800
801/* FindTabIndex --
802 *	Find the index of the specified tab.
803 *	Tab identifiers are one of:
804 *
805 *	+ positional specifications @x,y,
806 *	+ "current",
807 *	+ numeric indices [0..nTabs],
808 *	+ slave window names
809 *
810 *	Stores index of specified tab in *index_rtn, -1 if not found.
811 *
812 *	Returns TCL_ERROR and leaves an error message in interp->result
813 *	if the tab identifier was incorrect.
814 *
815 *	See also: GetTabIndex.
816 */
817static int FindTabIndex(
818    Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn)
819{
820    const char *string = Tcl_GetString(objPtr);
821    int x, y;
822
823    *index_rtn = -1;
824
825    /* Check for @x,y ...
826     */
827    if (string[0] == '@' && sscanf(string, "@%d,%d",&x,&y) == 2) {
828	*index_rtn = IdentifyTab(nb, x, y);
829	return TCL_OK;
830    }
831
832    /* ... or "current" ...
833     */
834    if (!strcmp(string, "current")) {
835	*index_rtn = nb->notebook.currentIndex;
836	return TCL_OK;
837    }
838
839    /* ... or integer index or slave window name:
840     */
841    if (Ttk_GetSlaveIndexFromObj(
842	    interp, nb->notebook.mgr, objPtr, index_rtn) == TCL_OK)
843    {
844	return TCL_OK;
845    }
846
847    /* Nothing matched; Ttk_GetSlaveIndexFromObj will have left error message.
848     */
849    return TCL_ERROR;
850}
851
852/* GetTabIndex --
853 * 	Get the index of an existing tab.
854 * 	Tab identifiers are as per FindTabIndex.
855 * 	Returns TCL_ERROR if the tab does not exist.
856 */
857static int GetTabIndex(
858    Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn)
859{
860    int status = FindTabIndex(interp, nb, objPtr, index_rtn);
861
862    if (status == TCL_OK && *index_rtn < 0) {
863	Tcl_ResetResult(interp);
864	Tcl_AppendResult(interp,
865	    "tab '", Tcl_GetString(objPtr), "' not found",
866	    NULL);
867	status = TCL_ERROR;
868    }
869    return status;
870}
871
872/*------------------------------------------------------------------------
873 * +++ Widget command routines.
874 */
875
876/* $nb add window ?options ... ?
877 */
878static int NotebookAddCommand(
879    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
880{
881    Notebook *nb = recordPtr;
882    int index = Ttk_NumberSlaves(nb->notebook.mgr);
883    Tk_Window slaveWindow;
884    int slaveIndex;
885    Tab *tab;
886
887    if (objc <= 2 || objc % 2 != 1) {
888	Tcl_WrongNumArgs(interp, 2, objv, "window ?-option value ...?");
889	return TCL_ERROR;
890    }
891
892    slaveWindow = Tk_NameToWindow(interp,Tcl_GetString(objv[2]),nb->core.tkwin);
893    if (!slaveWindow) {
894	return TCL_ERROR;
895    }
896    slaveIndex = Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow);
897
898    if (slaveIndex < 0) { /* New tab */
899	return AddTab(interp, nb, index, slaveWindow, objc-3,objv+3);
900    }
901
902    tab = Ttk_SlaveData(nb->notebook.mgr, slaveIndex);
903    if (tab->state == TAB_STATE_HIDDEN) {
904	tab->state = TAB_STATE_NORMAL;
905    }
906    if (ConfigureTab(interp, nb, tab, slaveWindow, objc-4,objv+4) != TCL_OK) {
907	return TCL_ERROR;
908    }
909
910    TtkRedisplayWidget(&nb->core);
911
912    return TCL_OK;
913}
914
915/* $nb insert $index $tab ?-option value ...?
916 * 	Insert new tab, or move existing one.
917 */
918static int NotebookInsertCommand(
919    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
920{
921    Notebook *nb = recordPtr;
922    int current = nb->notebook.currentIndex;
923    int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr);
924    int srcIndex, destIndex;
925
926    if (objc < 4) {
927	Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?");
928	return TCL_ERROR;
929    }
930
931    if (!strcmp(Tcl_GetString(objv[2]), "end")) {
932	destIndex = Ttk_NumberSlaves(nb->notebook.mgr);
933    } else if (TCL_OK != Ttk_GetSlaveIndexFromObj(
934		interp, nb->notebook.mgr, objv[2], &destIndex)) {
935	return TCL_ERROR;
936    }
937
938    if (Tcl_GetString(objv[3])[0] == '.') {
939	/* Window name -- could be new or existing slave.
940	 */
941	Tk_Window slaveWindow =
942	    Tk_NameToWindow(interp,Tcl_GetString(objv[3]),nb->core.tkwin);
943
944	if (!slaveWindow) {
945	    return TCL_ERROR;
946	}
947
948	srcIndex = Ttk_SlaveIndex(nb->notebook.mgr, slaveWindow);
949	if (srcIndex < 0) {	/* New slave */
950	    return AddTab(interp, nb, destIndex, slaveWindow, objc-4,objv+4);
951	}
952    } else if (Ttk_GetSlaveIndexFromObj(
953		interp, nb->notebook.mgr, objv[3], &srcIndex) != TCL_OK)
954    {
955	return TCL_ERROR;
956    }
957
958    /* Move existing slave:
959     */
960    if (ConfigureTab(interp, nb,
961	     Ttk_SlaveData(nb->notebook.mgr,srcIndex),
962	     Ttk_SlaveWindow(nb->notebook.mgr,srcIndex),
963	     objc-4,objv+4) != TCL_OK)
964    {
965	return TCL_ERROR;
966    }
967
968    if (destIndex >= nSlaves) {
969	destIndex  = nSlaves - 1;
970    }
971    Ttk_ReorderSlave(nb->notebook.mgr, srcIndex, destIndex);
972
973    /* Adjust internal indexes:
974     */
975    nb->notebook.activeIndex = -1;
976    if (current == srcIndex) {
977	nb->notebook.currentIndex = destIndex;
978    } else if (destIndex <= current && current < srcIndex) {
979	++nb->notebook.currentIndex;
980    } else if (srcIndex < current && current <= destIndex) {
981	--nb->notebook.currentIndex;
982    }
983
984    TtkRedisplayWidget(&nb->core);
985
986    return TCL_OK;
987}
988
989/* $nb forget $tab --
990 * 	Removes the specified tab.
991 */
992static int NotebookForgetCommand(
993    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
994{
995    Notebook *nb = recordPtr;
996    int index;
997
998    if (objc != 3) {
999	Tcl_WrongNumArgs(interp, 2, objv, "tab");
1000	return TCL_ERROR;
1001    }
1002
1003    if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1004	return TCL_ERROR;
1005    }
1006
1007    Ttk_ForgetSlave(nb->notebook.mgr, index);
1008    TtkRedisplayWidget(&nb->core);
1009
1010    return TCL_OK;
1011}
1012
1013/* $nb hide $tab --
1014 * 	Hides the specified tab.
1015 */
1016static int NotebookHideCommand(
1017    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1018{
1019    Notebook *nb = recordPtr;
1020    int index;
1021    Tab *tab;
1022
1023    if (objc != 3) {
1024	Tcl_WrongNumArgs(interp, 2, objv, "tab");
1025	return TCL_ERROR;
1026    }
1027
1028    if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1029	return TCL_ERROR;
1030    }
1031
1032    tab = Ttk_SlaveData(nb->notebook.mgr, index);
1033    tab->state = TAB_STATE_HIDDEN;
1034    if (index == nb->notebook.currentIndex) {
1035	SelectNearestTab(nb);
1036    }
1037
1038    TtkRedisplayWidget(&nb->core);
1039
1040    return TCL_OK;
1041}
1042
1043/* $nb identify $x $y --
1044 * 	Returns name of tab element at $x,$y; empty string if none.
1045 */
1046static int NotebookIdentifyCommand(
1047    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1048{
1049    static const char *whatTable[] = { "element", "tab", NULL };
1050    enum { IDENTIFY_ELEMENT, IDENTIFY_TAB };
1051    int what = IDENTIFY_ELEMENT;
1052    Notebook *nb = recordPtr;
1053    Ttk_Element element = NULL;
1054    int x, y, tabIndex;
1055
1056    if (objc < 4 || objc > 5) {
1057	Tcl_WrongNumArgs(interp, 2,objv, "?what? x y");
1058	return TCL_ERROR;
1059    }
1060
1061    if (   Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK
1062	|| Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK
1063	|| (objc == 5 &&
1064	    Tcl_GetIndexFromObj(interp, objv[2], whatTable, "option", 0, &what)
1065		!= TCL_OK)
1066    ) {
1067	return TCL_ERROR;
1068    }
1069
1070    tabIndex = IdentifyTab(nb, x, y);
1071    if (tabIndex >= 0) {
1072	Tab *tab = Ttk_SlaveData(nb->notebook.mgr, tabIndex);
1073	Ttk_State state = TabState(nb, tabIndex);
1074	Ttk_Layout tabLayout = nb->notebook.tabLayout;
1075
1076	Ttk_RebindSublayout(tabLayout, tab);
1077	Ttk_PlaceLayout(tabLayout, state, tab->parcel);
1078
1079	element = Ttk_IdentifyElement(tabLayout, x, y);
1080    }
1081
1082    switch (what) {
1083	case IDENTIFY_ELEMENT:
1084	    if (element) {
1085		const char *elementName = Ttk_ElementName(element);
1086		Tcl_SetObjResult(interp,Tcl_NewStringObj(elementName,-1));
1087	    }
1088	    break;
1089	case IDENTIFY_TAB:
1090	    if (tabIndex >= 0) {
1091		Tcl_SetObjResult(interp, Tcl_NewIntObj(tabIndex));
1092	    }
1093	    break;
1094    }
1095    return TCL_OK;
1096}
1097
1098/* $nb index $item --
1099 * 	Returns the integer index of the tab specified by $item,
1100 * 	the empty string if $item does not identify a tab.
1101 *	See above for valid item formats.
1102 */
1103static int NotebookIndexCommand(
1104    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1105{
1106    Notebook *nb = recordPtr;
1107    int index, status;
1108
1109    if (objc != 3) {
1110	Tcl_WrongNumArgs(interp, 2, objv, "tab");
1111	return TCL_ERROR;
1112    }
1113
1114    /*
1115     * Special-case for "end":
1116     */
1117    if (!strcmp("end", Tcl_GetString(objv[2]))) {
1118	int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr);
1119	Tcl_SetObjResult(interp, Tcl_NewIntObj(nSlaves));
1120	return TCL_OK;
1121    }
1122
1123    status = FindTabIndex(interp, nb, objv[2], &index);
1124    if (status == TCL_OK && index >= 0) {
1125	Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
1126    }
1127
1128    return status;
1129}
1130
1131/* $nb select ?$item? --
1132 * 	Select the specified tab, or return the widget path of
1133 * 	the currently-selected pane.
1134 */
1135static int NotebookSelectCommand(
1136    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1137{
1138    Notebook *nb = recordPtr;
1139
1140    if (objc == 2) {
1141	if (nb->notebook.currentIndex >= 0) {
1142	    Tk_Window pane = Ttk_SlaveWindow(
1143		nb->notebook.mgr, nb->notebook.currentIndex);
1144	    Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(pane), -1));
1145	}
1146	return TCL_OK;
1147    } else if (objc == 3) {
1148	int index, status = GetTabIndex(interp, nb, objv[2], &index);
1149	if (status == TCL_OK) {
1150	    SelectTab(nb, index);
1151	}
1152	return status;
1153    } /*else*/
1154    Tcl_WrongNumArgs(interp, 2, objv, "?tab?");
1155    return TCL_ERROR;
1156}
1157
1158/* $nb tabs --
1159 * 	Return list of tabs.
1160 */
1161static int NotebookTabsCommand(
1162    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1163{
1164    Notebook *nb = recordPtr;
1165    Ttk_Manager *mgr = nb->notebook.mgr;
1166    Tcl_Obj *result;
1167    int i;
1168
1169    if (objc != 2) {
1170	Tcl_WrongNumArgs(interp, 2, objv, "");
1171	return TCL_ERROR;
1172    }
1173
1174    result = Tcl_NewListObj(0, NULL);
1175    for (i = 0; i < Ttk_NumberSlaves(mgr); ++i) {
1176	const char *pathName = Tk_PathName(Ttk_SlaveWindow(mgr,i));
1177	Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(pathName,-1));
1178    }
1179    Tcl_SetObjResult(interp, result);
1180
1181    return TCL_OK;
1182}
1183
1184/* $nb tab $tab ?-option ?value -option value...??
1185 */
1186static int NotebookTabCommand(
1187    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1188{
1189    Notebook *nb = recordPtr;
1190    Ttk_Manager *mgr = nb->notebook.mgr;
1191    int index;
1192    Tk_Window slaveWindow;
1193    Tab *tab;
1194
1195    if (objc < 3) {
1196	Tcl_WrongNumArgs(interp, 2, objv, "tab ?-option ?value??...");
1197	return TCL_ERROR;
1198    }
1199
1200    if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1201	return TCL_ERROR;
1202    }
1203
1204    tab = Ttk_SlaveData(mgr, index);
1205    slaveWindow = Ttk_SlaveWindow(mgr, index);
1206
1207    if (objc == 3) {
1208	return TtkEnumerateOptions(interp, tab,
1209	    PaneOptionSpecs, nb->notebook.paneOptionTable, slaveWindow);
1210    } else if (objc == 4) {
1211	return TtkGetOptionValue(interp, tab, objv[3],
1212	    nb->notebook.paneOptionTable, slaveWindow);
1213    } /* else */
1214
1215    if (ConfigureTab(interp, nb, tab, slaveWindow, objc-3,objv+3) != TCL_OK) {
1216	return TCL_ERROR;
1217    }
1218
1219    /* If the current tab has become disabled or hidden,
1220     * select the next nondisabled, unhidden one:
1221     */
1222    if (index == nb->notebook.currentIndex && tab->state != TAB_STATE_NORMAL) {
1223	SelectNearestTab(nb);
1224    }
1225
1226    return TCL_OK;
1227}
1228
1229/* Subcommand table:
1230 */
1231static const Ttk_Ensemble NotebookCommands[] = {
1232    { "add",    	NotebookAddCommand,0 },
1233    { "configure",	TtkWidgetConfigureCommand,0 },
1234    { "cget",		TtkWidgetCgetCommand,0 },
1235    { "forget",		NotebookForgetCommand,0 },
1236    { "hide",		NotebookHideCommand,0 },
1237    { "identify",	NotebookIdentifyCommand,0 },
1238    { "index",		NotebookIndexCommand,0 },
1239    { "insert",  	NotebookInsertCommand,0 },
1240    { "instate",	TtkWidgetInstateCommand,0 },
1241    { "select",		NotebookSelectCommand,0 },
1242    { "state",  	TtkWidgetStateCommand,0 },
1243    { "tab",   		NotebookTabCommand,0 },
1244    { "tabs",   	NotebookTabsCommand,0 },
1245    { 0,0,0 }
1246};
1247
1248/*------------------------------------------------------------------------
1249 * +++ Widget class hooks.
1250 */
1251
1252static void NotebookInitialize(Tcl_Interp *interp, void *recordPtr)
1253{
1254    Notebook *nb = recordPtr;
1255
1256    nb->notebook.mgr = Ttk_CreateManager(
1257	    &NotebookManagerSpec, recordPtr, nb->core.tkwin);
1258
1259    nb->notebook.tabOptionTable = Tk_CreateOptionTable(interp,TabOptionSpecs);
1260    nb->notebook.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs);
1261
1262    nb->notebook.currentIndex = -1;
1263    nb->notebook.activeIndex = -1;
1264    nb->notebook.tabLayout = 0;
1265
1266    nb->notebook.clientArea = Ttk_MakeBox(0,0,1,1);
1267
1268    Tk_CreateEventHandler(
1269	nb->core.tkwin, NotebookEventMask, NotebookEventHandler, recordPtr);
1270}
1271
1272static void NotebookCleanup(void *recordPtr)
1273{
1274    Notebook *nb = recordPtr;
1275
1276    Ttk_DeleteManager(nb->notebook.mgr);
1277    if (nb->notebook.tabLayout)
1278	Ttk_FreeLayout(nb->notebook.tabLayout);
1279}
1280
1281static int NotebookConfigure(Tcl_Interp *interp, void *clientData, int mask)
1282{
1283    Notebook *nb = clientData;
1284
1285    /*
1286     * Error-checks:
1287     */
1288    if (nb->notebook.paddingObj) {
1289	/* Check for valid -padding: */
1290	Ttk_Padding unused;
1291	if (Ttk_GetPaddingFromObj(
1292		    interp, nb->core.tkwin, nb->notebook.paddingObj, &unused)
1293		!= TCL_OK) {
1294	    return TCL_ERROR;
1295	}
1296    }
1297
1298    return TtkCoreConfigure(interp, clientData, mask);
1299}
1300
1301/* NotebookGetLayout  --
1302 * 	GetLayout widget hook.
1303 */
1304static Ttk_Layout NotebookGetLayout(
1305    Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
1306{
1307    Notebook *nb = recordPtr;
1308    Ttk_Layout notebookLayout = TtkWidgetGetLayout(interp, theme, recordPtr);
1309    Ttk_Layout tabLayout;
1310
1311    if (!notebookLayout) {
1312	return NULL;
1313    }
1314
1315    tabLayout = Ttk_CreateSublayout(
1316	interp, theme, notebookLayout, ".Tab",	nb->notebook.tabOptionTable);
1317
1318    if (tabLayout) {
1319	if (nb->notebook.tabLayout) {
1320	    Ttk_FreeLayout(nb->notebook.tabLayout);
1321	}
1322	nb->notebook.tabLayout = tabLayout;
1323    }
1324
1325    return notebookLayout;
1326}
1327
1328/*------------------------------------------------------------------------
1329 * +++ Display routines.
1330 */
1331
1332static void DisplayTab(Notebook *nb, int index, Drawable d)
1333{
1334    Ttk_Layout tabLayout = nb->notebook.tabLayout;
1335    Tab *tab = Ttk_SlaveData(nb->notebook.mgr, index);
1336    Ttk_State state = TabState(nb, index);
1337
1338    if (tab->state != TAB_STATE_HIDDEN) {
1339	Ttk_RebindSublayout(tabLayout, tab);
1340	Ttk_PlaceLayout(tabLayout, state, tab->parcel);
1341	Ttk_DrawLayout(tabLayout, state, d);
1342    }
1343}
1344
1345static void NotebookDisplay(void *clientData, Drawable d)
1346{
1347    Notebook *nb = clientData;
1348    int nSlaves = Ttk_NumberSlaves(nb->notebook.mgr);
1349    int index;
1350
1351    /* Draw notebook background (base layout):
1352     */
1353    Ttk_DrawLayout(nb->core.layout, nb->core.state, d);
1354
1355    /* Draw tabs from left to right, but draw the current tab last
1356     * so it will overwrite its neighbors.
1357     */
1358    for (index = 0; index < nSlaves; ++index) {
1359	if (index != nb->notebook.currentIndex) {
1360	    DisplayTab(nb, index, d);
1361	}
1362    }
1363    if (nb->notebook.currentIndex >= 0) {
1364	DisplayTab(nb, nb->notebook.currentIndex, d);
1365    }
1366}
1367
1368/*------------------------------------------------------------------------
1369 * +++ Widget specification and layout definitions.
1370 */
1371
1372static WidgetSpec NotebookWidgetSpec =
1373{
1374    "TNotebook",		/* className */
1375    sizeof(Notebook),		/* recordSize */
1376    NotebookOptionSpecs,	/* optionSpecs */
1377    NotebookCommands,		/* subcommands */
1378    NotebookInitialize,		/* initializeProc */
1379    NotebookCleanup,		/* cleanupProc */
1380    NotebookConfigure,		/* configureProc */
1381    TtkNullPostConfigure,	/* postConfigureProc */
1382    NotebookGetLayout, 		/* getLayoutProc */
1383    NotebookSize,		/* geometryProc */
1384    NotebookDoLayout,		/* layoutProc */
1385    NotebookDisplay		/* displayProc */
1386};
1387
1388TTK_BEGIN_LAYOUT(NotebookLayout)
1389    TTK_NODE("Notebook.client", TTK_FILL_BOTH)
1390TTK_END_LAYOUT
1391
1392TTK_BEGIN_LAYOUT(TabLayout)
1393    TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
1394	TTK_GROUP("Notebook.padding", TTK_PACK_TOP|TTK_FILL_BOTH,
1395	    TTK_GROUP("Notebook.focus", TTK_PACK_TOP|TTK_FILL_BOTH,
1396		TTK_NODE("Notebook.label", TTK_PACK_TOP))))
1397TTK_END_LAYOUT
1398
1399/*------------------------------------------------------------------------
1400 * +++ Initialization.
1401 */
1402
1403MODULE_SCOPE
1404void TtkNotebook_Init(Tcl_Interp *interp)
1405{
1406    Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp);
1407
1408    Ttk_RegisterLayout(themePtr, "Tab", TabLayout);
1409    Ttk_RegisterLayout(themePtr, "TNotebook", NotebookLayout);
1410
1411    RegisterWidget(interp, "ttk::notebook", &NotebookWidgetSpec);
1412}
1413
1414/*EOF*/
1415