/* * tkPanedWindow.c -- * * This module implements "paned window" widgets that are object based. A * "paned window" is a widget that manages the geometry for some number * of other widgets, placing a movable "sash" between them, which can be * used to alter the relative sizes of adjacent widgets. * * Copyright © 1997 Sun Microsystems, Inc. * Copyright © 2000 Ajuba Solutions. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkInt.h" #include "default.h" /* * Flag values for "sticky"ness. The 16 combinations subsume the packer's * notion of anchor and fill. * * STICK_NORTH This window sticks to the top of its cavity. * STICK_EAST This window sticks to the right edge of its cavity. * STICK_SOUTH This window sticks to the bottom of its cavity. * STICK_WEST This window sticks to the left edge of its cavity. */ #define STICK_NORTH 1 #define STICK_EAST 2 #define STICK_SOUTH 4 #define STICK_WEST 8 /* * The following table defines the legal values for the -orient option. */ static const char *const orientStrings[] = { "horizontal", "vertical", NULL }; enum orient { ORIENT_HORIZONTAL, ORIENT_VERTICAL }; /* * The following table defines the legal values for the -stretch option. */ static const char *const stretchStrings[] = { "always", "first", "last", "middle", "never", NULL }; enum stretch { STRETCH_ALWAYS, /* Always give extra space to this pane. */ STRETCH_FIRST, /* Give extra space to pane if it is first. */ STRETCH_LAST, /* Give extra space to pane if it is last. */ STRETCH_MIDDLE, /* Give extra space to pane only if it is * neither first nor last. */ STRETCH_NEVER /* Never give extra space to this pane. */ }; /* * Codify the stretchiness rule in one place. */ #define IsStretchable(stretch,index,first,last) \ (((stretch) == STRETCH_ALWAYS) || \ ((stretch) == STRETCH_FIRST && (index) == (first)) || \ ((stretch) == STRETCH_LAST && (index) == (last)) || \ ((stretch) == STRETCH_MIDDLE && (index) != (first) && (index) != (last))) typedef struct { Tk_OptionTable pwOptions; /* Token for paned window option table. */ Tk_OptionTable paneOpts; /* Token for pane cget option table. */ } OptionTables; /* * One structure of the following type is kept for each window * managed by a paned window widget. */ typedef struct Pane { Tk_Window tkwin; /* Window being managed. */ int minSize; /* Minimum size of this pane, on the relevant * axis, in pixels. */ int padx; /* Additional padding requested for pane, in * the x dimension. */ int pady; /* Additional padding requested for pane, in * the y dimension. */ Tcl_Obj *widthPtr, *heightPtr; /* Tcl_Obj rep's of pane width/height, to * allow for null values. */ int width; /* Pane width. */ int height; /* Pane height. */ int sticky; /* Sticky string. */ int x, y; /* Coordinates of the widget. */ int paneWidth, paneHeight; /* Pane dimensions (may be different from * pane width/height). */ int sashx, sashy; /* Coordinates of the sash of the right or * bottom of this pane. */ int markx, marky; /* Coordinates of the last mark set for the * sash. */ int handlex, handley; /* Coordinates of the sash handle. */ enum stretch stretch; /* Controls how pane grows/shrinks */ int hide; /* Controls visibility of pane */ struct PanedWindow *containerPtr; /* Paned window managing the window. */ Tk_Window after; /* Placeholder for parsing options. */ Tk_Window before; /* Placeholder for parsing options. */ } Pane; /* * A data structure of the following type is kept for each paned window widget * managed by this file: */ typedef struct PanedWindow { Tk_Window tkwin; /* Window that embodies the paned window. */ Tk_Window proxywin; /* Window for the resizing proxy. */ Display *display; /* X's token for the window's display. */ Tcl_Interp *interp; /* Interpreter associated with widget. */ Tcl_Command widgetCmd; /* Token for square's widget command. */ Tk_OptionTable optionTable; /* Token representing the configuration * specifications. */ Tk_OptionTable paneOpts; /* Token for pane cget table. */ Tk_3DBorder background; /* Background color. */ int borderWidth; /* Value of -borderwidth option. */ int relief; /* 3D border effect (TK_RELIEF_RAISED, etc) */ Tcl_Obj *widthPtr; /* Tcl_Obj rep for width. */ Tcl_Obj *heightPtr; /* Tcl_Obj rep for height. */ int width, height; /* Width and height of the widget. */ enum orient orient; /* Orientation of the widget. */ Tk_Cursor cursor; /* Current cursor for window, or None. */ int resizeOpaque; /* Boolean indicating whether resize should be * opaque or rubberband style. */ int sashRelief; /* Relief used to draw sash. */ int sashWidth; /* Width of each sash, in pixels. */ Tcl_Obj *sashWidthPtr; /* Tcl_Obj rep for sash width. */ int sashPad; /* Additional padding around each sash. */ Tcl_Obj *sashPadPtr; /* Tcl_Obj rep for sash padding. */ int showHandle; /* Boolean indicating whether sash handles * should be drawn. */ int handleSize; /* Size of one side of a sash handle (handles * are square), in pixels. */ int handlePad; /* Distance from border to draw handle. */ Tcl_Obj *handleSizePtr; /* Tcl_Obj rep for handle size. */ Tk_Cursor sashCursor; /* Cursor used when mouse is above a sash. */ GC gc; /* Graphics context for copying from * off-screen pixmap onto screen. */ int proxyx, proxyy; /* Proxy x,y coordinates. */ Tk_3DBorder proxyBackground;/* Background color used to draw proxy. If NULL, use background. */ Tcl_Obj *proxyBorderWidthPtr; /* Tcl_Obj rep for proxyBorderWidth */ int proxyBorderWidth; /* Borderwidth used to draw proxy. */ int proxyRelief; /* Relief used to draw proxy, if TK_RELIEF_NULL then use relief. */ Pane **panes; /* Pointer to array of Panes. */ int numPanes; /* Number of panes. */ int sizeofPanes; /* Number of elements in the panes array. */ int flags; /* Flags for widget; see below. */ } PanedWindow; /* * Flags used for paned windows: * * REDRAW_PENDING: Non-zero means a DoWhenIdle handler has been * queued to redraw this window. * * WIDGET_DELETED: Non-zero means that the paned window has been, * or is in the process of being, deleted. * * RESIZE_PENDING: Non-zero means that the window might need to * change its size (or the size of its panes) * because of a change in the size of one of its * children. */ #define REDRAW_PENDING 0x0001 #define WIDGET_DELETED 0x0002 #define REQUESTED_RELAYOUT 0x0004 #define RECOMPUTE_GEOMETRY 0x0008 #define PROXY_REDRAW_PENDING 0x0010 #define RESIZE_PENDING 0x0020 /* * Forward declarations for functions defined later in this file: */ static void PanedWindowCmdDeletedProc(void *clientData); static int ConfigurePanedWindow(Tcl_Interp *interp, PanedWindow *pwPtr, int objc, Tcl_Obj *const objv[]); static void DestroyPanedWindow(PanedWindow *pwPtr); static void DisplayPanedWindow(void *clientData); static void PanedWindowEventProc(void *clientData, XEvent *eventPtr); static void ProxyWindowEventProc(void *clientData, XEvent *eventPtr); static void DisplayProxyWindow(void *clientData); static void PanedWindowWorldChanged(void *instanceData); static int PanedWindowWidgetObjCmd(void *clientData, Tcl_Interp *, int objc, Tcl_Obj * const objv[]); static void PanedWindowLostPaneProc(void *clientData, Tk_Window tkwin); static void PanedWindowReqProc(void *clientData, Tk_Window tkwin); static void ArrangePanes(void *clientData); static void Unlink(Pane *panePtr); static Pane * GetPane(PanedWindow *pwPtr, Tk_Window tkwin); static void GetFirstLastVisiblePane(PanedWindow *pwPtr, int *firstPtr, int *lastPtr); static void PaneStructureProc(void *clientData, XEvent *eventPtr); static int PanedWindowSashCommand(PanedWindow *pwPtr, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); static int PanedWindowProxyCommand(PanedWindow *pwPtr, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); static void ComputeGeometry(PanedWindow *pwPtr); static int ConfigurePanes(PanedWindow *pwPtr, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); static void DestroyOptionTables(void *clientData, Tcl_Interp *interp); static int SetSticky(void *clientData, Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj **value, char *recordPtr, Tcl_Size internalOffset, char *oldInternalPtr, int flags); static Tcl_Obj * GetSticky(void *clientData, Tk_Window tkwin, char *recordPtr, Tcl_Size internalOffset); static void RestoreSticky(void *clientData, Tk_Window tkwin, char *internalPtr, char *oldInternalPtr); static void AdjustForSticky(int sticky, int cavityWidth, int cavityHeight, int *xPtr, int *yPtr, int *paneWidthPtr, int *paneHeightPtr); static void MoveSash(PanedWindow *pwPtr, int sash, int diff); static int ObjectIsEmpty(Tcl_Obj *objPtr); static void * ComputeSlotAddress(void *recordPtr, Tcl_Size offset); static int PanedWindowIdentifyCoords(PanedWindow *pwPtr, Tcl_Interp *interp, int x, int y); /* * Sashes are between panes only, so there is one less sash than panes */ #define ValidSashIndex(pwPtr, sash) \ (((sash) >= 0) && ((sash) < ((pwPtr)->numPanes-1))) static const Tk_GeomMgr panedWindowMgrType = { "panedwindow", /* name */ PanedWindowReqProc, /* requestProc */ PanedWindowLostPaneProc, /* lostPaneProc */ }; /* * Information used for objv parsing. */ #define GEOMETRY 0x0001 /* * The following structure contains pointers to functions used for processing * the custom "-sticky" option for panes. */ static const Tk_ObjCustomOption stickyOption = { "sticky", /* name */ SetSticky, /* setProc */ GetSticky, /* getProc */ RestoreSticky, /* restoreProc */ NULL, /* freeProc */ 0 }; static const Tk_OptionSpec optionSpecs[] = { {TK_OPTION_BORDER, "-background", "background", "Background", DEF_PANEDWINDOW_BG_COLOR, TCL_INDEX_NONE, offsetof(PanedWindow, background), 0, DEF_PANEDWINDOW_BG_MONO, 0}, {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL, 0, TCL_INDEX_NONE, 0, "-borderwidth", 0}, {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL, 0, TCL_INDEX_NONE, 0, "-background", 0}, {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_PANEDWINDOW_BORDERWIDTH, TCL_INDEX_NONE, offsetof(PanedWindow, borderWidth), 0, 0, GEOMETRY}, {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", DEF_PANEDWINDOW_CURSOR, TCL_INDEX_NONE, offsetof(PanedWindow, cursor), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-handlepad", "handlePad", "HandlePad", DEF_PANEDWINDOW_HANDLEPAD, TCL_INDEX_NONE, offsetof(PanedWindow, handlePad), 0, 0, GEOMETRY}, {TK_OPTION_PIXELS, "-handlesize", "handleSize", "HandleSize", DEF_PANEDWINDOW_HANDLESIZE, offsetof(PanedWindow, handleSizePtr), offsetof(PanedWindow, handleSize), 0, 0, GEOMETRY}, {TK_OPTION_PIXELS, "-height", "height", "Height", DEF_PANEDWINDOW_HEIGHT, offsetof(PanedWindow, heightPtr), offsetof(PanedWindow, height), TK_OPTION_NULL_OK, 0, GEOMETRY}, {TK_OPTION_BOOLEAN, "-opaqueresize", "opaqueResize", "OpaqueResize", DEF_PANEDWINDOW_OPAQUERESIZE, TCL_INDEX_NONE, offsetof(PanedWindow, resizeOpaque), 0, 0, 0}, {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", DEF_PANEDWINDOW_ORIENT, TCL_INDEX_NONE, offsetof(PanedWindow, orient), TK_OPTION_ENUM_VAR, orientStrings, GEOMETRY}, {TK_OPTION_BORDER, "-proxybackground", "proxyBackground", "ProxyBackground", 0, TCL_INDEX_NONE, offsetof(PanedWindow, proxyBackground), TK_OPTION_NULL_OK, (void *)DEF_PANEDWINDOW_BG_MONO, 0}, {TK_OPTION_PIXELS, "-proxyborderwidth", "proxyBorderWidth", "ProxyBorderWidth", DEF_PANEDWINDOW_PROXYBORDER, offsetof(PanedWindow, proxyBorderWidthPtr), offsetof(PanedWindow, proxyBorderWidth), 0, 0, GEOMETRY}, {TK_OPTION_RELIEF, "-proxyrelief", "proxyRelief", "Relief", 0, TCL_INDEX_NONE, offsetof(PanedWindow, proxyRelief), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", DEF_PANEDWINDOW_RELIEF, TCL_INDEX_NONE, offsetof(PanedWindow, relief), 0, 0, 0}, {TK_OPTION_CURSOR, "-sashcursor", "sashCursor", "Cursor", DEF_PANEDWINDOW_SASHCURSOR, TCL_INDEX_NONE, offsetof(PanedWindow, sashCursor), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-sashpad", "sashPad", "SashPad", DEF_PANEDWINDOW_SASHPAD, TCL_INDEX_NONE, offsetof(PanedWindow, sashPad), 0, 0, GEOMETRY}, {TK_OPTION_RELIEF, "-sashrelief", "sashRelief", "Relief", DEF_PANEDWINDOW_SASHRELIEF, TCL_INDEX_NONE, offsetof(PanedWindow, sashRelief), 0, 0, 0}, {TK_OPTION_PIXELS, "-sashwidth", "sashWidth", "Width", DEF_PANEDWINDOW_SASHWIDTH, offsetof(PanedWindow, sashWidthPtr), offsetof(PanedWindow, sashWidth), 0, 0, GEOMETRY}, {TK_OPTION_BOOLEAN, "-showhandle", "showHandle", "ShowHandle", DEF_PANEDWINDOW_SHOWHANDLE, TCL_INDEX_NONE, offsetof(PanedWindow, showHandle), 0, 0, GEOMETRY}, {TK_OPTION_PIXELS, "-width", "width", "Width", DEF_PANEDWINDOW_WIDTH, offsetof(PanedWindow, widthPtr), offsetof(PanedWindow, width), TK_OPTION_NULL_OK, 0, GEOMETRY}, {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0} }; static const Tk_OptionSpec paneOptionSpecs[] = { {TK_OPTION_WINDOW, "-after", NULL, NULL, DEF_PANEDWINDOW_PANE_AFTER, TCL_INDEX_NONE, offsetof(Pane, after), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_WINDOW, "-before", NULL, NULL, DEF_PANEDWINDOW_PANE_BEFORE, TCL_INDEX_NONE, offsetof(Pane, before), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-height", NULL, NULL, DEF_PANEDWINDOW_PANE_HEIGHT, offsetof(Pane, heightPtr), offsetof(Pane, height), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide", DEF_PANEDWINDOW_PANE_HIDE, TCL_INDEX_NONE, offsetof(Pane, hide), 0,0,GEOMETRY}, {TK_OPTION_PIXELS, "-minsize", NULL, NULL, DEF_PANEDWINDOW_PANE_MINSIZE, TCL_INDEX_NONE, offsetof(Pane, minSize), 0, 0, 0}, {TK_OPTION_PIXELS, "-padx", NULL, NULL, DEF_PANEDWINDOW_PANE_PADX, TCL_INDEX_NONE, offsetof(Pane, padx), 0, 0, 0}, {TK_OPTION_PIXELS, "-pady", NULL, NULL, DEF_PANEDWINDOW_PANE_PADY, TCL_INDEX_NONE, offsetof(Pane, pady), 0, 0, 0}, {TK_OPTION_CUSTOM, "-sticky", NULL, NULL, DEF_PANEDWINDOW_PANE_STICKY, TCL_INDEX_NONE, offsetof(Pane, sticky), 0, &stickyOption, 0}, {TK_OPTION_STRING_TABLE, "-stretch", "stretch", "Stretch", DEF_PANEDWINDOW_PANE_STRETCH, TCL_INDEX_NONE, offsetof(Pane, stretch), TK_OPTION_ENUM_VAR, stretchStrings, 0}, {TK_OPTION_PIXELS, "-width", NULL, NULL, DEF_PANEDWINDOW_PANE_WIDTH, offsetof(Pane, widthPtr), offsetof(Pane, width), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0} }; /* *-------------------------------------------------------------- * * Tk_PanedWindowObjCmd -- * * This function is invoked to process the "panedwindow" Tcl command. It * creates a new "panedwindow" widget. * * Results: * A standard Tcl result. * * Side effects: * A new widget is created and configured. * *-------------------------------------------------------------- */ int Tk_PanedWindowObjCmd( TCL_UNUSED(void *), /* NULL. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj * const objv[]) /* Argument objects. */ { PanedWindow *pwPtr; Tk_Window tkwin, parent; OptionTables *pwOpts; XSetWindowAttributes atts; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?"); return TCL_ERROR; } tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), Tcl_GetString(objv[1]), NULL); if (tkwin == NULL) { return TCL_ERROR; } pwOpts = (OptionTables *) Tcl_GetAssocData(interp, "PanedWindowOptionTables", NULL); if (pwOpts == NULL) { /* * The first time this function is invoked, the option tables will be * NULL. We then create the option tables from the templates and store * a pointer to the tables as the command's clientData so we'll have * easy access to it in the future. */ pwOpts = (OptionTables *)ckalloc(sizeof(OptionTables)); /* * Set up an exit handler to free the optionTables struct. */ Tcl_SetAssocData(interp, "PanedWindowOptionTables", DestroyOptionTables, pwOpts); /* * Create the paned window option tables. */ pwOpts->pwOptions = Tk_CreateOptionTable(interp, optionSpecs); pwOpts->paneOpts = Tk_CreateOptionTable(interp, paneOptionSpecs); } Tk_SetClass(tkwin, "Panedwindow"); /* * Allocate and initialize the widget record. */ pwPtr = (PanedWindow *)ckalloc(sizeof(PanedWindow)); memset((void *)pwPtr, 0, (sizeof(PanedWindow))); pwPtr->tkwin = tkwin; pwPtr->display = Tk_Display(tkwin); pwPtr->interp = interp; pwPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(pwPtr->tkwin), PanedWindowWidgetObjCmd, pwPtr, PanedWindowCmdDeletedProc); pwPtr->optionTable = pwOpts->pwOptions; pwPtr->paneOpts = pwOpts->paneOpts; pwPtr->relief = TK_RELIEF_RAISED; pwPtr->gc = NULL; pwPtr->cursor = NULL; pwPtr->sashCursor = NULL; /* * Keep a hold of the associated tkwin until we destroy the widget, * otherwise Tk might free it while we still need it. */ Tcl_Preserve(pwPtr->tkwin); if (Tk_InitOptions(interp, pwPtr, pwOpts->pwOptions, tkwin) != TCL_OK) { Tk_DestroyWindow(pwPtr->tkwin); return TCL_ERROR; } Tk_CreateEventHandler(pwPtr->tkwin, ExposureMask|StructureNotifyMask, PanedWindowEventProc, pwPtr); /* * Find the toplevel ancestor of the panedwindow, and make a proxy win as * a child of that window; this way the proxy can always float above * panes in the panedwindow. */ parent = Tk_Parent(pwPtr->tkwin); while (!(Tk_IsTopLevel(parent))) { parent = Tk_Parent(parent); if (parent == NULL) { parent = pwPtr->tkwin; break; } } pwPtr->proxywin = Tk_CreateAnonymousWindow(interp, parent, NULL); /* * The proxy window has to be able to share GCs with the main panedwindow * despite being children of windows with potentially different * characteristics, and it looks better that way too. [Bug 702230] Also * set the X window save under attribute to avoid expose events as the * proxy sash is dragged across the panes. [Bug 1036963] */ Tk_SetWindowVisual(pwPtr->proxywin, Tk_Visual(tkwin), Tk_Depth(tkwin), Tk_Colormap(tkwin)); Tk_CreateEventHandler(pwPtr->proxywin, ExposureMask, ProxyWindowEventProc, pwPtr); atts.save_under = True; Tk_ChangeWindowAttributes(pwPtr->proxywin, CWSaveUnder, &atts); if (ConfigurePanedWindow(interp, pwPtr, objc - 2, objv + 2) != TCL_OK) { Tk_DestroyWindow(pwPtr->proxywin); Tk_DestroyWindow(pwPtr->tkwin); return TCL_ERROR; } Tcl_SetObjResult(interp, Tk_NewWindowObj(pwPtr->tkwin)); return TCL_OK; } /* *-------------------------------------------------------------- * * PanedWindowWidgetObjCmd -- * * This function is invoked to process the Tcl command that corresponds * to a widget managed by this module. See the user documentation for * details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int PanedWindowWidgetObjCmd( void *clientData, /* Information about square widget. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj * const objv[]) /* Argument objects. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; int result = TCL_OK; static const char *const optionStrings[] = { "add", "cget", "configure", "forget", "identify", "panecget", "paneconfigure", "panes", "proxy", "sash", NULL }; enum options { PW_ADD, PW_CGET, PW_CONFIGURE, PW_FORGET, PW_IDENTIFY, PW_PANECGET, PW_PANECONFIGURE, PW_PANES, PW_PROXY, PW_SASH }; Tcl_Obj *resultObj; int index, count, i, x, y; Tk_Window tkwin; Pane *panePtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "command", 0, &index) != TCL_OK) { return TCL_ERROR; } Tcl_Preserve(pwPtr); switch ((enum options) index) { case PW_ADD: if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?"); result = TCL_ERROR; break; } result = ConfigurePanes(pwPtr, interp, objc, objv); break; case PW_CGET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "option"); result = TCL_ERROR; break; } resultObj = Tk_GetOptionValue(interp, pwPtr, pwPtr->optionTable, objv[2], pwPtr->tkwin); if (resultObj == NULL) { result = TCL_ERROR; } else { Tcl_SetObjResult(interp, resultObj); } break; case PW_CONFIGURE: resultObj = NULL; if (objc <= 3) { resultObj = Tk_GetOptionInfo(interp, pwPtr, pwPtr->optionTable, (objc == 3) ? objv[2] : NULL, pwPtr->tkwin); if (resultObj == NULL) { result = TCL_ERROR; } else { Tcl_SetObjResult(interp, resultObj); } } else { result = ConfigurePanedWindow(interp, pwPtr, objc - 2, objv + 2); } break; case PW_FORGET: { if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?"); result = TCL_ERROR; break; } /* * Clean up each window named in the arg list. */ for (count = 0, i = 2; i < objc; i++) { Tk_Window pane = Tk_NameToWindow(interp, Tcl_GetString(objv[i]), pwPtr->tkwin); if (pane == NULL) { continue; } panePtr = GetPane(pwPtr, pane); if ((panePtr != NULL) && (panePtr->containerPtr != NULL)) { count++; Tk_ManageGeometry(pane, NULL, NULL); Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin); Tk_DeleteEventHandler(panePtr->tkwin, StructureNotifyMask, PaneStructureProc, panePtr); Tk_UnmapWindow(panePtr->tkwin); Unlink(panePtr); } if (count != 0) { ComputeGeometry(pwPtr); } } break; } case PW_IDENTIFY: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "x y"); result = TCL_ERROR; break; } if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { result = TCL_ERROR; break; } result = PanedWindowIdentifyCoords(pwPtr, interp, x, y); break; case PW_PANECGET: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "pane option"); result = TCL_ERROR; break; } tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]), pwPtr->tkwin); if (tkwin == NULL) { result = TCL_ERROR; break; } resultObj = NULL; for (i = 0; i < pwPtr->numPanes; i++) { if (pwPtr->panes[i]->tkwin == tkwin) { resultObj = Tk_GetOptionValue(interp, pwPtr->panes[i], pwPtr->paneOpts, objv[3], tkwin); } } if (resultObj == NULL) { if (i == pwPtr->numPanes) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "not managed by this window", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "PANEDWINDOW", "UNMANAGED", NULL); } result = TCL_ERROR; } else { Tcl_SetObjResult(interp, resultObj); } break; case PW_PANECONFIGURE: if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "pane ?-option value ...?"); result = TCL_ERROR; break; } resultObj = NULL; if (objc <= 4) { tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]), pwPtr->tkwin); if (tkwin == NULL) { /* * Just a plain old bad window; Tk_NameToWindow filled in an * error message for us. */ result = TCL_ERROR; break; } for (i = 0; i < pwPtr->numPanes; i++) { if (pwPtr->panes[i]->tkwin == tkwin) { resultObj = Tk_GetOptionInfo(interp, pwPtr->panes[i], pwPtr->paneOpts, (objc == 4) ? objv[3] : NULL, pwPtr->tkwin); if (resultObj == NULL) { result = TCL_ERROR; } else { Tcl_SetObjResult(interp, resultObj); } break; } } } else { result = ConfigurePanes(pwPtr, interp, objc, objv); } break; case PW_PANES: resultObj = Tcl_NewObj(); for (i = 0; i < pwPtr->numPanes; i++) { Tcl_ListObjAppendElement(NULL, resultObj, Tk_NewWindowObj(pwPtr->panes[i]->tkwin)); } Tcl_SetObjResult(interp, resultObj); break; case PW_PROXY: result = PanedWindowProxyCommand(pwPtr, interp, objc, objv); break; case PW_SASH: result = PanedWindowSashCommand(pwPtr, interp, objc, objv); break; } Tcl_Release(pwPtr); return result; } /* *---------------------------------------------------------------------- * * ConfigurePanes -- * * Add or alter the configuration options of a pane in a paned window. * * Results: * Standard Tcl result. * * Side effects: * Depends on options; may add a pane to the paned window, may alter the * geometry management options of a pane. * *---------------------------------------------------------------------- */ static int ConfigurePanes( PanedWindow *pwPtr, /* Information about paned window. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int i, firstOptionArg, j, found, doubleBw, index, numNewPanes, haveLoc; int insertIndex; Tk_Window tkwin = NULL, ancestor, parent; Pane *panePtr, **inserts, **newPanes; Pane options; const char *arg; /* * Find the non-window name arguments; these are the configure options for * the panes. Also validate that the window names given are legitimate * (ie, they are real windows, they are not the panedwindow itself, etc.). */ for (i = 2; i < objc; i++) { arg = Tcl_GetString(objv[i]); if (arg[0] == '-') { break; } else { tkwin = Tk_NameToWindow(interp, arg, pwPtr->tkwin); if (tkwin == NULL) { /* * Just a plain old bad window; Tk_NameToWindow filled in an * error message for us. */ return TCL_ERROR; } else if (tkwin == pwPtr->tkwin) { /* * A panedwindow cannot manage itself. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't add %s to itself", arg)); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "SELF", NULL); return TCL_ERROR; } else if (Tk_IsTopLevel(tkwin)) { /* * A panedwindow cannot manage a toplevel. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't add toplevel %s to %s", arg, Tk_PathName(pwPtr->tkwin))); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "TOPLEVEL", NULL); return TCL_ERROR; } else { /* * Make sure the panedwindow is the parent of the pane, * or a descendant of the pane's parent. */ parent = Tk_Parent(tkwin); for (ancestor = pwPtr->tkwin;;ancestor = Tk_Parent(ancestor)) { if (ancestor == parent) { break; } if (Tk_IsTopLevel(ancestor)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't add %s to %s", arg, Tk_PathName(pwPtr->tkwin))); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "HIERARCHY", NULL); return TCL_ERROR; } } } } } firstOptionArg = i; /* * Pre-parse the configuration options, to get the before/after specifiers * into an easy-to-find location (a local variable). Also, check the * return from Tk_SetOptions once, here, so we can save a little bit of * extra testing in the for loop below. */ memset((void *)&options, 0, sizeof(Pane)); if (Tk_SetOptions(interp, &options, pwPtr->paneOpts, objc - firstOptionArg, objv + firstOptionArg, pwPtr->tkwin, NULL, NULL) != TCL_OK) { return TCL_ERROR; } /* * If either -after or -before was given, find the numerical index that * corresponds to the given window. If both -after and -before are given, * the option precedence is: -after, then -before. */ index = -1; haveLoc = 0; if (options.after != NULL) { tkwin = options.after; haveLoc = 1; for (i = 0; i < pwPtr->numPanes; i++) { if (options.after == pwPtr->panes[i]->tkwin) { index = i + 1; break; } } } else if (options.before != NULL) { tkwin = options.before; haveLoc = 1; for (i = 0; i < pwPtr->numPanes; i++) { if (options.before == pwPtr->panes[i]->tkwin) { index = i; break; } } } /* * If a window was given for -after/-before, but it's not a window managed * by the panedwindow, throw an error */ if (haveLoc && index == -1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "window \"%s\" is not managed by %s", Tk_PathName(tkwin), Tk_PathName(pwPtr->tkwin))); Tcl_SetErrorCode(interp, "TK", "PANEDWINDOW", "UNMANAGED", NULL); Tk_FreeConfigOptions(&options, pwPtr->paneOpts, pwPtr->tkwin); return TCL_ERROR; } /* * Allocate an array to hold, in order, the pointers to the pane * structures corresponding to the windows specified. Some of those * structures may already have existed, some may be new. */ inserts = (Pane **)ckalloc(sizeof(Pane *) * (firstOptionArg - 2)); insertIndex = 0; /* * Populate the inserts array, creating new pane structures as necessary, * applying the options to each structure as we go, and, if necessary, * marking the spot in the original panes array as empty (for * pre-existing pane structures). */ for (i = 0, numNewPanes = 0; i < firstOptionArg - 2; i++) { /* * We don't check that tkwin is NULL here, because the pre-pass above * guarantees that the input at this stage is good. */ tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[i + 2]), pwPtr->tkwin); found = 0; for (j = 0; j < pwPtr->numPanes; j++) { if (pwPtr->panes[j] != NULL && pwPtr->panes[j]->tkwin == tkwin) { Tk_SetOptions(interp, pwPtr->panes[j], pwPtr->paneOpts, objc - firstOptionArg, objv + firstOptionArg, pwPtr->tkwin, NULL, NULL); if (pwPtr->panes[j]->minSize < 0) { pwPtr->panes[j]->minSize = 0; } found = 1; /* * If the pane is supposed to move, add it to the inserts * array now; otherwise, leave it where it is. */ if (index != -1) { inserts[insertIndex++] = pwPtr->panes[j]; pwPtr->panes[j] = NULL; } break; } } if (found) { continue; } /* * Make sure this pane wasn't already put into the inserts array, * i.e., when the user specifies the same window multiple times in a * single add commaned. */ for (j = 0; j < insertIndex; j++) { if (inserts[j]->tkwin == tkwin) { found = 1; break; } } if (found) { continue; } /* * Create a new pane structure and initialize it. All panes start * out with their "natural" dimensions. */ panePtr = (Pane *)ckalloc(sizeof(Pane)); memset(panePtr, 0, sizeof(Pane)); Tk_InitOptions(interp, panePtr, pwPtr->paneOpts, pwPtr->tkwin); Tk_SetOptions(interp, panePtr, pwPtr->paneOpts, objc - firstOptionArg, objv + firstOptionArg, pwPtr->tkwin, NULL, NULL); panePtr->tkwin = tkwin; panePtr->containerPtr = pwPtr; doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width; if (panePtr->width > 0) { panePtr->paneWidth = panePtr->width; } else { panePtr->paneWidth = Tk_ReqWidth(tkwin) + doubleBw; } if (panePtr->height > 0) { panePtr->paneHeight = panePtr->height; } else { panePtr->paneHeight = Tk_ReqHeight(tkwin) + doubleBw; } if (panePtr->minSize < 0) { panePtr->minSize = 0; } /* * Set up the geometry management callbacks for this pane. */ Tk_CreateEventHandler(panePtr->tkwin, StructureNotifyMask, PaneStructureProc, panePtr); Tk_ManageGeometry(panePtr->tkwin, &panedWindowMgrType, panePtr); inserts[insertIndex++] = panePtr; numNewPanes++; } /* * Allocate the new panes array, then copy the panes into it, in order. */ i = sizeof(Pane *) * (pwPtr->numPanes + numNewPanes); newPanes = (Pane **)ckalloc(i); memset(newPanes, 0, i); if (index == -1) { /* * If none of the existing panes have to be moved, just copy the old * and append the new. * Be careful about the case pwPtr->numPanes == 0 since in this case * pwPtr->panes is NULL, and the memcpy would have undefined behavior. */ if (pwPtr->numPanes) { memcpy(newPanes, pwPtr->panes, sizeof(Pane *) * pwPtr->numPanes); } memcpy(&newPanes[pwPtr->numPanes], inserts, sizeof(Pane *) * numNewPanes); } else { /* * If some of the existing panes were moved, the old panes array * will be partially populated, with some valid and some invalid * entries. Walk through it, copying valid entries to the new panes * array as we go; when we get to the insert location for the new * panes, copy the inserts array over, then finish off the old panes * array. */ for (i = 0, j = 0; i < index; i++) { if (pwPtr->panes[i] != NULL) { newPanes[j] = pwPtr->panes[i]; j++; } } memcpy(&newPanes[j], inserts, sizeof(Pane *)*insertIndex); j += firstOptionArg - 2; for (i = index; i < pwPtr->numPanes; i++) { if (pwPtr->panes[i] != NULL) { newPanes[j] = pwPtr->panes[i]; j++; } } } /* * Make the new panes array the paned window's pane array, and clean up. */ ckfree(pwPtr->panes); ckfree(inserts); pwPtr->panes = newPanes; /* * Set the paned window's pane count to the new value. */ pwPtr->numPanes += numNewPanes; Tk_FreeConfigOptions(&options, pwPtr->paneOpts, pwPtr->tkwin); ComputeGeometry(pwPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * PanedWindowSashCommand -- * * Implementation of the panedwindow sash subcommand. See the user * documentation for details on what it does. * * Results: * Standard Tcl result. * * Side effects: * Depends on the arguments. * *---------------------------------------------------------------------- */ static int PanedWindowSashCommand( PanedWindow *pwPtr, /* Pointer to paned window information. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const sashOptionStrings[] = { "coord", "dragto", "mark", "place", NULL }; enum sashOptions { SASH_COORD, SASH_DRAGTO, SASH_MARK, SASH_PLACE }; int index, sash, x, y, diff; Tcl_Obj *coords[2]; Pane *panePtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], sashOptionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch ((enum sashOptions) index) { case SASH_COORD: if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "index"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) { return TCL_ERROR; } if (!ValidSashIndex(pwPtr, sash)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "invalid sash index", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL); return TCL_ERROR; } panePtr = pwPtr->panes[sash]; coords[0] = Tcl_NewWideIntObj(panePtr->sashx); coords[1] = Tcl_NewWideIntObj(panePtr->sashy); Tcl_SetObjResult(interp, Tcl_NewListObj(2, coords)); break; case SASH_MARK: if (objc != 6 && objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "index ?x y?"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) { return TCL_ERROR; } if (!ValidSashIndex(pwPtr, sash)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "invalid sash index", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL); return TCL_ERROR; } if (objc == 6) { if (Tcl_GetIntFromObj(interp, objv[4], &x) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) { return TCL_ERROR; } pwPtr->panes[sash]->markx = x; pwPtr->panes[sash]->marky = y; } else { coords[0] = Tcl_NewWideIntObj(pwPtr->panes[sash]->markx); coords[1] = Tcl_NewWideIntObj(pwPtr->panes[sash]->marky); Tcl_SetObjResult(interp, Tcl_NewListObj(2, coords)); } break; case SASH_DRAGTO: case SASH_PLACE: if (objc != 6) { Tcl_WrongNumArgs(interp, 3, objv, "index x y"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) { return TCL_ERROR; } if (!ValidSashIndex(pwPtr, sash)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "invalid sash index", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[4], &x) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) { return TCL_ERROR; } panePtr = pwPtr->panes[sash]; if (pwPtr->orient == ORIENT_HORIZONTAL) { if (index == SASH_PLACE) { diff = x - pwPtr->panes[sash]->sashx; } else { diff = x - pwPtr->panes[sash]->markx; } } else { if (index == SASH_PLACE) { diff = y - pwPtr->panes[sash]->sashy; } else { diff = y - pwPtr->panes[sash]->marky; } } MoveSash(pwPtr, sash, diff); ComputeGeometry(pwPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ConfigurePanedWindow -- * * This function is called to process an argv/argc list in conjunction * with the Tk option database to configure (or reconfigure) a paned * window widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is returned, * then the interp's result contains an error message. * * Side effects: * Configuration information, such as colors, border width, etc. get set * for pwPtr; old resources get freed, if there were any. * *---------------------------------------------------------------------- */ static int ConfigurePanedWindow( Tcl_Interp *interp, /* Used for error reporting. */ PanedWindow *pwPtr, /* Information about widget. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { Tk_SavedOptions savedOptions; int typemask = 0; if (Tk_SetOptions(interp, pwPtr, pwPtr->optionTable, objc, objv, pwPtr->tkwin, &savedOptions, &typemask) != TCL_OK) { Tk_RestoreSavedOptions(&savedOptions); return TCL_ERROR; } Tk_FreeSavedOptions(&savedOptions); PanedWindowWorldChanged(pwPtr); /* * If an option that affects geometry has changed, make a re-layout * request. */ if (typemask & GEOMETRY) { ComputeGeometry(pwPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * PanedWindowWorldChanged -- * * This function is invoked anytime a paned window's world has changed in * some way that causes the widget to have to recompute graphics contexts * and geometry. * * Results: * None. * * Side effects: * Paned window will be relayed out and redisplayed. * *---------------------------------------------------------------------- */ static void PanedWindowWorldChanged( void *instanceData) /* Information about the paned window. */ { XGCValues gcValues; GC newGC; PanedWindow *pwPtr = (PanedWindow *)instanceData; /* * Allocated a graphics context for drawing the paned window widget * elements (background, sashes, etc.) and set the window background. */ gcValues.background = Tk_3DBorderColor(pwPtr->background)->pixel; newGC = Tk_GetGC(pwPtr->tkwin, GCBackground, &gcValues); if (pwPtr->gc != NULL) { Tk_FreeGC(pwPtr->display, pwPtr->gc); } pwPtr->gc = newGC; Tk_SetWindowBackground(pwPtr->tkwin, gcValues.background); /* * Issue geometry size requests to Tk. */ Tk_SetInternalBorder(pwPtr->tkwin, pwPtr->borderWidth); if (pwPtr->width > 0 && pwPtr->height > 0) { Tk_GeometryRequest(pwPtr->tkwin, pwPtr->width, pwPtr->height); } /* * Arrange for the window to be redrawn, if neccessary. */ if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayPanedWindow, pwPtr); pwPtr->flags |= REDRAW_PENDING; } } /* *-------------------------------------------------------------- * * PanedWindowEventProc -- * * This function is invoked by the Tk dispatcher for various events on * paned windows. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get cleaned up. When * it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void PanedWindowEventProc( void *clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; int i; if (eventPtr->type == Expose) { if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayPanedWindow, pwPtr); pwPtr->flags |= REDRAW_PENDING; } } else if (eventPtr->type == ConfigureNotify) { pwPtr->flags |= REQUESTED_RELAYOUT; if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayPanedWindow, pwPtr); pwPtr->flags |= REDRAW_PENDING; } } else if (eventPtr->type == DestroyNotify) { DestroyPanedWindow(pwPtr); } else if (eventPtr->type == UnmapNotify) { for (i = 0; i < pwPtr->numPanes; i++) { if (!pwPtr->panes[i]->hide) { Tk_UnmapWindow(pwPtr->panes[i]->tkwin); } } } else if (eventPtr->type == MapNotify) { for (i = 0; i < pwPtr->numPanes; i++) { if (!pwPtr->panes[i]->hide) { Tk_MapWindow(pwPtr->panes[i]->tkwin); } } } } /* *---------------------------------------------------------------------- * * PanedWindowCmdDeletedProc -- * * This function is invoked when a widget command is deleted. If the * widget isn't already in the process of being destroyed, this command * destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void PanedWindowCmdDeletedProc( void *clientData) /* Pointer to widget record for widget. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; /* * This function could be invoked either because the window was destroyed * and the command was then deleted or because the command was deleted, * and then this function destroys the widget. The WIDGET_DELETED flag * distinguishes these cases. */ if (!(pwPtr->flags & WIDGET_DELETED)) { Tk_DestroyWindow(pwPtr->proxywin); Tk_DestroyWindow(pwPtr->tkwin); } } /* *-------------------------------------------------------------- * * DisplayPanedWindow -- * * This function redraws the contents of a paned window widget. It is * invoked as a do-when-idle handler, so it only runs when there's * nothing else for the application to do. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void DisplayPanedWindow( void *clientData) /* Information about window. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; Pane *panePtr; Pixmap pixmap; Tk_Window tkwin = pwPtr->tkwin; int i, sashWidth, sashHeight; const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL); int first, last; pwPtr->flags &= ~REDRAW_PENDING; if ((pwPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } if (pwPtr->flags & REQUESTED_RELAYOUT) { ArrangePanes(clientData); } #ifndef TK_NO_DOUBLE_BUFFERING /* * Create a pixmap for double-buffering, if necessary. */ pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin)); #else pixmap = Tk_WindowId(tkwin); #endif /* TK_NO_DOUBLE_BUFFERING */ /* * Redraw the widget's background and border. */ Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), pwPtr->borderWidth, pwPtr->relief); /* * Set up boilerplate geometry values for sashes (width, height, common * coordinates). */ if (horizontal) { sashHeight = Tk_Height(tkwin) - (2 * Tk_InternalBorderLeft(tkwin)); sashWidth = pwPtr->sashWidth; } else { sashWidth = Tk_Width(tkwin) - (2 * Tk_InternalBorderLeft(tkwin)); sashHeight = pwPtr->sashWidth; } /* * Draw the sashes. */ GetFirstLastVisiblePane(pwPtr, &first, &last); for (i = 0; i < pwPtr->numPanes - 1; i++) { panePtr = pwPtr->panes[i]; if (panePtr->hide || i == last) { continue; } if (sashWidth > 0 && sashHeight > 0) { Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, panePtr->sashx, panePtr->sashy, sashWidth, sashHeight, 1, pwPtr->sashRelief); } if (pwPtr->showHandle) { Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, panePtr->handlex, panePtr->handley, pwPtr->handleSize, pwPtr->handleSize, 1, TK_RELIEF_RAISED); } } #ifndef TK_NO_DOUBLE_BUFFERING /* * Copy the information from the off-screen pixmap onto the screen, then * delete the pixmap. */ XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc, 0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0); Tk_FreePixmap(Tk_Display(tkwin), pixmap); #endif /* TK_NO_DOUBLE_BUFFERING */ } /* *---------------------------------------------------------------------- * * DestroyPanedWindow -- * * This function is invoked by PanedWindowEventProc to free the internal * structure of a paned window. * * Results: * None. * * Side effects: * Everything associated with the paned window is freed up. * *---------------------------------------------------------------------- */ static void DestroyPanedWindow( PanedWindow *pwPtr) /* Info about paned window widget. */ { int i; /* * First mark the widget as in the process of being deleted, so that any * code that causes calls to other paned window functions will abort. */ pwPtr->flags |= WIDGET_DELETED; /* * Cancel idle callbacks for redrawing the widget and for rearranging the * panes. */ if (pwPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayPanedWindow, pwPtr); } if (pwPtr->flags & RESIZE_PENDING) { Tcl_CancelIdleCall(ArrangePanes, pwPtr); } /* * Clean up the pane list; foreach pane: * o Cancel the pane's structure notification callback * o Cancel geometry management for the pane. * o Free memory for the pane */ for (i = 0; i < pwPtr->numPanes; i++) { Tk_DeleteEventHandler(pwPtr->panes[i]->tkwin, StructureNotifyMask, PaneStructureProc, pwPtr->panes[i]); Tk_ManageGeometry(pwPtr->panes[i]->tkwin, NULL, NULL); Tk_FreeConfigOptions(pwPtr->panes[i], pwPtr->paneOpts, pwPtr->tkwin); ckfree(pwPtr->panes[i]); pwPtr->panes[i] = NULL; } if (pwPtr->panes) { ckfree(pwPtr->panes); } /* * Remove the widget command from the interpreter. */ Tcl_DeleteCommandFromToken(pwPtr->interp, pwPtr->widgetCmd); /* * Let Tk_FreeConfigOptions clean up the rest. */ Tk_FreeConfigOptions(pwPtr, pwPtr->optionTable, pwPtr->tkwin); Tcl_Release(pwPtr->tkwin); pwPtr->tkwin = NULL; Tcl_EventuallyFree(pwPtr, TCL_DYNAMIC); } /* *-------------------------------------------------------------- * * PanedWindowReqProc -- * * This function is invoked by Tk_GeometryRequest for windows managed by * a paned window. * * Results: * None. * * Side effects: * Arranges for tkwin, and all its managed siblings, to be re-arranged at * the next idle point. * *-------------------------------------------------------------- */ static void PanedWindowReqProc( void *clientData, /* Paned window's information about window * that got new preferred geometry. */ TCL_UNUSED(Tk_Window)) /* Other Tk-related information about the * window. */ { Pane *panePtr = (Pane *)clientData; PanedWindow *pwPtr = (PanedWindow *) panePtr->containerPtr; if (Tk_IsMapped(pwPtr->tkwin)) { if (!(pwPtr->flags & RESIZE_PENDING)) { pwPtr->flags |= RESIZE_PENDING; Tcl_DoWhenIdle(ArrangePanes, pwPtr); } } else { int doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width; if (panePtr->width <= 0) { panePtr->paneWidth = Tk_ReqWidth(panePtr->tkwin) + doubleBw; } if (panePtr->height <= 0) { panePtr->paneHeight = Tk_ReqHeight(panePtr->tkwin) + doubleBw; } ComputeGeometry(pwPtr); } } /* *-------------------------------------------------------------- * * PanedWindowLostPaneProc -- * * This function is invoked by Tk whenever some other geometry claims * control over a pane that used to be managed by us. * * Results: * None. * * Side effects: * Forgets all information about the pane. Causes geometry to be * recomputed for the panedwindow. * *-------------------------------------------------------------- */ static void PanedWindowLostPaneProc( void *clientData, /* Grid structure for the pane that was * stolen away. */ TCL_UNUSED(Tk_Window)) /* Tk's handle for the pane. */ { Pane *panePtr = (Pane *)clientData; PanedWindow *pwPtr = (PanedWindow *) panePtr->containerPtr; if (pwPtr->tkwin != Tk_Parent(panePtr->tkwin)) { Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin); } Unlink(panePtr); Tk_DeleteEventHandler(panePtr->tkwin, StructureNotifyMask, PaneStructureProc, panePtr); Tk_UnmapWindow(panePtr->tkwin); panePtr->tkwin = NULL; ckfree(panePtr); ComputeGeometry(pwPtr); } /* *-------------------------------------------------------------- * * ArrangePanes -- * * This function is invoked (using the Tcl_DoWhenIdle mechanism) to * re-layout a set of windows managed by a paned window. It is invoked at * idle time so that a series of pane requests can be merged into a * single layout operation. * * Results: * None. * * Side effects: * The panes of containerPtr may get resized or moved. * *-------------------------------------------------------------- */ static void ArrangePanes( void *clientData) /* Structure describing parent whose panes * are to be re-layed out. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; Pane *panePtr; int i, newPaneWidth, newPaneHeight, paneX, paneY; int paneWidth, paneHeight, paneSize, paneMinSize; int doubleBw; int x, y; int sashWidth, sashOffset, sashCount, handleOffset; int sashReserve, sxReserve, syReserve; int internalBW; int paneDynSize, paneDynMinSize, pwHeight, pwWidth, pwSize; int first, last; int stretchReserve, stretchAmount; const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL); pwPtr->flags &= ~(REQUESTED_RELAYOUT|RESIZE_PENDING); /* * If the parent has no panes anymore, then don't do anything at all: * just leave the parent's size as-is. Otherwise there is no way to * "relinquish" control over the parent so another geometry manager can * take over. */ if (pwPtr->numPanes == 0) { return; } Tcl_Preserve(pwPtr); /* * Find index of first and last visible panes. */ GetFirstLastVisiblePane(pwPtr, &first, &last); /* * First pass; compute sizes */ paneDynSize = paneDynMinSize = 0; internalBW = Tk_InternalBorderLeft(pwPtr->tkwin); pwHeight = Tk_Height(pwPtr->tkwin) - (2 * internalBW); pwWidth = Tk_Width(pwPtr->tkwin) - (2 * internalBW); x = y = internalBW; stretchReserve = (horizontal ? pwWidth : pwHeight); /* * Calculate the sash width, including handle and padding, and the sash * and handle offsets. */ sashOffset = handleOffset = pwPtr->sashPad; if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) { sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize; sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2) + pwPtr->sashPad; } else { sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth; handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2) + pwPtr->sashPad; } for (i = sashCount = 0; i < pwPtr->numPanes; i++) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { continue; } /* * Compute the total size needed by all the panes and the left-over, * or shortage of space available. */ if (horizontal) { if (panePtr->width > 0) { paneSize = panePtr->width; } else { paneSize = panePtr->paneWidth; } stretchReserve -= paneSize + (2 * panePtr->padx); } else { if (panePtr->height > 0) { paneSize = panePtr->height; } else { paneSize = panePtr->paneHeight; } stretchReserve -= paneSize + (2 * panePtr->pady); } if (IsStretchable(panePtr->stretch,i,first,last) && Tk_IsMapped(pwPtr->tkwin)) { paneDynSize += paneSize; paneDynMinSize += panePtr->minSize; } if (i != last) { stretchReserve -= sashWidth; sashCount++; } } /* * Second pass; adjust/arrange panes. */ for (i = 0; i < pwPtr->numPanes; i++) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin); Tk_UnmapWindow(panePtr->tkwin); continue; } /* * Compute the size of this pane. The algorithm (assuming a * horizontal paned window) is: * * 1. Get "base" dimensions. If a width or height is specified for * this pane, use those values; else use the ReqWidth/ReqHeight. * 2. Using base dimensions, pane dimensions, and sticky values, * determine the x and y, and actual width and height of the * widget. */ doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width; newPaneWidth = (panePtr->width > 0 ? panePtr->width : Tk_ReqWidth(panePtr->tkwin) + doubleBw); newPaneHeight = (panePtr->height > 0 ? panePtr->height : Tk_ReqHeight(panePtr->tkwin) + doubleBw); paneMinSize = panePtr->minSize; /* * Calculate pane width and height. */ if (horizontal) { if (panePtr->width > 0) { paneSize = panePtr->width; } else { paneSize = panePtr->paneWidth; } pwSize = pwWidth; } else { if (panePtr->height > 0) { paneSize = panePtr->height; } else { paneSize = panePtr->paneHeight; } pwSize = pwHeight; } if (IsStretchable(panePtr->stretch, i, first, last)) { double frac; if (paneDynSize > 0) { frac = (double)paneSize / (double)paneDynSize; } else { frac = (double)paneSize / (double)pwSize; } paneDynSize -= paneSize; paneDynMinSize -= panePtr->minSize; stretchAmount = (int) (frac * stretchReserve); if (paneSize + stretchAmount >= paneMinSize) { stretchReserve -= stretchAmount; paneSize += stretchAmount; } else { stretchReserve += paneSize - paneMinSize; paneSize = paneMinSize; } if (i == last && stretchReserve > 0) { paneSize += stretchReserve; stretchReserve = 0; } } else if (paneDynSize - paneDynMinSize + stretchReserve < 0) { if (paneSize + paneDynSize - paneDynMinSize + stretchReserve <= paneMinSize) { stretchReserve += paneSize - paneMinSize; paneSize = paneMinSize; } else { paneSize += paneDynSize - paneDynMinSize + stretchReserve; stretchReserve = paneDynMinSize - paneDynSize; } } if (horizontal) { paneWidth = paneSize; paneHeight = pwHeight - (2 * panePtr->pady); } else { paneWidth = pwWidth - (2 * panePtr->padx); paneHeight = paneSize; } /* * Adjust for area reserved for sashes. */ if (sashCount) { sashReserve = sashWidth * sashCount; if (horizontal) { sxReserve = sashReserve; syReserve = 0; } else { sxReserve = 0; syReserve = sashReserve; } } else { sxReserve = syReserve = 0; } if (pwWidth - sxReserve < x + paneWidth - internalBW) { paneWidth = pwWidth - sxReserve - x + internalBW; } if (pwHeight - syReserve < y + paneHeight - internalBW) { paneHeight = pwHeight - syReserve - y + internalBW; } if (newPaneWidth > paneWidth) { newPaneWidth = paneWidth; } if (newPaneHeight > paneHeight) { newPaneHeight = paneHeight; } panePtr->x = x; panePtr->y = y; /* * Compute the location of the sash at the right or bottom of the * parcel and the location of the next parcel. */ if (horizontal) { x += paneWidth + (2 * panePtr->padx); if (x < internalBW) { x = internalBW; } panePtr->sashx = x + sashOffset; panePtr->sashy = y; panePtr->handlex = x + handleOffset; panePtr->handley = y + pwPtr->handlePad; x += sashWidth; } else { y += paneHeight + (2 * panePtr->pady); if (y < internalBW) { y = internalBW; } panePtr->sashx = x; panePtr->sashy = y + sashOffset; panePtr->handlex = x + pwPtr->handlePad; panePtr->handley = y + handleOffset; y += sashWidth; } /* * Compute the actual dimensions of the pane in the pane. */ paneX = panePtr->x; paneY = panePtr->y; AdjustForSticky(panePtr->sticky, paneWidth, paneHeight, &paneX, &paneY, &newPaneWidth, &newPaneHeight); paneX += panePtr->padx; paneY += panePtr->pady; /* * Now put the window in the proper spot. */ if (newPaneWidth <= 0 || newPaneHeight <= 0 || (horizontal ? paneX - internalBW > pwWidth : paneY - internalBW > pwHeight)) { Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin); Tk_UnmapWindow(panePtr->tkwin); } else { Tk_MaintainGeometry(panePtr->tkwin, pwPtr->tkwin, paneX, paneY, newPaneWidth, newPaneHeight); } sashCount--; } Tcl_Release(pwPtr); } /* *---------------------------------------------------------------------- * * Unlink -- * * Remove a pane from a paned window. * * Results: * None. * * Side effects: * The paned window will be scheduled for re-arranging and redrawing. * *---------------------------------------------------------------------- */ static void Unlink( Pane *panePtr) /* Window to unlink. */ { PanedWindow *containerPtr; int i, j; containerPtr = panePtr->containerPtr; if (containerPtr == NULL) { return; } /* * Find the specified pane in the panedwindow's list of panes, then * remove it from that list. */ for (i = 0; i < containerPtr->numPanes; i++) { if (containerPtr->panes[i] == panePtr) { for (j = i; j < containerPtr->numPanes - 1; j++) { containerPtr->panes[j] = containerPtr->panes[j + 1]; } break; } } /* * Clean out any -after or -before references to this pane */ for (i = 0; i < containerPtr->numPanes; i++) { if (containerPtr->panes[i]->before == panePtr->tkwin) { containerPtr->panes[i]->before = NULL; } if (containerPtr->panes[i]->after == panePtr->tkwin) { containerPtr->panes[i]->after = NULL; } } containerPtr->flags |= REQUESTED_RELAYOUT; if (!(containerPtr->flags & REDRAW_PENDING)) { containerPtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(DisplayPanedWindow, containerPtr); } /* * Set the pane's containerPtr to NULL, so that we can tell that the pane * is no longer attached to any panedwindow. */ panePtr->containerPtr = NULL; containerPtr->numPanes--; } /* *---------------------------------------------------------------------- * * GetPane -- * * Given a token to a Tk window, find the pane that corresponds to that * token in a given paned window. * * Results: * Pointer to the pane structure, or NULL if the window is not managed * by this paned window. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Pane * GetPane( PanedWindow *pwPtr, /* Pointer to the paned window info. */ Tk_Window tkwin) /* Window to search for. */ { int i; for (i = 0; i < pwPtr->numPanes; i++) { if (pwPtr->panes[i]->tkwin == tkwin) { return pwPtr->panes[i]; } } return NULL; } /* *---------------------------------------------------------------------- * * GetFirstLastVisiblePane -- * * Given panedwindow, find the index of the first and last visible panes * of that paned window. * * Results: * Index of the first and last visible panes. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void GetFirstLastVisiblePane( PanedWindow *pwPtr, /* Pointer to the paned window info. */ int *firstPtr, /* Returned index for first. */ int *lastPtr) /* Returned index for last. */ { int i; for (i = 0, *lastPtr = 0, *firstPtr = -1; i < pwPtr->numPanes; i++) { if (pwPtr->panes[i]->hide == 0) { if (*firstPtr < 0) { *firstPtr = i; } *lastPtr = i; } } } /* *-------------------------------------------------------------- * * PaneStructureProc -- * * This function is invoked whenever StructureNotify events occur for a * window that's managed by a paned window. This function's only purpose * is to clean up when windows are deleted. * * Results: * None. * * Side effects: * The paned window pane structure associated with the window * is freed, and the pane is disassociated from the paned * window which managed it. * *-------------------------------------------------------------- */ static void PaneStructureProc( void *clientData, /* Pointer to record describing window item. */ XEvent *eventPtr) /* Describes what just happened. */ { Pane *panePtr = (Pane *)clientData; PanedWindow *pwPtr = panePtr->containerPtr; if (eventPtr->type == DestroyNotify) { Unlink(panePtr); panePtr->tkwin = NULL; ckfree(panePtr); ComputeGeometry(pwPtr); } } /* *---------------------------------------------------------------------- * * ComputeGeometry -- * * Compute geometry for the paned window, including coordinates of all * panes and each sash. * * Results: * None. * * Side effects: * Recomputes geometry information for a paned window. * *---------------------------------------------------------------------- */ static void ComputeGeometry( PanedWindow *pwPtr) /* Pointer to the Paned Window structure. */ { int i, x, y, doubleBw, internalBw; int sashWidth, sashOffset, handleOffset; int reqWidth, reqHeight, dim; Pane *panePtr; const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL); pwPtr->flags |= REQUESTED_RELAYOUT; x = y = internalBw = Tk_InternalBorderLeft(pwPtr->tkwin); reqWidth = reqHeight = 0; /* * Sashes and handles share space on the display. To simplify processing * below, precompute the x and y offsets of the handles and sashes within * the space occupied by their combination; later, just add those offsets * blindly (avoiding the extra showHandle, etc, checks). */ sashOffset = handleOffset = pwPtr->sashPad; if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) { sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize; sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2) + pwPtr->sashPad; } else { sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth; handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2) + pwPtr->sashPad; } for (i = 0; i < pwPtr->numPanes; i++) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { continue; } /* * First set the coordinates for the top left corner of the pane's * parcel. */ panePtr->x = x; panePtr->y = y; /* * Make sure the pane's paned dimension is at least minsize. This * check may be redundant, since the only way to change a pane's size * is by moving a sash, and that code checks the minsize. */ if (horizontal) { if (panePtr->paneWidth < panePtr->minSize) { panePtr->paneWidth = panePtr->minSize; } } else { if (panePtr->paneHeight < panePtr->minSize) { panePtr->paneHeight = panePtr->minSize; } } /* * Compute the location of the sash at the right or bottom of the * parcel. */ if (horizontal) { x += panePtr->paneWidth + (2 * panePtr->padx); panePtr->sashx = x + sashOffset; panePtr->sashy = y; panePtr->handlex = x + handleOffset; panePtr->handley = y + pwPtr->handlePad; x += sashWidth; } else { y += panePtr->paneHeight + (2 * panePtr->pady); panePtr->sashx = x; panePtr->sashy = y + sashOffset; panePtr->handlex = x + pwPtr->handlePad; panePtr->handley = y + handleOffset; y += sashWidth; } /* * Find the maximum height/width of the panes, for computing the * requested height/width of the paned window. */ if (horizontal) { /* * If the pane has an explicit height set, use that; otherwise, * use the pane's requested height. */ if (panePtr->height > 0) { dim = panePtr->height; } else { doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width; dim = Tk_ReqHeight(panePtr->tkwin) + doubleBw; } dim += 2 * panePtr->pady; if (dim > reqHeight) { reqHeight = dim; } } else { /* * If the pane has an explicit width set use that; otherwise, use * the pane's requested width. */ if (panePtr->width > 0) { dim = panePtr->width; } else { doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width; dim = Tk_ReqWidth(panePtr->tkwin) + doubleBw; } dim += 2 * panePtr->padx; if (dim > reqWidth) { reqWidth = dim; } } } /* * The loop above should have left x (or y) equal to the sum of the widths * (or heights) of the widgets, plus the size of one sash and the sash * padding for each widget, plus the width of the left (or top) border of * the paned window. * * The requested width (or height) is therefore x (or y) minus the size of * one sash and padding, plus the width of the right (or bottom) border of * the paned window. * * The height (or width) is equal to the maximum height (or width) of the * panes, plus the width of the border of the top and bottom (or left and * right) of the paned window. * * If the panedwindow has an explicit width/height set use that; * otherwise, use the requested width/height. */ if (horizontal) { reqWidth = (pwPtr->width > 0 ? pwPtr->width : x - sashWidth + internalBw); reqHeight = (pwPtr->height > 0 ? pwPtr->height : reqHeight + (2 * internalBw)); } else { reqWidth = (pwPtr->width > 0 ? pwPtr->width : reqWidth + (2 * internalBw)); reqHeight = (pwPtr->height > 0 ? pwPtr->height : y - sashWidth + internalBw); } Tk_GeometryRequest(pwPtr->tkwin, reqWidth, reqHeight); if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) { pwPtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(DisplayPanedWindow, pwPtr); } } /* *---------------------------------------------------------------------- * * DestroyOptionTables -- * * This function is registered as an exit callback when the paned window * command is first called. It cleans up the OptionTables structure * allocated by that command. * * Results: * None. * * Side effects: * Frees memory. * *---------------------------------------------------------------------- */ static void DestroyOptionTables( void *clientData, /* Pointer to the OptionTables struct */ TCL_UNUSED(Tcl_Interp *)) /* Pointer to the calling interp */ { ckfree(clientData); } /* *---------------------------------------------------------------------- * * GetSticky - * * Converts an internal boolean combination of "sticky" bits into a Tcl * string obj containing zero or more of n, s, e, or w. * * Results: * Tcl_Obj containing the string representation of the sticky value. * * Side effects: * Creates a new Tcl_Obj. * *---------------------------------------------------------------------- */ static Tcl_Obj * GetSticky( TCL_UNUSED(void *), TCL_UNUSED(Tk_Window), char *recordPtr, /* Pointer to widget record. */ Tcl_Size internalOffset) /* Offset within *recordPtr containing the * sticky value. */ { int sticky = *(int *)(recordPtr + internalOffset); char buffer[5]; char *p = &buffer[0]; if (sticky & STICK_NORTH) { *p++ = 'n'; } if (sticky & STICK_EAST) { *p++ = 'e'; } if (sticky & STICK_SOUTH) { *p++ = 's'; } if (sticky & STICK_WEST) { *p++ = 'w'; } *p = '\0'; return Tcl_NewStringObj(buffer, TCL_INDEX_NONE); } /* *---------------------------------------------------------------------- * * SetSticky -- * * Converts a Tcl_Obj representing a widgets stickyness into an integer * value. * * Results: * Standard Tcl result. * * Side effects: * May store the integer value into the internal representation pointer. * May change the pointer to the Tcl_Obj to NULL to indicate that the * specified string was empty and that is acceptable. * *---------------------------------------------------------------------- */ static int SetSticky( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interp; may be used for errors. */ TCL_UNUSED(Tk_Window), /* Window for which option is being set. */ Tcl_Obj **value, /* Pointer to the pointer to the value object. * We use a pointer to the pointer because we * may need to return a value (NULL). */ char *recordPtr, /* Pointer to storage for the widget record. */ Tcl_Size internalOffset, /* Offset within *recordPtr at which the * internal value is to be stored. */ char *oldInternalPtr, /* Pointer to storage for the old value. */ int flags) /* Flags for the option, set Tk_SetOptions. */ { int sticky = 0; char c; void *internalPtr; const char *string; internalPtr = ComputeSlotAddress(recordPtr, internalOffset); if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) { *value = NULL; } else { /* * Convert the sticky specifier into an integer value. */ string = Tcl_GetString(*value); while ((c = *string++) != '\0') { switch (c) { case 'n': case 'N': sticky |= STICK_NORTH; break; case 'e': case 'E': sticky |= STICK_EAST; break; case 's': case 'S': sticky |= STICK_SOUTH; break; case 'w': case 'W': sticky |= STICK_WEST; break; case ' ': case ',': case '\t': case '\r': case '\n': break; default: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad stickyness value \"%s\": must be a string" " containing zero or more of n, e, s, and w", Tcl_GetString(*value))); Tcl_SetErrorCode(interp, "TK", "VALUE", "STICKY", NULL); return TCL_ERROR; } } } if (internalPtr != NULL) { *((int *) oldInternalPtr) = *((int *) internalPtr); *((int *) internalPtr) = sticky; } return TCL_OK; } /* *---------------------------------------------------------------------- * * RestoreSticky -- * * Restore a sticky option value from a saved value. * * Results: * None. * * Side effects: * Restores the old value. * *---------------------------------------------------------------------- */ static void RestoreSticky( TCL_UNUSED(void *), TCL_UNUSED(Tk_Window), char *internalPtr, /* Pointer to storage for value. */ char *oldInternalPtr) /* Pointer to old value. */ { *(int *)internalPtr = *(int *)oldInternalPtr; } /* *---------------------------------------------------------------------- * * AdjustForSticky -- * * Given the x,y coords of the top-left corner of a pane, the dimensions * of that pane, and the dimensions of a pane, compute the x,y coords * and actual dimensions of the pane based on the pane's sticky value. * * Results: * No direct return; sets the x, y, paneWidth and paneHeight to correct * values. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void AdjustForSticky( int sticky, /* Sticky value; see top of file for * definition. */ int cavityWidth, /* Width of the cavity. */ int cavityHeight, /* Height of the cavity. */ int *xPtr, int *yPtr, /* Initially, coordinates of the top-left * corner of cavity; also return values for * actual x, y coords of pane. */ int *paneWidthPtr, /* Pane width. */ int *paneHeightPtr) /* Pane height. */ { int diffx = 0; /* Cavity width - pane width. */ int diffy = 0; /* Cavity hight - pane height. */ if (cavityWidth > *paneWidthPtr) { diffx = cavityWidth - *paneWidthPtr; } if (cavityHeight > *paneHeightPtr) { diffy = cavityHeight - *paneHeightPtr; } if ((sticky & STICK_EAST) && (sticky & STICK_WEST)) { *paneWidthPtr += diffx; } if ((sticky & STICK_NORTH) && (sticky & STICK_SOUTH)) { *paneHeightPtr += diffy; } if (!(sticky & STICK_WEST)) { *xPtr += (sticky & STICK_EAST) ? diffx : diffx/2; } if (!(sticky & STICK_NORTH)) { *yPtr += (sticky & STICK_SOUTH) ? diffy : diffy/2; } } /* *---------------------------------------------------------------------- * * MoveSash -- * * Move the sash given by index the amount given. * * Results: * None. * * Side effects: * Recomputes the sizes of the panes in a panedwindow. * *---------------------------------------------------------------------- */ static void MoveSash( PanedWindow *pwPtr, int sash, int diff) { int i; int expandPane, reduceFirst, reduceLast, reduceIncr, paneSize, sashOffset; Pane *panePtr; int stretchReserve = 0; int nextSash = sash + 1; const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL); if (diff == 0) return; /* * Update the pane sizes with their real sizes. */ if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) { sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2) + pwPtr->sashPad; } else { sashOffset = pwPtr->sashPad; } for (i = 0; i < pwPtr->numPanes; i++) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { continue; } if (horizontal) { panePtr->paneWidth = panePtr->width = panePtr->sashx - sashOffset - panePtr->x - (2 * panePtr->padx); } else { panePtr->paneHeight = panePtr->height = panePtr->sashy - sashOffset - panePtr->y - (2 * panePtr->pady); } } /* * There must be a next sash since it is only possible to enter this * routine when moving an actual sash which implies there exists a visible * pane to either side of the sash. */ while (nextSash < pwPtr->numPanes-1 && pwPtr->panes[nextSash]->hide) { nextSash++; } /* * Consolidate +/-diff variables to reduce duplicate code. */ if (diff > 0) { expandPane = sash; reduceFirst = nextSash; reduceLast = pwPtr->numPanes; reduceIncr = 1; } else { diff = abs(diff); expandPane = nextSash; reduceFirst = sash; reduceLast = -1; reduceIncr = -1; } /* * Calculate how much room we have to stretch in and adjust diff value * accordingly. */ for (i = reduceFirst; i != reduceLast; i += reduceIncr) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { continue; } if (horizontal) { stretchReserve += panePtr->width - panePtr->minSize; } else { stretchReserve += panePtr->height - panePtr->minSize; } } if (stretchReserve <= 0) { return; } if (diff > stretchReserve) { diff = stretchReserve; } /* * Expand pane by diff amount. */ panePtr = pwPtr->panes[expandPane]; if (horizontal) { panePtr->paneWidth = panePtr->width += diff; } else { panePtr->paneHeight = panePtr->height += diff; } /* * Reduce panes, respecting minsize, until diff amount has been used. */ for (i = reduceFirst; i != reduceLast; i += reduceIncr) { panePtr = pwPtr->panes[i]; if (panePtr->hide) { continue; } if (horizontal) { paneSize = panePtr->width; } else { paneSize = panePtr->height; } if (diff > (paneSize - panePtr->minSize)) { diff -= paneSize - panePtr->minSize; paneSize = panePtr->minSize; } else { paneSize -= diff; i = reduceLast - reduceIncr; } if (horizontal) { panePtr->paneWidth = panePtr->width = paneSize; } else { panePtr->paneHeight = panePtr->height = paneSize; } } } /* *---------------------------------------------------------------------- * * ProxyWindowEventProc -- * * This function is invoked by the Tk dispatcher for various events on * paned window proxy windows. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get cleaned up. When * it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void ProxyWindowEventProc( void *clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; if (eventPtr->type == Expose) { if (pwPtr->proxywin != NULL &&!(pwPtr->flags & PROXY_REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayProxyWindow, pwPtr); pwPtr->flags |= PROXY_REDRAW_PENDING; } } } /* *-------------------------------------------------------------- * * DisplayProxyWindow -- * * This function redraws a paned window proxy window. It is invoked as a * do-when-idle handler, so it only runs when there's nothing else for * the application to do. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void DisplayProxyWindow( void *clientData) /* Information about window. */ { PanedWindow *pwPtr = (PanedWindow *)clientData; Pixmap pixmap; Tk_Window tkwin = pwPtr->proxywin; pwPtr->flags &= ~PROXY_REDRAW_PENDING; if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } #ifndef TK_NO_DOUBLE_BUFFERING /* * Create a pixmap for double-buffering, if necessary. */ pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin)); #else pixmap = Tk_WindowId(tkwin); #endif /* TK_NO_DOUBLE_BUFFERING */ /* * Redraw the widget's background and border. */ Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->proxyBackground ? pwPtr->proxyBackground : pwPtr->background, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), pwPtr->proxyBorderWidth, (pwPtr->proxyRelief != TK_RELIEF_NULL) ? pwPtr->proxyRelief : pwPtr->sashRelief); #ifndef TK_NO_DOUBLE_BUFFERING /* * Copy the pixmap to the display. */ XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc, 0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0); Tk_FreePixmap(Tk_Display(tkwin), pixmap); #endif /* TK_NO_DOUBLE_BUFFERING */ } /* *---------------------------------------------------------------------- * * PanedWindowProxyCommand -- * * Handles the panedwindow proxy subcommand. See the user documentation * for details. * * Results: * Standard Tcl result. * * Side effects: * May map or unmap the proxy sash. * *---------------------------------------------------------------------- */ static int PanedWindowProxyCommand( PanedWindow *pwPtr, /* Pointer to paned window information. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const optionStrings[] = { "coord", "forget", "place", NULL }; enum options { PROXY_COORD, PROXY_FORGET, PROXY_PLACE }; int index, x, y, sashWidth, sashHeight; int internalBW, pwWidth, pwHeight; Tcl_Obj *coords[2]; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch ((enum options) index) { case PROXY_COORD: if (objc != 3) { Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } coords[0] = Tcl_NewWideIntObj(pwPtr->proxyx); coords[1] = Tcl_NewWideIntObj(pwPtr->proxyy); Tcl_SetObjResult(interp, Tcl_NewListObj(2, coords)); break; case PROXY_FORGET: if (objc != 3) { Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } if (Tk_IsMapped(pwPtr->proxywin)) { Tk_UnmapWindow(pwPtr->proxywin); Tk_UnmaintainGeometry(pwPtr->proxywin, pwPtr->tkwin); } break; case PROXY_PLACE: if (objc != 5) { Tcl_WrongNumArgs(interp, 3, objv, "x y"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) { return TCL_ERROR; } internalBW = Tk_InternalBorderLeft(pwPtr->tkwin); if (pwPtr->orient == ORIENT_HORIZONTAL) { if (x < 0) { x = 0; } pwWidth = Tk_Width(pwPtr->tkwin) - (2 * internalBW); if (x > pwWidth) { x = pwWidth; } y = Tk_InternalBorderLeft(pwPtr->tkwin); sashWidth = pwPtr->sashWidth; sashHeight = Tk_Height(pwPtr->tkwin) - (2 * Tk_InternalBorderLeft(pwPtr->tkwin)); } else { if (y < 0) { y = 0; } pwHeight = Tk_Height(pwPtr->tkwin) - (2 * internalBW); if (y > pwHeight) { y = pwHeight; } x = Tk_InternalBorderLeft(pwPtr->tkwin); sashHeight = pwPtr->sashWidth; sashWidth = Tk_Width(pwPtr->tkwin) - (2 * Tk_InternalBorderLeft(pwPtr->tkwin)); } if (sashWidth < 1) { sashWidth = 1; } if (sashHeight < 1) { sashHeight = 1; } /* * Stash the proxy coordinates for future "proxy coord" calls. */ pwPtr->proxyx = x; pwPtr->proxyy = y; /* * Make sure the proxy window is higher in the stacking order than the * panes, so that it will be visible when drawn. It would be more * correct to push the proxy window just high enough to appear above * the highest pane, but it's much easier to just force it all the * way to the top of the stacking order. */ Tk_RestackWindow(pwPtr->proxywin, Above, NULL); /* * Let Tk_MaintainGeometry take care of placing the window at the * right coordinates. */ Tk_MaintainGeometry(pwPtr->proxywin, pwPtr->tkwin, x, y, sashWidth, sashHeight); break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ObjectIsEmpty -- * * This function tests whether the string value of an object is empty. * * Results: * The return value is 1 if the string value of objPtr has length zero, * and 0 otherwise. * * Side effects: * May cause object shimmering, since this function can force a * conversion to a string object. * *---------------------------------------------------------------------- */ static int ObjectIsEmpty( Tcl_Obj *objPtr) /* Object to test. May be NULL. */ { if (objPtr == NULL) { return 1; } if (objPtr->bytes == NULL) { Tcl_GetString(objPtr); } return (objPtr->length == 0); } /* *---------------------------------------------------------------------- * * ComputeInternalPointer -- * * Given a pointer to the start of a record and the offset of a slot * within that record, compute the address of that slot. * * Results: * If offset is non-negative, returns the computed address; else, returns * NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void * ComputeSlotAddress( void *recordPtr, /* Pointer to the start of a record. */ Tcl_Size offset) /* Offset of a slot within that record; may be TCL_INDEX_NONE. */ { if (offset != TCL_INDEX_NONE) { return (char *)recordPtr + offset; } else { return NULL; } } /* *---------------------------------------------------------------------- * * PanedWindowIdentifyCoords -- * * Given a pair of x,y coordinates, identify the panedwindow component at * that point, if any. * * Results: * Standard Tcl result. * * Side effects: * Modifies the interpreter's result to contain either an empty list, or * a two element list of the form {sash n} or {handle n} to indicate that * the point lies within the n'th sash or handle. * *---------------------------------------------------------------------- */ static int PanedWindowIdentifyCoords( PanedWindow *pwPtr, /* Information about the widget. */ Tcl_Interp *interp, /* Interpreter in which to store result. */ int x, int y) /* Coordinates of the point to identify. */ { int i, sashHeight, sashWidth, thisx, thisy; int found, isHandle, lpad, rpad, tpad, bpad; int first, last; if (pwPtr->orient == ORIENT_HORIZONTAL) { if (Tk_IsMapped(pwPtr->tkwin)) { sashHeight = Tk_Height(pwPtr->tkwin); } else { sashHeight = Tk_ReqHeight(pwPtr->tkwin); } sashHeight -= 2 * Tk_InternalBorderLeft(pwPtr->tkwin); if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) { sashWidth = pwPtr->handleSize; lpad = (pwPtr->handleSize - pwPtr->sashWidth) / 2; rpad = pwPtr->handleSize - lpad; lpad += pwPtr->sashPad; rpad += pwPtr->sashPad; } else { sashWidth = pwPtr->sashWidth; lpad = rpad = pwPtr->sashPad; } tpad = bpad = 0; } else { if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) { sashHeight = pwPtr->handleSize; tpad = (pwPtr->handleSize - pwPtr->sashWidth) / 2; bpad = pwPtr->handleSize - tpad; tpad += pwPtr->sashPad; bpad += pwPtr->sashPad; } else { sashHeight = pwPtr->sashWidth; tpad = bpad = pwPtr->sashPad; } if (Tk_IsMapped(pwPtr->tkwin)) { sashWidth = Tk_Width(pwPtr->tkwin); } else { sashWidth = Tk_ReqWidth(pwPtr->tkwin); } sashWidth -= 2 * Tk_InternalBorderLeft(pwPtr->tkwin); lpad = rpad = 0; } GetFirstLastVisiblePane(pwPtr, &first, &last); isHandle = 0; found = -1; for (i = 0; i < pwPtr->numPanes - 1; i++) { if (pwPtr->panes[i]->hide || i == last) { continue; } thisx = pwPtr->panes[i]->sashx; thisy = pwPtr->panes[i]->sashy; if (((thisx - lpad) <= x && x <= (thisx + rpad + sashWidth)) && ((thisy - tpad) <= y && y <= (thisy + bpad + sashHeight))) { found = i; /* * Determine if the point is over the handle or the sash. */ if (pwPtr->showHandle) { thisx = pwPtr->panes[i]->handlex; thisy = pwPtr->panes[i]->handley; if (pwPtr->orient == ORIENT_HORIZONTAL) { if (thisy <= y && y <= (thisy + pwPtr->handleSize)) { isHandle = 1; } } else { if (thisx <= x && x <= (thisx + pwPtr->handleSize)) { isHandle = 1; } } } break; } } /* * Set results. Note that the empty string is the default (this function * is called inside the implementation of a command). */ if (found != -1) { Tcl_Obj *list[2]; list[0] = Tcl_NewWideIntObj(found); list[1] = Tcl_NewStringObj((isHandle ? "handle" : "sash"), TCL_INDEX_NONE); Tcl_SetObjResult(interp, Tcl_NewListObj(2, list)); } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */