1/* $Id$ 2 * 3 * Copyright (c) 2005, Joe English. Freely redistributable. 4 * 5 * ttk::panedwindow widget implementation. 6 * 7 * TODO: track active/pressed sash. 8 */ 9 10#include <string.h> 11#include <tk.h> 12#include "ttkManager.h" 13#include "ttkTheme.h" 14#include "ttkWidget.h" 15 16/*------------------------------------------------------------------------ 17 * +++ Layout algorithm. 18 * 19 * (pos=x/y, size=width/height, depending on -orient=horizontal/vertical) 20 * 21 * Each pane carries two pieces of state: the request size and the 22 * position of the following sash. (The final pane has no sash, 23 * its sash position is used as a sentinel value). 24 * 25 * Pane geometry is determined by the sash positions. 26 * When resizing, sash positions are computed from the request sizes, 27 * the available space, and pane weights (see PlaceSashes()). 28 * This ensures continuous resize behavior (that is: changing 29 * the size by X pixels then changing the size by Y pixels 30 * gives the same result as changing the size by X+Y pixels 31 * in one step). 32 * 33 * The request size is initially set to the slave window's requested size. 34 * When the user drags a sash, each pane's request size is set to its 35 * actual size. This ensures that panes "stay put" on the next resize. 36 * 37 * If reqSize == 0, use 0 for the weight as well. This ensures that 38 * "collapsed" panes stay collapsed during a resize, regardless of 39 * their nominal -weight. 40 * 41 * +++ Invariants. 42 * 43 * #sash = #pane - 1 44 * pos(pane[0]) = 0 45 * pos(sash[i]) = pos(pane[i]) + size(pane[i]), 0 <= i <= #sash 46 * pos(pane[i+1]) = pos(sash[i]) + size(sash[i]), 0 <= i < #sash 47 * pos(sash[#sash]) = size(pw) // sentinel value, constraint 48 * 49 * size(pw) = sum(size(pane(0..#pane))) + sum(size(sash(0..#sash))) 50 * size(pane[i]) >= 0, for 0 <= i < #pane 51 * size(sash[i]) >= 0, for 0 <= i < #sash 52 * ==> pos(pane[i]) <= pos(sash[i]) <= pos(pane[i+1]), for 0 <= i < #sash 53 * 54 * Assumption: all sashes are the same size. 55 */ 56 57/*------------------------------------------------------------------------ 58 * +++ Widget record. 59 */ 60 61typedef struct { 62 Tcl_Obj *orientObj; 63 int orient; 64 int width; 65 int height; 66 Ttk_Manager *mgr; 67 Tk_OptionTable paneOptionTable; 68 Ttk_Layout sashLayout; 69 int sashThickness; 70} PanedPart; 71 72typedef struct { 73 WidgetCore core; 74 PanedPart paned; 75} Paned; 76 77/* @@@ NOTE: -orient is readonly 'cause dynamic oriention changes NYI 78 */ 79static Tk_OptionSpec PanedOptionSpecs[] = { 80 {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical", 81 Tk_Offset(Paned,paned.orientObj), Tk_Offset(Paned,paned.orient), 82 0,(ClientData)ttkOrientStrings,READONLY_OPTION|STYLE_CHANGED }, 83 {TK_OPTION_INT, "-width", "width", "Width", "0", 84 -1,Tk_Offset(Paned,paned.width), 85 0,0,GEOMETRY_CHANGED }, 86 {TK_OPTION_INT, "-height", "height", "Height", "0", 87 -1,Tk_Offset(Paned,paned.height), 88 0,0,GEOMETRY_CHANGED }, 89 90 WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) 91}; 92 93/*------------------------------------------------------------------------ 94 * +++ Slave pane record. 95 */ 96typedef struct { 97 int reqSize; /* Pane request size */ 98 int sashPos; /* Folowing sash position */ 99 int weight; /* Pane -weight, for resizing */ 100} Pane; 101 102static Tk_OptionSpec PaneOptionSpecs[] = { 103 {TK_OPTION_INT, "-weight", "weight", "Weight", "0", 104 -1,Tk_Offset(Pane,weight), 0,0,GEOMETRY_CHANGED }, 105 {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} 106}; 107 108/* CreatePane -- 109 * Create a new pane record. 110 */ 111static Pane *CreatePane(Tcl_Interp *interp, Paned *pw, Tk_Window slaveWindow) 112{ 113 Tk_OptionTable optionTable = pw->paned.paneOptionTable; 114 void *record = ckalloc(sizeof(Pane)); 115 Pane *pane = record; 116 117 memset(record, 0, sizeof(Pane)); 118 if (Tk_InitOptions(interp, record, optionTable, slaveWindow) != TCL_OK) { 119 ckfree(record); 120 return NULL; 121 } 122 123 pane->reqSize 124 = pw->paned.orient == TTK_ORIENT_HORIZONTAL 125 ? Tk_ReqWidth(slaveWindow) : Tk_ReqHeight(slaveWindow); 126 127 return pane; 128} 129 130/* DestroyPane -- 131 * Free pane record. 132 */ 133static void DestroyPane(Paned *pw, Pane *pane) 134{ 135 void *record = pane; 136 Tk_FreeConfigOptions(record, pw->paned.paneOptionTable, pw->core.tkwin); 137 ckfree(record); 138} 139 140/* ConfigurePane -- 141 * Set pane options. 142 */ 143static int ConfigurePane( 144 Tcl_Interp *interp, Paned *pw, Pane *pane, Tk_Window slaveWindow, 145 int objc, Tcl_Obj *const objv[]) 146{ 147 Ttk_Manager *mgr = pw->paned.mgr; 148 Tk_SavedOptions savedOptions; 149 int mask = 0; 150 151 if (Tk_SetOptions(interp, (void*)pane, pw->paned.paneOptionTable, 152 objc, objv, slaveWindow, &savedOptions, &mask) != TCL_OK) 153 { 154 return TCL_ERROR; 155 } 156 157 /* Sanity-check: 158 */ 159 if (pane->weight < 0) { 160 Tcl_AppendResult(interp, "-weight must be nonnegative", NULL); 161 goto error; 162 } 163 164 /* Done. 165 */ 166 Tk_FreeSavedOptions(&savedOptions); 167 Ttk_ManagerSizeChanged(mgr); 168 return TCL_OK; 169 170error: 171 Tk_RestoreSavedOptions(&savedOptions); 172 return TCL_ERROR; 173} 174 175 176/*------------------------------------------------------------------------ 177 * +++ Sash adjustment. 178 */ 179 180/* ShoveUp -- 181 * Place sash i at specified position, recursively shoving 182 * previous sashes upwards as needed, until hitting the top 183 * of the window. If that happens, shove back down. 184 * 185 * Returns: final position of sash i. 186 */ 187 188static int ShoveUp(Paned *pw, int i, int pos) 189{ 190 Pane *pane = Ttk_SlaveData(pw->paned.mgr, i); 191 int sashThickness = pw->paned.sashThickness; 192 193 if (i == 0) { 194 if (pos < 0) 195 pos = 0; 196 } else { 197 Pane *prevPane = Ttk_SlaveData(pw->paned.mgr, i-1); 198 if (pos < prevPane->sashPos + sashThickness) 199 pos = ShoveUp(pw, i-1, pos - sashThickness) + sashThickness; 200 } 201 return pane->sashPos = pos; 202} 203 204/* ShoveDown -- 205 * Same as ShoveUp, but going in the opposite direction 206 * and stopping at the sentinel sash. 207 */ 208static int ShoveDown(Paned *pw, int i, int pos) 209{ 210 Pane *pane = Ttk_SlaveData(pw->paned.mgr,i); 211 int sashThickness = pw->paned.sashThickness; 212 213 if (i == Ttk_NumberSlaves(pw->paned.mgr) - 1) { 214 pos = pane->sashPos; /* Sentinel value == master window size */ 215 } else { 216 Pane *nextPane = Ttk_SlaveData(pw->paned.mgr,i+1); 217 if (pos + sashThickness > nextPane->sashPos) 218 pos = ShoveDown(pw, i+1, pos + sashThickness) - sashThickness; 219 } 220 return pane->sashPos = pos; 221} 222 223/* PanedSize -- 224 * Compute the requested size of the paned widget 225 * from the individual pane request sizes. 226 * 227 * Used as the WidgetSpec sizeProc and the ManagerSpec sizeProc. 228 */ 229static int PanedSize(void *recordPtr, int *widthPtr, int *heightPtr) 230{ 231 Paned *pw = recordPtr; 232 int nPanes = Ttk_NumberSlaves(pw->paned.mgr); 233 int nSashes = nPanes - 1; 234 int sashThickness = pw->paned.sashThickness; 235 int width = 0, height = 0; 236 int index; 237 238 if (pw->paned.orient == TTK_ORIENT_HORIZONTAL) { 239 for (index = 0; index < nPanes; ++index) { 240 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 241 Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); 242 243 if (height < Tk_ReqHeight(slaveWindow)) 244 height = Tk_ReqHeight(slaveWindow); 245 width += pane->reqSize; 246 } 247 width += nSashes * sashThickness; 248 } else { 249 for (index = 0; index < nPanes; ++index) { 250 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 251 Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); 252 253 if (width < Tk_ReqWidth(slaveWindow)) 254 width = Tk_ReqWidth(slaveWindow); 255 height += pane->reqSize; 256 } 257 height += nSashes * sashThickness; 258 } 259 260 *widthPtr = pw->paned.width > 0 ? pw->paned.width : width; 261 *heightPtr = pw->paned.height > 0 ? pw->paned.height : height; 262 return 1; 263} 264 265/* AdjustPanes -- 266 * Set pane request sizes from sash positions. 267 * 268 * NOTE: 269 * AdjustPanes followed by PlaceSashes (called during relayout) 270 * will leave the sashes in the same place, as long as available size 271 * remains contant. 272 */ 273static void AdjustPanes(Paned *pw) 274{ 275 int sashThickness = pw->paned.sashThickness; 276 int pos = 0; 277 int index; 278 279 for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { 280 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 281 int size = pane->sashPos - pos; 282 pane->reqSize = size >= 0 ? size : 0; 283 pos = pane->sashPos + sashThickness; 284 } 285} 286 287/* PlaceSashes -- 288 * Set sash positions from pane request sizes and available space. 289 * The sentinel sash position is set to the available space. 290 * 291 * Allocate pane->reqSize pixels to each pane, and distribute 292 * the difference = available size - requested size according 293 * to pane->weight. 294 * 295 * If there's still some left over, squeeze panes from the bottom up 296 * (This can happen if all weights are zero, or if one or more panes 297 * are too small to absorb the required shrinkage). 298 * 299 * Notes: 300 * This doesn't distribute the remainder pixels as evenly as it could 301 * when more than one pane has weight > 1. 302 */ 303static void PlaceSashes(Paned *pw, int width, int height) 304{ 305 Ttk_Manager *mgr = pw->paned.mgr; 306 int nPanes = Ttk_NumberSlaves(mgr); 307 int sashThickness = pw->paned.sashThickness; 308 int available = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? width : height; 309 int reqSize = 0, totalWeight = 0; 310 int difference, delta, remainder, pos, i; 311 312 if (nPanes == 0) 313 return; 314 315 /* Compute total required size and total available weight: 316 */ 317 for (i = 0; i < nPanes; ++i) { 318 Pane *pane = Ttk_SlaveData(mgr, i); 319 reqSize += pane->reqSize; 320 totalWeight += pane->weight * (pane->reqSize != 0); 321 } 322 323 /* Compute difference to be redistributed: 324 */ 325 difference = available - reqSize - sashThickness*(nPanes-1); 326 if (totalWeight != 0) { 327 delta = difference / totalWeight; 328 remainder = difference % totalWeight; 329 if (remainder < 0) { 330 --delta; 331 remainder += totalWeight; 332 } 333 } else { 334 delta = remainder = 0; 335 } 336 /* ASSERT: 0 <= remainder < totalWeight */ 337 338 /* Place sashes: 339 */ 340 pos = 0; 341 for (i = 0; i < nPanes; ++i) { 342 Pane *pane = Ttk_SlaveData(mgr, i); 343 int weight = pane->weight * (pane->reqSize != 0); 344 int size = pane->reqSize + delta * weight; 345 346 if (weight > remainder) 347 weight = remainder; 348 remainder -= weight; 349 size += weight; 350 351 if (size < 0) 352 size = 0; 353 354 pane->sashPos = (pos += size); 355 pos += sashThickness; 356 } 357 358 /* Handle emergency shrink/emergency stretch: 359 * Set sentinel sash position to end of widget, 360 * shove preceding sashes up. 361 */ 362 ShoveUp(pw, nPanes - 1, available); 363} 364 365/* PlacePanes -- 366 * Places slave panes based on sash positions. 367 */ 368static void PlacePanes(Paned *pw) 369{ 370 int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; 371 int width = Tk_Width(pw->core.tkwin), height = Tk_Height(pw->core.tkwin); 372 int sashThickness = pw->paned.sashThickness; 373 int pos = 0; 374 int index; 375 376 for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { 377 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 378 int size = pane->sashPos - pos; 379 380 if (size > 0) { 381 if (horizontal) { 382 Ttk_PlaceSlave(pw->paned.mgr, index, pos, 0, size, height); 383 } else { 384 Ttk_PlaceSlave(pw->paned.mgr, index, 0, pos, width, size); 385 } 386 } else { 387 Ttk_UnmapSlave(pw->paned.mgr, index); 388 } 389 390 pos = pane->sashPos + sashThickness; 391 } 392} 393 394/*------------------------------------------------------------------------ 395 * +++ Manager specification. 396 */ 397 398static void PanedPlaceSlaves(void *managerData) 399{ 400 Paned *pw = managerData; 401 PlaceSashes(pw, Tk_Width(pw->core.tkwin), Tk_Height(pw->core.tkwin)); 402 PlacePanes(pw); 403} 404 405static void PaneRemoved(void *managerData, int index) 406{ 407 Paned *pw = managerData; 408 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 409 DestroyPane(pw, pane); 410} 411 412static int AddPane( 413 Tcl_Interp *interp, Paned *pw, 414 int destIndex, Tk_Window slaveWindow, 415 int objc, Tcl_Obj *const objv[]) 416{ 417 Pane *pane; 418 if (!Ttk_Maintainable(interp, slaveWindow, pw->core.tkwin)) { 419 return TCL_ERROR; 420 } 421 if (Ttk_SlaveIndex(pw->paned.mgr, slaveWindow) >= 0) { 422 Tcl_AppendResult(interp, 423 Tk_PathName(slaveWindow), " already added", 424 NULL); 425 return TCL_ERROR; 426 } 427 428 pane = CreatePane(interp, pw, slaveWindow); 429 if (!pane) { 430 return TCL_ERROR; 431 } 432 if (ConfigurePane(interp, pw, pane, slaveWindow, objc, objv) != TCL_OK) { 433 DestroyPane(pw, pane); 434 return TCL_ERROR; 435 } 436 437 Ttk_InsertSlave(pw->paned.mgr, destIndex, slaveWindow, pane); 438 return TCL_OK; 439} 440 441/* PaneRequest -- 442 * Only update pane request size if slave is currently unmapped. 443 * Geometry requests from mapped slaves are not directly honored 444 * in order to avoid unexpected pane resizes (esp. while the 445 * user is dragging a sash [#1325286]). 446 */ 447static int PaneRequest(void *managerData, int index, int width, int height) 448{ 449 Paned *pw = managerData; 450 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 451 Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); 452 int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; 453 454 if (!Tk_IsMapped(slaveWindow)) { 455 pane->reqSize = horizontal ? width : height; 456 } 457 return 1; 458} 459 460static Ttk_ManagerSpec PanedManagerSpec = { 461 { "panedwindow", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, 462 PanedSize, 463 PanedPlaceSlaves, 464 PaneRequest, 465 PaneRemoved 466}; 467 468/*------------------------------------------------------------------------ 469 * +++ Event handler. 470 * 471 * <<NOTE-PW-LEAVE-NOTIFYINFERIOR>> 472 * Tk does not execute binding scripts for <Leave> events when 473 * the pointer crosses from a parent to a child. This widget 474 * needs to know when that happens, though, so it can reset 475 * the cursor. 476 * 477 * This event handler generates an <<EnteredChild>> virtual event 478 * on LeaveNotify/NotifyInferior. 479 */ 480 481static const unsigned PanedEventMask = LeaveWindowMask; 482static void PanedEventProc(ClientData clientData, XEvent *eventPtr) 483{ 484 WidgetCore *corePtr = clientData; 485 if ( eventPtr->type == LeaveNotify 486 && eventPtr->xcrossing.detail == NotifyInferior) 487 { 488 TtkSendVirtualEvent(corePtr->tkwin, "EnteredChild"); 489 } 490} 491 492/*------------------------------------------------------------------------ 493 * +++ Initialization and cleanup hooks. 494 */ 495 496static void PanedInitialize(Tcl_Interp *interp, void *recordPtr) 497{ 498 Paned *pw = recordPtr; 499 500 Tk_CreateEventHandler(pw->core.tkwin, 501 PanedEventMask, PanedEventProc, recordPtr); 502 pw->paned.mgr = Ttk_CreateManager(&PanedManagerSpec, pw, pw->core.tkwin); 503 pw->paned.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs); 504 pw->paned.sashLayout = 0; 505 pw->paned.sashThickness = 1; 506} 507 508static void PanedCleanup(void *recordPtr) 509{ 510 Paned *pw = recordPtr; 511 512 if (pw->paned.sashLayout) 513 Ttk_FreeLayout(pw->paned.sashLayout); 514 Tk_DeleteEventHandler(pw->core.tkwin, 515 PanedEventMask, PanedEventProc, recordPtr); 516 Ttk_DeleteManager(pw->paned.mgr); 517} 518 519/* Post-configuration hook. 520 */ 521static int PanedPostConfigure(Tcl_Interp *interp, void *clientData, int mask) 522{ 523 Paned *pw = clientData; 524 525 if (mask & GEOMETRY_CHANGED) { 526 /* User has changed -width or -height. 527 * Recalculate sash positions based on requested size. 528 */ 529 Tk_Window tkwin = pw->core.tkwin; 530 PlaceSashes(pw, 531 pw->paned.width > 0 ? pw->paned.width : Tk_Width(tkwin), 532 pw->paned.height > 0 ? pw->paned.height : Tk_Height(tkwin)); 533 } 534 535 return TCL_OK; 536} 537 538/*------------------------------------------------------------------------ 539 * +++ Layout management hooks. 540 */ 541static Ttk_Layout PanedGetLayout( 542 Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) 543{ 544 Paned *pw = recordPtr; 545 Ttk_Layout panedLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr); 546 547 if (panedLayout) { 548 int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; 549 const char *layoutName = 550 horizontal ? ".Vertical.Sash" : ".Horizontal.Sash"; 551 Ttk_Layout sashLayout = Ttk_CreateSublayout( 552 interp, themePtr, panedLayout, layoutName, pw->core.optionTable); 553 554 if (sashLayout) { 555 int sashWidth, sashHeight; 556 557 Ttk_LayoutSize(sashLayout, 0, &sashWidth, &sashHeight); 558 pw->paned.sashThickness = horizontal ? sashWidth : sashHeight; 559 560 if (pw->paned.sashLayout) 561 Ttk_FreeLayout(pw->paned.sashLayout); 562 pw->paned.sashLayout = sashLayout; 563 } else { 564 Ttk_FreeLayout(panedLayout); 565 return 0; 566 } 567 } 568 569 return panedLayout; 570} 571 572/*------------------------------------------------------------------------ 573 * +++ Drawing routines. 574 */ 575 576/* SashLayout -- 577 * Place the sash sublayout after the specified pane, 578 * in preparation for drawing. 579 */ 580static Ttk_Layout SashLayout(Paned *pw, int index) 581{ 582 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 583 int thickness = pw->paned.sashThickness, 584 height = Tk_Height(pw->core.tkwin), 585 width = Tk_Width(pw->core.tkwin), 586 sashPos = pane->sashPos; 587 588 Ttk_PlaceLayout( 589 pw->paned.sashLayout, pw->core.state, 590 pw->paned.orient == TTK_ORIENT_HORIZONTAL 591 ? Ttk_MakeBox(sashPos, 0, thickness, height) 592 : Ttk_MakeBox(0, sashPos, width, thickness)); 593 594 return pw->paned.sashLayout; 595} 596 597static void DrawSash(Paned *pw, int index, Drawable d) 598{ 599 Ttk_DrawLayout(SashLayout(pw, index), pw->core.state, d); 600} 601 602static void PanedDisplay(void *recordPtr, Drawable d) 603{ 604 Paned *pw = recordPtr; 605 int i, nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; 606 607 TtkWidgetDisplay(recordPtr, d); 608 for (i = 0; i < nSashes; ++i) { 609 DrawSash(pw, i, d); 610 } 611} 612 613/*------------------------------------------------------------------------ 614 * +++ Widget commands. 615 */ 616 617/* $pw add window [ options ... ] 618 */ 619static int PanedAddCommand( 620 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 621{ 622 Paned *pw = recordPtr; 623 Tk_Window slaveWindow; 624 625 if (objc < 3) { 626 Tcl_WrongNumArgs(interp, 2, objv, "window"); 627 return TCL_ERROR; 628 } 629 630 slaveWindow = Tk_NameToWindow( 631 interp, Tcl_GetString(objv[2]), pw->core.tkwin); 632 633 if (!slaveWindow) { 634 return TCL_ERROR; 635 } 636 637 return AddPane(interp, pw, Ttk_NumberSlaves(pw->paned.mgr), slaveWindow, 638 objc - 3, objv + 3); 639} 640 641/* $pw insert $index $slave ?-option value ...? 642 * Insert new slave, or move existing one. 643 */ 644static int PanedInsertCommand( 645 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 646{ 647 Paned *pw = recordPtr; 648 int nSlaves = Ttk_NumberSlaves(pw->paned.mgr); 649 int srcIndex, destIndex; 650 Tk_Window slaveWindow; 651 652 if (objc < 4) { 653 Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?"); 654 return TCL_ERROR; 655 } 656 657 slaveWindow = Tk_NameToWindow( 658 interp, Tcl_GetString(objv[3]), pw->core.tkwin); 659 if (!slaveWindow) { 660 return TCL_ERROR; 661 } 662 663 if (!strcmp(Tcl_GetString(objv[2]), "end")) { 664 destIndex = Ttk_NumberSlaves(pw->paned.mgr); 665 } else if (TCL_OK != Ttk_GetSlaveIndexFromObj( 666 interp,pw->paned.mgr,objv[2],&destIndex)) 667 { 668 return TCL_ERROR; 669 } 670 671 srcIndex = Ttk_SlaveIndex(pw->paned.mgr, slaveWindow); 672 if (srcIndex < 0) { /* New slave: */ 673 return AddPane(interp, pw, destIndex, slaveWindow, objc-4, objv+4); 674 } /* else -- move existing slave: */ 675 676 if (destIndex >= nSlaves) 677 destIndex = nSlaves - 1; 678 Ttk_ReorderSlave(pw->paned.mgr, srcIndex, destIndex); 679 680 return objc == 4 ? TCL_OK : 681 ConfigurePane(interp, pw, 682 Ttk_SlaveData(pw->paned.mgr, destIndex), 683 Ttk_SlaveWindow(pw->paned.mgr, destIndex), 684 objc-4,objv+4); 685} 686 687/* $pw forget $pane 688 */ 689static int PanedForgetCommand( 690 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 691{ 692 Paned *pw = recordPtr; 693 int paneIndex; 694 695 if (objc != 3) { 696 Tcl_WrongNumArgs(interp, 2,objv, "pane"); 697 return TCL_ERROR; 698 } 699 700 if (TCL_OK != Ttk_GetSlaveIndexFromObj( 701 interp, pw->paned.mgr, objv[2], &paneIndex)) 702 { 703 return TCL_ERROR; 704 } 705 Ttk_ForgetSlave(pw->paned.mgr, paneIndex); 706 707 return TCL_OK; 708} 709 710/* $pw identify ?what? $x $y -- 711 * Return index of sash at $x,$y 712 */ 713static int PanedIdentifyCommand( 714 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 715{ 716 const char *whatTable[] = { "element", "sash", NULL }; 717 enum { IDENTIFY_ELEMENT, IDENTIFY_SASH }; 718 int what = IDENTIFY_SASH; 719 Paned *pw = recordPtr; 720 int sashThickness = pw->paned.sashThickness; 721 int nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; 722 int x, y, pos; 723 int index; 724 725 if (objc < 4 || objc > 5) { 726 Tcl_WrongNumArgs(interp, 2,objv, "?what? x y"); 727 return TCL_ERROR; 728 } 729 730 if ( Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK 731 || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK 732 || (objc == 5 && 733 Tcl_GetIndexFromObj(interp, objv[2], whatTable, "option", 0, &what) 734 != TCL_OK) 735 ) { 736 return TCL_ERROR; 737 } 738 739 pos = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? x : y; 740 for (index = 0; index < nSashes; ++index) { 741 Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); 742 if (pane->sashPos <= pos && pos <= pane->sashPos + sashThickness) { 743 /* Found it. */ 744 switch (what) { 745 case IDENTIFY_SASH: 746 Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); 747 return TCL_OK; 748 case IDENTIFY_ELEMENT: 749 { 750 Ttk_Element element = 751 Ttk_IdentifyElement(SashLayout(pw, index), x, y); 752 if (element) { 753 Tcl_SetObjResult(interp, 754 Tcl_NewStringObj(Ttk_ElementName(element), -1)); 755 } 756 return TCL_OK; 757 } 758 } 759 } 760 } 761 762 return TCL_OK; /* nothing found - return empty string */ 763} 764 765/* $pw pane $pane ?-option ?value -option value ...?? 766 * Query/modify pane options. 767 */ 768static int PanedPaneCommand( 769 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 770{ 771 Paned *pw = recordPtr; 772 int paneIndex; 773 Tk_Window slaveWindow; 774 Pane *pane; 775 776 if (objc < 3) { 777 Tcl_WrongNumArgs(interp, 2,objv, "pane ?-option value ...?"); 778 return TCL_ERROR; 779 } 780 781 if (TCL_OK != Ttk_GetSlaveIndexFromObj( 782 interp,pw->paned.mgr,objv[2],&paneIndex)) 783 { 784 return TCL_ERROR; 785 } 786 787 pane = Ttk_SlaveData(pw->paned.mgr, paneIndex); 788 slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, paneIndex); 789 790 switch (objc) { 791 case 3: 792 return TtkEnumerateOptions(interp, pane, PaneOptionSpecs, 793 pw->paned.paneOptionTable, slaveWindow); 794 case 4: 795 return TtkGetOptionValue(interp, pane, objv[3], 796 pw->paned.paneOptionTable, slaveWindow); 797 default: 798 return ConfigurePane(interp, pw, pane, slaveWindow, objc-3,objv+3); 799 } 800} 801 802/* $pw panes -- 803 * Return list of managed panes. 804 */ 805static int PanedPanesCommand( 806 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 807{ 808 Paned *pw = recordPtr; 809 Ttk_Manager *mgr = pw->paned.mgr; 810 Tcl_Obj *panes; 811 int i; 812 813 if (objc != 2) { 814 Tcl_WrongNumArgs(interp, 2, objv, ""); 815 return TCL_ERROR; 816 } 817 818 panes = Tcl_NewListObj(0, NULL); 819 for (i = 0; i < Ttk_NumberSlaves(mgr); ++i) { 820 const char *pathName = Tk_PathName(Ttk_SlaveWindow(mgr,i)); 821 Tcl_ListObjAppendElement(interp, panes, Tcl_NewStringObj(pathName,-1)); 822 } 823 Tcl_SetObjResult(interp, panes); 824 825 return TCL_OK; 826} 827 828 829/* $pw sashpos $index ?$newpos? 830 * Query or modify sash position. 831 */ 832static int PanedSashposCommand( 833 void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) 834{ 835 Paned *pw = recordPtr; 836 int sashIndex, position = -1; 837 Pane *pane; 838 839 if (objc < 3 || objc > 4) { 840 Tcl_WrongNumArgs(interp, 2,objv, "index ?newpos?"); 841 return TCL_ERROR; 842 } 843 if (Tcl_GetIntFromObj(interp, objv[2], &sashIndex) != TCL_OK) { 844 return TCL_ERROR; 845 } 846 if (sashIndex < 0 || sashIndex >= Ttk_NumberSlaves(pw->paned.mgr) - 1) { 847 Tcl_AppendResult(interp, 848 "sash index ", Tcl_GetString(objv[2]), " out of range", 849 NULL); 850 return TCL_ERROR; 851 } 852 853 pane = Ttk_SlaveData(pw->paned.mgr, sashIndex); 854 855 if (objc == 3) { 856 Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); 857 return TCL_OK; 858 } 859 /* else -- set new sash position */ 860 861 if (Tcl_GetIntFromObj(interp, objv[3], &position) != TCL_OK) { 862 return TCL_ERROR; 863 } 864 865 if (position < pane->sashPos) { 866 ShoveUp(pw, sashIndex, position); 867 } else { 868 ShoveDown(pw, sashIndex, position); 869 } 870 871 AdjustPanes(pw); 872 Ttk_ManagerLayoutChanged(pw->paned.mgr); 873 874 Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); 875 return TCL_OK; 876} 877 878static const Ttk_Ensemble PanedCommands[] = { 879 { "add", PanedAddCommand,0 }, 880 { "configure", TtkWidgetConfigureCommand,0 }, 881 { "cget", TtkWidgetCgetCommand,0 }, 882 { "forget", PanedForgetCommand,0 }, 883 { "identify", PanedIdentifyCommand,0 }, 884 { "insert", PanedInsertCommand,0 }, 885 { "instate", TtkWidgetInstateCommand,0 }, 886 { "pane", PanedPaneCommand,0 }, 887 { "panes", PanedPanesCommand,0 }, 888 { "sashpos", PanedSashposCommand,0 }, 889 { "state", TtkWidgetStateCommand,0 }, 890 { 0,0,0 } 891}; 892 893/*------------------------------------------------------------------------ 894 * +++ Widget specification. 895 */ 896 897static WidgetSpec PanedWidgetSpec = 898{ 899 "TPanedwindow", /* className */ 900 sizeof(Paned), /* recordSize */ 901 PanedOptionSpecs, /* optionSpecs */ 902 PanedCommands, /* subcommands */ 903 PanedInitialize, /* initializeProc */ 904 PanedCleanup, /* cleanupProc */ 905 TtkCoreConfigure, /* configureProc */ 906 PanedPostConfigure, /* postConfigureProc */ 907 PanedGetLayout, /* getLayoutProc */ 908 PanedSize, /* sizeProc */ 909 TtkWidgetDoLayout, /* layoutProc */ 910 PanedDisplay /* displayProc */ 911}; 912 913/*------------------------------------------------------------------------ 914 * +++ Elements and layouts. 915 */ 916 917static const int DEFAULT_SASH_THICKNESS = 5; 918 919typedef struct { 920 Tcl_Obj *thicknessObj; 921} SashElement; 922 923static Ttk_ElementOptionSpec SashElementOptions[] = { 924 { "-sashthickness", TK_OPTION_INT, 925 Tk_Offset(SashElement,thicknessObj), "5" }, 926 { NULL, 0, 0, NULL } 927}; 928 929static void SashElementSize( 930 void *clientData, void *elementRecord, Tk_Window tkwin, 931 int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) 932{ 933 SashElement *sash = elementRecord; 934 int thickness = DEFAULT_SASH_THICKNESS; 935 Tcl_GetIntFromObj(NULL, sash->thicknessObj, &thickness); 936 *widthPtr = *heightPtr = thickness; 937} 938 939static Ttk_ElementSpec SashElementSpec = { 940 TK_STYLE_VERSION_2, 941 sizeof(SashElement), 942 SashElementOptions, 943 SashElementSize, 944 TtkNullElementDraw 945}; 946 947TTK_BEGIN_LAYOUT(PanedLayout) 948 TTK_NODE("Panedwindow.background", 0)/* @@@ BUG: empty layouts don't work */ 949TTK_END_LAYOUT 950 951TTK_BEGIN_LAYOUT(HorizontalSashLayout) 952 TTK_NODE("Sash.hsash", TTK_FILL_X) 953TTK_END_LAYOUT 954 955TTK_BEGIN_LAYOUT(VerticalSashLayout) 956 TTK_NODE("Sash.vsash", TTK_FILL_Y) 957TTK_END_LAYOUT 958 959/*------------------------------------------------------------------------ 960 * +++ Registration routine. 961 */ 962MODULE_SCOPE 963void TtkPanedwindow_Init(Tcl_Interp *interp) 964{ 965 Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); 966 RegisterWidget(interp, "ttk::panedwindow", &PanedWidgetSpec); 967 968 Ttk_RegisterElement(interp, themePtr, "hsash", &SashElementSpec, 0); 969 Ttk_RegisterElement(interp, themePtr, "vsash", &SashElementSpec, 0); 970 971 Ttk_RegisterLayout(themePtr, "TPanedwindow", PanedLayout); 972 Ttk_RegisterLayout(themePtr, "Horizontal.Sash", HorizontalSashLayout); 973 Ttk_RegisterLayout(themePtr, "Vertical.Sash", VerticalSashLayout); 974} 975 976