diff options
Diffstat (limited to 'generic/ttk/ttkPanedwindow.c')
-rw-r--r-- | generic/ttk/ttkPanedwindow.c | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/generic/ttk/ttkPanedwindow.c b/generic/ttk/ttkPanedwindow.c new file mode 100644 index 0000000..771d266 --- /dev/null +++ b/generic/ttk/ttkPanedwindow.c @@ -0,0 +1,974 @@ +/* + * Copyright (c) 2005, Joe English. Freely redistributable. + * + * ttk::panedwindow widget implementation. + * + * TODO: track active/pressed sash. + */ + +#include <string.h> +#include <tk.h> +#include "ttkManager.h" +#include "ttkTheme.h" +#include "ttkWidget.h" + +/*------------------------------------------------------------------------ + * +++ Layout algorithm. + * + * (pos=x/y, size=width/height, depending on -orient=horizontal/vertical) + * + * Each pane carries two pieces of state: the request size and the + * position of the following sash. (The final pane has no sash, + * its sash position is used as a sentinel value). + * + * Pane geometry is determined by the sash positions. + * When resizing, sash positions are computed from the request sizes, + * the available space, and pane weights (see PlaceSashes()). + * This ensures continuous resize behavior (that is: changing + * the size by X pixels then changing the size by Y pixels + * gives the same result as changing the size by X+Y pixels + * in one step). + * + * The request size is initially set to the slave window's requested size. + * When the user drags a sash, each pane's request size is set to its + * actual size. This ensures that panes "stay put" on the next resize. + * + * If reqSize == 0, use 0 for the weight as well. This ensures that + * "collapsed" panes stay collapsed during a resize, regardless of + * their nominal -weight. + * + * +++ Invariants. + * + * #sash = #pane - 1 + * pos(pane[0]) = 0 + * pos(sash[i]) = pos(pane[i]) + size(pane[i]), 0 <= i <= #sash + * pos(pane[i+1]) = pos(sash[i]) + size(sash[i]), 0 <= i < #sash + * pos(sash[#sash]) = size(pw) // sentinel value, constraint + * + * size(pw) = sum(size(pane(0..#pane))) + sum(size(sash(0..#sash))) + * size(pane[i]) >= 0, for 0 <= i < #pane + * size(sash[i]) >= 0, for 0 <= i < #sash + * ==> pos(pane[i]) <= pos(sash[i]) <= pos(pane[i+1]), for 0 <= i < #sash + * + * Assumption: all sashes are the same size. + */ + +/*------------------------------------------------------------------------ + * +++ Widget record. + */ + +typedef struct { + Tcl_Obj *orientObj; + int orient; + int width; + int height; + Ttk_Manager *mgr; + Tk_OptionTable paneOptionTable; + Ttk_Layout sashLayout; + int sashThickness; +} PanedPart; + +typedef struct { + WidgetCore core; + PanedPart paned; +} Paned; + +/* @@@ NOTE: -orient is readonly 'cause dynamic oriention changes NYI + */ +static Tk_OptionSpec PanedOptionSpecs[] = { + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical", + Tk_Offset(Paned,paned.orientObj), Tk_Offset(Paned,paned.orient), + 0,(ClientData)ttkOrientStrings,READONLY_OPTION|STYLE_CHANGED }, + {TK_OPTION_INT, "-width", "width", "Width", "0", + -1,Tk_Offset(Paned,paned.width), + 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_INT, "-height", "height", "Height", "0", + -1,Tk_Offset(Paned,paned.height), + 0,0,GEOMETRY_CHANGED }, + + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +/*------------------------------------------------------------------------ + * +++ Slave pane record. + */ +typedef struct { + int reqSize; /* Pane request size */ + int sashPos; /* Folowing sash position */ + int weight; /* Pane -weight, for resizing */ +} Pane; + +static Tk_OptionSpec PaneOptionSpecs[] = { + {TK_OPTION_INT, "-weight", "weight", "Weight", "0", + -1,Tk_Offset(Pane,weight), 0,0,GEOMETRY_CHANGED }, + {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0} +}; + +/* CreatePane -- + * Create a new pane record. + */ +static Pane *CreatePane(Tcl_Interp *interp, Paned *pw, Tk_Window slaveWindow) +{ + Tk_OptionTable optionTable = pw->paned.paneOptionTable; + void *record = ckalloc(sizeof(Pane)); + Pane *pane = record; + + memset(record, 0, sizeof(Pane)); + if (Tk_InitOptions(interp, record, optionTable, slaveWindow) != TCL_OK) { + ckfree(record); + return NULL; + } + + pane->reqSize + = pw->paned.orient == TTK_ORIENT_HORIZONTAL + ? Tk_ReqWidth(slaveWindow) : Tk_ReqHeight(slaveWindow); + + return pane; +} + +/* DestroyPane -- + * Free pane record. + */ +static void DestroyPane(Paned *pw, Pane *pane) +{ + void *record = pane; + Tk_FreeConfigOptions(record, pw->paned.paneOptionTable, pw->core.tkwin); + ckfree(record); +} + +/* ConfigurePane -- + * Set pane options. + */ +static int ConfigurePane( + Tcl_Interp *interp, Paned *pw, Pane *pane, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Ttk_Manager *mgr = pw->paned.mgr; + Tk_SavedOptions savedOptions; + int mask = 0; + + if (Tk_SetOptions(interp, (void*)pane, pw->paned.paneOptionTable, + objc, objv, slaveWindow, &savedOptions, &mask) != TCL_OK) + { + return TCL_ERROR; + } + + /* Sanity-check: + */ + if (pane->weight < 0) { + Tcl_AppendResult(interp, "-weight must be nonnegative", NULL); + goto error; + } + + /* Done. + */ + Tk_FreeSavedOptions(&savedOptions); + Ttk_ManagerSizeChanged(mgr); + return TCL_OK; + +error: + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; +} + + +/*------------------------------------------------------------------------ + * +++ Sash adjustment. + */ + +/* ShoveUp -- + * Place sash i at specified position, recursively shoving + * previous sashes upwards as needed, until hitting the top + * of the window. If that happens, shove back down. + * + * Returns: final position of sash i. + */ + +static int ShoveUp(Paned *pw, int i, int pos) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr, i); + int sashThickness = pw->paned.sashThickness; + + if (i == 0) { + if (pos < 0) + pos = 0; + } else { + Pane *prevPane = Ttk_SlaveData(pw->paned.mgr, i-1); + if (pos < prevPane->sashPos + sashThickness) + pos = ShoveUp(pw, i-1, pos - sashThickness) + sashThickness; + } + return pane->sashPos = pos; +} + +/* ShoveDown -- + * Same as ShoveUp, but going in the opposite direction + * and stopping at the sentinel sash. + */ +static int ShoveDown(Paned *pw, int i, int pos) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr,i); + int sashThickness = pw->paned.sashThickness; + + if (i == Ttk_NumberSlaves(pw->paned.mgr) - 1) { + pos = pane->sashPos; /* Sentinel value == master window size */ + } else { + Pane *nextPane = Ttk_SlaveData(pw->paned.mgr,i+1); + if (pos + sashThickness > nextPane->sashPos) + pos = ShoveDown(pw, i+1, pos + sashThickness) - sashThickness; + } + return pane->sashPos = pos; +} + +/* PanedSize -- + * Compute the requested size of the paned widget + * from the individual pane request sizes. + * + * Used as the WidgetSpec sizeProc and the ManagerSpec sizeProc. + */ +static int PanedSize(void *recordPtr, int *widthPtr, int *heightPtr) +{ + Paned *pw = recordPtr; + int nPanes = Ttk_NumberSlaves(pw->paned.mgr); + int nSashes = nPanes - 1; + int sashThickness = pw->paned.sashThickness; + int width = 0, height = 0; + int index; + + if (pw->paned.orient == TTK_ORIENT_HORIZONTAL) { + for (index = 0; index < nPanes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + + if (height < Tk_ReqHeight(slaveWindow)) + height = Tk_ReqHeight(slaveWindow); + width += pane->reqSize; + } + width += nSashes * sashThickness; + } else { + for (index = 0; index < nPanes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + + if (width < Tk_ReqWidth(slaveWindow)) + width = Tk_ReqWidth(slaveWindow); + height += pane->reqSize; + } + height += nSashes * sashThickness; + } + + *widthPtr = pw->paned.width > 0 ? pw->paned.width : width; + *heightPtr = pw->paned.height > 0 ? pw->paned.height : height; + return 1; +} + +/* AdjustPanes -- + * Set pane request sizes from sash positions. + * + * NOTE: + * AdjustPanes followed by PlaceSashes (called during relayout) + * will leave the sashes in the same place, as long as available size + * remains contant. + */ +static void AdjustPanes(Paned *pw) +{ + int sashThickness = pw->paned.sashThickness; + int pos = 0; + int index; + + for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int size = pane->sashPos - pos; + pane->reqSize = size >= 0 ? size : 0; + pos = pane->sashPos + sashThickness; + } +} + +/* PlaceSashes -- + * Set sash positions from pane request sizes and available space. + * The sentinel sash position is set to the available space. + * + * Allocate pane->reqSize pixels to each pane, and distribute + * the difference = available size - requested size according + * to pane->weight. + * + * If there's still some left over, squeeze panes from the bottom up + * (This can happen if all weights are zero, or if one or more panes + * are too small to absorb the required shrinkage). + * + * Notes: + * This doesn't distribute the remainder pixels as evenly as it could + * when more than one pane has weight > 1. + */ +static void PlaceSashes(Paned *pw, int width, int height) +{ + Ttk_Manager *mgr = pw->paned.mgr; + int nPanes = Ttk_NumberSlaves(mgr); + int sashThickness = pw->paned.sashThickness; + int available = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? width : height; + int reqSize = 0, totalWeight = 0; + int difference, delta, remainder, pos, i; + + if (nPanes == 0) + return; + + /* Compute total required size and total available weight: + */ + for (i = 0; i < nPanes; ++i) { + Pane *pane = Ttk_SlaveData(mgr, i); + reqSize += pane->reqSize; + totalWeight += pane->weight * (pane->reqSize != 0); + } + + /* Compute difference to be redistributed: + */ + difference = available - reqSize - sashThickness*(nPanes-1); + if (totalWeight != 0) { + delta = difference / totalWeight; + remainder = difference % totalWeight; + if (remainder < 0) { + --delta; + remainder += totalWeight; + } + } else { + delta = remainder = 0; + } + /* ASSERT: 0 <= remainder < totalWeight */ + + /* Place sashes: + */ + pos = 0; + for (i = 0; i < nPanes; ++i) { + Pane *pane = Ttk_SlaveData(mgr, i); + int weight = pane->weight * (pane->reqSize != 0); + int size = pane->reqSize + delta * weight; + + if (weight > remainder) + weight = remainder; + remainder -= weight; + size += weight; + + if (size < 0) + size = 0; + + pane->sashPos = (pos += size); + pos += sashThickness; + } + + /* Handle emergency shrink/emergency stretch: + * Set sentinel sash position to end of widget, + * shove preceding sashes up. + */ + ShoveUp(pw, nPanes - 1, available); +} + +/* PlacePanes -- + * Places slave panes based on sash positions. + */ +static void PlacePanes(Paned *pw) +{ + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + int width = Tk_Width(pw->core.tkwin), height = Tk_Height(pw->core.tkwin); + int sashThickness = pw->paned.sashThickness; + int pos = 0; + int index; + + for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int size = pane->sashPos - pos; + + if (size > 0) { + if (horizontal) { + Ttk_PlaceSlave(pw->paned.mgr, index, pos, 0, size, height); + } else { + Ttk_PlaceSlave(pw->paned.mgr, index, 0, pos, width, size); + } + } else { + Ttk_UnmapSlave(pw->paned.mgr, index); + } + + pos = pane->sashPos + sashThickness; + } +} + +/*------------------------------------------------------------------------ + * +++ Manager specification. + */ + +static void PanedPlaceSlaves(void *managerData) +{ + Paned *pw = managerData; + PlaceSashes(pw, Tk_Width(pw->core.tkwin), Tk_Height(pw->core.tkwin)); + PlacePanes(pw); +} + +static void PaneRemoved(void *managerData, int index) +{ + Paned *pw = managerData; + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + DestroyPane(pw, pane); +} + +static int AddPane( + Tcl_Interp *interp, Paned *pw, + int destIndex, Tk_Window slaveWindow, + int objc, Tcl_Obj *const objv[]) +{ + Pane *pane; + if (!Ttk_Maintainable(interp, slaveWindow, pw->core.tkwin)) { + return TCL_ERROR; + } + if (Ttk_SlaveIndex(pw->paned.mgr, slaveWindow) >= 0) { + Tcl_AppendResult(interp, + Tk_PathName(slaveWindow), " already added", + NULL); + return TCL_ERROR; + } + + pane = CreatePane(interp, pw, slaveWindow); + if (!pane) { + return TCL_ERROR; + } + if (ConfigurePane(interp, pw, pane, slaveWindow, objc, objv) != TCL_OK) { + DestroyPane(pw, pane); + return TCL_ERROR; + } + + Ttk_InsertSlave(pw->paned.mgr, destIndex, slaveWindow, pane); + return TCL_OK; +} + +/* PaneRequest -- + * Only update pane request size if slave is currently unmapped. + * Geometry requests from mapped slaves are not directly honored + * in order to avoid unexpected pane resizes (esp. while the + * user is dragging a sash [#1325286]). + */ +static int PaneRequest(void *managerData, int index, int width, int height) +{ + Paned *pw = managerData; + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index); + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + + if (!Tk_IsMapped(slaveWindow)) { + pane->reqSize = horizontal ? width : height; + } + return 1; +} + +static Ttk_ManagerSpec PanedManagerSpec = { + { "panedwindow", Ttk_GeometryRequestProc, Ttk_LostSlaveProc }, + PanedSize, + PanedPlaceSlaves, + PaneRequest, + PaneRemoved +}; + +/*------------------------------------------------------------------------ + * +++ Event handler. + * + * <<NOTE-PW-LEAVE-NOTIFYINFERIOR>> + * Tk does not execute binding scripts for <Leave> events when + * the pointer crosses from a parent to a child. This widget + * needs to know when that happens, though, so it can reset + * the cursor. + * + * This event handler generates an <<EnteredChild>> virtual event + * on LeaveNotify/NotifyInferior. + */ + +static const unsigned PanedEventMask = LeaveWindowMask; +static void PanedEventProc(ClientData clientData, XEvent *eventPtr) +{ + WidgetCore *corePtr = clientData; + if ( eventPtr->type == LeaveNotify + && eventPtr->xcrossing.detail == NotifyInferior) + { + TtkSendVirtualEvent(corePtr->tkwin, "EnteredChild"); + } +} + +/*------------------------------------------------------------------------ + * +++ Initialization and cleanup hooks. + */ + +static void PanedInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Paned *pw = recordPtr; + + Tk_CreateEventHandler(pw->core.tkwin, + PanedEventMask, PanedEventProc, recordPtr); + pw->paned.mgr = Ttk_CreateManager(&PanedManagerSpec, pw, pw->core.tkwin); + pw->paned.paneOptionTable = Tk_CreateOptionTable(interp,PaneOptionSpecs); + pw->paned.sashLayout = 0; + pw->paned.sashThickness = 1; +} + +static void PanedCleanup(void *recordPtr) +{ + Paned *pw = recordPtr; + + if (pw->paned.sashLayout) + Ttk_FreeLayout(pw->paned.sashLayout); + Tk_DeleteEventHandler(pw->core.tkwin, + PanedEventMask, PanedEventProc, recordPtr); + Ttk_DeleteManager(pw->paned.mgr); +} + +/* Post-configuration hook. + */ +static int PanedPostConfigure(Tcl_Interp *interp, void *clientData, int mask) +{ + Paned *pw = clientData; + + if (mask & GEOMETRY_CHANGED) { + /* User has changed -width or -height. + * Recalculate sash positions based on requested size. + */ + Tk_Window tkwin = pw->core.tkwin; + PlaceSashes(pw, + pw->paned.width > 0 ? pw->paned.width : Tk_Width(tkwin), + pw->paned.height > 0 ? pw->paned.height : Tk_Height(tkwin)); + } + + return TCL_OK; +} + +/*------------------------------------------------------------------------ + * +++ Layout management hooks. + */ +static Ttk_Layout PanedGetLayout( + Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr) +{ + Paned *pw = recordPtr; + Ttk_Layout panedLayout = TtkWidgetGetLayout(interp, themePtr, recordPtr); + + if (panedLayout) { + int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL; + const char *layoutName = + horizontal ? ".Vertical.Sash" : ".Horizontal.Sash"; + Ttk_Layout sashLayout = Ttk_CreateSublayout( + interp, themePtr, panedLayout, layoutName, pw->core.optionTable); + + if (sashLayout) { + int sashWidth, sashHeight; + + Ttk_LayoutSize(sashLayout, 0, &sashWidth, &sashHeight); + pw->paned.sashThickness = horizontal ? sashWidth : sashHeight; + + if (pw->paned.sashLayout) + Ttk_FreeLayout(pw->paned.sashLayout); + pw->paned.sashLayout = sashLayout; + } else { + Ttk_FreeLayout(panedLayout); + return 0; + } + } + + return panedLayout; +} + +/*------------------------------------------------------------------------ + * +++ Drawing routines. + */ + +/* SashLayout -- + * Place the sash sublayout after the specified pane, + * in preparation for drawing. + */ +static Ttk_Layout SashLayout(Paned *pw, int index) +{ + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + int thickness = pw->paned.sashThickness, + height = Tk_Height(pw->core.tkwin), + width = Tk_Width(pw->core.tkwin), + sashPos = pane->sashPos; + + Ttk_PlaceLayout( + pw->paned.sashLayout, pw->core.state, + pw->paned.orient == TTK_ORIENT_HORIZONTAL + ? Ttk_MakeBox(sashPos, 0, thickness, height) + : Ttk_MakeBox(0, sashPos, width, thickness)); + + return pw->paned.sashLayout; +} + +static void DrawSash(Paned *pw, int index, Drawable d) +{ + Ttk_DrawLayout(SashLayout(pw, index), pw->core.state, d); +} + +static void PanedDisplay(void *recordPtr, Drawable d) +{ + Paned *pw = recordPtr; + int i, nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; + + TtkWidgetDisplay(recordPtr, d); + for (i = 0; i < nSashes; ++i) { + DrawSash(pw, i, d); + } +} + +/*------------------------------------------------------------------------ + * +++ Widget commands. + */ + +/* $pw add window [ options ... ] + */ +static int PanedAddCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + Tk_Window slaveWindow; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "window"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow( + interp, Tcl_GetString(objv[2]), pw->core.tkwin); + + if (!slaveWindow) { + return TCL_ERROR; + } + + return AddPane(interp, pw, Ttk_NumberSlaves(pw->paned.mgr), slaveWindow, + objc - 3, objv + 3); +} + +/* $pw insert $index $slave ?-option value ...? + * Insert new slave, or move existing one. + */ +static int PanedInsertCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int nSlaves = Ttk_NumberSlaves(pw->paned.mgr); + int srcIndex, destIndex; + Tk_Window slaveWindow; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index slave ?-option value ...?"); + return TCL_ERROR; + } + + slaveWindow = Tk_NameToWindow( + interp, Tcl_GetString(objv[3]), pw->core.tkwin); + if (!slaveWindow) { + return TCL_ERROR; + } + + if (!strcmp(Tcl_GetString(objv[2]), "end")) { + destIndex = Ttk_NumberSlaves(pw->paned.mgr); + } else if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp,pw->paned.mgr,objv[2],&destIndex)) + { + return TCL_ERROR; + } + + srcIndex = Ttk_SlaveIndex(pw->paned.mgr, slaveWindow); + if (srcIndex < 0) { /* New slave: */ + return AddPane(interp, pw, destIndex, slaveWindow, objc-4, objv+4); + } /* else -- move existing slave: */ + + if (destIndex >= nSlaves) + destIndex = nSlaves - 1; + Ttk_ReorderSlave(pw->paned.mgr, srcIndex, destIndex); + + return objc == 4 ? TCL_OK : + ConfigurePane(interp, pw, + Ttk_SlaveData(pw->paned.mgr, destIndex), + Ttk_SlaveWindow(pw->paned.mgr, destIndex), + objc-4,objv+4); +} + +/* $pw forget $pane + */ +static int PanedForgetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int paneIndex; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2,objv, "pane"); + return TCL_ERROR; + } + + if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp, pw->paned.mgr, objv[2], &paneIndex)) + { + return TCL_ERROR; + } + Ttk_ForgetSlave(pw->paned.mgr, paneIndex); + + return TCL_OK; +} + +/* $pw identify ?what? $x $y -- + * Return index of sash at $x,$y + */ +static int PanedIdentifyCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + const char *whatTable[] = { "element", "sash", NULL }; + enum { IDENTIFY_ELEMENT, IDENTIFY_SASH }; + int what = IDENTIFY_SASH; + Paned *pw = recordPtr; + int sashThickness = pw->paned.sashThickness; + int nSashes = Ttk_NumberSlaves(pw->paned.mgr) - 1; + int x, y, pos; + int index; + + if (objc < 4 || objc > 5) { + Tcl_WrongNumArgs(interp, 2,objv, "?what? x y"); + return TCL_ERROR; + } + + if ( Tcl_GetIntFromObj(interp, objv[objc-2], &x) != TCL_OK + || Tcl_GetIntFromObj(interp, objv[objc-1], &y) != TCL_OK + || (objc == 5 && + Tcl_GetIndexFromObj(interp, objv[2], whatTable, "option", 0, &what) + != TCL_OK) + ) { + return TCL_ERROR; + } + + pos = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? x : y; + for (index = 0; index < nSashes; ++index) { + Pane *pane = Ttk_SlaveData(pw->paned.mgr, index); + if (pane->sashPos <= pos && pos <= pane->sashPos + sashThickness) { + /* Found it. */ + switch (what) { + case IDENTIFY_SASH: + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + return TCL_OK; + case IDENTIFY_ELEMENT: + { + Ttk_Element element = + Ttk_IdentifyElement(SashLayout(pw, index), x, y); + if (element) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj(Ttk_ElementName(element), -1)); + } + return TCL_OK; + } + } + } + } + + return TCL_OK; /* nothing found - return empty string */ +} + +/* $pw pane $pane ?-option ?value -option value ...?? + * Query/modify pane options. + */ +static int PanedPaneCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int paneIndex; + Tk_Window slaveWindow; + Pane *pane; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2,objv, "pane ?-option value ...?"); + return TCL_ERROR; + } + + if (TCL_OK != Ttk_GetSlaveIndexFromObj( + interp,pw->paned.mgr,objv[2],&paneIndex)) + { + return TCL_ERROR; + } + + pane = Ttk_SlaveData(pw->paned.mgr, paneIndex); + slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, paneIndex); + + switch (objc) { + case 3: + return TtkEnumerateOptions(interp, pane, PaneOptionSpecs, + pw->paned.paneOptionTable, slaveWindow); + case 4: + return TtkGetOptionValue(interp, pane, objv[3], + pw->paned.paneOptionTable, slaveWindow); + default: + return ConfigurePane(interp, pw, pane, slaveWindow, objc-3,objv+3); + } +} + +/* $pw panes -- + * Return list of managed panes. + */ +static int PanedPanesCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + Ttk_Manager *mgr = pw->paned.mgr; + Tcl_Obj *panes; + int i; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + panes = Tcl_NewListObj(0, NULL); + for (i = 0; i < Ttk_NumberSlaves(mgr); ++i) { + const char *pathName = Tk_PathName(Ttk_SlaveWindow(mgr,i)); + Tcl_ListObjAppendElement(interp, panes, Tcl_NewStringObj(pathName,-1)); + } + Tcl_SetObjResult(interp, panes); + + return TCL_OK; +} + + +/* $pw sashpos $index ?$newpos? + * Query or modify sash position. + */ +static int PanedSashposCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Paned *pw = recordPtr; + int sashIndex, position = -1; + Pane *pane; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 2,objv, "index ?newpos?"); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[2], &sashIndex) != TCL_OK) { + return TCL_ERROR; + } + if (sashIndex < 0 || sashIndex >= Ttk_NumberSlaves(pw->paned.mgr) - 1) { + Tcl_AppendResult(interp, + "sash index ", Tcl_GetString(objv[2]), " out of range", + NULL); + return TCL_ERROR; + } + + pane = Ttk_SlaveData(pw->paned.mgr, sashIndex); + + if (objc == 3) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); + return TCL_OK; + } + /* else -- set new sash position */ + + if (Tcl_GetIntFromObj(interp, objv[3], &position) != TCL_OK) { + return TCL_ERROR; + } + + if (position < pane->sashPos) { + ShoveUp(pw, sashIndex, position); + } else { + ShoveDown(pw, sashIndex, position); + } + + AdjustPanes(pw); + Ttk_ManagerLayoutChanged(pw->paned.mgr); + + Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos)); + return TCL_OK; +} + +static const Ttk_Ensemble PanedCommands[] = { + { "add", PanedAddCommand,0 }, + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "forget", PanedForgetCommand,0 }, + { "identify", PanedIdentifyCommand,0 }, + { "insert", PanedInsertCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "pane", PanedPaneCommand,0 }, + { "panes", PanedPanesCommand,0 }, + { "sashpos", PanedSashposCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { 0,0,0 } +}; + +/*------------------------------------------------------------------------ + * +++ Widget specification. + */ + +static WidgetSpec PanedWidgetSpec = +{ + "TPanedwindow", /* className */ + sizeof(Paned), /* recordSize */ + PanedOptionSpecs, /* optionSpecs */ + PanedCommands, /* subcommands */ + PanedInitialize, /* initializeProc */ + PanedCleanup, /* cleanupProc */ + TtkCoreConfigure, /* configureProc */ + PanedPostConfigure, /* postConfigureProc */ + PanedGetLayout, /* getLayoutProc */ + PanedSize, /* sizeProc */ + TtkWidgetDoLayout, /* layoutProc */ + PanedDisplay /* displayProc */ +}; + +/*------------------------------------------------------------------------ + * +++ Elements and layouts. + */ + +static const int DEFAULT_SASH_THICKNESS = 5; + +typedef struct { + Tcl_Obj *thicknessObj; +} SashElement; + +static Ttk_ElementOptionSpec SashElementOptions[] = { + { "-sashthickness", TK_OPTION_INT, + Tk_Offset(SashElement,thicknessObj), "5" }, + { NULL, 0, 0, NULL } +}; + +static void SashElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SashElement *sash = elementRecord; + int thickness = DEFAULT_SASH_THICKNESS; + Tcl_GetIntFromObj(NULL, sash->thicknessObj, &thickness); + *widthPtr = *heightPtr = thickness; +} + +static Ttk_ElementSpec SashElementSpec = { + TK_STYLE_VERSION_2, + sizeof(SashElement), + SashElementOptions, + SashElementSize, + TtkNullElementDraw +}; + +TTK_BEGIN_LAYOUT(PanedLayout) + TTK_NODE("Panedwindow.background", 0)/* @@@ BUG: empty layouts don't work */ +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(HorizontalSashLayout) + TTK_NODE("Sash.hsash", TTK_FILL_X) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(VerticalSashLayout) + TTK_NODE("Sash.vsash", TTK_FILL_Y) +TTK_END_LAYOUT + +/*------------------------------------------------------------------------ + * +++ Registration routine. + */ +MODULE_SCOPE +void TtkPanedwindow_Init(Tcl_Interp *interp) +{ + Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); + RegisterWidget(interp, "ttk::panedwindow", &PanedWidgetSpec); + + Ttk_RegisterElement(interp, themePtr, "hsash", &SashElementSpec, 0); + Ttk_RegisterElement(interp, themePtr, "vsash", &SashElementSpec, 0); + + Ttk_RegisterLayout(themePtr, "TPanedwindow", PanedLayout); + Ttk_RegisterLayout(themePtr, "Horizontal.Sash", HorizontalSashLayout); + Ttk_RegisterLayout(themePtr, "Vertical.Sash", VerticalSashLayout); +} + |