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