/* $Id: ttkPanedwindow.c,v 1.22 2010/02/05 21:33:14 jenglish Exp $
*
* 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);
}