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