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