2 * Copyright (c) 2004, Joe English
9 #include "ttkManager.h"
11 #define MIN(a,b) ((a) < (b) ? (a) : (b))
12 #define MAX(a,b) ((a) > (b) ? (a) : (b))
14 /*------------------------------------------------------------------------
18 #define DEFAULT_MIN_TAB_WIDTH 24
20 static const char *const TabStateStrings[] = { "normal", "disabled", "hidden", 0 };
22 TAB_STATE_NORMAL, TAB_STATE_DISABLED, TAB_STATE_HIDDEN
29 int width, height; /* Requested size of tab */
30 Ttk_Box parcel; /* Tab position */
36 /* Child window options:
38 Tcl_Obj *paddingObj; /* Padding inside pane */
48 Tcl_Obj *underlineObj;
52 /* Two different option tables are used for tabs:
53 * TabOptionSpecs is used to draw the tab, and only includes resources
54 * relevant to the tab.
56 * PaneOptionSpecs includes additional options for child window placement
57 * and is used to configure the content window.
59 static Tk_OptionSpec TabOptionSpecs[] =
61 {TK_OPTION_STRING_TABLE, "-state", "", "",
62 "normal", -1,Tk_Offset(Tab,state),
63 0, (void *)TabStateStrings, 0 },
64 {TK_OPTION_STRING, "-text", "text", "Text", "",
65 Tk_Offset(Tab,textObj), -1, 0,0,GEOMETRY_CHANGED },
66 {TK_OPTION_STRING, "-image", "image", "Image", NULL/*default*/,
67 Tk_Offset(Tab,imageObj), -1, TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
68 {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
69 NULL, Tk_Offset(Tab,compoundObj), -1,
70 TK_OPTION_NULL_OK, (void *)ttkCompoundStrings, GEOMETRY_CHANGED },
71 {TK_OPTION_INT, "-underline", "underline", "Underline", "-1",
72 Tk_Offset(Tab,underlineObj), -1, 0,0,GEOMETRY_CHANGED },
73 {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0 }
76 static Tk_OptionSpec PaneOptionSpecs[] =
78 {TK_OPTION_STRING, "-padding", "padding", "Padding", "0",
79 Tk_Offset(Tab,paddingObj), -1, 0,0,GEOMETRY_CHANGED },
80 {TK_OPTION_STRING, "-sticky", "sticky", "Sticky", "nsew",
81 Tk_Offset(Tab,stickyObj), -1, 0,0,GEOMETRY_CHANGED },
83 WIDGET_INHERIT_OPTIONS(TabOptionSpecs)
86 /*------------------------------------------------------------------------
87 * +++ Notebook resources.
91 Tcl_Obj *widthObj; /* Default width */
92 Tcl_Obj *heightObj; /* Default height */
93 Tcl_Obj *paddingObj; /* Padding around notebook */
95 Ttk_Manager *mgr; /* Geometry manager */
96 Tk_OptionTable tabOptionTable; /* Tab options */
97 Tk_OptionTable paneOptionTable; /* Tab+pane options */
98 int currentIndex; /* index of currently selected tab */
99 int activeIndex; /* index of currently active tab */
100 Ttk_Layout tabLayout; /* Sublayout for tabs */
102 Ttk_Box clientArea; /* Where to pack content widgets */
108 NotebookPart notebook;
111 static Tk_OptionSpec NotebookOptionSpecs[] =
113 {TK_OPTION_INT, "-width", "width", "Width", "0",
114 Tk_Offset(Notebook,notebook.widthObj),-1,
115 0,0,GEOMETRY_CHANGED },
116 {TK_OPTION_INT, "-height", "height", "Height", "0",
117 Tk_Offset(Notebook,notebook.heightObj),-1,
118 0,0,GEOMETRY_CHANGED },
119 {TK_OPTION_STRING, "-padding", "padding", "Padding", NULL,
120 Tk_Offset(Notebook,notebook.paddingObj),-1,
121 TK_OPTION_NULL_OK,0,GEOMETRY_CHANGED },
123 WIDGET_TAKEFOCUS_TRUE,
124 WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
127 /* Notebook style options:
131 Ttk_PositionSpec tabPosition; /* Where to place tabs */
132 Ttk_Padding tabMargins; /* Margins around tab row */
133 Ttk_PositionSpec tabPlacement; /* How to pack tabs within tab row */
134 Ttk_Orient tabOrient; /* ... */
135 int minTabWidth; /* Minimum tab width */
136 Ttk_Padding padding; /* External padding */
139 static void NotebookStyleOptions(Notebook *nb, NotebookStyle *nbstyle)
143 nbstyle->tabPosition = TTK_PACK_TOP | TTK_STICK_W;
144 if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabposition", 0)) != 0) {
145 TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPosition);
148 /* Guess default tabPlacement as function of tabPosition:
150 if (nbstyle->tabPosition & TTK_PACK_LEFT) {
151 nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_E;
152 } else if (nbstyle->tabPosition & TTK_PACK_RIGHT) {
153 nbstyle->tabPlacement = TTK_PACK_TOP | TTK_STICK_W;
154 } else if (nbstyle->tabPosition & TTK_PACK_BOTTOM) {
155 nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_N;
156 } else { /* Assume TTK_PACK_TOP */
157 nbstyle->tabPlacement = TTK_PACK_LEFT | TTK_STICK_S;
159 if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabplacement", 0)) != 0) {
160 TtkGetLabelAnchorFromObj(NULL, objPtr, &nbstyle->tabPlacement);
163 /* Compute tabOrient as function of tabPlacement:
165 if (nbstyle->tabPlacement & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) {
166 nbstyle->tabOrient = TTK_ORIENT_HORIZONTAL;
168 nbstyle->tabOrient = TTK_ORIENT_VERTICAL;
171 nbstyle->tabMargins = Ttk_UniformPadding(0);
172 if ((objPtr = Ttk_QueryOption(nb->core.layout, "-tabmargins", 0)) != 0) {
173 Ttk_GetBorderFromObj(NULL, objPtr, &nbstyle->tabMargins);
176 nbstyle->padding = Ttk_UniformPadding(0);
177 if ((objPtr = Ttk_QueryOption(nb->core.layout, "-padding", 0)) != 0) {
178 Ttk_GetPaddingFromObj(NULL,nb->core.tkwin,objPtr,&nbstyle->padding);
181 nbstyle->minTabWidth = DEFAULT_MIN_TAB_WIDTH;
182 if ((objPtr = Ttk_QueryOption(nb->core.layout, "-mintabwidth", 0)) != 0) {
183 Tcl_GetIntFromObj(NULL, objPtr, &nbstyle->minTabWidth);
187 /*------------------------------------------------------------------------
188 * +++ Tab management.
191 static Tab *CreateTab(Tcl_Interp *interp, Notebook *nb, Tk_Window window)
193 Tk_OptionTable optionTable = nb->notebook.paneOptionTable;
194 Tab *record = (Tab *)ckalloc(sizeof(Tab));
195 memset(record, 0, sizeof(Tab));
197 if (Tk_InitOptions(interp, (char *)record, optionTable, window) != TCL_OK) {
205 static void DestroyTab(Notebook *nb, Tab *tab)
208 Tk_FreeConfigOptions(record, nb->notebook.paneOptionTable, nb->core.tkwin);
212 static int ConfigureTab(
213 Tcl_Interp *interp, Notebook *nb, Tab *tab, Tk_Window window,
214 int objc, Tcl_Obj *const objv[])
216 Ttk_Sticky sticky = tab->sticky;
217 Ttk_Padding padding = tab->padding;
218 Tk_SavedOptions savedOptions;
221 if (Tk_SetOptions(interp, (void *)tab, nb->notebook.paneOptionTable,
222 objc, objv, window, &savedOptions, &mask) != TCL_OK)
228 * @@@ TODO: validate -image option.
230 if (Ttk_GetStickyFromObj(interp, tab->stickyObj, &sticky) != TCL_OK)
234 if (Ttk_GetPaddingFromObj(interp, window, tab->paddingObj, &padding)
240 tab->sticky = sticky;
241 tab->padding = padding;
243 Tk_FreeSavedOptions(&savedOptions);
244 Ttk_ManagerSizeChanged(nb->notebook.mgr);
245 TtkRedisplayWidget(&nb->core);
249 Tk_RestoreSavedOptions(&savedOptions);
255 * Return the index of the tab at point x,y,
256 * or -1 if no tab at that point.
258 static int IdentifyTab(Notebook *nb, int x, int y)
261 for (index = 0; index < Ttk_NumberContent(nb->notebook.mgr); ++index) {
262 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr,index);
263 if ( tab->state != TAB_STATE_HIDDEN
264 && Ttk_BoxContains(tab->parcel, x,y))
274 * Set the active tab index, redisplay if necessary.
276 static void ActivateTab(Notebook *nb, int index)
278 if (index != nb->notebook.activeIndex) {
279 nb->notebook.activeIndex = index;
280 TtkRedisplayWidget(&nb->core);
286 * Return the state of the specified tab, based on
287 * notebook state, currentIndex, activeIndex, and user-specified tab state.
288 * The USER1 bit is set for the leftmost visible tab, and USER2
289 * is set for the rightmost visible tab.
291 static Ttk_State TabState(Notebook *nb, int index)
293 Ttk_State state = nb->core.state;
294 Tab *itab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
297 if (index == nb->notebook.currentIndex) {
298 state |= TTK_STATE_SELECTED;
300 state &= ~TTK_STATE_FOCUS;
303 if (index == nb->notebook.activeIndex) {
304 state |= TTK_STATE_ACTIVE;
306 for (i = 0; i < Ttk_NumberContent(nb->notebook.mgr); ++i) {
307 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, i);
308 if (tab->state == TAB_STATE_HIDDEN) {
312 state |= TTK_STATE_USER1;
316 for (i = Ttk_NumberContent(nb->notebook.mgr) - 1; i != -1; --i) {
317 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, i);
318 if (tab->state == TAB_STATE_HIDDEN) {
322 state |= TTK_STATE_USER2;
326 if (itab->state == TAB_STATE_DISABLED) {
327 state |= TTK_STATE_DISABLED;
333 /*------------------------------------------------------------------------
334 * +++ Geometry management - size computation.
338 * Compute max height and total width of all tabs (horizontal layouts)
339 * or total height and max width (vertical layouts).
340 * The -mintabwidth style option is taken into account (for the width
344 * Sets width and height fields for all tabs.
347 * Hidden tabs are included in the perpendicular computation
348 * (max height/width) but not parallel (total width/height).
350 static void TabrowSize(
351 Notebook *nb, Ttk_Orient orient, int minTabWidth, int *widthPtr, int *heightPtr)
353 Ttk_Layout tabLayout = nb->notebook.tabLayout;
354 int tabrowWidth = 0, tabrowHeight = 0;
357 for (i = 0; i < Ttk_NumberContent(nb->notebook.mgr); ++i) {
358 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, i);
359 Ttk_State tabState = TabState(nb,i);
361 Ttk_RebindSublayout(tabLayout, tab);
362 Ttk_LayoutSize(tabLayout,tabState,&tab->width,&tab->height);
363 tab->width = MAX(tab->width, minTabWidth);
365 if (orient == TTK_ORIENT_HORIZONTAL) {
366 tabrowHeight = MAX(tabrowHeight, tab->height);
367 if (tab->state != TAB_STATE_HIDDEN) { tabrowWidth += tab->width; }
369 tabrowWidth = MAX(tabrowWidth, tab->width);
370 if (tab->state != TAB_STATE_HIDDEN) { tabrowHeight += tab->height; }
374 *widthPtr = tabrowWidth;
375 *heightPtr = tabrowHeight;
378 /* NotebookSize -- GM and widget size hook.
380 * Total height is tab height + client area height + pane internal padding
381 * Total width is max(client width, tab width) + pane internal padding
382 * Client area size determined by max size of content windows,
383 * overridden by -width and/or -height if nonzero.
386 static int NotebookSize(void *clientData, int *widthPtr, int *heightPtr)
388 Notebook *nb = (Notebook *)clientData;
389 NotebookStyle nbstyle;
391 Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client");
392 int clientWidth = 0, clientHeight = 0,
393 reqWidth = 0, reqHeight = 0,
394 tabrowWidth = 0, tabrowHeight = 0;
397 NotebookStyleOptions(nb, &nbstyle);
399 /* Compute max requested size of all content windows:
401 for (i = 0; i < Ttk_NumberContent(nb->notebook.mgr); ++i) {
402 Tk_Window window = Ttk_ContentWindow(nb->notebook.mgr, i);
403 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, i);
405 = Tk_ReqWidth(window) + Ttk_PaddingWidth(tab->padding);
407 = Tk_ReqHeight(window) + Ttk_PaddingHeight(tab->padding);
409 clientWidth = MAX(clientWidth, width);
410 clientHeight = MAX(clientHeight, height);
413 /* Client width/height overridable by widget options:
415 Tcl_GetIntFromObj(NULL, nb->notebook.widthObj,&reqWidth);
416 Tcl_GetIntFromObj(NULL, nb->notebook.heightObj,&reqHeight);
418 clientWidth = reqWidth;
420 clientHeight = reqHeight;
424 TabrowSize(nb, nbstyle.tabOrient, nbstyle.minTabWidth, &tabrowWidth, &tabrowHeight);
425 tabrowHeight += Ttk_PaddingHeight(nbstyle.tabMargins);
426 tabrowWidth += Ttk_PaddingWidth(nbstyle.tabMargins);
428 /* Account for exterior and interior padding:
430 padding = nbstyle.padding;
433 Ttk_LayoutNodeInternalPadding(nb->core.layout, clientNode);
434 padding = Ttk_AddPadding(padding, ipad);
437 if (nbstyle.tabPosition & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) {
438 *widthPtr = MAX(tabrowWidth, clientWidth) + Ttk_PaddingWidth(padding);
439 *heightPtr = tabrowHeight + clientHeight + Ttk_PaddingHeight(padding);
441 *widthPtr = tabrowWidth + clientWidth + Ttk_PaddingWidth(padding);
442 *heightPtr = MAX(tabrowHeight,clientHeight) + Ttk_PaddingHeight(padding);
448 /*------------------------------------------------------------------------
449 * +++ Geometry management - layout.
453 * Squeeze or stretch tabs to fit within the tab area parcel.
454 * This happens independently of the -mintabwidth style option.
456 * All tabs are adjusted by an equal amount.
458 * @@@ <<NOTE-TABPOSITION>> bug: only works for horizontal orientations
459 * @@@ <<NOTE-SQUEEZE-HIDDEN>> does not account for hidden tabs.
462 static void SqueezeTabs(
463 Notebook *nb, int needed, int available)
465 int nTabs = Ttk_NumberContent(nb->notebook.mgr);
468 int difference = available - needed;
469 double delta = (double)difference / needed;
473 for (i = 0; i < nTabs; ++i) {
474 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr,i);
475 double ad = slack + tab->width * delta;
476 tab->width += (int)ad;
477 slack = ad - (int)ad;
483 * Compute all tab parcels.
485 static void PlaceTabs(
486 Notebook *nb, Ttk_Box tabrowBox, Ttk_PositionSpec tabPlacement)
488 Ttk_Layout tabLayout = nb->notebook.tabLayout;
489 int nTabs = Ttk_NumberContent(nb->notebook.mgr);
492 for (i = 0; i < nTabs; ++i) {
493 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, i);
494 Ttk_State tabState = TabState(nb, i);
496 if (tab->state != TAB_STATE_HIDDEN) {
497 Ttk_Padding expand = Ttk_UniformPadding(0);
498 Tcl_Obj *expandObj = Ttk_QueryOption(tabLayout,"-expand",tabState);
501 Ttk_GetBorderFromObj(NULL, expandObj, &expand);
506 Ttk_PositionBox(&tabrowBox,
507 tab->width, tab->height, tabPlacement),
513 /* NotebookDoLayout --
514 * Computes notebook layout and places tabs.
517 * Sets clientArea, used to place panes.
519 static void NotebookDoLayout(void *recordPtr)
521 Notebook *nb = (Notebook *)recordPtr;
522 Tk_Window nbwin = nb->core.tkwin;
523 Ttk_Box cavity = Ttk_WinBox(nbwin);
524 int tabrowWidth = 0, tabrowHeight = 0;
525 Ttk_Element clientNode = Ttk_FindElement(nb->core.layout, "client");
527 NotebookStyle nbstyle;
529 NotebookStyleOptions(nb, &nbstyle);
531 /* Notebook internal padding:
533 cavity = Ttk_PadBox(cavity, nbstyle.padding);
535 /* Layout for notebook background (base layout):
537 Ttk_PlaceLayout(nb->core.layout, nb->core.state, Ttk_WinBox(nbwin));
540 * Note: TabrowSize() takes into account -mintabwidth, but the tabs will
541 * actually have this minimum size when displayed only if there is enough
542 * space to draw the tabs with this width. Otherwise some of the tabs can
543 * be squeezed to a size smaller than -mintabwidth because we prefer
544 * displaying all tabs than than honoring -mintabwidth for all of them.
546 TabrowSize(nb, nbstyle.tabOrient, nbstyle.minTabWidth, &tabrowWidth, &tabrowHeight);
547 tabrowBox = Ttk_PadBox(
548 Ttk_PositionBox(&cavity,
549 tabrowWidth + Ttk_PaddingWidth(nbstyle.tabMargins),
550 tabrowHeight + Ttk_PaddingHeight(nbstyle.tabMargins),
551 nbstyle.tabPosition),
554 SqueezeTabs(nb, tabrowWidth, tabrowBox.width);
555 PlaceTabs(nb, tabrowBox, nbstyle.tabPlacement);
557 /* Layout for client area frame:
560 Ttk_PlaceElement(nb->core.layout, clientNode, cavity);
561 cavity = Ttk_LayoutNodeInternalParcel(nb->core.layout, clientNode);
564 if (cavity.height <= 0) cavity.height = 1;
565 if (cavity.width <= 0) cavity.width = 1;
567 nb->notebook.clientArea = cavity;
571 * NotebookPlaceContent --
572 * Set the position and size of a child widget
573 * based on the current client area and content window options:
575 static void NotebookPlaceContent(Notebook *nb, int index)
577 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
578 Tk_Window window = Ttk_ContentWindow(nb->notebook.mgr, index);
580 Ttk_StickBox(Ttk_PadBox(nb->notebook.clientArea, tab->padding),
581 Tk_ReqWidth(window), Tk_ReqHeight(window),tab->sticky);
583 Ttk_PlaceContent(nb->notebook.mgr, index,
584 box.x, box.y, box.width, box.height);
587 /* NotebookPlaceContents --
588 * Geometry manager hook.
590 static void NotebookPlaceContents(void *recordPtr)
592 Notebook *nb = (Notebook *)recordPtr;
593 int currentIndex = nb->notebook.currentIndex;
594 if (currentIndex >= 0) {
595 NotebookDoLayout(nb);
596 NotebookPlaceContent(nb, currentIndex);
601 * SelectTab(nb, index) --
602 * Change the currently-selected tab.
604 static void SelectTab(Notebook *nb, int index)
606 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
607 int currentIndex = nb->notebook.currentIndex;
609 if (index == currentIndex) {
613 if (TabState(nb, index) & TTK_STATE_DISABLED) {
617 /* Unhide the tab if it is currently hidden and being selected.
619 if (tab->state == TAB_STATE_HIDDEN) {
620 tab->state = TAB_STATE_NORMAL;
623 if (currentIndex >= 0) {
624 Ttk_UnmapContent(nb->notebook.mgr, currentIndex);
627 /* Must be set before calling NotebookPlaceContent(), otherwise it may
628 * happen that NotebookPlaceContents(), triggered by an interveaning
629 * geometry request, will swap to old index. */
630 nb->notebook.currentIndex = index;
632 NotebookPlaceContent(nb, index);
633 TtkRedisplayWidget(&nb->core);
635 TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged");
639 * Returns the index of the next tab after the specified tab
640 * in the normal state (e.g., not hidden or disabled),
641 * or -1 if all tabs are disabled or hidden.
643 static int NextTab(Notebook *nb, int index)
645 int nTabs = Ttk_NumberContent(nb->notebook.mgr);
648 /* Scan forward for following usable tab:
650 for (nextIndex = index + 1; nextIndex < nTabs; ++nextIndex) {
651 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, nextIndex);
652 if (tab->state == TAB_STATE_NORMAL) {
657 /* Not found -- scan backwards.
659 for (nextIndex = index - 1; nextIndex >= 0; --nextIndex) {
660 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, nextIndex);
661 if (tab->state == TAB_STATE_NORMAL) {
666 /* Still nothing. Give up.
671 /* SelectNearestTab --
672 * Handles the case where the current tab is forgotten, hidden,
675 * Unmap the current tab and schedule the next available one
676 * to be mapped at the next GM update.
678 static void SelectNearestTab(Notebook *nb)
680 int currentIndex = nb->notebook.currentIndex;
681 int nextIndex = NextTab(nb, currentIndex);
683 if (currentIndex >= 0) {
684 Ttk_UnmapContent(nb->notebook.mgr, currentIndex);
686 if (currentIndex != nextIndex) {
687 TtkSendVirtualEvent(nb->core.tkwin, "NotebookTabChanged");
690 nb->notebook.currentIndex = nextIndex;
691 Ttk_ManagerLayoutChanged(nb->notebook.mgr);
692 TtkRedisplayWidget(&nb->core);
695 /* TabRemoved -- GM TabRemoved hook.
696 * Select the next tab if the current one is being removed.
697 * Adjust currentIndex to account for removed content window.
699 static void TabRemoved(void *managerData, int index)
701 Notebook *nb = (Notebook *)managerData;
702 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
704 if (index == nb->notebook.currentIndex) {
705 SelectNearestTab(nb);
708 if (index < nb->notebook.currentIndex) {
709 --nb->notebook.currentIndex;
714 TtkRedisplayWidget(&nb->core);
717 static int TabRequest(
727 * Add new tab at specified index.
730 Tcl_Interp *interp, Notebook *nb,
731 int destIndex, Tk_Window window,
732 int objc, Tcl_Obj *const objv[])
735 if (!Ttk_Maintainable(interp, window, nb->core.tkwin)) {
738 #if 0 /* can't happen */
739 if (Ttk_ContentIndex(nb->notebook.mgr, window) >= 0) {
740 Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s already added",
741 Tk_PathName(window)));
742 Tcl_SetErrorCode(interp, "TTK", "NOTEBOOK", "PRESENT", NULL);
747 /* Create and insert tab.
749 tab = CreateTab(interp, nb, window);
753 if (ConfigureTab(interp, nb, tab, window, objc, objv) != TCL_OK) {
758 Ttk_InsertContent(nb->notebook.mgr, destIndex, window, tab);
760 /* Adjust indices and/or autoselect first tab:
762 if (nb->notebook.currentIndex < 0) {
763 SelectTab(nb, destIndex);
764 } else if (nb->notebook.currentIndex >= destIndex) {
765 ++nb->notebook.currentIndex;
771 static Ttk_ManagerSpec NotebookManagerSpec = {
772 { "notebook", Ttk_GeometryRequestProc, Ttk_LostContentProc },
774 NotebookPlaceContents,
779 /*------------------------------------------------------------------------
780 * +++ Event handlers.
783 /* NotebookEventHandler --
784 * Tracks the active tab.
786 static const int NotebookEventMask
787 = StructureNotifyMask
791 static void NotebookEventHandler(ClientData clientData, XEvent *eventPtr)
793 Notebook *nb = (Notebook *)clientData;
795 if (eventPtr->type == DestroyNotify) { /* Remove self */
796 Tk_DeleteEventHandler(nb->core.tkwin,
797 NotebookEventMask, NotebookEventHandler, clientData);
798 } else if (eventPtr->type == MotionNotify) {
799 int index = IdentifyTab(nb, eventPtr->xmotion.x, eventPtr->xmotion.y);
800 ActivateTab(nb, index);
801 } else if (eventPtr->type == LeaveNotify) {
806 /*------------------------------------------------------------------------
811 * Find the index of the specified tab.
812 * Tab identifiers are one of:
814 * + positional specifications @x,y,
816 * + numeric indices [0..nTabs],
817 * + content window names
819 * Stores index of specified tab in *index_rtn, -1 if not found.
821 * Returns TCL_ERROR and leaves an error message in interp->result
822 * if the tab identifier was incorrect.
824 * See also: GetTabIndex.
826 static int FindTabIndex(
827 Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn)
829 const char *string = Tcl_GetString(objPtr);
834 /* Check for @x,y ...
836 if (string[0] == '@' && sscanf(string, "@%d,%d",&x,&y) == 2) {
837 *index_rtn = IdentifyTab(nb, x, y);
841 /* ... or "current" ...
843 if (!strcmp(string, "current")) {
844 *index_rtn = nb->notebook.currentIndex;
848 /* ... or integer index or content window name:
850 if (Ttk_GetContentIndexFromObj(
851 interp, nb->notebook.mgr, objPtr, index_rtn) == TCL_OK)
856 /* Nothing matched; Ttk_GetContentIndexFromObj will have left error message.
862 * Get the index of an existing tab.
863 * Tab identifiers are as per FindTabIndex.
864 * Returns TCL_ERROR if the tab does not exist.
866 static int GetTabIndex(
867 Tcl_Interp *interp, Notebook *nb, Tcl_Obj *objPtr, int *index_rtn)
869 int status = FindTabIndex(interp, nb, objPtr, index_rtn);
871 if (status == TCL_OK && *index_rtn < 0) {
872 Tcl_SetObjResult(interp, Tcl_ObjPrintf(
873 "tab '%s' not found", Tcl_GetString(objPtr)));
874 Tcl_SetErrorCode(interp, "TTK", "NOTEBOOK", "TAB", NULL);
880 /*------------------------------------------------------------------------
881 * +++ Widget command routines.
884 /* $nb add window ?options ... ?
886 static int NotebookAddCommand(
887 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
889 Notebook *nb = (Notebook *)recordPtr;
894 if (objc <= 2 || objc % 2 != 1) {
895 Tcl_WrongNumArgs(interp, 2, objv, "window ?-option value ...?");
899 window = Tk_NameToWindow(interp,Tcl_GetString(objv[2]),nb->core.tkwin);
903 index = Ttk_ContentIndex(nb->notebook.mgr, window);
905 if (index < 0) { /* New tab */
906 return AddTab(interp, nb, Ttk_NumberContent(nb->notebook.mgr), window, objc-3,objv+3);
909 tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
910 if (tab->state == TAB_STATE_HIDDEN) {
911 tab->state = TAB_STATE_NORMAL;
913 if (ConfigureTab(interp, nb, tab, window, objc-3,objv+3) != TCL_OK) {
917 TtkRedisplayWidget(&nb->core);
922 /* $nb insert $index $tab ?-option value ...?
923 * Insert new tab, or move existing one.
925 static int NotebookInsertCommand(
926 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
928 Notebook *nb = (Notebook *)recordPtr;
929 int current = nb->notebook.currentIndex;
930 int nContent = Ttk_NumberContent(nb->notebook.mgr);
931 int srcIndex, destIndex;
934 Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?");
938 if (!strcmp(Tcl_GetString(objv[2]), "end")) {
939 destIndex = Ttk_NumberContent(nb->notebook.mgr);
940 } else if (TCL_OK != Ttk_GetContentIndexFromObj(
941 interp, nb->notebook.mgr, objv[2], &destIndex)) {
945 if (Tcl_GetString(objv[3])[0] == '.') {
946 /* Window name -- could be new or existing content window.
949 Tk_NameToWindow(interp,Tcl_GetString(objv[3]),nb->core.tkwin);
955 srcIndex = Ttk_ContentIndex(nb->notebook.mgr, window);
956 if (srcIndex < 0) { /* New content window */
957 return AddTab(interp, nb, destIndex, window, objc-4,objv+4);
959 } else if (Ttk_GetContentIndexFromObj(
960 interp, nb->notebook.mgr, objv[3], &srcIndex) != TCL_OK)
965 /* Move existing content window:
967 if (ConfigureTab(interp, nb,
968 (Tab *)Ttk_ContentData(nb->notebook.mgr, srcIndex),
969 Ttk_ContentWindow(nb->notebook.mgr, srcIndex),
970 objc-4,objv+4) != TCL_OK)
975 if (destIndex >= nContent) {
976 destIndex = nContent - 1;
978 Ttk_ReorderContent(nb->notebook.mgr, srcIndex, destIndex);
980 /* Adjust internal indexes:
982 nb->notebook.activeIndex = -1;
983 if (current == srcIndex) {
984 nb->notebook.currentIndex = destIndex;
985 } else if (destIndex <= current && current < srcIndex) {
986 ++nb->notebook.currentIndex;
987 } else if (srcIndex < current && current <= destIndex) {
988 --nb->notebook.currentIndex;
991 TtkRedisplayWidget(&nb->core);
996 /* $nb forget $tab --
997 * Removes the specified tab.
999 static int NotebookForgetCommand(
1000 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1002 Notebook *nb = (Notebook *)recordPtr;
1006 Tcl_WrongNumArgs(interp, 2, objv, "tab");
1010 if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1014 Ttk_ForgetContent(nb->notebook.mgr, index);
1015 TtkRedisplayWidget(&nb->core);
1021 * Hides the specified tab.
1023 static int NotebookHideCommand(
1024 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1026 Notebook *nb = (Notebook *)recordPtr;
1031 Tcl_WrongNumArgs(interp, 2, objv, "tab");
1035 if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1039 tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
1040 tab->state = TAB_STATE_HIDDEN;
1041 if (index == nb->notebook.currentIndex) {
1042 SelectNearestTab(nb);
1045 TtkRedisplayWidget(&nb->core);
1050 /* $nb identify $x $y --
1051 * Returns name of tab element at $x,$y; empty string if none.
1053 static int NotebookIdentifyCommand(
1054 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1056 static const char *whatTable[] = { "element", "tab", NULL };
1057 enum { IDENTIFY_ELEMENT, IDENTIFY_TAB };
1058 int what = IDENTIFY_ELEMENT;
1059 Notebook *nb = (Notebook *)recordPtr;
1060 Ttk_Element element = NULL;
1064 if (objc < 4 || objc > 5) {
1065 Tcl_WrongNumArgs(interp, 2,objv, "?what? x y");
1069 if (Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK
1070 || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK
1071 || (objc == 5 && Tcl_GetIndexFromObjStruct(interp, objv[2], whatTable,
1072 sizeof(char *), "option", 0, &what) != TCL_OK)
1077 tabIndex = IdentifyTab(nb, x, y);
1078 if (tabIndex >= 0) {
1079 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, tabIndex);
1080 Ttk_State state = TabState(nb, tabIndex);
1081 Ttk_Layout tabLayout = nb->notebook.tabLayout;
1083 Ttk_RebindSublayout(tabLayout, tab);
1084 Ttk_PlaceLayout(tabLayout, state, tab->parcel);
1086 element = Ttk_IdentifyElement(tabLayout, x, y);
1090 case IDENTIFY_ELEMENT:
1092 const char *elementName = Ttk_ElementName(element);
1094 Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
1098 if (tabIndex >= 0) {
1099 Tcl_SetObjResult(interp, Tcl_NewIntObj(tabIndex));
1106 /* $nb index $item --
1107 * Returns the integer index of the tab specified by $item,
1108 * the empty string if $item does not identify a tab.
1109 * See above for valid item formats.
1111 static int NotebookIndexCommand(
1112 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1114 Notebook *nb = (Notebook *)recordPtr;
1119 Tcl_WrongNumArgs(interp, 2, objv, "tab");
1124 * Special-case for "end":
1126 if (!strcmp("end", Tcl_GetString(objv[2]))) {
1127 int nContent = Ttk_NumberContent(nb->notebook.mgr);
1128 Tcl_SetObjResult(interp, Tcl_NewIntObj(nContent));
1132 status = FindTabIndex(interp, nb, objv[2], &index);
1133 if (status == TCL_OK && index >= 0) {
1134 Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
1140 /* $nb select ?$item? --
1141 * Select the specified tab, or return the widget path of
1142 * the currently-selected pane.
1144 static int NotebookSelectCommand(
1145 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1147 Notebook *nb = (Notebook *)recordPtr;
1150 if (nb->notebook.currentIndex >= 0) {
1151 Tk_Window pane = Ttk_ContentWindow(
1152 nb->notebook.mgr, nb->notebook.currentIndex);
1153 Tcl_SetObjResult(interp, Tcl_NewStringObj(Tk_PathName(pane), -1));
1156 } else if (objc == 3) {
1157 int index, status = GetTabIndex(interp, nb, objv[2], &index);
1158 if (status == TCL_OK) {
1159 SelectTab(nb, index);
1163 Tcl_WrongNumArgs(interp, 2, objv, "?tab?");
1168 * Return list of tabs.
1170 static int NotebookTabsCommand(
1171 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1173 Notebook *nb = (Notebook *)recordPtr;
1174 Ttk_Manager *mgr = nb->notebook.mgr;
1179 Tcl_WrongNumArgs(interp, 2, objv, "");
1183 result = Tcl_NewListObj(0, NULL);
1184 for (i = 0; i < Ttk_NumberContent(mgr); ++i) {
1185 const char *pathName = Tk_PathName(Ttk_ContentWindow(mgr,i));
1187 Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(pathName,-1));
1189 Tcl_SetObjResult(interp, result);
1193 /* $nb tab $tab ?-option ?value -option value...??
1195 static int NotebookTabCommand(
1196 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1198 Notebook *nb = (Notebook *)recordPtr;
1199 Ttk_Manager *mgr = nb->notebook.mgr;
1205 Tcl_WrongNumArgs(interp, 2, objv, "tab ?-option ?value??...");
1209 if (GetTabIndex(interp, nb, objv[2], &index) != TCL_OK) {
1213 tab = (Tab *)Ttk_ContentData(mgr, index);
1214 window = Ttk_ContentWindow(mgr, index);
1217 return TtkEnumerateOptions(interp, tab,
1218 PaneOptionSpecs, nb->notebook.paneOptionTable, window);
1219 } else if (objc == 4) {
1220 return TtkGetOptionValue(interp, tab, objv[3],
1221 nb->notebook.paneOptionTable, window);
1224 if (ConfigureTab(interp, nb, tab, window, objc-3,objv+3) != TCL_OK) {
1228 /* If the current tab has become disabled or hidden,
1229 * select the next nondisabled, unhidden one:
1231 if (index == nb->notebook.currentIndex && tab->state != TAB_STATE_NORMAL) {
1232 SelectNearestTab(nb);
1238 /* Subcommand table:
1240 static const Ttk_Ensemble NotebookCommands[] = {
1241 { "add", NotebookAddCommand,0 },
1242 { "configure", TtkWidgetConfigureCommand,0 },
1243 { "cget", TtkWidgetCgetCommand,0 },
1244 { "forget", NotebookForgetCommand,0 },
1245 { "hide", NotebookHideCommand,0 },
1246 { "identify", NotebookIdentifyCommand,0 },
1247 { "index", NotebookIndexCommand,0 },
1248 { "insert", NotebookInsertCommand,0 },
1249 { "instate", TtkWidgetInstateCommand,0 },
1250 { "select", NotebookSelectCommand,0 },
1251 { "state", TtkWidgetStateCommand,0 },
1252 { "tab", NotebookTabCommand,0 },
1253 { "tabs", NotebookTabsCommand,0 },
1257 /*------------------------------------------------------------------------
1258 * +++ Widget class hooks.
1261 static void NotebookInitialize(Tcl_Interp *interp, void *recordPtr)
1263 Notebook *nb = (Notebook *)recordPtr;
1265 nb->notebook.mgr = Ttk_CreateManager(
1266 &NotebookManagerSpec, recordPtr, nb->core.tkwin);
1268 nb->notebook.tabOptionTable = Tk_CreateOptionTable(interp,TabOptionSpecs);
1269 nb->notebook.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs);
1271 nb->notebook.currentIndex = -1;
1272 nb->notebook.activeIndex = -1;
1273 nb->notebook.tabLayout = 0;
1275 nb->notebook.clientArea = Ttk_MakeBox(0,0,1,1);
1277 Tk_CreateEventHandler(
1278 nb->core.tkwin, NotebookEventMask, NotebookEventHandler, recordPtr);
1281 static void NotebookCleanup(void *recordPtr)
1283 Notebook *nb = (Notebook *)recordPtr;
1285 Ttk_DeleteManager(nb->notebook.mgr);
1286 if (nb->notebook.tabLayout)
1287 Ttk_FreeLayout(nb->notebook.tabLayout);
1290 static int NotebookConfigure(Tcl_Interp *interp, void *clientData, int mask)
1292 Notebook *nb = (Notebook *)clientData;
1297 if (nb->notebook.paddingObj) {
1298 /* Check for valid -padding: */
1300 if (Ttk_GetPaddingFromObj(
1301 interp, nb->core.tkwin, nb->notebook.paddingObj, &unused)
1307 return TtkCoreConfigure(interp, clientData, mask);
1310 /* NotebookGetLayout --
1311 * GetLayout widget hook.
1313 static Ttk_Layout NotebookGetLayout(
1314 Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
1316 Notebook *nb = (Notebook *)recordPtr;
1317 Ttk_Layout notebookLayout = TtkWidgetGetLayout(interp, theme, recordPtr);
1318 Ttk_Layout tabLayout;
1320 if (!notebookLayout) {
1324 tabLayout = Ttk_CreateSublayout(
1325 interp, theme, notebookLayout, ".Tab", nb->notebook.tabOptionTable);
1328 if (nb->notebook.tabLayout) {
1329 Ttk_FreeLayout(nb->notebook.tabLayout);
1331 nb->notebook.tabLayout = tabLayout;
1334 return notebookLayout;
1337 /*------------------------------------------------------------------------
1338 * +++ Display routines.
1341 static void DisplayTab(Notebook *nb, int index, Drawable d)
1343 Ttk_Layout tabLayout = nb->notebook.tabLayout;
1344 Tab *tab = (Tab *)Ttk_ContentData(nb->notebook.mgr, index);
1345 Ttk_State state = TabState(nb, index);
1347 if (tab->state != TAB_STATE_HIDDEN) {
1348 Ttk_RebindSublayout(tabLayout, tab);
1349 Ttk_PlaceLayout(tabLayout, state, tab->parcel);
1350 Ttk_DrawLayout(tabLayout, state, d);
1354 static void NotebookDisplay(void *clientData, Drawable d)
1356 Notebook *nb = (Notebook *)clientData;
1357 int nContent = Ttk_NumberContent(nb->notebook.mgr);
1360 /* Draw notebook background (base layout):
1362 Ttk_DrawLayout(nb->core.layout, nb->core.state, d);
1364 /* Draw tabs from left to right, but draw the current tab last
1365 * so it will overwrite its neighbors.
1367 for (index = 0; index < nContent; ++index) {
1368 if (index != nb->notebook.currentIndex) {
1369 DisplayTab(nb, index, d);
1372 if (nb->notebook.currentIndex >= 0) {
1373 DisplayTab(nb, nb->notebook.currentIndex, d);
1377 /*------------------------------------------------------------------------
1378 * +++ Widget specification and layout definitions.
1381 static WidgetSpec NotebookWidgetSpec =
1383 "TNotebook", /* className */
1384 sizeof(Notebook), /* recordSize */
1385 NotebookOptionSpecs, /* optionSpecs */
1386 NotebookCommands, /* subcommands */
1387 NotebookInitialize, /* initializeProc */
1388 NotebookCleanup, /* cleanupProc */
1389 NotebookConfigure, /* configureProc */
1390 TtkNullPostConfigure, /* postConfigureProc */
1391 NotebookGetLayout, /* getLayoutProc */
1392 NotebookSize, /* geometryProc */
1393 NotebookDoLayout, /* layoutProc */
1394 NotebookDisplay /* displayProc */
1397 TTK_BEGIN_LAYOUT(NotebookLayout)
1398 TTK_NODE("Notebook.client", TTK_FILL_BOTH)
1401 TTK_BEGIN_LAYOUT(TabLayout)
1402 TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
1403 TTK_GROUP("Notebook.padding", TTK_PACK_TOP|TTK_FILL_BOTH,
1404 TTK_GROUP("Notebook.focus", TTK_PACK_TOP|TTK_FILL_BOTH,
1405 TTK_NODE("Notebook.label", TTK_PACK_TOP))))
1408 /*------------------------------------------------------------------------
1409 * +++ Initialization.
1413 void TtkNotebook_Init(Tcl_Interp *interp)
1415 Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp);
1417 Ttk_RegisterLayout(themePtr, "Tab", TabLayout);
1418 Ttk_RegisterLayout(themePtr, "TNotebook", NotebookLayout);
1420 RegisterWidget(interp, "ttk::notebook", &NotebookWidgetSpec);