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