diff options
Diffstat (limited to 'blt/src/bltTabnotebook.c')
-rw-r--r-- | blt/src/bltTabnotebook.c | 5712 |
1 files changed, 5712 insertions, 0 deletions
diff --git a/blt/src/bltTabnotebook.c b/blt/src/bltTabnotebook.c new file mode 100644 index 00000000000..0ec10cdabc1 --- /dev/null +++ b/blt/src/bltTabnotebook.c @@ -0,0 +1,5712 @@ +/* + * bltTabnotebook.c -- + * + * This module implements a tab notebook widget for the BLT toolkit. + * + * Copyright 1998 Lucent Technologies, Inc. + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and warranty + * disclaimer appear in supporting documentation, and that the names + * of Lucent Technologies or any of their entities not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. + * + * Lucent Technologies disclaims all warranties with regard to this + * software, including all implied warranties of merchantability and + * fitness. In no event shall Lucent Technologies be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether in + * an action of contract, negligence or other tortuous action, arising + * out of or in connection with the use or performance of this + * software. + * + * Tabnotebook widget created by George A. Howlett (gah@bell-labs.com) + * + */ + +#include "bltInt.h" + +#ifndef NO_TABNOTEBOOK +#include "bltBind.h" +#include "bltChain.h" +#include "bltHash.h" +#include "bltTile.h" + +#if (TK_MAJOR_VERSION == 4) +#define TK_REPARENTED 0x2000 +#endif + +#define INVALID_FAIL 0 +#define INVALID_OK 1 + +/* + * The macro below is used to modify a "char" value (e.g. by casting + * it to an unsigned character) so that it can be used safely with + * macros such as isspace. + */ +#define CLAMP(val,low,hi) \ + (((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val)) + +#define GAP 3 +#define SELECT_PADX 4 +#define SELECT_PADY 2 +#define OUTER_PAD 2 +#define LABEL_PAD 1 +#define LABEL_PADX 2 +#define LABEL_PADY 2 +#define IMAGE_PAD 1 +#define CORNER_OFFSET 3 + +#define TAB_SCROLL_OFFSET 10 + +#define SLANT_NONE 0 +#define SLANT_LEFT 1 +#define SLANT_RIGHT 2 +#define SLANT_BOTH (SLANT_LEFT | SLANT_RIGHT) + +#define END (-1) +#define ODD(x) ((x) | 0x01) + +#define TABWIDTH(s, t) \ + ((s)->side & SIDE_VERTICAL) ? (t)->height : (t)->width) +#define TABHEIGHT(s, t) \ + ((s)->side & SIDE_VERTICAL) ? (t)->height : (t)->width) + +#define VPORTWIDTH(s) \ + (((s)->side & SIDE_HORIZONTAL) ? (Tk_Width((s)->tkwin) - 2 * (s)->inset) : \ + (Tk_Height((s)->tkwin) - 2 * (s)->inset)) + +#define VPORTHEIGHT(s) \ + (((s)->side & SIDE_VERTICAL) ? (Tk_Width((s)->tkwin) - 2 * (s)->inset) : \ + (Tk_Height((s)->tkwin) - 2 * (s)->inset)) + +#define GETATTR(t,attr) \ + (((t)->attr != NULL) ? (t)->attr : (t)->nbPtr->defTabStyle.attr) + +/* + * ---------------------------------------------------------------------------- + * + * Internal widget flags: + * + * TNB_LAYOUT The layout of the widget needs to be + * recomputed. + * + * TNB_REDRAW A redraw request is pending for the widget. + * + * TNB_SCROLL A scroll request is pending. + * + * TNB_FOCUS The widget is receiving keyboard events. + * Draw the focus highlight border around the + * widget. + * + * TNB_MULTIPLE_TIER Notebook is using multiple tiers. + * + * TNB_STATIC Notebook does not scroll. + * + * --------------------------------------------------------------------------- + */ +#define TNB_LAYOUT (1<<0) +#define TNB_REDRAW (1<<1) +#define TNB_SCROLL (1<<2) +#define TNB_FOCUS (1<<4) + +#define TNB_STATIC (1<<8) +#define TNB_MULTIPLE_TIER (1<<9) + +#define PERFORATION_ACTIVE (1<<10) + +#define SIDE_TOP (1<<0) +#define SIDE_RIGHT (1<<1) +#define SIDE_LEFT (1<<2) +#define SIDE_BOTTOM (1<<3) + +#define SIDE_VERTICAL (SIDE_LEFT | SIDE_RIGHT) +#define SIDE_HORIZONTAL (SIDE_TOP | SIDE_BOTTOM) + +#define DEF_TNB_ACTIVE_BG_COLOR RGB_GREY90 +#define DEF_TNB_ACTIVE_BG_MONO STD_MONO_ACTIVE_BG +#define DEF_TNB_ACTIVE_FG_COLOR STD_COLOR_ACTIVE_FG +#define DEF_TNB_ACTIVE_FG_MONO STD_MONO_ACTIVE_FG +#define DEF_TNB_BG_MONO STD_MONO_NORMAL_BG +#define DEF_TNB_BG_COLOR STD_COLOR_NORMAL_BG +#define DEF_TNB_BORDER_WIDTH "1" +#define DEF_TNB_COMMAND (char *)NULL +#define DEF_TNB_CURSOR (char *)NULL +#define DEF_TNB_DASHES "1" +#define DEF_TNB_FG_COLOR STD_COLOR_NORMAL_FG +#define DEF_TNB_FG_MONO STD_MONO_NORMAL_FG +#define DEF_TNB_FONT STD_FONT +#define DEF_TNB_GAP "3" +#define DEF_TNB_HEIGHT "0" +#define DEF_TNB_HIGHLIGHT_BG_COLOR STD_COLOR_NORMAL_BG +#define DEF_TNB_HIGHLIGHT_BG_MONO STD_MONO_NORMAL_BG +#define DEF_TNB_HIGHLIGHT_COLOR RGB_BLACK +#define DEF_TNB_HIGHLIGHT_WIDTH "2" +#define DEF_TNB_NORMAL_BG_COLOR STD_COLOR_NORMAL_BG +#define DEF_TNB_NORMAL_FG_MONO STD_MONO_ACTIVE_FG +#define DEF_TNB_OUTER_PAD "3" +#define DEF_TNB_RELIEF "sunken" +#define DEF_TNB_ROTATE "0.0" +#define DEF_TNB_SCROLL_INCREMENT "0" +#define DEF_TNB_SELECT_BG_COLOR STD_COLOR_NORMAL_BG +#define DEF_TNB_SELECT_BG_MONO STD_MONO_SELECT_BG +#define DEF_TNB_SELECT_BORDER_WIDTH "1" +#define DEF_TNB_SELECT_CMD (char *)NULL +#define DEF_TNB_SELECT_FG_COLOR STD_COLOR_SELECT_FG +#define DEF_TNB_SELECT_FG_MONO STD_MONO_SELECT_FG +#define DEF_TNB_SELECT_MODE "multiple" +#define DEF_TNB_SELECT_RELIEF "raised" +#define DEF_TNB_SELECT_PAD "5" +#define DEF_TNB_SHADOW_COLOR RGB_BLACK +#define DEF_TNB_SIDE "top" +#define DEF_TNB_SLANT "none" +#define DEF_TNB_TAB_BG_COLOR RGB_GREY82 +#define DEF_TNB_TAB_BG_MONO STD_MONO_SELECT_BG +#define DEF_TNB_TAB_RELIEF "raised" +#define DEF_TNB_TAKE_FOCUS "1" +#define DEF_TNB_TEXT_COLOR STD_COLOR_NORMAL_FG +#define DEF_TNB_TEXT_MONO STD_MONO_NORMAL_FG +#define DEF_TNB_TEXT_SIDE "left" +#define DEF_TNB_TIERS "1" +#define DEF_TNB_TILE (char *)NULL +#define DEF_TNB_WIDTH "0" +#define DEF_TNB_SAME_WIDTH "yes" +#define DEF_TNB_TEAROFF "yes" +#define DEF_TNB_PAGE_WIDTH "0" +#define DEF_TNB_PAGE_HEIGHT "0" + +#define DEF_TAB_ACTIVE_BG (char *)NULL +#define DEF_TAB_ACTIVE_FG (char *)NULL +#define DEF_TAB_ANCHOR "center" +#define DEF_TAB_BG (char *)NULL +#define DEF_TAB_COMMAND (char *)NULL +#define DEF_TAB_DATA (char *)NULL +#define DEF_TAB_FG (char *)NULL +#define DEF_TAB_FILL "none" +#define DEF_TAB_FONT (char *)NULL +#define DEF_TAB_HEIGHT "0" +#define DEF_TAB_IMAGE (char *)NULL +#define DEF_TAB_IPAD "0" +#define DEF_TAB_PAD "3" +#define DEF_TAB_PERF_COMMAND (char *)NULL +#define DEF_TAB_SELECT_BG (char *)NULL +#define DEF_TAB_SELECT_BORDER_WIDTH "1" +#define DEF_TAB_SELECT_CMD (char *)NULL +#define DEF_TAB_SELECT_FG (char *)NULL +#define DEF_TAB_SHADOW (char *)NULL +#define DEF_TAB_STATE "normal" +#define DEF_TAB_STIPPLE "BLT" +#define DEF_TAB_BIND_TAGS "all" +#define DEF_TAB_TEXT (char *)NULL +#define DEF_TAB_VISUAL (char *)NULL +#define DEF_TAB_WIDTH "0" +#define DEF_TAB_WINDOW (char *)NULL + +typedef struct NotebookStruct Notebook; + +static void EmbeddedWidgetGeometryProc _ANSI_ARGS_((ClientData, Tk_Window)); +static void EmbeddedWidgetCustodyProc _ANSI_ARGS_((ClientData, Tk_Window)); + +static Tk_GeomMgr tabMgrInfo = +{ + "notebook", /* Name of geometry manager used by winfo */ + EmbeddedWidgetGeometryProc, /* Procedure to for new geometry requests */ + EmbeddedWidgetCustodyProc, /* Procedure when window is taken away */ +}; + +extern Tk_CustomOption bltDashesOption; +extern Tk_CustomOption bltFillOption; +extern Tk_CustomOption bltDistanceOption; +extern Tk_CustomOption bltPositiveDistanceOption; +extern Tk_CustomOption bltPositiveCountOption; +extern Tk_CustomOption bltListOption; +extern Tk_CustomOption bltPadOption; +extern Tk_CustomOption bltShadowOption; +extern Tk_CustomOption bltStateOption; +extern Tk_CustomOption bltTileOption; +extern Tk_CustomOption bltUidOption; + +static int StringToImage _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, + int offset)); +static char *ImageToString _ANSI_ARGS_((ClientData clientData, + Tk_Window tkwin, char *widgRec, int offset, + Tcl_FreeProc **freeProcPtrPtr)); + +static int StringToWindow _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, + int offset)); +static char *WindowToString _ANSI_ARGS_((ClientData clientData, + Tk_Window tkwin, char *widgRec, int offset, + Tcl_FreeProc **freeProcPtrPtr)); + +static int StringToSide _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, + int offset)); +static char *SideToString _ANSI_ARGS_((ClientData clientData, + Tk_Window tkwin, char *widgRec, int offset, + Tcl_FreeProc **freeProcPtrPtr)); + +static int StringToSlant _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, + int offset)); +static char *SlantToString _ANSI_ARGS_((ClientData clientData, + Tk_Window tkwin, char *widgRec, int offset, + Tcl_FreeProc **freeProcPtrPtr)); + +/* + * Contains a pointer to the widget that's currently being configured. + * This is used in the custom configuration parse routine for images. + */ +static Notebook *lastNotebookInstance; + +static Tk_CustomOption imageOption = +{ + StringToImage, ImageToString, (ClientData)&lastNotebookInstance, +}; + +static Tk_CustomOption sideOption = +{ + StringToSide, SideToString, (ClientData)0, +}; + +static Tk_CustomOption windowOption = +{ + StringToWindow, WindowToString, (ClientData)0, +}; + +static Tk_CustomOption slantOption = +{ + StringToSlant, SlantToString, (ClientData)0, +}; + +/* + * TabImage -- + * + * When multiple instances of an image are displayed in the + * same widget, this can be inefficient in terms of both memory + * and time. We only need one instance of each image, regardless + * of number of times we use it. And searching/deleting instances + * can be very slow as the list gets large. + * + * The workaround, employed below, is to maintain a hash table of + * images that maintains a reference count for each image. + */ + +typedef struct TabImageStruct { + int refCount; /* Reference counter for this image. */ + Tk_Image tkImage; /* The Tk image being cached. */ + int width, height; /* Dimensions of the cached image. */ + Blt_HashEntry *hashPtr; /* Hash table pointer to the image. */ + +} *TabImage; + +#define ImageHeight(image) ((image)->height) +#define ImageWidth(image) ((image)->width) +#define ImageData(image) ((image)->tkImage) + +#define TAB_VISIBLE (1<<0) +#define TAB_REDRAW (1<<2) + +typedef struct { + char *name; /* Identifier for tab entry */ + int state; /* State of the tab: Disabled, active, or + * normal. */ + unsigned int flags; + + int tier; /* Index of tier [1..numTiers] containing + * this tab. */ + + int worldX, worldY; /* Position of the tab in world coordinates. */ + int worldWidth, worldHeight;/* Dimensions of the tab, corrected for + * orientation (-side). It includes the + * border, padding, label, etc. */ + int screenX, screenY; + short int screenWidth, screenHeight; /* */ + + Notebook *nbPtr; /* Notebook that includes this + * tab. Needed for callbacks can pass + * only a tab pointer. */ + Tk_Uid tags; + + /* + * Tab label: + */ + Tk_Uid text; /* String displayed as the tab's label. */ + TabImage image; /* Image displayed as the label. */ + + short int textWidth, textHeight; + short int labelWidth, labelHeight; + Blt_Pad iPadX, iPadY; /* Internal padding around the text */ + + Tk_Font font; + + /* + * Normal: + */ + XColor *textColor; /* Text color */ + Tk_3DBorder border; /* Background color and border for tab.*/ + + /* + * Selected: Tab is currently selected. + */ + XColor *selColor; /* Selected text color */ + Tk_3DBorder selBorder; /* 3D border of selected folder. */ + + /* + * Active: Mouse passes over the tab. + */ + Tk_3DBorder activeBorder; /* Active background color. */ + XColor *activeFgColor; /* Active text color */ + + Shadow shadow; + Pixmap stipple; /* Stipple for outline of embedded window + * when torn off. */ + /* + * Embedded widget information: + */ + Tk_Window tkwin; /* Widget to be mapped when the tab is + * selected. If NULL, don't make + * space for the page. */ + + int reqWidth, reqHeight; /* If non-zero, overrides the + * requested dimensions of the + * embedded widget. */ + + Tk_Window container; /* The window containing the embedded + * widget. Does not necessarily have + * to be the parent. */ + + Tk_Anchor anchor; /* Anchor: indicates how the embedded + * widget is positioned within the + * extra space on the page. */ + + Blt_Pad padX, padY; /* Padding around embedded widget */ + + int fill; /* Indicates how the window should + * fill the page. */ + + /* + * Auxillary information: + */ + Tk_Uid command; /* Command (malloc-ed) invoked when the tab + * is selected */ + Tk_Uid data; /* This value isn't used in C code. + * It may be used by clients in Tcl bindings + * to associate extra data (other than the + * label or name) with the tab. */ + + Blt_ChainLink *linkPtr; /* Pointer to where the tab resides in the + * list of tabs. */ + Tk_Uid perfCommand; /* Command (malloc-ed) invoked when the tab + * is selected */ + GC textGC; + GC backGC; + + Blt_Tile tile; + +} Tab; + +static Tk_ConfigSpec tabConfigSpecs[] = +{ + {TK_CONFIG_BORDER, "-activebackground", "activeBackground", + "ActiveBackground", DEF_TAB_ACTIVE_BG, + Tk_Offset(Tab, activeBorder), TK_CONFIG_NULL_OK}, + {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", + "ActiveForeground", DEF_TAB_ACTIVE_FG, + Tk_Offset(Tab, activeFgColor), TK_CONFIG_NULL_OK}, + {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", + DEF_TAB_ANCHOR, Tk_Offset(Tab, anchor), TK_CONFIG_DONT_SET_DEFAULT}, + {TK_CONFIG_BORDER, "-background", "background", "Background", + DEF_TAB_BG, Tk_Offset(Tab, border), TK_CONFIG_NULL_OK}, + {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags", + DEF_TAB_BIND_TAGS, Tk_Offset(Tab, tags), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_CUSTOM, "-command", "command", "Command", + DEF_TAB_COMMAND, Tk_Offset(Tab, command), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_CUSTOM, "-data", "data", "data", + DEF_TAB_DATA, Tk_Offset(Tab, data), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, + {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill", + DEF_TAB_FILL, Tk_Offset(Tab, fill), + TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption}, + {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_TAB_FG, Tk_Offset(Tab, textColor), TK_CONFIG_NULL_OK}, + {TK_CONFIG_FONT, "-font", "font", "Font", + DEF_TAB_FONT, Tk_Offset(Tab, font), 0}, + {TK_CONFIG_CUSTOM, "-image", "image", "image", + DEF_TAB_IMAGE, Tk_Offset(Tab, image), + TK_CONFIG_NULL_OK, &imageOption}, + {TK_CONFIG_CUSTOM, "-ipadx", "iPadX", "PadX", + DEF_TAB_IPAD, Tk_Offset(Tab, iPadX), + TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption}, + {TK_CONFIG_CUSTOM, "-ipady", "iPadY", "PadY", + DEF_TAB_IPAD, Tk_Offset(Tab, iPadY), + TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption}, + {TK_CONFIG_CUSTOM, "-padx", "padX", "PadX", + DEF_TAB_PAD, Tk_Offset(Tab, padX), 0, &bltPadOption}, + {TK_CONFIG_CUSTOM, "-pady", "padY", "PadY", + DEF_TAB_PAD, Tk_Offset(Tab, padY), 0, &bltPadOption}, + {TK_CONFIG_CUSTOM, "-perforationcommand", "perforationcommand", + "PerforationCommand", + DEF_TAB_PERF_COMMAND, Tk_Offset(Tab, perfCommand), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background", + DEF_TAB_SELECT_BG, Tk_Offset(Tab, selBorder), TK_CONFIG_NULL_OK}, + {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground", + DEF_TAB_SELECT_FG, Tk_Offset(Tab, selColor), TK_CONFIG_NULL_OK}, + {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow", + DEF_TAB_SHADOW, Tk_Offset(Tab, shadow), + TK_CONFIG_NULL_OK, &bltShadowOption}, + {TK_CONFIG_CUSTOM, "-state", "state", "State", + DEF_TAB_STATE, Tk_Offset(Tab, state), + TK_CONFIG_DONT_SET_DEFAULT, &bltStateOption}, + {TK_CONFIG_BITMAP, "-stipple", "stipple", "Stipple", + DEF_TAB_STIPPLE, Tk_Offset(Tab, stipple), 0}, + {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile", + (char *)NULL, Tk_Offset(Tab, tile), TK_CONFIG_NULL_OK, + &bltTileOption}, + {TK_CONFIG_CUSTOM, "-text", "Text", "Text", + DEF_TAB_TEXT, Tk_Offset(Tab, text), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_CUSTOM, "-window", "window", "Window", + DEF_TAB_WINDOW, Tk_Offset(Tab, tkwin), + TK_CONFIG_NULL_OK, &windowOption}, + {TK_CONFIG_CUSTOM, "-windowheight", "windowHeight", "WindowHeight", + DEF_TAB_HEIGHT, Tk_Offset(Tab, reqHeight), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_CUSTOM, "-windowwidth", "windowWidth", "WindowWidth", + DEF_TAB_WIDTH, Tk_Offset(Tab, reqWidth), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, 0, 0} +}; + +/* + * TabAttributes -- + */ +typedef struct { + char *name; +} Perforation; + +typedef struct { + Tk_Window tkwin; /* Default window to map pages. */ + + int reqWidth, reqHeight; /* Requested tab size. */ + int constWidth; + int borderWidth; /* Width of 3D border around the tab's + * label. */ + int pad; /* Extra padding of a tab entry */ + + XColor *activeFgColor; /* Active foreground. */ + Tk_3DBorder activeBorder; /* Active background. */ + XColor *selColor; /* Selected foreground. */ + Tk_Font font; + XColor *textColor; + + Tk_3DBorder border; /* Normal background. */ + Tk_3DBorder selBorder; /* Selected background. */ + + Blt_Dashes dashes; + GC normalGC, activeGC; + int relief; + char *command; + char *perfCommand; /* Command (malloc-ed) invoked when the tab + * is selected */ + double rotate; + int textSide; + +} TabAttributes; + +struct NotebookStruct { + Tk_Window tkwin; /* Window that embodies the widget. + * NULL means that the window has been + * destroyed but the data structures + * haven't yet been cleaned up.*/ + + Display *display; /* Display containing widget; needed, + * among other things, to release + * resources after tkwin has already + * gone away. */ + + Tcl_Interp *interp; /* Interpreter associated with widget. */ + + Tcl_Command cmdToken; /* Token for widget's command. */ + + unsigned int flags; /* For bitfield definitions, see below */ + + int inset; /* Total width of all borders, including + * traversal highlight and 3-D border. + * Indicates how much interior stuff must + * be offset from outside edges to leave + * room for borders. */ + + int inset2; /* Total width of 3-D folder border + corner, + * Indicates how much interior stuff must + * be offset from outside edges of folder.*/ + + int yPad; /* Extra offset for selected tab. Only + * for single tiers. */ + + int pageTop; /* Offset from top of notebook to the + * start of the page. */ + + Tk_Cursor cursor; /* X Cursor */ + + Tk_3DBorder border; /* 3D border surrounding the window. */ + int borderWidth; /* Width of 3D border. */ + int relief; /* 3D border relief. */ + + XColor *shadowColor; /* Shadow color around folder. */ + /* + * Focus highlight ring + */ + int highlightWidth; /* Width in pixels of highlight to draw + * around widget when it has the focus. + * <= 0 means don't draw a highlight. */ + XColor *highlightBgColor; /* Color for drawing traversal highlight + * area when highlight is off. */ + XColor *highlightColor; /* Color for drawing traversal highlight. */ + + GC highlightGC; /* GC for focus highlight. */ + + char *takeFocus; /* Says whether to select this widget during + * tab traveral operations. This value isn't + * used in C code, but for the widget's Tcl + * bindings. */ + + + int side; /* How notebook is oriented: either SIDE_LEFT, + * SIDE_RIGHT, SIDE_TOP, or SIDE_BOTTOM. */ + + int slant; + int overlap; + int gap; + int tabWidth, tabHeight; + int xSelectPad, ySelectPad; /* Padding around label of the selected tab. */ + int outerPad; /* Padding around the exterior of the notebook + * and folder. */ + + TabAttributes defTabStyle; /* Global attribute information specific to + * tabs. */ + Blt_Tile tile; + + int reqWidth, reqHeight; /* Requested dimensions of the notebook + * window. */ + int pageWidth, pageHeight; /* Dimensions of a page in the folder. */ + int reqPageWidth, reqPageHeight; /* Requested dimensions of a page. */ + + int lastX, lastY; + /* + * Scrolling information: + */ + int worldWidth; + int scrollOffset; /* Offset of viewport in world coordinates. */ + char *scrollCmdPrefix; /* Command strings to control scrollbar.*/ + + int scrollUnits; /* Smallest unit of scrolling for tabs. */ + + /* + * Scanning information: + */ + int scanAnchor; /* Scan anchor in screen coordinates. */ + int scanOffset; /* Offset of the start of the scan in world + * coordinates.*/ + + + int corner; /* Number of pixels to offset next point + * when drawing corners of the folder. */ + int reqTiers; /* Requested number of tiers. Zero means to + * dynamically scroll if there are too many + * tabs to be display on a single tier. */ + int nTiers; /* Actual number of tiers. */ + + Blt_HashTable imageTable; + + + Tab *selectPtr; /* The currently selected tab. + * (i.e. its page is displayed). */ + + Tab *activePtr; /* Tab last located under the pointer. + * It is displayed with its active + * foreground/background colors. */ + + Tab *focusPtr; /* Tab currently receiving focus. */ + + Tab *startPtr; /* The first tab on the first tier. */ + + Blt_Chain *chainPtr; /* List of tab entries. Used to + * arrange placement of tabs. */ + + Blt_HashTable tabTable; /* Hash table of tab entries. Used for + * lookups of tabs by name. */ + int nextId; + + int nVisible; /* Number of tabs that are currently visible + * in the view port. */ + + Blt_BindTable bindTable; /* Tab binding information */ + Blt_HashTable tagTable; /* Table of bind tags. */ + + Perforation perforation; + int tearoff; +}; + +static Tk_ConfigSpec configSpecs[] = +{ + {TK_CONFIG_BORDER, "-activebackground", "activeBackground", + "activeBackground", + DEF_TNB_ACTIVE_BG_COLOR, Tk_Offset(Notebook, defTabStyle.activeBorder), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_BORDER, "-activebackground", "activeBackground", + "activeBackground", + DEF_TNB_ACTIVE_BG_MONO, Tk_Offset(Notebook, defTabStyle.activeBorder), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", + "activeForeground", DEF_TNB_ACTIVE_FG_COLOR, + Tk_Offset(Notebook, defTabStyle.activeFgColor), TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", + "activeForeground", DEF_TNB_ACTIVE_FG_MONO, + Tk_Offset(Notebook, defTabStyle.activeFgColor), TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_BORDER, "-background", "background", "Background", + DEF_TNB_BG_MONO, Tk_Offset(Notebook, border), TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_BORDER, "-background", "background", "Background", + DEF_TNB_BG_COLOR, Tk_Offset(Notebook, border), TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, + {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", + DEF_TNB_CURSOR, Tk_Offset(Notebook, cursor), TK_CONFIG_NULL_OK}, + {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", + DEF_TNB_BORDER_WIDTH, Tk_Offset(Notebook, borderWidth), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes", + DEF_TNB_DASHES, Tk_Offset(Notebook, defTabStyle.dashes), + TK_CONFIG_NULL_OK, &bltDashesOption}, + {TK_CONFIG_SYNONYM, "-fg", "tabForeground", (char *)NULL, + (char *)NULL, 0, 0}, + {TK_CONFIG_FONT, "-font", "font", "Font", + DEF_TNB_FONT, Tk_Offset(Notebook, defTabStyle.font), 0}, + {TK_CONFIG_SYNONYM, "-foreground", "tabForeground", (char *)NULL, + (char *)NULL, 0, 0}, + {TK_CONFIG_PIXELS, "-gap", "gap", "Gap", + DEF_TNB_GAP, Tk_Offset(Notebook, gap), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_CUSTOM, "-height", "height", "Height", + DEF_TNB_HEIGHT, Tk_Offset(Notebook, reqHeight), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", + DEF_TNB_HIGHLIGHT_BG_COLOR, Tk_Offset(Notebook, highlightBgColor), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", + DEF_TNB_HIGHLIGHT_BG_MONO, Tk_Offset(Notebook, highlightBgColor), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", + DEF_TNB_HIGHLIGHT_COLOR, Tk_Offset(Notebook, highlightColor), 0}, + {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", + "HighlightThickness", + DEF_TNB_HIGHLIGHT_WIDTH, Tk_Offset(Notebook, highlightWidth), + TK_CONFIG_DONT_SET_DEFAULT}, + {TK_CONFIG_CUSTOM, "-outerpad", "outerPad", "OuterPad", + DEF_TNB_OUTER_PAD, Tk_Offset(Notebook, outerPad), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_CUSTOM, "-pageheight", "pageHeight", "PageHeight", + DEF_TNB_PAGE_HEIGHT, Tk_Offset(Notebook, reqPageHeight), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_CUSTOM, "-pagewidth", "pageWidth", "PageWidth", + DEF_TNB_PAGE_WIDTH, Tk_Offset(Notebook, reqPageWidth), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_STRING, "-perforationcommand", "perforationcommand", + "PerforationCommand", + DEF_TAB_PERF_COMMAND, Tk_Offset(Notebook, defTabStyle.perfCommand), + TK_CONFIG_NULL_OK, &bltUidOption}, + {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_TNB_RELIEF, Tk_Offset(Notebook, relief), 0}, + {TK_CONFIG_DOUBLE, "-rotate", "rotate", "Rotate", + DEF_TNB_ROTATE, Tk_Offset(Notebook, defTabStyle.rotate), + TK_CONFIG_DONT_SET_DEFAULT}, + {TK_CONFIG_BOOLEAN, "-samewidth", "sameWidth", "SameWidth", + DEF_TNB_SAME_WIDTH, Tk_Offset(Notebook, defTabStyle.constWidth), + TK_CONFIG_DONT_SET_DEFAULT}, + {TK_CONFIG_STRING, "-scrollcommand", "scrollCommand", "ScrollCommand", + (char *)NULL, Tk_Offset(Notebook, scrollCmdPrefix), TK_CONFIG_NULL_OK}, + {TK_CONFIG_CUSTOM, "-scrollincrement", "scrollIncrement", + "ScrollIncrement", + DEF_TNB_SCROLL_INCREMENT, Tk_Offset(Notebook, scrollUnits), + TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveDistanceOption}, + {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_TNB_SELECT_BG_MONO, Tk_Offset(Notebook, defTabStyle.selBorder), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_TNB_SELECT_BG_COLOR, Tk_Offset(Notebook, defTabStyle.selBorder), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand", + DEF_TNB_SELECT_CMD, Tk_Offset(Notebook, defTabStyle.command), + TK_CONFIG_NULL_OK}, + {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_TNB_SELECT_FG_MONO, Tk_Offset(Notebook, defTabStyle.selColor), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_TNB_SELECT_FG_COLOR, Tk_Offset(Notebook, defTabStyle.selColor), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_CUSTOM, "-selectpad", "selectPad", "SelectPad", + DEF_TNB_SELECT_PAD, Tk_Offset(Notebook, xSelectPad), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_COLOR, "-shadowcolor", "shadowColor", "ShadowColor", + DEF_TNB_SHADOW_COLOR, Tk_Offset(Notebook, shadowColor), 0}, + {TK_CONFIG_CUSTOM, "-side", "side", "side", + DEF_TNB_SIDE, Tk_Offset(Notebook, side), + TK_CONFIG_DONT_SET_DEFAULT, &sideOption}, + {TK_CONFIG_CUSTOM, "-slant", "slant", "Slant", + DEF_TNB_SLANT, Tk_Offset(Notebook, slant), + TK_CONFIG_DONT_SET_DEFAULT, &slantOption}, + {TK_CONFIG_BORDER, "-tabbackground", "tabBackground", "Background", + DEF_TNB_TAB_BG_MONO, Tk_Offset(Notebook, defTabStyle.border), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_BORDER, "-tabbackground", "tabBackground", "Background", + DEF_TNB_TAB_BG_COLOR, Tk_Offset(Notebook, defTabStyle.border), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_CUSTOM, "-tabborderwidth", "tabBorderWidth", "BorderWidth", + DEF_TNB_BORDER_WIDTH, Tk_Offset(Notebook, defTabStyle.borderWidth), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_COLOR, "-tabforeground", "tabForeground", "Foreground", + DEF_TNB_TEXT_COLOR, Tk_Offset(Notebook, defTabStyle.textColor), + TK_CONFIG_COLOR_ONLY}, + {TK_CONFIG_COLOR, "-tabforeground", "tabForeground", "Foreground", + DEF_TNB_TEXT_MONO, Tk_Offset(Notebook, defTabStyle.textColor), + TK_CONFIG_MONO_ONLY}, + {TK_CONFIG_RELIEF, "-tabrelief", "tabRelief", "TabRelief", + DEF_TNB_TAB_RELIEF, Tk_Offset(Notebook, defTabStyle.relief), 0}, + {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_TNB_TAKE_FOCUS, Tk_Offset(Notebook, takeFocus), TK_CONFIG_NULL_OK}, + {TK_CONFIG_BOOLEAN, "-tearoff", "tearoff", "Tearoff", + DEF_TNB_TEAROFF, Tk_Offset(Notebook, tearoff), + TK_CONFIG_DONT_SET_DEFAULT}, + {TK_CONFIG_CUSTOM, "-textside", "textSide", "TextSide", + DEF_TNB_TEXT_SIDE, Tk_Offset(Notebook, defTabStyle.textSide), + TK_CONFIG_DONT_SET_DEFAULT, &sideOption}, + {TK_CONFIG_CUSTOM, "-tiers", "tiers", "Tiers", + DEF_TNB_TIERS, Tk_Offset(Notebook, reqTiers), + TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveCountOption}, + {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile", + (char *)NULL, Tk_Offset(Notebook, tile), TK_CONFIG_NULL_OK, + &bltTileOption}, + {TK_CONFIG_CUSTOM, "-width", "width", "Width", + DEF_TNB_WIDTH, Tk_Offset(Notebook, reqWidth), + TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, + {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, 0, 0} +}; + +/* Forward Declarations */ +static void DestroyNotebook _ANSI_ARGS_((DestroyData dataPtr)); +static void DestroyTearoff _ANSI_ARGS_((DestroyData dataPtr)); +static void EmbeddedWidgetEventProc _ANSI_ARGS_((ClientData clientdata, + XEvent *eventPtr)); +static void TearoffEventProc _ANSI_ARGS_((ClientData clientdata, + XEvent *eventPtr)); +static void NotebookEventProc _ANSI_ARGS_((ClientData clientdata, + XEvent *eventPtr)); +static void DrawLabel _ANSI_ARGS_((Notebook *nbPtr, Tab *tabPtr, + Drawable drawable)); +static void DrawFolder _ANSI_ARGS_((Notebook *nbPtr, Tab *tabPtr, + Drawable drawable)); +static void DisplayNotebook _ANSI_ARGS_((ClientData clientData)); +static void DisplayTearoff _ANSI_ARGS_((ClientData clientData)); +static void NotebookInstDeletedCmd _ANSI_ARGS_((ClientData clientdata)); +static int NotebookInstCmd _ANSI_ARGS_((ClientData clientdata, + Tcl_Interp *interp, int argc, char **argv)); +static void GetWindowRectangle _ANSI_ARGS_((Tab *tabPtr, Tk_Window parent, + int tearOff, XRectangle *rectPtr)); +static void ArrangeWindow _ANSI_ARGS_((Tk_Window tkwin, XRectangle *rectPtr, + int force)); +static void EventuallyRedraw _ANSI_ARGS_((Notebook *nbPtr)); +static void EventuallyRedrawTearoff _ANSI_ARGS_((Tab *tabPtr)); +static void ComputeLayout _ANSI_ARGS_((Notebook *nbPtr)); +static void DrawOuterBorders _ANSI_ARGS_((Notebook *nbPtr, + Drawable drawable)); + +#ifdef __STDC__ +static Tk_ImageChangedProc ImageChangedProc; +static Blt_TileChangedProc TileChangedProc; +static Blt_BindTagProc GetTags; +static Blt_BindPickProc PickTab; +static Tcl_IdleProc AdoptWindow; +static Tcl_CmdProc NotebookCmd; +#endif /* __STDC__ */ + +static ClientData +MakeTag(nbPtr, tagName) + Notebook *nbPtr; + char *tagName; +{ + Blt_HashEntry *hPtr; + int isNew; + + hPtr = Blt_CreateHashEntry(&(nbPtr->tagTable), tagName, &isNew); + assert(hPtr); + return Blt_GetHashKey(&(nbPtr->tagTable), hPtr); +} + +/* + *---------------------------------------------------------------------- + * + * WorldToScreen -- + * + * Converts world coordinates to screen coordinates. Note that + * the world view is always tabs up. + * + * Results: + * The screen coordinates are returned via *xScreenPtr and *yScreenPtr. + * + *---------------------------------------------------------------------- + */ +static void +WorldToScreen(nbPtr, x, y, xScreenPtr, yScreenPtr) + Notebook *nbPtr; + int x, y; + int *xScreenPtr, *yScreenPtr; +{ + int sx, sy; + + sx = sy = 0; /* Suppress compiler warning. */ + + /* Translate world X-Y to screen coordinates */ + /* + * Note that the world X-coordinate is translated by the selected label's + * X padding. This is done only to keep the scroll range is between + * 0.0 and 1.0, rather adding/subtracting the pad in various locations. + * It may be changed back in the future. + */ + x += (nbPtr->inset + + nbPtr->xSelectPad - + nbPtr->scrollOffset); + y += nbPtr->inset + nbPtr->yPad; + + switch (nbPtr->side) { + case SIDE_TOP: + sx = x, sy = y; /* Do nothing */ + break; + case SIDE_RIGHT: + sx = Tk_Width(nbPtr->tkwin) - y; + sy = x; + break; + case SIDE_LEFT: + sx = y, sy = x; /* Flip coordinates */ + break; + case SIDE_BOTTOM: + sx = x; + sy = Tk_Height(nbPtr->tkwin) - y; + break; + } + *xScreenPtr = sx; + *yScreenPtr = sy; +} + +/* + *---------------------------------------------------------------------- + * + * EventuallyRedraw -- + * + * Queues a request to redraw the widget at the next idle point. + * + * Results: + * None. + * + * Side effects: + * Information gets redisplayed. Right now we don't do selective + * redisplays: the whole window will be redrawn. + * + *---------------------------------------------------------------------- + */ +static void +EventuallyRedraw(nbPtr) + Notebook *nbPtr; +{ + if ((nbPtr->tkwin != NULL) && !(nbPtr->flags & TNB_REDRAW)) { + nbPtr->flags |= TNB_REDRAW; + Tcl_DoWhenIdle(DisplayNotebook, nbPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * EventuallyRedrawTearoff -- + * + * Queues a request to redraw the tearoff at the next idle point. + * + * Results: + * None. + * + * Side effects: + * Information gets redisplayed. Right now we don't do selective + * redisplays: the whole window will be redrawn. + * + *---------------------------------------------------------------------- + */ +static void +EventuallyRedrawTearoff(tabPtr) + Tab *tabPtr; +{ + if ((tabPtr->tkwin != NULL) && !(tabPtr->flags & TAB_REDRAW)) { + tabPtr->flags |= TAB_REDRAW; + Tcl_DoWhenIdle(DisplayTearoff, tabPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * ImageChangedProc + * + * This routine is called whenever an image displayed in a tab + * changes. In this case, we assume that everything will change + * and queue a request to re-layout and redraw the entire notebook. + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +ImageChangedProc(clientData, x, y, width, height, imageWidth, imageHeight) + ClientData clientData; + int x, y, width, height; /* Not used. */ + int imageWidth, imageHeight;/* Not used. */ +{ + Notebook *nbPtr = clientData; + + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); +} + +/* + *---------------------------------------------------------------------- + * + * GetImage -- + * + * This is a wrapper procedure for Tk_GetImage. The problem is + * that if the same image is used repeatedly in the same widget, + * the separate instances are saved in a linked list. This makes + * it especially slow to destroy the widget. As a workaround, + * this routine hashes the image and maintains a reference count + * for it. + * + * Results: + * Returns a pointer to the new image. + * + *---------------------------------------------------------------------- + */ +static TabImage +GetImage(nbPtr, interp, tkwin, name) + Notebook *nbPtr; + Tcl_Interp *interp; + Tk_Window tkwin; + char *name; +{ + struct TabImageStruct *imagePtr; + int isNew; + Blt_HashEntry *hPtr; + + hPtr = Blt_CreateHashEntry(&(nbPtr->imageTable), name, &isNew); + if (isNew) { + Tk_Image tkImage; + int width, height; + + tkImage = Tk_GetImage(interp, tkwin, name, ImageChangedProc, nbPtr); + if (tkImage == NULL) { + Blt_DeleteHashEntry(&(nbPtr->imageTable), hPtr); + return NULL; + } + Tk_SizeOfImage(tkImage, &width, &height); + imagePtr = Blt_Malloc(sizeof(struct TabImageStruct)); + imagePtr->tkImage = tkImage; + imagePtr->hashPtr = hPtr; + imagePtr->refCount = 1; + imagePtr->width = width; + imagePtr->height = height; + Blt_SetHashValue(hPtr, imagePtr); + } else { + imagePtr = (struct TabImageStruct *)Blt_GetHashValue(hPtr); + imagePtr->refCount++; + } + return imagePtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeImage -- + * + * Releases the image if it's not being used anymore by this + * widget. Note there may be several uses of the same image + * by many tabs. + * + * Results: + * None. + * + * Side Effects: + * The reference count is decremented and the image is freed + * is it's not being used anymore. + * + *---------------------------------------------------------------------- + */ +static void +FreeImage(nbPtr, imagePtr) + Notebook *nbPtr; + struct TabImageStruct *imagePtr; +{ + imagePtr->refCount--; + if (imagePtr->refCount == 0) { + Blt_DeleteHashEntry(&(nbPtr->imageTable), imagePtr->hashPtr); + Tk_FreeImage(imagePtr->tkImage); + Blt_Free(imagePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * StringToImage -- + * + * Converts an image name into a Tk image token. + * + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left + * in interpreter's result field. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +StringToImage(clientData, interp, tkwin, string, widgRec, offset) + ClientData clientData; /* Contains a pointer to the notebook containing + * this image. */ + Tcl_Interp *interp; /* Interpreter to send results back to */ + Tk_Window tkwin; /* Window associated with the notebook. */ + char *string; /* String representation */ + char *widgRec; /* Widget record */ + int offset; /* Offset to field in structure */ +{ + Notebook *nbPtr = *(Notebook **)clientData; + TabImage *imagePtr = (TabImage *) (widgRec + offset); + TabImage image; + + image = NULL; + if ((string != NULL) && (*string != '\0')) { + image = GetImage(nbPtr, interp, tkwin, string); + if (image == NULL) { + return TCL_ERROR; + } + } + if (*imagePtr != NULL) { + FreeImage(nbPtr, *imagePtr); + } + *imagePtr = image; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ImageToString -- + * + * Converts the Tk image back to its string representation (i.e. + * its name). + * + * Results: + * The name of the image is returned. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static char * +ImageToString(clientData, tkwin, widgRec, offset, freeProcPtr) + ClientData clientData; /* Pointer to notebook containing image. */ + Tk_Window tkwin; /* Not used. */ + char *widgRec; /* Widget record */ + int offset; /* Offset of field in record */ + Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ +{ + Notebook *nbPtr = *(Notebook **)clientData; + TabImage *imagePtr = (TabImage *) (widgRec + offset); + + if (*imagePtr == NULL) { + return ""; + } + return Blt_GetHashKey(&(nbPtr->imageTable), (*imagePtr)->hashPtr); +} + +/* + *---------------------------------------------------------------------- + * + * StringToWindow -- + * + * Converts a window name into Tk window. + * + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left + * in interpreter's result field. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +StringToWindow(clientData, interp, parent, string, widgRec, offset) + ClientData clientData; /* Not used. */ + Tcl_Interp *interp; /* Interpreter to send results back to */ + Tk_Window parent; /* Parent window */ + char *string; /* String representation. */ + char *widgRec; /* Widget record */ + int offset; /* Offset to field in structure */ +{ + Tab *tabPtr = (Tab *)widgRec; + Tk_Window *tkwinPtr = (Tk_Window *)(widgRec + offset); + Tk_Window old, tkwin; + Notebook *nbPtr; + + old = *tkwinPtr; + tkwin = NULL; + nbPtr = tabPtr->nbPtr; + if ((string != NULL) && (*string != '\0')) { + tkwin = Tk_NameToWindow(interp, string, parent); + if (tkwin == NULL) { + return TCL_ERROR; + } + if (tkwin == old) { + return TCL_OK; + } + /* + * Allow only widgets that are children of the notebook to be + * embedded into the page. This way we can make assumptions about + * the window based upon its parent; either it's the notebook window + * or it has been torn off. + */ + parent = Tk_Parent(tkwin); + if (parent != nbPtr->tkwin) { + Tcl_AppendResult(interp, "can't manage \"", Tk_PathName(tkwin), + "\" in notebook \"", Tk_PathName(nbPtr->tkwin), "\"", + (char *)NULL); + return TCL_ERROR; + } + Tk_ManageGeometry(tkwin, &tabMgrInfo, tabPtr); + Tk_CreateEventHandler(tkwin, StructureNotifyMask, + EmbeddedWidgetEventProc, tabPtr); + + /* + * We need to make the window to exist immediately. If the + * window is torn off (placed into another container window), + * the timing between the container and the its new child + * (this window) gets tricky. This should work for Tk 4.2. + */ + Tk_MakeWindowExist(tkwin); + } + if (old != NULL) { + if (tabPtr->container != NULL) { + Tcl_EventuallyFree(tabPtr, DestroyTearoff); + } + Tk_DeleteEventHandler(old, StructureNotifyMask, + EmbeddedWidgetEventProc, tabPtr); + Tk_ManageGeometry(old, (Tk_GeomMgr *) NULL, tabPtr); + Tk_UnmapWindow(old); + } + *tkwinPtr = tkwin; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * WindowToString -- + * + * Converts the Tk window back to its string representation (i.e. + * its name). + * + * Results: + * The name of the window is returned. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static char * +WindowToString(clientData, parent, widgRec, offset, freeProcPtr) + ClientData clientData; /* Not used. */ + Tk_Window parent; /* Not used. */ + char *widgRec; /* Widget record */ + int offset; /* Offset of field in record */ + Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ +{ + Tk_Window tkwin = *(Tk_Window *)(widgRec + offset); + + if (tkwin == NULL) { + return ""; + } + return Tk_PathName(tkwin); +} + +/* + *---------------------------------------------------------------------- + * + * StringToSide -- + * + * Converts "left", "right", "top", "bottom", into a numeric token + * designating the side of the notebook which to display tabs. + * + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left + * in interpreter's result field. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED */ +static int +StringToSide(clientData, interp, parent, string, widgRec, offset) + ClientData clientData; /* Not used. */ + Tcl_Interp *interp; /* Interpreter to send results back to */ + Tk_Window parent; /* Parent window */ + char *string; /* Option value string */ + char *widgRec; /* Widget record */ + int offset; /* offset to field in structure */ +{ + int *sidePtr = (int *)(widgRec + offset); + char c; + unsigned int length; + + c = string[0]; + length = strlen(string); + if ((c == 'l') && (strncmp(string, "left", length) == 0)) { + *sidePtr = SIDE_LEFT; + } else if ((c == 'r') && (strncmp(string, "right", length) == 0)) { + *sidePtr = SIDE_RIGHT; + } else if ((c == 't') && (strncmp(string, "top", length) == 0)) { + *sidePtr = SIDE_TOP; + } else if ((c == 'b') && (strncmp(string, "bottom", length) == 0)) { + *sidePtr = SIDE_BOTTOM; + } else { + Tcl_AppendResult(interp, "bad side \"", string, + "\": should be left, right, top, or bottom", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SideToString -- + * + * Converts the window into its string representation (its name). + * + * Results: + * The name of the window is returned. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static char * +SideToString(clientData, parent, widgRec, offset, freeProcPtr) + ClientData clientData; /* Not used. */ + Tk_Window parent; /* Not used. */ + char *widgRec; /* Widget record */ + int offset; /* offset of windows array in record */ + Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ +{ + int side = *(int *)(widgRec + offset); + + switch (side) { + case SIDE_LEFT: + return "left"; + case SIDE_RIGHT: + return "right"; + case SIDE_BOTTOM: + return "bottom"; + case SIDE_TOP: + return "top"; + } + return "unknown side value"; +} + +/* + *---------------------------------------------------------------------- + * + * StringToSlant -- + * + * Converts the slant style string into its numeric representation. + * + * Valid style strings are: + * + * "none" Both sides are straight. + * "left" Left side is slanted. + * "right" Right side is slanted. + * "both" Both sides are slanted. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +StringToSlant(clientData, interp, tkwin, string, widgRec, offset) + ClientData clientData; /* Not used. */ + Tcl_Interp *interp; /* Interpreter to send results back to */ + Tk_Window tkwin; /* Not used. */ + char *string; /* String representation of attribute. */ + char *widgRec; /* Widget record */ + int offset; /* Offset of field in widget record. */ +{ + int *slantPtr = (int *)(widgRec + offset); + unsigned int length; + char c; + + c = string[0]; + length = strlen(string); + if ((c == 'n') && (strncmp(string, "none", length) == 0)) { + *slantPtr = SLANT_NONE; + } else if ((c == 'l') && (strncmp(string, "left", length) == 0)) { + *slantPtr = SLANT_LEFT; + } else if ((c == 'r') && (strncmp(string, "right", length) == 0)) { + *slantPtr = SLANT_RIGHT; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *slantPtr = SLANT_BOTH; + } else { + Tcl_AppendResult(interp, "bad argument \"", string, + "\": should be \"none\", \"left\", \"right\", or \"both\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SlantToString -- + * + * Returns the slant style string based upon the slant flags. + * + * Results: + * The slant style string is returned. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static char * +SlantToString(clientData, tkwin, widgRec, offset, freeProcPtr) + ClientData clientData; /* Not used. */ + Tk_Window tkwin; /* Not used. */ + char *widgRec; /* Widget structure record. */ + int offset; /* Offset of field in widget record. */ + Tcl_FreeProc **freeProcPtr; /* Not used. */ +{ + int slant = *(int *)(widgRec + offset); + + switch (slant) { + case SLANT_LEFT: + return "left"; + case SLANT_RIGHT: + return "right"; + case SLANT_NONE: + return "none"; + case SLANT_BOTH: + return "both"; + default: + return "unknown value"; + } +} + + +static int +WorldY(tabPtr) + Tab *tabPtr; +{ + int tier; + + tier = tabPtr->nbPtr->nTiers - tabPtr->tier; + return tier * tabPtr->nbPtr->tabHeight; +} + +static int +TabIndex(nbPtr, tabPtr) + Notebook *nbPtr; + Tab *tabPtr; +{ + Tab *t2Ptr; + int count; + Blt_ChainLink *linkPtr; + + count = 0; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + t2Ptr = Blt_ChainGetValue(linkPtr); + if (t2Ptr == tabPtr) { + return count; + } + count++; + } + return -1; +} + +/* + * ---------------------------------------------------------------------- + * + * RenumberTiers -- + * + * In multi-tier mode, we need to find the start of the tier + * containing the newly selected tab. + * + * Tiers are draw from the last tier to the first, so that + * the the lower-tiered tabs will partially cover the bottoms + * of tab directly above it. This simplifies the drawing of + * tabs because we don't worry how tabs are clipped by their + * neighbors. + * + * In addition, tabs are re-marked with the correct tier number. + * + * Results: + * None. + * + * Side Effects: + * Renumbering the tab's tier will change the vertical placement + * of the tab (i.e. shift tiers). + * + * ---------------------------------------------------------------------- + */ +static void +RenumberTiers(nbPtr, tabPtr) + Notebook *nbPtr; + Tab *tabPtr; +{ + int tier; + Tab *prevPtr; + Blt_ChainLink *linkPtr, *lastPtr; + + nbPtr->focusPtr = nbPtr->selectPtr = tabPtr; + Blt_SetFocusItem(nbPtr->bindTable, nbPtr->focusPtr); + + tier = tabPtr->tier; + for (linkPtr = Blt_ChainPrevLink(tabPtr->linkPtr); linkPtr != NULL; + linkPtr = lastPtr) { + lastPtr = Blt_ChainPrevLink(linkPtr); + prevPtr = Blt_ChainGetValue(linkPtr); + if ((prevPtr == NULL) || (prevPtr->tier != tier)) { + break; + } + tabPtr = prevPtr; + } + nbPtr->startPtr = tabPtr; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->tier = (tabPtr->tier - tier + 1); + if (tabPtr->tier < 1) { + tabPtr->tier += nbPtr->nTiers; + } + tabPtr->worldY = WorldY(tabPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * PickTab -- + * + * Searches the tab located within the given screen X-Y coordinates + * in the viewport. Note that tabs overlap slightly, so that its + * important to search from the innermost tier out. + * + * Results: + * Returns the pointer to the tab. If the pointer isn't contained + * by any tab, NULL is returned. + * + *---------------------------------------------------------------------- + */ +static ClientData +PickTab(clientData, x, y) + ClientData clientData; + int x, y; /* Screen coordinates to test. */ +{ + Notebook *nbPtr = clientData; + Tab *tabPtr; + Blt_ChainLink *linkPtr; + + tabPtr = nbPtr->selectPtr; + if ((nbPtr->tearoff) && (tabPtr != NULL) && + (tabPtr->container == NULL) && (tabPtr->tkwin != NULL)) { + int top, bottom, left, right; + int sx, sy; + + /* Check first for perforation on the selected tab. */ + WorldToScreen(nbPtr, tabPtr->worldX + 2, + tabPtr->worldY + tabPtr->worldHeight + 4, &sx, &sy); + if (nbPtr->side & SIDE_HORIZONTAL) { + left = sx - 2; + right = left + tabPtr->screenWidth; + top = sy - 4; + bottom = sy + 4; + } else { + left = sx - 4; + right = sx + 4; + top = sy - 2; + bottom = top + tabPtr->screenHeight; + } + if ((x >= left) && (y >= top) && (x < right) && (y < bottom)) { + return &nbPtr->perforation; + } + } + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + if (!(tabPtr->flags & TAB_VISIBLE)) { + continue; + } + if ((x >= tabPtr->screenX) && (y >= tabPtr->screenY) && + (x <= (tabPtr->screenX + tabPtr->screenWidth)) && + (y < (tabPtr->screenY + tabPtr->screenHeight))) { + return tabPtr; + } + } + return NULL; +} + +static Tab * +TabLeft(tabPtr) + Tab *tabPtr; +{ + Blt_ChainLink *linkPtr; + + linkPtr = Blt_ChainPrevLink(tabPtr->linkPtr); + if (linkPtr != NULL) { + Tab *newPtr; + + newPtr = Blt_ChainGetValue(linkPtr); + /* Move only if the next tab is on another tier. */ + if (newPtr->tier == tabPtr->tier) { + tabPtr = newPtr; + } + } + return tabPtr; +} + +static Tab * +TabRight(tabPtr) + Tab *tabPtr; +{ + Blt_ChainLink *linkPtr; + + linkPtr = Blt_ChainNextLink(tabPtr->linkPtr); + if (linkPtr != NULL) { + Tab *newPtr; + + newPtr = Blt_ChainGetValue(linkPtr); + /* Move only if the next tab is on another tier. */ + if (newPtr->tier == tabPtr->tier) { + tabPtr = newPtr; + } + } + return tabPtr; +} + +static Tab * +TabUp(tabPtr) + Tab *tabPtr; +{ + if (tabPtr != NULL) { + Notebook *nbPtr; + int x, y; + int worldX, worldY; + + nbPtr = tabPtr->nbPtr; + worldX = tabPtr->worldX + (tabPtr->worldWidth / 2); + worldY = tabPtr->worldY - (nbPtr->tabHeight / 2); + WorldToScreen(nbPtr, worldX, worldY, &x, &y); + + tabPtr = (Tab *)PickTab(nbPtr, x, y); + if (tabPtr == NULL) { + /* + * We might have inadvertly picked the gap between two tabs, + * so if the first pick fails, try again a little to the left. + */ + WorldToScreen(nbPtr, worldX + nbPtr->gap, worldY, &x, &y); + tabPtr = (Tab *)PickTab(nbPtr, x, y); + } + if ((tabPtr == NULL) && + (nbPtr->focusPtr->tier < (nbPtr->nTiers - 1))) { + worldY -= nbPtr->tabHeight; + WorldToScreen(nbPtr, worldX, worldY, &x, &y); + tabPtr = (Tab *)PickTab(nbPtr, x, y); + } + if (tabPtr == NULL) { + tabPtr = nbPtr->focusPtr; + } + } + return tabPtr; +} + +static Tab * +TabDown(tabPtr) + Tab *tabPtr; +{ + if (tabPtr != NULL) { + Notebook *nbPtr; + int x, y; + int worldX, worldY; + + nbPtr = tabPtr->nbPtr; + worldX = tabPtr->worldX + (tabPtr->worldWidth / 2); + worldY = tabPtr->worldY + (3 * nbPtr->tabHeight) / 2; + WorldToScreen(nbPtr, worldX, worldY, &x, &y); + tabPtr = (Tab *)PickTab(nbPtr, x, y); + if (tabPtr == NULL) { + /* + * We might have inadvertly picked the gap between two tabs, + * so if the first pick fails, try again a little to the left. + */ + WorldToScreen(nbPtr, worldX - nbPtr->gap, worldY, &x, &y); + tabPtr = (Tab *)PickTab(nbPtr, x, y); + } + if ((tabPtr == NULL) && (nbPtr->focusPtr->tier > 2)) { + worldY += nbPtr->tabHeight; + WorldToScreen(nbPtr, worldX, worldY, &x, &y); + tabPtr = (Tab *)PickTab(nbPtr, x, y); + } + if (tabPtr == NULL) { + tabPtr = nbPtr->focusPtr; + } + } + return tabPtr; +} + +/* + *---------------------------------------------------------------------- + * + * GetTab -- + * + * Converts a string representing a tab index into a tab pointer. + * The index may be in one of the following forms: + * + * number Tab at position in the list of tabs. + * @x,y Tab closest to the specified X-Y screen coordinates. + * "active" Tab mouse is located over. + * "focus" Tab is the widget's focus. + * "select" Currently selected tab. + * "right" Next tab from the focus tab. + * "left" Previous tab from the focus tab. + * "up" Next tab from the focus tab. + * "down" Previous tab from the focus tab. + * "end" Last tab in list. + * + * Results: + * If the string is successfully converted, TCL_OK is returned. + * The pointer to the node is returned via tabPtrPtr. + * Otherwise, TCL_ERROR is returned and an error message is left + * in interpreter's result field. + * + *---------------------------------------------------------------------- + */ +static int +GetTab(nbPtr, string, tabPtrPtr, allowNull) + Notebook *nbPtr; + char *string; + Tab **tabPtrPtr; + int allowNull; /* Allow NULL tabPtr */ +{ + Tab *tabPtr; + Blt_ChainLink *linkPtr; + int position; + char c; + + c = string[0]; + linkPtr = NULL; + tabPtr = NULL; + if (nbPtr->focusPtr == NULL) { + nbPtr->focusPtr = nbPtr->selectPtr; + Blt_SetFocusItem(nbPtr->bindTable, nbPtr->focusPtr); + } + if ((isdigit(UCHAR(c))) && + (Tcl_GetInt(nbPtr->interp, string, &position) == TCL_OK)) { + linkPtr = Blt_ChainGetNthLink(nbPtr->chainPtr, position); + if (linkPtr == NULL) { + Tcl_AppendResult(nbPtr->interp, "can't find tab \"", string, + "\" in \"", Tk_PathName(nbPtr->tkwin), + "\": no such index", (char *)NULL); + return TCL_ERROR; + } + tabPtr = Blt_ChainGetValue(linkPtr); + } else if ((c == 'a') && (strcmp(string, "active") == 0)) { + tabPtr = nbPtr->activePtr; + } else if ((c == 'c') && (strcmp(string, "current") == 0)) { + tabPtr = (Tab *)Blt_GetCurrentItem(nbPtr->bindTable); + if (tabPtr->name == NULL) { + /* Selected perforation. */ + tabPtr = nbPtr->selectPtr; + } + } else if ((c == 's') && (strcmp(string, "select") == 0)) { + tabPtr = nbPtr->selectPtr; + } else if ((c == 'f') && (strcmp(string, "focus") == 0)) { + tabPtr = nbPtr->focusPtr; + } else if ((c == 'u') && (strcmp(string, "up") == 0)) { + switch (nbPtr->side) { + case SIDE_LEFT: + case SIDE_RIGHT: + tabPtr = TabLeft(nbPtr->focusPtr); + break; + + case SIDE_BOTTOM: + tabPtr = TabDown(nbPtr->focusPtr); + break; + + case SIDE_TOP: + tabPtr = TabUp(nbPtr->focusPtr); + break; + } + } else if ((c == 'd') && (strcmp(string, "down") == 0)) { + switch (nbPtr->side) { + case SIDE_LEFT: + case SIDE_RIGHT: + tabPtr = TabRight(nbPtr->focusPtr); + break; + + case SIDE_BOTTOM: + tabPtr = TabUp(nbPtr->focusPtr); + break; + + case SIDE_TOP: + tabPtr = TabDown(nbPtr->focusPtr); + break; + } + } else if ((c == 'l') && (strcmp(string, "left") == 0)) { + switch (nbPtr->side) { + case SIDE_LEFT: + tabPtr = TabUp(nbPtr->focusPtr); + break; + + case SIDE_RIGHT: + tabPtr = TabDown(nbPtr->focusPtr); + break; + + case SIDE_BOTTOM: + case SIDE_TOP: + tabPtr = TabLeft(nbPtr->focusPtr); + break; + } + } else if ((c == 'r') && (strcmp(string, "right") == 0)) { + switch (nbPtr->side) { + case SIDE_LEFT: + tabPtr = TabDown(nbPtr->focusPtr); + break; + + case SIDE_RIGHT: + tabPtr = TabUp(nbPtr->focusPtr); + break; + + case SIDE_BOTTOM: + case SIDE_TOP: + tabPtr = TabRight(nbPtr->focusPtr); + break; + } + } else if ((c == 'e') && (strcmp(string, "end") == 0)) { + linkPtr = Blt_ChainLastLink(nbPtr->chainPtr); + if (linkPtr != NULL) { + tabPtr = Blt_ChainGetValue(linkPtr); + } + } else if (c == '@') { + int x, y; + + if (Blt_GetXY(nbPtr->interp, nbPtr->tkwin, string, &x, &y) + != TCL_OK) { + return TCL_ERROR; + } + tabPtr = (Tab *)PickTab(nbPtr, x, y); + } else { + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&(nbPtr->tabTable), string); + if (hPtr != NULL) { + tabPtr = (Tab *)Blt_GetHashValue(hPtr); + } + } + *tabPtrPtr = tabPtr; + Tcl_ResetResult(nbPtr->interp); + + if ((!allowNull) && (tabPtr == NULL)) { + Tcl_AppendResult(nbPtr->interp, "can't find tab \"", string, + "\" in \"", Tk_PathName(nbPtr->tkwin), "\"", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +static Tab * +NextOrLastTab(tabPtr) + Tab *tabPtr; +{ + if (tabPtr->linkPtr != NULL) { + Blt_ChainLink *linkPtr; + + linkPtr = Blt_ChainNextLink(tabPtr->linkPtr); + if (linkPtr == NULL) { + linkPtr = Blt_ChainPrevLink(tabPtr->linkPtr); + } + if (linkPtr != NULL) { + return Blt_ChainGetValue(linkPtr); + } + } + return NULL; +} + +/* + * -------------------------------------------------------------- + * + * EmbeddedWidgetEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various + * events on embedded widgets contained in the notebook. + * + * Results: + * None. + * + * Side effects: + * When an embedded widget gets deleted, internal structures get + * cleaned up. When it gets resized, the notebook is redisplayed. + * + * -------------------------------------------------------------- + */ +static void +EmbeddedWidgetEventProc(clientData, eventPtr) + ClientData clientData; /* Information about the tab window. */ + XEvent *eventPtr; /* Information about event. */ +{ + Tab *tabPtr = clientData; + + if ((tabPtr == NULL) || (tabPtr->tkwin == NULL)) { + return; + } + switch (eventPtr->type) { + case ConfigureNotify: + /* + * If the window's requested size changes, redraw the window. + * But only if it's currently the selected page. + */ + if ((tabPtr->container == NULL) && (Tk_IsMapped(tabPtr->tkwin)) && + (tabPtr->nbPtr->selectPtr == tabPtr)) { + EventuallyRedraw(tabPtr->nbPtr); + } + break; + + case DestroyNotify: + /* + * Mark the tab as deleted by dereferencing the Tk window + * pointer. Redraw the window only if the tab is currently + * visible. + */ + if ((Tk_IsMapped(tabPtr->tkwin)) && + (tabPtr->nbPtr->selectPtr == tabPtr)) { + EventuallyRedraw(tabPtr->nbPtr); + } + Tk_DeleteEventHandler(tabPtr->tkwin, StructureNotifyMask, + EmbeddedWidgetEventProc, tabPtr); + tabPtr->tkwin = NULL; + break; + + } +} + +/* + * ---------------------------------------------------------------------- + * + * EmbeddedWidgetCustodyProc -- + * + * This procedure is invoked when a tab window has been + * stolen by another geometry manager. The information and + * memory associated with the tab window is released. + * + * Results: + * None. + * + * Side effects: + * Arranges for the widget formerly associated with the tab + * window to have its layout re-computed and arranged at the + * next idle point. + * + * --------------------------------------------------------------------- + */ + /* ARGSUSED */ +static void +EmbeddedWidgetCustodyProc(clientData, tkwin) + ClientData clientData; /* Information about the former tab window. */ + Tk_Window tkwin; /* Not used. */ +{ + Tab *tabPtr = clientData; + Notebook *nbPtr; + + if ((tabPtr == NULL) || (tabPtr->tkwin == NULL)) { + return; + } + nbPtr = tabPtr->nbPtr; + if (tabPtr->container != NULL) { + Tcl_EventuallyFree(tabPtr, DestroyTearoff); + } + /* + * Mark the tab as deleted by dereferencing the Tk window + * pointer. Redraw the window only if the tab is currently + * visible. + */ + if (tabPtr->tkwin != NULL) { + if (Tk_IsMapped(tabPtr->tkwin) && (nbPtr->selectPtr == tabPtr)) { + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + } + Tk_DeleteEventHandler(tabPtr->tkwin, StructureNotifyMask, + EmbeddedWidgetEventProc, tabPtr); + tabPtr->tkwin = NULL; + } +} + +/* + * ------------------------------------------------------------------------- + * + * EmbeddedWidgetGeometryProc -- + * + * This procedure is invoked by Tk_GeometryRequest for tab + * windows managed by the widget. + * + * Results: + * None. + * + * Side effects: + * Arranges for tkwin, and all its managed siblings, to be + * repacked and drawn at the next idle point. + * + * ------------------------------------------------------------------------ + */ + /* ARGSUSED */ +static void +EmbeddedWidgetGeometryProc(clientData, tkwin) + ClientData clientData; /* Information about window that got new + * preferred geometry. */ + Tk_Window tkwin; /* Other Tk-related information about the + * window. */ +{ + Tab *tabPtr = clientData; + + if ((tabPtr == NULL) || (tabPtr->tkwin == NULL)) { + fprintf(stderr, "%s: line %d \"tkwin is null\"", __FILE__, __LINE__); + return; + } + tabPtr->nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(tabPtr->nbPtr); +} + +/* + * ---------------------------------------------------------------------- + * + * DestroyTab -- + * + * ---------------------------------------------------------------------- + */ +static void +DestroyTab(nbPtr, tabPtr) + Notebook *nbPtr; + Tab *tabPtr; +{ + Blt_HashEntry *hPtr; + + if (tabPtr->flags & TAB_REDRAW) { + Tcl_CancelIdleCall(DisplayTearoff, tabPtr); + } + if (tabPtr->container != NULL) { + Tk_DestroyWindow(tabPtr->container); + } + if (tabPtr->tkwin != NULL) { + Tk_ManageGeometry(tabPtr->tkwin, (Tk_GeomMgr *)NULL, tabPtr); + Tk_DeleteEventHandler(tabPtr->tkwin, StructureNotifyMask, + EmbeddedWidgetEventProc, tabPtr); + if (Tk_IsMapped(tabPtr->tkwin)) { + Tk_UnmapWindow(tabPtr->tkwin); + } + } + if (tabPtr == nbPtr->activePtr) { + nbPtr->activePtr = NULL; + } + if (tabPtr == nbPtr->selectPtr) { + nbPtr->selectPtr = NextOrLastTab(tabPtr); + } + if (tabPtr == nbPtr->focusPtr) { + nbPtr->focusPtr = nbPtr->selectPtr; + Blt_SetFocusItem(nbPtr->bindTable, nbPtr->focusPtr); + } + if (tabPtr == nbPtr->startPtr) { + nbPtr->startPtr = NULL; + } + Tk_FreeOptions(tabConfigSpecs, (char *)tabPtr, nbPtr->display, 0); + if (tabPtr->text != NULL) { + Blt_FreeUid(tabPtr->text); + } + hPtr = Blt_FindHashEntry(&(nbPtr->tabTable), tabPtr->name); + assert(hPtr); + Blt_DeleteHashEntry(&(nbPtr->tabTable), hPtr); + + if (tabPtr->image != NULL) { + FreeImage(nbPtr, tabPtr->image); + } + if (tabPtr->name != NULL) { + Blt_Free(tabPtr->name); + } + if (tabPtr->textGC != NULL) { + Tk_FreeGC(nbPtr->display, tabPtr->textGC); + } + if (tabPtr->backGC != NULL) { + Tk_FreeGC(nbPtr->display, tabPtr->backGC); + } + if (tabPtr->command != NULL) { + Blt_FreeUid(tabPtr->command); + } + if (tabPtr->linkPtr != NULL) { + Blt_ChainDeleteLink(nbPtr->chainPtr, tabPtr->linkPtr); + } + if (tabPtr->tags != NULL) { + Blt_FreeUid(tabPtr->tags); + } + Blt_DeleteBindings(nbPtr->bindTable, tabPtr); + Blt_Free(tabPtr); +} + +/* + * ---------------------------------------------------------------------- + * + * CreateTab -- + * + * Creates a new tab structure. A tab contains information about + * the state of the tab and its embedded window. + * + * Results: + * Returns a pointer to the new tab structure. + * + * ---------------------------------------------------------------------- + */ +static Tab * +CreateTab(nbPtr) + Notebook *nbPtr; +{ + Tab *tabPtr; + Blt_HashEntry *hPtr; + int isNew; + char string[200]; + + tabPtr = Blt_Calloc(1, sizeof(Tab)); + assert(tabPtr); + tabPtr->nbPtr = nbPtr; + sprintf(string, "tab%d", nbPtr->nextId++); + tabPtr->name = Blt_Strdup(string); + tabPtr->text = Blt_GetUid(string); + tabPtr->fill = FILL_NONE; + tabPtr->anchor = TK_ANCHOR_CENTER; + tabPtr->container = NULL; + tabPtr->state = STATE_NORMAL; + hPtr = Blt_CreateHashEntry(&(nbPtr->tabTable), string, &isNew); + Blt_SetHashValue(hPtr, tabPtr); + return tabPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TileChangedProc + * + * Stub for image change notifications. Since we immediately draw + * the image into a pixmap, we don't really care about image changes. + * + * It would be better if Tk checked for NULL proc pointers. + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +TileChangedProc(clientData, tile) + ClientData clientData; + Blt_Tile tile; /* Not used. */ +{ + Notebook *nbPtr = clientData; + + if (nbPtr->tkwin != NULL) { + EventuallyRedraw(nbPtr); + } +} + +static int +ConfigureTab(nbPtr, tabPtr) + Notebook *nbPtr; + Tab *tabPtr; +{ + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + int labelWidth, labelHeight; + Tk_Font font; + Tk_3DBorder border; + + font = GETATTR(tabPtr, font); + labelWidth = labelHeight = 0; + if (tabPtr->text != NULL) { + TextStyle ts; + + Blt_InitTextStyle(&ts); + ts.font = font; + ts.shadow.offset = tabPtr->shadow.offset; + ts.padX.side1 = ts.padX.side2 = 2; + Blt_GetTextExtents(&ts, tabPtr->text, &labelWidth, &labelHeight); + Blt_GetBoundingBox(labelWidth, labelHeight, nbPtr->defTabStyle.rotate, + &labelWidth, &labelHeight, (Point2D *)NULL); + } + tabPtr->textWidth = (short int)labelWidth; + tabPtr->textHeight = (short int)labelHeight; + if (tabPtr->image != NULL) { + int width, height; + + width = ImageWidth(tabPtr->image) + 2 * IMAGE_PAD; + height = ImageHeight(tabPtr->image) + 2 * IMAGE_PAD; + if (nbPtr->defTabStyle.textSide & SIDE_VERTICAL) { + labelWidth += width; + labelHeight = MAX(labelHeight, height); + } else { + labelHeight += height; + labelWidth = MAX(labelWidth, width); + } + } + labelWidth += PADDING(tabPtr->iPadX); + labelHeight += PADDING(tabPtr->iPadY); + + tabPtr->labelWidth = ODD(labelWidth); + tabPtr->labelHeight = ODD(labelHeight); + + newGC = NULL; + if (tabPtr->text != NULL) { + XColor *colorPtr; + + gcMask = GCForeground | GCFont; + colorPtr = GETATTR(tabPtr, textColor); + gcValues.foreground = colorPtr->pixel; + gcValues.font = Tk_FontId(font); + newGC = Tk_GetGC(nbPtr->tkwin, gcMask, &gcValues); + } + if (tabPtr->textGC != NULL) { + Tk_FreeGC(nbPtr->display, tabPtr->textGC); + } + tabPtr->textGC = newGC; + + gcMask = GCForeground | GCStipple | GCFillStyle; + gcValues.fill_style = FillStippled; + border = GETATTR(tabPtr, border); + gcValues.foreground = Tk_3DBorderColor(border)->pixel; + gcValues.stipple = tabPtr->stipple; + newGC = Tk_GetGC(nbPtr->tkwin, gcMask, &gcValues); + if (tabPtr->backGC != NULL) { + Tk_FreeGC(nbPtr->display, tabPtr->backGC); + } + tabPtr->backGC = newGC; + /* + * GC for tiled background. + */ + if (tabPtr->tile != NULL) { + Blt_SetTileChangedProc(tabPtr->tile, TileChangedProc, nbPtr); + } + if (tabPtr->flags & TAB_VISIBLE) { + EventuallyRedraw(nbPtr); + } + return TCL_OK; +} + +/* + * -------------------------------------------------------------- + * + * TearoffEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various + * events on the tearoff widget. + * + * Results: + * None. + * + * Side effects: + * When the tearoff gets deleted, internal structures get + * cleaned up. When it gets resized or exposed, it's redisplayed. + * + * -------------------------------------------------------------- + */ +static void +TearoffEventProc(clientData, eventPtr) + ClientData clientData; /* Information about the tab window. */ + XEvent *eventPtr; /* Information about event. */ +{ + Tab *tabPtr = clientData; + + if ((tabPtr == NULL) || (tabPtr->tkwin == NULL) || + (tabPtr->container == NULL)) { + return; + } + switch (eventPtr->type) { + case Expose: + if (eventPtr->xexpose.count == 0) { + EventuallyRedrawTearoff(tabPtr); + } + break; + + case ConfigureNotify: + EventuallyRedrawTearoff(tabPtr); + break; + + case DestroyNotify: + if (tabPtr->flags & TAB_REDRAW) { + tabPtr->flags &= ~TAB_REDRAW; + Tcl_CancelIdleCall(DisplayTearoff, clientData); + } + Tk_DestroyWindow(tabPtr->container); + tabPtr->container = NULL; + break; + + } +} + +/* + * ---------------------------------------------------------------------------- + * + * GetReqWidth -- + * + * Returns the width requested by the embedded tab window and + * any requested padding around it. This represents the requested + * width of the page. + * + * Results: + * Returns the requested width of the page. + * + * ---------------------------------------------------------------------------- + */ +static int +GetReqWidth(tabPtr) + Tab *tabPtr; +{ + int width; + + if (tabPtr->reqWidth > 0) { + width = tabPtr->reqWidth; + } else { + width = Tk_ReqWidth(tabPtr->tkwin); + } + width += PADDING(tabPtr->padX) + + 2 * Tk_Changes(tabPtr->tkwin)->border_width; + if (width < 1) { + width = 1; + } + return width; +} + +/* + * ---------------------------------------------------------------------------- + * + * GetReqHeight -- + * + * Returns the height requested by the window and padding around + * the window. This represents the requested height of the page. + * + * Results: + * Returns the requested height of the page. + * + * ---------------------------------------------------------------------------- + */ +static int +GetReqHeight(tabPtr) + Tab *tabPtr; +{ + int height; + + if (tabPtr->reqHeight > 0) { + height = tabPtr->reqHeight; + } else { + height = Tk_ReqHeight(tabPtr->tkwin); + } + height += PADDING(tabPtr->padY) + + 2 * Tk_Changes(tabPtr->tkwin)->border_width; + if (height < 1) { + height = 1; + } + return height; +} + +/* + * ---------------------------------------------------------------------------- + * + * TranslateAnchor -- + * + * Translate the coordinates of a given bounding box based upon the + * anchor specified. The anchor indicates where the given xy position + * is in relation to the bounding box. + * + * nw --- n --- ne + * | | x,y ---+ + * w center e | | + * | | +-----+ + * sw --- s --- se + * + * Results: + * The translated coordinates of the bounding box are returned. + * + * ---------------------------------------------------------------------------- + */ +static void +TranslateAnchor(dx, dy, anchor, xPtr, yPtr) + int dx, dy; /* Difference between outer and inner regions + */ + Tk_Anchor anchor; /* Direction of the anchor */ + int *xPtr, *yPtr; +{ + int x, y; + + x = y = 0; + switch (anchor) { + case TK_ANCHOR_NW: /* Upper left corner */ + break; + case TK_ANCHOR_W: /* Left center */ + y = (dy / 2); + break; + case TK_ANCHOR_SW: /* Lower left corner */ + y = dy; + break; + case TK_ANCHOR_N: /* Top center */ + x = (dx / 2); + break; + case TK_ANCHOR_CENTER: /* Centered */ + x = (dx / 2); + y = (dy / 2); + break; + case TK_ANCHOR_S: /* Bottom center */ + x = (dx / 2); + y = dy; + break; + case TK_ANCHOR_NE: /* Upper right corner */ + x = dx; + break; + case TK_ANCHOR_E: /* Right center */ + x = dx; + y = (dy / 2); + break; + case TK_ANCHOR_SE: /* Lower right corner */ + x = dx; + y = dy; + break; + } + *xPtr = (*xPtr) + x; + *yPtr = (*yPtr) + y; +} + + +static void +GetWindowRectangle(tabPtr, parent, tearoff, rectPtr) + Tab *tabPtr; + Tk_Window parent; + int tearoff; + XRectangle *rectPtr; +{ + int pad; + Notebook *nbPtr; + int cavityWidth, cavityHeight; + int width, height; + int dx, dy; + int x, y; + + nbPtr = tabPtr->nbPtr; + pad = nbPtr->inset + nbPtr->inset2; + + if (!tearoff) { + switch (nbPtr->side) { + case SIDE_RIGHT: + case SIDE_BOTTOM: + x = nbPtr->inset + nbPtr->inset2; + y = nbPtr->inset + nbPtr->inset2; + break; + + case SIDE_LEFT: + x = nbPtr->pageTop; + y = nbPtr->inset + nbPtr->inset2; + break; + + case SIDE_TOP: + x = nbPtr->inset + nbPtr->inset2; + y = nbPtr->pageTop; + break; + } + + if (nbPtr->side & SIDE_VERTICAL) { + cavityWidth = Tk_Width(nbPtr->tkwin) - (nbPtr->pageTop + pad); + cavityHeight = Tk_Height(nbPtr->tkwin) - (2 * pad); + } else { + cavityWidth = Tk_Width(nbPtr->tkwin) - (2 * pad); + cavityHeight = Tk_Height(nbPtr->tkwin) - (nbPtr->pageTop + pad); + } + + } else { + x = nbPtr->inset + nbPtr->inset2; +#define TEAR_OFF_TAB_SIZE 5 + y = nbPtr->inset + nbPtr->inset2 + nbPtr->yPad + nbPtr->outerPad + + TEAR_OFF_TAB_SIZE; + cavityWidth = Tk_Width(parent) - (2 * pad); + cavityHeight = Tk_Height(parent) - (y + pad); + } + cavityWidth -= PADDING(tabPtr->padX); + cavityHeight -= PADDING(tabPtr->padY); + if (cavityWidth < 1) { + cavityWidth = 1; + } + if (cavityHeight < 1) { + cavityHeight = 1; + } + width = GetReqWidth(tabPtr); + height = GetReqHeight(tabPtr); + + /* + * Resize the embedded window is of the following is true: + * + * 1) It's been torn off. + * 2) The -fill option (horizontal or vertical) is set. + * 3) the window is bigger than the cavity. + */ + if ((tearoff) || (cavityWidth < width) || (tabPtr->fill & FILL_X)) { + width = cavityWidth; + } + if ((tearoff) || (cavityHeight < height) || (tabPtr->fill & FILL_Y)) { + height = cavityHeight; + } + dx = (cavityWidth - width); + dy = (cavityHeight - height); + if ((dx > 0) || (dy > 0)) { + TranslateAnchor(dx, dy, tabPtr->anchor, &x, &y); + } + /* Remember that X11 windows must be at least 1 pixel. */ + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + rectPtr->x = (short)(x + tabPtr->padLeft); + rectPtr->y = (short)(y + tabPtr->padTop); + rectPtr->width = (short)width; + rectPtr->height = (short)height; +} + +static void +ArrangeWindow(tkwin, rectPtr, force) + Tk_Window tkwin; + XRectangle *rectPtr; + int force; +{ + if ((force) || + (rectPtr->x != Tk_X(tkwin)) || + (rectPtr->y != Tk_Y(tkwin)) || + (rectPtr->width != Tk_Width(tkwin)) || + (rectPtr->height != Tk_Height(tkwin))) { + Tk_MoveResizeWindow(tkwin, rectPtr->x, rectPtr->y, + rectPtr->width, rectPtr->height); + } + if (!Tk_IsMapped(tkwin)) { + Tk_MapWindow(tkwin); + } +} + + +/*ARGSUSED*/ +static void +GetTags(table, object, list) + Blt_BindTable table; + ClientData object; + Blt_List list; +{ + Tab *tabPtr = (Tab *)object; + Notebook *nbPtr; + + nbPtr = (Notebook *)table->clientData; + if (tabPtr->name == NULL) { + Blt_ListAppend(list, MakeTag(nbPtr, "Perforation"), 0); + } else { + Blt_ListAppend(list, MakeTag(nbPtr, tabPtr->name), 0); + if (tabPtr->tags != NULL) { + int nNames; + char **names; + register char **p; + + /* + * This is a space/time trade-off in favor of space. The tags + * are stored as character strings in a hash table. That way, + * tabs can share the strings. It's likely that they will. The + * down side is that the same string is split over and over again. + */ + if (Tcl_SplitList((Tcl_Interp *)NULL, tabPtr->tags, &nNames, + &names) == TCL_OK) { + for (p = names; *p != NULL; p++) { + Blt_ListAppend(list, MakeTag(nbPtr, *p), 0); + } + Blt_Free(names); + } + } + } +} + +/* + * -------------------------------------------------------------- + * + * NotebookEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various + * events on notebook widgets. + * + * Results: + * None. + * + * Side Effects: + * When the window gets deleted, internal structures get + * cleaned up. When it gets exposed, it is redisplayed. + * + * -------------------------------------------------------------- + */ +static void +NotebookEventProc(clientData, eventPtr) + ClientData clientData; /* Information about window. */ + XEvent *eventPtr; /* Information about event. */ +{ + Notebook *nbPtr = clientData; + + switch (eventPtr->type) { + case Expose: + if (eventPtr->xexpose.count == 0) { + EventuallyRedraw(nbPtr); + } + break; + + case ConfigureNotify: + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + break; + + case FocusIn: + case FocusOut: + if (eventPtr->xfocus.detail != NotifyInferior) { + if (eventPtr->type == FocusIn) { + nbPtr->flags |= TNB_FOCUS; + } else { + nbPtr->flags &= ~TNB_FOCUS; + } + EventuallyRedraw(nbPtr); + } + break; + + case DestroyNotify: + if (nbPtr->tkwin != NULL) { + nbPtr->tkwin = NULL; + Tcl_DeleteCommandFromToken(nbPtr->interp, nbPtr->cmdToken); + } + if (nbPtr->flags & TNB_REDRAW) { + Tcl_CancelIdleCall(DisplayNotebook, nbPtr); + } + Tcl_EventuallyFree(nbPtr, DestroyNotebook); + break; + + } +} + +/* + * ---------------------------------------------------------------------- + * + * DestroyNotebook -- + * + * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release + * to clean up the internal structure of the widget at a safe + * time (when no-one is using it anymore). + * + * Results: + * None. + * + * Side Effects: + * Everything associated with the widget is freed up. + * + * ---------------------------------------------------------------------- + */ +static void +DestroyNotebook(dataPtr) + DestroyData dataPtr; /* Pointer to the widget record. */ +{ + Notebook *nbPtr = (Notebook *)dataPtr; + Tab *tabPtr; + Blt_ChainLink *linkPtr; + + if (nbPtr->highlightGC != NULL) { + Tk_FreeGC(nbPtr->display, nbPtr->highlightGC); + } + if (nbPtr->tile != NULL) { + Blt_FreeTile(nbPtr->tile); + } + if (nbPtr->defTabStyle.activeGC != NULL) { + Blt_FreePrivateGC(nbPtr->display, nbPtr->defTabStyle.activeGC); + } + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->linkPtr = NULL; + DestroyTab(nbPtr, tabPtr); + } + Blt_ChainDestroy(nbPtr->chainPtr); + Blt_DestroyBindingTable(nbPtr->bindTable); + Blt_DeleteHashTable(&(nbPtr->tabTable)); + Blt_DeleteHashTable(&(nbPtr->tagTable)); + Tk_FreeOptions(configSpecs, (char *)nbPtr, nbPtr->display, 0); + Blt_Free(nbPtr); +} + +/* + * ---------------------------------------------------------------------- + * + * CreateNotebook -- + * + * ---------------------------------------------------------------------- + */ +static Notebook * +CreateNotebook(interp, tkwin) + Tcl_Interp *interp; + Tk_Window tkwin; +{ + Notebook *nbPtr; + + nbPtr = Blt_Calloc(1, sizeof(Notebook)); + assert(nbPtr); + + Tk_SetClass(tkwin, "Tabnotebook"); + nbPtr->tkwin = tkwin; + nbPtr->display = Tk_Display(tkwin); + nbPtr->interp = interp; + + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + nbPtr->side = SIDE_TOP; + nbPtr->borderWidth = nbPtr->highlightWidth = 2; + nbPtr->ySelectPad = SELECT_PADY; + nbPtr->xSelectPad = SELECT_PADX; + nbPtr->relief = TK_RELIEF_SUNKEN; + nbPtr->defTabStyle.relief = TK_RELIEF_RAISED; + nbPtr->defTabStyle.borderWidth = 1; + nbPtr->defTabStyle.constWidth = TRUE; + nbPtr->defTabStyle.textSide = SIDE_LEFT; + nbPtr->scrollUnits = 2; + nbPtr->corner = CORNER_OFFSET; + nbPtr->gap = GAP; + nbPtr->outerPad = OUTER_PAD; + nbPtr->slant = SLANT_NONE; + nbPtr->overlap = 0; + nbPtr->tearoff = TRUE; + nbPtr->bindTable = Blt_CreateBindingTable(interp, tkwin, nbPtr, PickTab, + GetTags); + nbPtr->chainPtr = Blt_ChainCreate(); + Blt_InitHashTable(&(nbPtr->tabTable), TCL_STRING_KEYS); + Blt_InitHashTable(&(nbPtr->imageTable), BLT_STRING_KEYS); + Blt_InitHashTable(&(nbPtr->tagTable), BLT_STRING_KEYS); +#if (TK_MAJOR_VERSION > 4) + Blt_SetWindowInstanceData(tkwin, nbPtr); +#endif + return nbPtr; +} + +/* + * ---------------------------------------------------------------------- + * + * ConfigureNotebook -- + * + * This procedure is called to process an argv/argc list, plus + * the Tk option database, in order to configure (or reconfigure) + * the widget. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is + * returned, then interp->result contains an error message. + * + * Side Effects: + * Configuration information, such as text string, colors, font, + * etc. get set for nbPtr; old resources get freed, if there + * were any. The widget is redisplayed. + * + * ---------------------------------------------------------------------- + */ +static int +ConfigureNotebook(interp, nbPtr, argc, argv, flags) + Tcl_Interp *interp; /* Interpreter to report errors. */ + Notebook *nbPtr; /* Information about widget; may or + * may not already have values for + * some fields. */ + int argc; + char **argv; + int flags; +{ + XGCValues gcValues; + unsigned long gcMask; + GC newGC; + + lastNotebookInstance = nbPtr; + if (Tk_ConfigureWidget(interp, nbPtr->tkwin, configSpecs, argc, argv, + (char *)nbPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (Blt_ConfigModified(configSpecs, "-width", "-height", "-side", "-gap", + "-slant", (char *)NULL)) { + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + } + if ((nbPtr->reqHeight > 0) && (nbPtr->reqWidth > 0)) { + Tk_GeometryRequest(nbPtr->tkwin, nbPtr->reqWidth, + nbPtr->reqHeight); + } + /* + * GC for focus highlight. + */ + gcMask = GCForeground; + gcValues.foreground = nbPtr->highlightColor->pixel; + newGC = Tk_GetGC(nbPtr->tkwin, gcMask, &gcValues); + if (nbPtr->highlightGC != NULL) { + Tk_FreeGC(nbPtr->display, nbPtr->highlightGC); + } + nbPtr->highlightGC = newGC; + + /* + * GC for tiled background. + */ + if (nbPtr->tile != NULL) { + Blt_SetTileChangedProc(nbPtr->tile, TileChangedProc, nbPtr); + } + /* + * GC for active line. + */ + gcMask = GCForeground | GCLineWidth | GCLineStyle | GCCapStyle; + gcValues.foreground = nbPtr->defTabStyle.activeFgColor->pixel; + gcValues.line_width = 0; + gcValues.cap_style = CapProjecting; + gcValues.line_style = (LineIsDashed(nbPtr->defTabStyle.dashes)) + ? LineOnOffDash : LineSolid; + + newGC = Blt_GetPrivateGC(nbPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(nbPtr->defTabStyle.dashes)) { + nbPtr->defTabStyle.dashes.offset = 2; + Blt_SetDashes(nbPtr->display, newGC, &(nbPtr->defTabStyle.dashes)); + } + if (nbPtr->defTabStyle.activeGC != NULL) { + Blt_FreePrivateGC(nbPtr->display, + nbPtr->defTabStyle.activeGC); + } + nbPtr->defTabStyle.activeGC = newGC; + + nbPtr->defTabStyle.rotate = FMOD(nbPtr->defTabStyle.rotate, 360.0); + if (nbPtr->defTabStyle.rotate < 0.0) { + nbPtr->defTabStyle.rotate += 360.0; + } + nbPtr->inset = nbPtr->highlightWidth + nbPtr->borderWidth + nbPtr->outerPad; + if (Blt_ConfigModified(configSpecs, "-font", "-*foreground", "-rotate", + "-*background", "-side", (char *)NULL)) { + Blt_ChainLink *linkPtr; + Tab *tabPtr; + + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); + linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + ConfigureTab(nbPtr, tabPtr); + } + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + } + nbPtr->inset2 = nbPtr->defTabStyle.borderWidth + nbPtr->corner; + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +/* + * -------------------------------------------------------------- + * + * Notebook operations + * + * -------------------------------------------------------------- + */ +/* + *---------------------------------------------------------------------- + * + * ActivateOp -- + * + * Selects the tab to appear active. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ActivateOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + + if (argv[2][0] == '\0') { + tabPtr = NULL; + } else if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if ((tabPtr != NULL) && (tabPtr->state == STATE_DISABLED)) { + tabPtr = NULL; + } + if (tabPtr != nbPtr->activePtr) { + nbPtr->activePtr = tabPtr; + EventuallyRedraw(nbPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * BindOp -- + * + * .t bind index sequence command + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BindOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + if (argc == 2) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + char *tagName; + + for (hPtr = Blt_FirstHashEntry(&(nbPtr->tagTable), &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + tagName = Blt_GetHashKey(&(nbPtr->tagTable), hPtr); + Tcl_AppendElement(interp, tagName); + } + return TCL_OK; + } + return Blt_ConfigureBindings(interp, nbPtr->bindTable, + MakeTag(nbPtr, argv[2]), argc - 3, argv + 3); +} + +/* + *---------------------------------------------------------------------- + * + * CgetOp -- + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +CgetOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + lastNotebookInstance = nbPtr; + return Tk_ConfigureValue(interp, nbPtr->tkwin, configSpecs, + (char *)nbPtr, argv[2], 0); +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureOp -- + * + * This procedure is called to process an argv/argc list, plus + * the Tk option database, in order to configure (or reconfigure) + * the widget. + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + * Side Effects: + * Configuration information, such as text string, colors, font, + * etc. get set for nbPtr; old resources get freed, if there + * were any. The widget is redisplayed. + * + *---------------------------------------------------------------------- + */ +static int +ConfigureOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + lastNotebookInstance = nbPtr; + if (argc == 2) { + return Tk_ConfigureInfo(interp, nbPtr->tkwin, configSpecs, + (char *)nbPtr, (char *)NULL, 0); + } else if (argc == 3) { + return Tk_ConfigureInfo(interp, nbPtr->tkwin, configSpecs, + (char *)nbPtr, argv[2], 0); + } + if (ConfigureNotebook(interp, nbPtr, argc - 2, argv + 2, + TK_CONFIG_ARGV_ONLY) != TCL_OK) { + return TCL_ERROR; + } + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * DeleteOp -- + * + * Deletes tab from the set. Deletes either a range of + * tabs or a single node. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DeleteOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *firstPtr, *lastPtr; + + lastPtr = NULL; + if (GetTab(nbPtr, argv[2], &firstPtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; + } + if ((argc == 4) && + (GetTab(nbPtr, argv[3], &lastPtr, INVALID_FAIL) != TCL_OK)) { + return TCL_ERROR; + } + if (lastPtr == NULL) { + DestroyTab(nbPtr, firstPtr); + } else { + Tab *tabPtr; + Blt_ChainLink *linkPtr, *nextLinkPtr; + + tabPtr = NULL; /* Suppress compiler warning. */ + + /* Make sure that the first tab is before the last. */ + for (linkPtr = firstPtr->linkPtr; linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + if (tabPtr == lastPtr) { + break; + } + } + if (tabPtr != lastPtr) { + return TCL_OK; + } + linkPtr = firstPtr->linkPtr; + while (linkPtr != NULL) { + nextLinkPtr = Blt_ChainNextLink(linkPtr); + tabPtr = Blt_ChainGetValue(linkPtr); + DestroyTab(nbPtr, tabPtr); + linkPtr = nextLinkPtr; + if (tabPtr == lastPtr) { + break; + } + } + } + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * FocusOp -- + * + * Selects the tab to get focus. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +FocusOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; + } + if (tabPtr != NULL) { + nbPtr->focusPtr = tabPtr; + Blt_SetFocusItem(nbPtr->bindTable, nbPtr->focusPtr); + EventuallyRedraw(nbPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * IndexOp -- + * + * Converts a string representing a tab index. + * + * Results: + * A standard Tcl result. Interp->result will contain the + * identifier of each index found. If an index could not be found, + * then the serial identifier will be the empty string. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +IndexOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if (tabPtr != NULL) { + Tcl_SetResult(interp, Blt_Itoa(TabIndex(nbPtr, tabPtr)), + TCL_VOLATILE); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * IdOp -- + * + * Converts a tab index into the tab identifier. + * + * Results: + * A standard Tcl result. Interp->result will contain the + * identifier of each index found. If an index could not be found, + * then the serial identifier will be the empty string. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +IdOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if (tabPtr == NULL) { + Tcl_SetResult(interp, "", TCL_STATIC); + } else { + Tcl_SetResult(interp, tabPtr->name, TCL_VOLATILE); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * InsertOp -- + * + * Add new entries into a tab set. + * + * .t insert end label option-value label option-value... + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InsertOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + Blt_ChainLink *linkPtr, *beforeLinkPtr; + char c; + + c = argv[2][0]; + if ((c == 'e') && (strcmp(argv[2], "end") == 0)) { + beforeLinkPtr = NULL; + } else if (isdigit(UCHAR(c))) { + int position; + + if (Tcl_GetInt(interp, argv[2], &position) != TCL_OK) { + return TCL_ERROR; + } + if (position < 0) { + beforeLinkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); + } else if (position > Blt_ChainGetLength(nbPtr->chainPtr)) { + beforeLinkPtr = NULL; + } else { + beforeLinkPtr = Blt_ChainGetNthLink(nbPtr->chainPtr, position); + } + } else { + Tab *beforePtr; + + if (GetTab(nbPtr, argv[2], &beforePtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; + } + beforeLinkPtr = beforePtr->linkPtr; + } + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + tabPtr = CreateTab(nbPtr); + if (tabPtr == NULL) { + return TCL_ERROR; + } + lastNotebookInstance = nbPtr; + if (Blt_ConfigureWidgetComponent(interp, nbPtr->tkwin, tabPtr->name, + "Tab", tabConfigSpecs, argc - 3, argv + 3, (char *)tabPtr, 0) + != TCL_OK) { + DestroyTab(nbPtr, tabPtr); + return TCL_ERROR; + } + if (ConfigureTab(nbPtr, tabPtr) != TCL_OK) { + DestroyTab(nbPtr, tabPtr); + return TCL_ERROR; + } + linkPtr = Blt_ChainNewLink(); + if (beforeLinkPtr == NULL) { + Blt_ChainAppendLink(nbPtr->chainPtr, linkPtr); + } else { + Blt_ChainLinkBefore(nbPtr->chainPtr, linkPtr, beforeLinkPtr); + } + tabPtr->linkPtr = linkPtr; + Blt_ChainSetValue(linkPtr, tabPtr); + Tcl_SetResult(interp, tabPtr->name, TCL_VOLATILE); + return TCL_OK; + +} + +/* + * Preprocess the command string for percent substitution. + */ +static void +PercentSubst(nbPtr, tabPtr, command, resultPtr) + Notebook *nbPtr; + Tab *tabPtr; + char *command; + Tcl_DString *resultPtr; +{ + register char *last, *p; + /* + * Get the full path name of the node, in case we need to + * substitute for it. + */ + Tcl_DStringInit(resultPtr); + for (last = p = command; *p != '\0'; p++) { + if (*p == '%') { + char *string; + char buf[3]; + + if (p > last) { + *p = '\0'; + Tcl_DStringAppend(resultPtr, last, -1); + *p = '%'; + } + switch (*(p + 1)) { + case '%': /* Percent sign */ + string = "%"; + break; + case 'W': /* Widget name */ + string = Tk_PathName(nbPtr->tkwin); + break; + case 'i': /* Tab Index */ + string = Blt_Itoa(TabIndex(nbPtr, tabPtr)); + break; + case 'n': /* Tab name */ + string = tabPtr->name; + break; + default: + if (*(p + 1) == '\0') { + p--; + } + buf[0] = *p, buf[1] = *(p + 1), buf[2] = '\0'; + string = buf; + break; + } + Tcl_DStringAppend(resultPtr, string, -1); + p++; + last = p + 1; + } + } + if (p > last) { + *p = '\0'; + Tcl_DStringAppend(resultPtr, last, -1); + } +} + +/* + *---------------------------------------------------------------------- + * + * InvokeOp -- + * + * This procedure is called to invoke a selection command. + * + * .h invoke index + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + * Side Effects: + * Configuration information, such as text string, colors, font, + * etc. get set; old resources get freed, if there were any. + * The widget is redisplayed if needed. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InvokeOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; /* Not used. */ + int argc; + char **argv; +{ + Tab *tabPtr; + char *command; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if ((tabPtr == NULL) || (tabPtr->state == STATE_DISABLED)) { + return TCL_OK; + } + Tcl_Preserve(tabPtr); + command = GETATTR(tabPtr, command); + if (command != NULL) { + Tcl_DString dString; + int result; + + PercentSubst(nbPtr, tabPtr, command, &dString); + result = Tcl_GlobalEval(nbPtr->interp, + Tcl_DStringValue(&dString)); + Tcl_DStringFree(&dString); + if (result != TCL_OK) { + return TCL_ERROR; + } + } + Tcl_Release(tabPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * MoveOp -- + * + * Moves a tab to a new location. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MoveOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr, *linkPtr; + int before; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if ((tabPtr == NULL) || (tabPtr->state == STATE_DISABLED)) { + return TCL_OK; + } + if ((argv[3][0] == 'b') && (strcmp(argv[3], "before") == 0)) { + before = 1; + } else if ((argv[3][0] == 'a') && (strcmp(argv[3], "after") == 0)) { + before = 0; + } else { + Tcl_AppendResult(interp, "bad key word \"", argv[3], + "\": should be \"after\" or \"before\"", (char *)NULL); + return TCL_ERROR; + } + if (GetTab(nbPtr, argv[4], &linkPtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; + } + if (tabPtr == linkPtr) { + return TCL_OK; + } + Blt_ChainUnlinkLink(nbPtr->chainPtr, tabPtr->linkPtr); + if (before) { + Blt_ChainLinkBefore(nbPtr->chainPtr, tabPtr->linkPtr, + linkPtr->linkPtr); + } else { + Blt_ChainLinkAfter(nbPtr->chainPtr, tabPtr->linkPtr, + linkPtr->linkPtr); + } + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +/*ARGSUSED*/ +static int +NearestOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + int x, y; /* Screen coordinates of the test point. */ + Tab *tabPtr; + + if ((Tk_GetPixels(interp, nbPtr->tkwin, argv[2], &x) != TCL_OK) || + (Tk_GetPixels(interp, nbPtr->tkwin, argv[3], &y) != TCL_OK)) { + return TCL_ERROR; + } + if (nbPtr->nVisible > 0) { + tabPtr = (Tab *)PickTab(nbPtr, x, y); + if (tabPtr != NULL) { + Tcl_SetResult(interp, tabPtr->name, TCL_VOLATILE); + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SelectOp -- + * + * This procedure is called to selecta tab. + * + * .h select index + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + * Side Effects: + * Configuration information, such as text string, colors, font, + * etc. get set; old resources get freed, if there were any. + * The widget is redisplayed if needed. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; /* Not used. */ + int argc; + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if ((tabPtr == NULL) || (tabPtr->state == STATE_DISABLED)) { + return TCL_OK; + } + if ((nbPtr->selectPtr != NULL) && (nbPtr->selectPtr != tabPtr) && + (nbPtr->selectPtr->tkwin != NULL)) { + if (nbPtr->selectPtr->container == NULL) { + if (Tk_IsMapped(nbPtr->selectPtr->tkwin)) { + Tk_UnmapWindow(nbPtr->selectPtr->tkwin); + } + } else { + /* Redraw now unselected container. */ + EventuallyRedrawTearoff(nbPtr->selectPtr); + } + } + nbPtr->selectPtr = tabPtr; + if ((nbPtr->nTiers > 1) && (tabPtr->tier != nbPtr->startPtr->tier)) { + RenumberTiers(nbPtr, tabPtr); + Blt_PickCurrentItem(nbPtr->bindTable); + } + nbPtr->flags |= (TNB_SCROLL); + if (tabPtr->container != NULL) { + EventuallyRedrawTearoff(tabPtr); + } + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +static int +ViewOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + int width; + + width = VPORTWIDTH(nbPtr); + if (argc == 2) { + double fract; + + /* + * Note: we are bounding the fractions between 0.0 and 1.0 to + * support the "canvas"-style of scrolling. + */ + + fract = (double)nbPtr->scrollOffset / nbPtr->worldWidth; + Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); + fract = (double)(nbPtr->scrollOffset + width) / nbPtr->worldWidth; + Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); + return TCL_OK; + } + if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(nbPtr->scrollOffset), + nbPtr->worldWidth, width, nbPtr->scrollUnits, + BLT_SCROLL_MODE_CANVAS) != TCL_OK) { + return TCL_ERROR; + } + nbPtr->flags |= TNB_SCROLL; + EventuallyRedraw(nbPtr); + return TCL_OK; +} + + +static void +AdoptWindow(clientData) + ClientData clientData; +{ + Tab *tabPtr = clientData; + int x, y; + Notebook *nbPtr = tabPtr->nbPtr; + + x = nbPtr->inset + nbPtr->inset2 + tabPtr->padLeft; +#define TEAR_OFF_TAB_SIZE 5 + y = nbPtr->inset + nbPtr->inset2 + nbPtr->yPad + + nbPtr->outerPad + TEAR_OFF_TAB_SIZE + tabPtr->padTop; + Blt_RelinkWindow(tabPtr->tkwin, tabPtr->container, x, y); + Tk_MapWindow(tabPtr->tkwin); +} + +static void +DestroyTearoff(dataPtr) + DestroyData dataPtr; +{ + Tab *tabPtr = (Tab *)dataPtr; + + if (tabPtr->container != NULL) { + Notebook *nbPtr; + Tk_Window tkwin; + nbPtr = tabPtr->nbPtr; + + tkwin = tabPtr->container; + if (tabPtr->flags & TAB_REDRAW) { + Tcl_CancelIdleCall(DisplayTearoff, tabPtr); + } + Tk_DeleteEventHandler(tkwin, StructureNotifyMask, TearoffEventProc, + tabPtr); + if (tabPtr->tkwin != NULL) { + XRectangle rect; + + GetWindowRectangle(tabPtr, nbPtr->tkwin, FALSE, &rect); + Blt_RelinkWindow(tabPtr->tkwin, nbPtr->tkwin, rect.x, rect.y); + if (tabPtr == nbPtr->selectPtr) { + ArrangeWindow(tabPtr->tkwin, &rect, TRUE); + } else { + Tk_UnmapWindow(tabPtr->tkwin); + } + } + Tk_DestroyWindow(tkwin); + tabPtr->container = NULL; + } +} + +static int +CreateTearoff(nbPtr, name, tabPtr) + Notebook *nbPtr; + char *name; + Tab *tabPtr; +{ + Tk_Window tkwin; + int width, height; + + tkwin = Tk_CreateWindowFromPath(nbPtr->interp, nbPtr->tkwin, name, + (char *)NULL); + if (tkwin == NULL) { + return TCL_ERROR; + } + tabPtr->container = tkwin; + if (Tk_WindowId(tkwin) == None) { + Tk_MakeWindowExist(tkwin); + } + Tk_SetClass(tkwin, "Tearoff"); + Tk_CreateEventHandler(tkwin, (ExposureMask | StructureNotifyMask), + TearoffEventProc, tabPtr); + if (Tk_WindowId(tabPtr->tkwin) == None) { + Tk_MakeWindowExist(tabPtr->tkwin); + } + width = Tk_Width(tabPtr->tkwin); + if (width < 2) { + width = (tabPtr->reqWidth > 0) + ? tabPtr->reqWidth : Tk_ReqWidth(tabPtr->tkwin); + } + width += PADDING(tabPtr->padX) + 2 * + Tk_Changes(tabPtr->tkwin)->border_width; + width += 2 * (nbPtr->inset2 + nbPtr->inset); +#define TEAR_OFF_TAB_SIZE 5 + height = Tk_Height(tabPtr->tkwin); + if (height < 2) { + height = (tabPtr->reqHeight > 0) + ? tabPtr->reqHeight : Tk_ReqHeight(tabPtr->tkwin); + } + height += PADDING(tabPtr->padY) + + 2 * Tk_Changes(tabPtr->tkwin)->border_width; + height += nbPtr->inset + nbPtr->inset2 + nbPtr->yPad + + TEAR_OFF_TAB_SIZE + nbPtr->outerPad; + Tk_GeometryRequest(tkwin, width, height); + Tk_UnmapWindow(tabPtr->tkwin); + /* Tk_MoveWindow(tabPtr->tkwin, 0, 0); */ + Tcl_SetResult(nbPtr->interp, Tk_PathName(tkwin), TCL_VOLATILE); +#ifdef WIN32 + AdoptWindow(tabPtr); +#else + Tcl_DoWhenIdle(AdoptWindow, tabPtr); +#endif + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TabCgetOp -- + * + * .h tab cget index option + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TabCgetOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[3], &tabPtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; + } + lastNotebookInstance = nbPtr; + return Tk_ConfigureValue(interp, nbPtr->tkwin, tabConfigSpecs, + (char *)tabPtr, argv[4], 0); +} + +/* + *---------------------------------------------------------------------- + * + * TabConfigureOp -- + * + * This procedure is called to process a list of configuration + * options database, in order to reconfigure the options for + * one or more tabs in the widget. + * + * .h tab configure index ?index...? ?option value?... + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + * Side Effects: + * Configuration information, such as text string, colors, font, + * etc. get set; old resources get freed, if there were any. + * The widget is redisplayed if needed. + * + *---------------------------------------------------------------------- + */ +static int +TabConfigureOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + int nTabs, nOpts, result; + char **options; + register int i; + Tab *tabPtr; + + /* Figure out where the option value pairs begin */ + argc -= 3; + argv += 3; + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-') { + break; + } + if (GetTab(nbPtr, argv[i], &tabPtr, INVALID_FAIL) != TCL_OK) { + return TCL_ERROR; /* Can't find node. */ + } + } + nTabs = i; /* Number of tab indices specified */ + nOpts = argc - i; /* Number of options specified */ + options = argv + i; /* Start of options in argv */ + + for (i = 0; i < nTabs; i++) { + GetTab(nbPtr, argv[i], &tabPtr, INVALID_FAIL); + if (argc == 1) { + return Tk_ConfigureInfo(interp, nbPtr->tkwin, tabConfigSpecs, + (char *)tabPtr, (char *)NULL, 0); + } else if (argc == 2) { + return Tk_ConfigureInfo(interp, nbPtr->tkwin, tabConfigSpecs, + (char *)tabPtr, argv[2], 0); + } + Tcl_Preserve(tabPtr); + lastNotebookInstance = nbPtr; + result = Tk_ConfigureWidget(interp, nbPtr->tkwin, tabConfigSpecs, + nOpts, options, (char *)tabPtr, TK_CONFIG_ARGV_ONLY); + if (result == TCL_OK) { + result = ConfigureTab(nbPtr, tabPtr); + } + Tcl_Release(tabPtr); + if (result == TCL_ERROR) { + return TCL_ERROR; + } + if (tabPtr->flags & TAB_VISIBLE) { + nbPtr->flags |= (TNB_LAYOUT | TNB_SCROLL); + EventuallyRedraw(nbPtr); + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TabDockallOp -- + * + * .h tab dockall + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TabDockallOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; /* Not used. */ +{ + Tab *tabPtr; + Blt_ChainLink *linkPtr; + + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + if (tabPtr->container != NULL) { + Tcl_EventuallyFree(tabPtr, DestroyTearoff); + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TabNamesOp -- + * + * .h tab names pattern + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TabNamesOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; /* Not used. */ +{ + Tab *tabPtr; + Blt_ChainLink *linkPtr; + + if (argc == 3) { + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + Tcl_AppendElement(interp, tabPtr->name); + } + } else { + register int i; + + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + for (i = 3; i < argc; i++) { + if (Tcl_StringMatch(tabPtr->name, argv[i])) { + Tcl_AppendElement(interp, tabPtr->name); + break; + } + } + } + } + return TCL_OK; +} +/* + *---------------------------------------------------------------------- + * + * TabTearoffOp -- + * + * .h tab tearoff index ?title? + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TabTearoffOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + Tab *tabPtr; + int result; + Tk_Window tkwin; + + result = TCL_OK; + + if (GetTab(nbPtr, argv[3], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if ((tabPtr == NULL) || (tabPtr->tkwin == NULL) || + (tabPtr->state == STATE_DISABLED)) { + return TCL_OK; /* No-op */ + } + if (argc == 4) { + Tk_Window parent; + + parent = (tabPtr->container == NULL) + ? nbPtr->tkwin : tabPtr->container; + Tcl_SetResult(nbPtr->interp, Tk_PathName(parent), TCL_VOLATILE); + return TCL_OK; + } + Tcl_Preserve(tabPtr); + result = TCL_OK; + + tkwin = Tk_NameToWindow(interp, argv[4], nbPtr->tkwin); + Tcl_ResetResult(interp); + + if (tabPtr->container != NULL) { + Tcl_EventuallyFree(tabPtr, DestroyTearoff); + } + if ((tkwin != nbPtr->tkwin) && (tabPtr->container == NULL)) { + result = CreateTearoff(nbPtr, argv[4], tabPtr); + } + Tcl_Release(tabPtr); + EventuallyRedraw(nbPtr); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TabOp -- + * + * This procedure handles tab operations. + * + * Results: + * A standard Tcl result. + * + *---------------------------------------------------------------------- + */ +static Blt_OpSpec tabOps[] = +{ + {"cget", 2, (Blt_Op)TabCgetOp, 5, 5, "nameOrIndex option",}, + {"configure", 2, (Blt_Op)TabConfigureOp, 4, 0, + "nameOrIndex ?option value?...",}, + {"dockall", 1, (Blt_Op)TabDockallOp, 3, 3, "" }, + {"names", 1, (Blt_Op)TabNamesOp, 3, 0, "?pattern...?",}, + {"tearoff", 1, (Blt_Op)TabTearoffOp, 4, 5, "index ?parent?",}, +}; + +static int nTabOps = sizeof(tabOps) / sizeof(Blt_OpSpec); + +static int +TabOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + Blt_Op proc; + int result; + + proc = Blt_GetOp(interp, nTabOps, tabOps, BLT_OP_ARG2, argc, argv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (nbPtr, interp, argc, argv); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * PerforationHighlightOp -- + * + * This procedure is called to highlight the perforation. + * + * .h perforation highlight boolean + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +PerforationHighlightOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; /* Not used. */ + int argc; + char **argv; +{ + int bool; + + if (Tcl_GetBoolean(interp, argv[3], &bool) != TCL_OK) { + return TCL_ERROR; + } + if (bool) { + nbPtr->flags |= PERFORATION_ACTIVE; + } else { + nbPtr->flags &= ~PERFORATION_ACTIVE; + } + EventuallyRedraw(nbPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * PerforationInvokeOp -- + * + * This procedure is called to invoke a perforation command. + * + * .t perforation invoke + * + * Results: + * A standard Tcl result. If TCL_ERROR is returned, then + * interp->result contains an error message. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +PerforationInvokeOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; /* Not used. */ + int argc; + char **argv; +{ + + if (nbPtr->selectPtr != NULL) { + char *cmd; + + cmd = GETATTR(nbPtr->selectPtr, perfCommand); + if (cmd != NULL) { + Tcl_DString dString; + int result; + + PercentSubst(nbPtr, nbPtr->selectPtr, cmd, &dString); + Tcl_Preserve(nbPtr); + result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)); + Tcl_Release(nbPtr); + Tcl_DStringFree(&dString); + if (result != TCL_OK) { + return TCL_ERROR; + } + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * PerforationOp -- + * + * This procedure handles tab operations. + * + * Results: + * A standard Tcl result. + * + *---------------------------------------------------------------------- + */ +static Blt_OpSpec perforationOps[] = +{ + {"highlight", 1, (Blt_Op)PerforationHighlightOp, 4, 4, "boolean" }, + {"invoke", 1, (Blt_Op)PerforationInvokeOp, 3, 3, "",}, +}; + +static int nPerforationOps = sizeof(perforationOps) / sizeof(Blt_OpSpec); + +static int +PerforationOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; + char **argv; +{ + Blt_Op proc; + int result; + + proc = Blt_GetOp(interp, nPerforationOps, perforationOps, BLT_OP_ARG2, + argc, argv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (nbPtr, interp, argc, argv); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * ScanOp -- + * + * Implements the quick scan. + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ScanOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; +{ + int x, y; + char c; + unsigned int length; + int oper; + +#define SCAN_MARK 1 +#define SCAN_DRAGTO 2 + c = argv[2][0]; + length = strlen(argv[2]); + if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { + oper = SCAN_MARK; + } else if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { + oper = SCAN_DRAGTO; + } else { + Tcl_AppendResult(interp, "bad scan operation \"", argv[2], + "\": should be either \"mark\" or \"dragto\"", (char *)NULL); + return TCL_ERROR; + } + if ((Tk_GetPixels(interp, nbPtr->tkwin, argv[3], &x) != TCL_OK) || + (Tk_GetPixels(interp, nbPtr->tkwin, argv[4], &y) != TCL_OK)) { + return TCL_ERROR; + } + if (oper == SCAN_MARK) { + if (nbPtr->side & SIDE_VERTICAL) { + nbPtr->scanAnchor = y; + } else { + nbPtr->scanAnchor = x; + } + nbPtr->scanOffset = nbPtr->scrollOffset; + } else { + int offset, delta; + + if (nbPtr->side & SIDE_VERTICAL) { + delta = nbPtr->scanAnchor - y; + } else { + delta = nbPtr->scanAnchor - x; + } + offset = nbPtr->scanOffset + (10 * delta); + offset = Blt_AdjustViewport(offset, nbPtr->worldWidth, + VPORTWIDTH(nbPtr), nbPtr->scrollUnits, BLT_SCROLL_MODE_CANVAS); + nbPtr->scrollOffset = offset; + nbPtr->flags |= TNB_SCROLL; + EventuallyRedraw(nbPtr); + } + return TCL_OK; +} + +/*ARGSUSED*/ +static int +SeeOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; /* Not used. */ + int argc; + char **argv; +{ + Tab *tabPtr; + + if (GetTab(nbPtr, argv[2], &tabPtr, INVALID_OK) != TCL_OK) { + return TCL_ERROR; + } + if (tabPtr != NULL) { + int left, right, width; + + width = VPORTWIDTH(nbPtr); + left = nbPtr->scrollOffset + nbPtr->xSelectPad; + right = nbPtr->scrollOffset + width - nbPtr->xSelectPad; + + /* If the tab is partially obscured, scroll so that it's + * entirely in view. */ + if (tabPtr->worldX < left) { + nbPtr->scrollOffset = tabPtr->worldX; + if (TabIndex(nbPtr, tabPtr) > 0) { + nbPtr->scrollOffset -= TAB_SCROLL_OFFSET; + } + } else if ((tabPtr->worldX + tabPtr->worldWidth) >= right) { + Blt_ChainLink *linkPtr; + + nbPtr->scrollOffset = tabPtr->worldX + tabPtr->worldWidth - + (width - 2 * nbPtr->xSelectPad); + linkPtr = Blt_ChainNextLink(tabPtr->linkPtr); + if (linkPtr != NULL) { + Tab *nextPtr; + + nextPtr = Blt_ChainGetValue(linkPtr); + if (nextPtr->tier == tabPtr->tier) { + nbPtr->scrollOffset += TAB_SCROLL_OFFSET; + } + } + } + nbPtr->flags |= TNB_SCROLL; + EventuallyRedraw(nbPtr); + } + return TCL_OK; +} + +/*ARGSUSED*/ +static int +SizeOp(nbPtr, interp, argc, argv) + Notebook *nbPtr; + Tcl_Interp *interp; + int argc; /* Not used. */ + char **argv; /* Not used. */ +{ + Tcl_SetResult(interp, Blt_Itoa(Blt_ChainGetLength(nbPtr->chainPtr)), + TCL_VOLATILE); + return TCL_OK; +} + + +static int +CountTabs(nbPtr) + Notebook *nbPtr; +{ + int count; + int width, height; + Blt_ChainLink *linkPtr; + register Tab *tabPtr; + register int pageWidth, pageHeight; + int labelWidth, labelHeight; + int tabWidth, tabHeight; + + pageWidth = pageHeight = 0; + count = 0; + + labelWidth = labelHeight = 0; + + /* + * Pass 1: Figure out the maximum area needed for a label and a + * page. Both the label and page dimensions are adjusted + * for orientation. In addition, reset the visibility + * flags and reorder the tabs. + */ + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + + /* Reset visibility flag and order of tabs. */ + + tabPtr->flags &= ~TAB_VISIBLE; + count++; + + if (tabPtr->tkwin != NULL) { + width = GetReqWidth(tabPtr); + if (pageWidth < width) { + pageWidth = width; + } + height = GetReqHeight(tabPtr); + if (pageHeight < height) { + pageHeight = height; + } + } + if (labelWidth < tabPtr->labelWidth) { + labelWidth = tabPtr->labelWidth; + } + if (labelHeight < tabPtr->labelHeight) { + labelHeight = tabPtr->labelHeight; + } + } + + nbPtr->overlap = 0; + + /* + * Pass 2: Set the individual sizes of each tab. This is different + * for constant and variable width tabs. Add the extra space + * needed for slanted tabs, now that we know maximum tab + * height. + */ + if (nbPtr->defTabStyle.constWidth) { + int slant; + + tabWidth = 2 * nbPtr->inset2; + tabHeight = nbPtr->inset2 /* + 4 */; + + if (nbPtr->side & SIDE_VERTICAL) { + tabWidth += labelHeight; + tabHeight += labelWidth; + slant = labelWidth; + } else { + tabWidth += labelWidth; + tabHeight += labelHeight; + slant = labelHeight; + } + if (nbPtr->slant & SLANT_LEFT) { + tabWidth += slant; + nbPtr->overlap += tabHeight / 2; + } + if (nbPtr->slant & SLANT_RIGHT) { + tabWidth += slant; + nbPtr->overlap += tabHeight / 2; + } + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->worldWidth = tabWidth; + tabPtr->worldHeight = tabHeight; + } + } else { + int slant; + + tabWidth = tabHeight = 0; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + + width = 2 * nbPtr->inset2; + height = nbPtr->inset2 /* + 4 */; + if (nbPtr->side & SIDE_VERTICAL) { + width += tabPtr->labelHeight; + height += labelWidth; + slant = labelWidth; + } else { + width += tabPtr->labelWidth; + height += labelHeight; + slant = labelHeight; + } + width += (nbPtr->slant & SLANT_LEFT) ? slant : nbPtr->corner; + width += (nbPtr->slant & SLANT_RIGHT) ? slant : nbPtr->corner; + + tabPtr->worldWidth = width; /* + 2 * (nbPtr->corner + nbPtr->xSelectPad) */ ; + tabPtr->worldHeight = height; + + if (tabWidth < width) { + tabWidth = width; + } + if (tabHeight < height) { + tabHeight = height; + } + } + if (nbPtr->slant & SLANT_LEFT) { + nbPtr->overlap += tabHeight / 2; + } + if (nbPtr->slant & SLANT_RIGHT) { + nbPtr->overlap += tabHeight / 2; + } + } + + nbPtr->tabWidth = tabWidth; + nbPtr->tabHeight = tabHeight; + + /* + * Let the user override any page dimension. + */ + nbPtr->pageWidth = pageWidth; + nbPtr->pageHeight = pageHeight; + if (nbPtr->reqPageWidth > 0) { + nbPtr->pageWidth = nbPtr->reqPageWidth; + } + if (nbPtr->reqPageHeight > 0) { + nbPtr->pageHeight = nbPtr->reqPageHeight; + } + return count; +} + + +static void +WidenTabs(nbPtr, startPtr, nTabs, adjustment) + Notebook *nbPtr; + Tab *startPtr; + int nTabs; + int adjustment; +{ + register Tab *tabPtr; + register int i; + int ration; + Blt_ChainLink *linkPtr; + int x; + + x = startPtr->tier; + while (adjustment > 0) { + ration = adjustment / nTabs; + if (ration == 0) { + ration = 1; + } + linkPtr = startPtr->linkPtr; + for (i = 0; (linkPtr != NULL) && (i < nTabs) && (adjustment > 0); i++) { + tabPtr = Blt_ChainGetValue(linkPtr); + adjustment -= ration; + tabPtr->worldWidth += ration; + assert(x == tabPtr->tier); + linkPtr = Blt_ChainNextLink(linkPtr); + } + } + /* + * Go back and reset the world X-coordinates of the tabs, + * now that their widths have changed. + */ + x = 0; + linkPtr = startPtr->linkPtr; + for (i = 0; (i < nTabs) && (linkPtr != NULL); i++) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->worldX = x; + x += tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + linkPtr = Blt_ChainNextLink(linkPtr); + } +} + + +static void +AdjustTabSizes(nbPtr, nTabs) + Notebook *nbPtr; + int nTabs; +{ + int tabsPerTier; + int total, count, extra; + Tab *startPtr, *nextPtr; + Blt_ChainLink *linkPtr; + register Tab *tabPtr; + int x, maxWidth; + + tabsPerTier = (nTabs + (nbPtr->nTiers - 1)) / nbPtr->nTiers; + x = 0; + maxWidth = 0; + if (nbPtr->defTabStyle.constWidth) { + register int i; + + linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); + count = 1; + while (linkPtr != NULL) { + for (i = 0; i < tabsPerTier; i++) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->tier = count; + tabPtr->worldX = x; + x += tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + linkPtr = Blt_ChainNextLink(linkPtr); + if (x > maxWidth) { + maxWidth = x; + } + if (linkPtr == NULL) { + goto done; + } + } + count++; + x = 0; + } + } + done: + /* Add to tab widths to fill out row. */ + if (((nTabs % tabsPerTier) != 0) && (nbPtr->defTabStyle.constWidth)) { + return; + } + startPtr = NULL; + count = total = 0; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + /*empty*/ ) { + tabPtr = Blt_ChainGetValue(linkPtr); + if (startPtr == NULL) { + startPtr = tabPtr; + } + count++; + total += tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + linkPtr = Blt_ChainNextLink(linkPtr); + if (linkPtr != NULL) { + nextPtr = Blt_ChainGetValue(linkPtr); + if (tabPtr->tier == nextPtr->tier) { + continue; + } + } + total += nbPtr->overlap; + extra = nbPtr->worldWidth - total; + assert(count > 0); + if (extra > 0) { + WidenTabs(nbPtr, startPtr, count, extra); + } + count = total = 0; + startPtr = NULL; + } +} + +/* + * + * tabWidth = textWidth + gap + (2 * (pad + outerBW)); + * + * tabHeight = textHeight + 2 * (pad + outerBW) + topMargin; + * + */ +static void +ComputeLayout(nbPtr) + Notebook *nbPtr; +{ + int width, height; + Blt_ChainLink *linkPtr; + Tab *tabPtr; + int x, extra; + int nTiers, nTabs; + + nbPtr->nTiers = 0; + nbPtr->pageTop = 0; + nbPtr->worldWidth = 1; + nbPtr->yPad = 0; + + nTiers = 0; + nTabs = CountTabs(nbPtr); + if (nTabs == 0) { + return; + } + /* Reset the pointers to the selected and starting tab. */ + if (nbPtr->selectPtr == NULL) { + linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); + if (linkPtr != NULL) { + nbPtr->selectPtr = Blt_ChainGetValue(linkPtr); + } + } + if (nbPtr->startPtr == NULL) { + nbPtr->startPtr = nbPtr->selectPtr; + } + if (nbPtr->focusPtr == NULL) { + nbPtr->focusPtr = nbPtr->selectPtr; + Blt_SetFocusItem(nbPtr->bindTable, nbPtr->focusPtr); + } + width = Tk_Width(nbPtr->tkwin) - (2 * nbPtr->inset) - + nbPtr->xSelectPad - nbPtr->corner; + height = Tk_Height(nbPtr->tkwin) - 2 * + (nbPtr->corner + nbPtr->xSelectPad); + + if (nbPtr->side & SIDE_VERTICAL) { + int temp; + + temp = width, width = height, height = temp; + } + nbPtr->flags |= TNB_STATIC; + if (nbPtr->reqTiers > 1) { + int total, maxWidth; + + /* Static multiple tier mode. */ + + /* Sum tab widths and determine the number of tiers needed. */ + nTiers = 1; + total = x = 0; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + if ((x + tabPtr->worldWidth) > width) { + nTiers++; + x = 0; + } + tabPtr->worldX = x; + tabPtr->tier = nTiers; + extra = tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + total += extra, x += extra; + } + maxWidth = width; + + if (nTiers > nbPtr->reqTiers) { + /* + * The tabs do not fit into the requested number of tiers. + * Go into scrolling mode. + */ + width = ((total + nbPtr->tabWidth) / nbPtr->reqTiers); + x = 0; + nTiers = 1; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); + linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->tier = nTiers; + /* + * Keep adding tabs to a tier until we overfill it. + */ + tabPtr->worldX = x; + x += tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + if (x > width) { + nTiers++; + if (x > maxWidth) { + maxWidth = x; + } + x = 0; + } + } + nbPtr->flags &= ~TNB_STATIC; + } + nbPtr->worldWidth = maxWidth; + nbPtr->nTiers = nTiers; + + if (nTiers > 1) { + AdjustTabSizes(nbPtr, nTabs); + } + if (nbPtr->flags & TNB_STATIC) { + nbPtr->worldWidth = VPORTWIDTH(nbPtr); + } else { + /* Do you add an offset ? */ + nbPtr->worldWidth += (nbPtr->xSelectPad + nbPtr->corner); + } + nbPtr->worldWidth += nbPtr->overlap; + if (nbPtr->selectPtr != NULL) { + RenumberTiers(nbPtr, nbPtr->selectPtr); + } + } else { + /* + * Scrollable single tier mode. + */ + nTiers = 1; + x = 0; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->tier = nTiers; + tabPtr->worldX = x; + tabPtr->worldY = 0; + x += tabPtr->worldWidth + nbPtr->gap - nbPtr->overlap; + } + nbPtr->worldWidth = x + nbPtr->corner - nbPtr->gap + + nbPtr->xSelectPad + nbPtr->overlap; + nbPtr->flags &= ~TNB_STATIC; + } + if (nTiers == 1) { + nbPtr->yPad = nbPtr->ySelectPad; + } + nbPtr->nTiers = nTiers; + nbPtr->pageTop = nbPtr->inset + nbPtr->yPad /* + 4 */ + + (nbPtr->nTiers * nbPtr->tabHeight) + nbPtr->inset2; + + if (nbPtr->side & SIDE_VERTICAL) { + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->screenWidth = (short int)nbPtr->tabHeight; + tabPtr->screenHeight = (short int)tabPtr->worldWidth; + } + } else { + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->screenWidth = (short int)tabPtr->worldWidth; + tabPtr->screenHeight = (short int)nbPtr->tabHeight; + } + } +} + +static void +ComputeVisibleTabs(nbPtr) + Notebook *nbPtr; +{ + int nVisibleTabs; + register Tab *tabPtr; + Blt_ChainLink *linkPtr; + + nbPtr->nVisible = 0; + if (Blt_ChainGetLength(nbPtr->chainPtr) == 0) { + return; + } + nVisibleTabs = 0; + if (nbPtr->flags & TNB_STATIC) { + + /* Static multiple tier mode. */ + + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->flags |= TAB_VISIBLE; + nVisibleTabs++; + } + } else { + int width, offset; + /* + * Scrollable (single or multiple) tier mode. + */ + offset = nbPtr->scrollOffset - (nbPtr->outerPad + nbPtr->xSelectPad); + width = VPORTWIDTH(nbPtr) + nbPtr->scrollOffset + + 2 * nbPtr->outerPad; + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + if ((tabPtr->worldX >= width) || + ((tabPtr->worldX + tabPtr->worldWidth) < offset)) { + tabPtr->flags &= ~TAB_VISIBLE; + } else { + tabPtr->flags |= TAB_VISIBLE; + nVisibleTabs++; + } + } + } + for (linkPtr = Blt_ChainFirstLink(nbPtr->chainPtr); linkPtr != NULL; + linkPtr = Blt_ChainNextLink(linkPtr)) { + tabPtr = Blt_ChainGetValue(linkPtr); + tabPtr->screenX = tabPtr->screenY = -1000; + if (tabPtr->flags & TAB_VISIBLE) { + WorldToScreen(nbPtr, tabPtr->worldX, tabPtr->worldY, + &(tabPtr->screenX), &(tabPtr->screenY)); + switch (nbPtr->side) { + case SIDE_RIGHT: + tabPtr->screenX -= nbPtr->tabHeight; + break; + + case SIDE_BOTTOM: + tabPtr->screenY -= nbPtr->tabHeight; + break; + } + } + } + nbPtr->nVisible = nVisibleTabs; + Blt_PickCurrentItem(nbPtr->bindTable); +} + + +static void +Draw3DFolder(nbPtr, tabPtr, drawable, side, pointArr, nPoints) + Notebook *nbPtr; + Tab *tabPtr; + Drawable drawable; + int side; + XPoint pointArr[]; + int nPoints; +{ + GC gc; + int relief, borderWidth; + Tk_3DBorder border; + + if (tabPtr == nbPtr->selectPtr) { + border = GETATTR(tabPtr, selBorder); + } else if (tabPtr->border != NULL) { + border = tabPtr->border; + } else { + border = nbPtr->defTabStyle.border; + } + relief = nbPtr->defTabStyle.relief; + if ((side == SIDE_RIGHT) || (side == SIDE_TOP)) { + borderWidth = -nbPtr->defTabStyle.borderWidth; + if (relief == TK_RELIEF_SUNKEN) { + relief = TK_RELIEF_RAISED; + } else if (relief == TK_RELIEF_RAISED) { + relief = TK_RELIEF_SUNKEN; + } + } else { + borderWidth = nbPtr->defTabStyle.borderWidth; + } + /* Draw the outline of the folder. */ + gc = Tk_GCForColor(nbPtr->shadowColor, drawable); + XDrawLines(nbPtr->display, drawable, gc, pointArr, nPoints, + CoordModeOrigin); + /* And the folder itself. */ + if (tabPtr->tile != NULL) { +#ifdef notdef + Tk_Fill3DPolygon(nbPtr->tkwin, drawable, border, pointArr, nPoints, + borderWidth, relief); +#endif + Blt_TilePolygon(nbPtr->tkwin, drawable, tabPtr->tile, pointArr, + nPoints); +#ifdef notdef + Tk_Draw3DPolygon(nbPtr->tkwin, drawable, border, pointArr, nPoints, + borderWidth, relief); +#endif + } else { + Tk_Fill3DPolygon(nbPtr->tkwin, drawable, border, pointArr, nPoints, + borderWidth, relief); + } +} + +/* + * x,y + * |1|2|3| 4 |3|2|1| + * + * 1. tab border width + * 2. corner offset + * 3. label pad + * 4. label width + * + * + */ +static void +DrawLabel(nbPtr, tabPtr, drawable) + Notebook *nbPtr; + Tab *tabPtr; + Drawable drawable; +{ + int x, y, dx, dy; + int tx, ty, ix, iy; + int imgWidth, imgHeight; + int active, selected; + XColor *fgColor, *bgColor; + Tk_3DBorder border; + GC gc; + + if (!(tabPtr->flags & TAB_VISIBLE)) { + return; + } + x = tabPtr->screenX; + y = tabPtr->screenY; + + active = (nbPtr->activePtr == tabPtr); + selected = (nbPtr->selectPtr == tabPtr); + + fgColor = GETATTR(tabPtr, textColor); + border = GETATTR(tabPtr, border); + if (selected) { + border = GETATTR(tabPtr, selBorder); + } + bgColor = Tk_3DBorderColor(border); + if (active) { + Tk_3DBorder activeBorder; + + activeBorder = GETATTR(tabPtr, activeBorder); + bgColor = Tk_3DBorderColor(activeBorder); + } + dx = (tabPtr->screenWidth - tabPtr->labelWidth) / 2; + dy = (tabPtr->screenHeight - tabPtr->labelHeight) / 2; + + + /* + * The label position is computed with screen coordinates. This + * is because both text and image components are oriented in + * screen coordinate space, and not according to the orientation + * of the tabs themselves. That's why we have to consider the + * side when correcting for left/right slants. + */ + switch (nbPtr->side) { + case SIDE_TOP: + case SIDE_BOTTOM: + if (nbPtr->slant == SLANT_LEFT) { + x += nbPtr->overlap; + } else if (nbPtr->slant == SLANT_RIGHT) { + x -= nbPtr->overlap; + } + break; + case SIDE_LEFT: + case SIDE_RIGHT: + if (nbPtr->slant == SLANT_LEFT) { + y += nbPtr->overlap; + } else if (nbPtr->slant == SLANT_RIGHT) { + y -= nbPtr->overlap; + } + break; + } + + /* + * Draw the active or normal background color over the entire + * label area. This includes both the tab's text and image. + * The rectangle should be 2 pixels wider/taller than this + * area. So if the label consists of just an image, we get an + * halo around the image when the tab is active. + */ + gc = Tk_GCForColor(bgColor, drawable); + XFillRectangle(nbPtr->display, drawable, gc, x + dx, y + dy, + tabPtr->labelWidth, tabPtr->labelHeight); + + if ((nbPtr->flags & TNB_FOCUS) && (nbPtr->focusPtr == tabPtr)) { + XDrawRectangle(nbPtr->display, drawable, nbPtr->defTabStyle.activeGC, + x + dx, y + dy, tabPtr->labelWidth - 1, tabPtr->labelHeight - 1); + } + tx = ty = ix = iy = 0; /* Suppress compiler warning. */ + + imgWidth = imgHeight = 0; + if (tabPtr->image != NULL) { + imgWidth = ImageWidth(tabPtr->image); + imgHeight = ImageHeight(tabPtr->image); + } + switch (nbPtr->defTabStyle.textSide) { + case SIDE_LEFT: + tx = x + dx + tabPtr->iPadX.side1; + ty = y + (tabPtr->screenHeight - tabPtr->textHeight) / 2; + ix = tx + tabPtr->textWidth + IMAGE_PAD; + iy = y + (tabPtr->screenHeight - imgHeight) / 2; + break; + case SIDE_RIGHT: + ix = x + dx + tabPtr->iPadX.side1 + IMAGE_PAD; + iy = y + (tabPtr->screenHeight - imgHeight) / 2; + tx = ix + imgWidth; + ty = y + (tabPtr->screenHeight - tabPtr->textHeight) / 2; + break; + case SIDE_BOTTOM: + iy = y + dy + tabPtr->iPadY.side1 + IMAGE_PAD; + ix = x + (tabPtr->screenWidth - imgWidth) / 2; + ty = iy + imgHeight; + tx = x + (tabPtr->screenWidth - tabPtr->textWidth) / 2; + break; + case SIDE_TOP: + tx = x + (tabPtr->screenWidth - tabPtr->textWidth) / 2; + ty = y + dy + tabPtr->iPadY.side1 + IMAGE_PAD; + ix = x + (tabPtr->screenWidth - imgWidth) / 2; + iy = ty + tabPtr->textHeight; + break; + } + if (tabPtr->image != NULL) { + Tk_RedrawImage(ImageData(tabPtr->image), 0, 0, imgWidth, imgHeight, + drawable, ix, iy); + } + if (tabPtr->text != NULL) { + TextStyle ts; + XColor *activeColor; + + activeColor = fgColor; + if (selected) { + activeColor = GETATTR(tabPtr, selColor); + } else if (active) { + activeColor = GETATTR(tabPtr, activeFgColor); + } + Blt_SetDrawTextStyle(&ts, GETATTR(tabPtr, font), tabPtr->textGC, + fgColor, activeColor, tabPtr->shadow.color, + nbPtr->defTabStyle.rotate, TK_ANCHOR_NW, TK_JUSTIFY_LEFT, + 0, tabPtr->shadow.offset); + ts.state = tabPtr->state; + ts.border = border; + ts.padX.side1 = ts.padX.side2 = 2; + if (selected || active) { + ts.state |= STATE_ACTIVE; + } + Blt_DrawText(nbPtr->tkwin, drawable, tabPtr->text, &ts, tx, ty); + } +} + + +static void +DrawPerforation(nbPtr, tabPtr, drawable) + Notebook *nbPtr; + Tab *tabPtr; + Drawable drawable; +{ + XPoint pointArr[2]; + int x, y; + int segmentWidth, max; + Tk_3DBorder border, perfBorder; + + if ((tabPtr->container != NULL) || (tabPtr->tkwin == NULL)) { + return; + } + WorldToScreen(nbPtr, tabPtr->worldX + 2, + tabPtr->worldY + tabPtr->worldHeight + 2, &x, &y); + border = GETATTR(tabPtr, selBorder); + segmentWidth = 3; + if (nbPtr->flags & PERFORATION_ACTIVE) { + perfBorder = GETATTR(tabPtr, activeBorder); + } else { + perfBorder = GETATTR(tabPtr, selBorder); + } + if (nbPtr->side & SIDE_HORIZONTAL) { + pointArr[0].x = x; + pointArr[0].y = pointArr[1].y = y; + max = tabPtr->screenX + tabPtr->screenWidth - 2; + Tk_Fill3DRectangle(nbPtr->tkwin, drawable, perfBorder, + x - 2, y - 4, tabPtr->screenWidth, 8, 0, TK_RELIEF_FLAT); + while (pointArr[0].x < max) { + pointArr[1].x = pointArr[0].x + segmentWidth; + if (pointArr[1].x > max) { + pointArr[1].x = max; + } + Tk_Draw3DPolygon(nbPtr->tkwin, drawable, border, pointArr, 2, 1, + TK_RELIEF_RAISED); + pointArr[0].x += 2 * segmentWidth; + } + } else { + pointArr[0].x = pointArr[1].x = x; + pointArr[0].y = y; + max = tabPtr->screenY + tabPtr->screenHeight - 2; + Tk_Fill3DRectangle(nbPtr->tkwin, drawable, perfBorder, + x - 4, y - 2, 8, tabPtr->screenHeight, 0, TK_RELIEF_FLAT); + while (pointArr[0].y < max) { + pointArr[1].y = pointArr[0].y + segmentWidth; + if (pointArr[1].y > max) { + pointArr[1].y = max; + } + Tk_Draw3DPolygon(nbPtr->tkwin, drawable, border, pointArr, 2, 1, + TK_RELIEF_RAISED); + pointArr[0].y += 2 * segmentWidth; + } + } +} + +#define NextPoint(px, py) \ + pointPtr->x = (px), pointPtr->y = (py), pointPtr++, nPoints++ + +#define BottomLeft(px, py) \ + NextPoint((px) + nbPtr->corner, (py)), \ + NextPoint((px), (py) - nbPtr->corner) + +#define TopLeft(px, py) \ + NextPoint((px), (py) + nbPtr->corner), \ + NextPoint((px) + nbPtr->corner, (py)) + +#define TopRight(px, py) \ + NextPoint((px) - nbPtr->corner, (py)), \ + NextPoint((px), (py) + nbPtr->corner) + +#define BottomRight(px, py) \ + NextPoint((px), (py) - nbPtr->corner), \ + NextPoint((px) - nbPtr->corner, (py)) + + +/* + * From the left edge: + * + * |a|b|c|d|e| f |d|e|g|h| i |h|g|e|d|f| j |e|d|c|b|a| + * + * a. highlight ring + * b. notebook 3D border + * c. outer gap + * d. page border + * e. page corner + * f. gap + select pad + * g. label pad x (worldX) + * h. internal pad x + * i. label width + * j. rest of page width + * + * worldX, worldY + * | + * | + * * 4+ . . +5 + * 3+ +6 + * . . + * . . + * 1+. . .2+ +7 . . . .+8 + * 0+ +9 + * . . + * . . + *13+ +10 + * 12+-------------------------+11 + * + */ +static void +DrawFolder(nbPtr, tabPtr, drawable) + Notebook *nbPtr; + Tab *tabPtr; + Drawable drawable; +{ + XPoint pointArr[16]; + XPoint *pointPtr; + int width, height; + int left, bottom, right, top, yBot, yTop; + int x, y; + register int i; + int nPoints; + + width = VPORTWIDTH(nbPtr); + height = VPORTHEIGHT(nbPtr); + + x = tabPtr->worldX; + y = tabPtr->worldY; + + nPoints = 0; + pointPtr = pointArr; + + /* Remember these are all world coordinates. */ + /* + * x Left side of tab. + * y Top of tab. + * yTop Top of folder. + * yBot Bottom of the tab. + * left Left side of the folder. + * right Right side of the folder. + * top Top of folder. + * bottom Bottom of folder. + */ + left = nbPtr->scrollOffset - nbPtr->xSelectPad; + right = left + width; + yTop = y + tabPtr->worldHeight; + yBot = nbPtr->pageTop - (nbPtr->inset + nbPtr->yPad); + top = yBot - nbPtr->inset2 /* - 4 */; + + if (nbPtr->pageHeight == 0) { + bottom = yBot + 2 * nbPtr->corner; + } else { + bottom = height - nbPtr->yPad - 1; + } + if (tabPtr != nbPtr->selectPtr) { + + /* + * Case 1: Unselected tab + * + * * 3+ . . +4 + * 2+ +5 + * . . + * 1+ +6 + * 0+ . . +7 + * + */ + + if (nbPtr->slant & SLANT_LEFT) { + NextPoint(x, yBot); + NextPoint(x, yTop); + NextPoint(x + nbPtr->tabHeight, y); + } else { + BottomLeft(x, yBot); + TopLeft(x, y); + } + x += tabPtr->worldWidth; + if (nbPtr->slant & SLANT_RIGHT) { + NextPoint(x - nbPtr->tabHeight, y); + NextPoint(x, yTop); + NextPoint(x, yBot); + } else { + TopRight(x, y); + BottomRight(x, yBot); + } + } else if (!(tabPtr->flags & TAB_VISIBLE)) { + + /* + * Case 2: Selected tab not visible in viewport. Draw folder only. + * + * * 3+ . . +4 + * 2+ +5 + * . . + * 1+ +6 + * 0+------+7 + * + */ + + TopLeft(left, top); + TopRight(right, top); + BottomRight(right, bottom); + BottomLeft(left, bottom); + } else { + int flags; + int tabWidth; + + x -= nbPtr->xSelectPad; + y -= nbPtr->yPad; + tabWidth = tabPtr->worldWidth + 2 * nbPtr->xSelectPad; + +#define CLIP_NONE 0 +#define CLIP_LEFT (1<<0) +#define CLIP_RIGHT (1<<1) + flags = 0; + if (x < left) { + flags |= CLIP_LEFT; + } + if ((x + tabWidth) > right) { + flags |= CLIP_RIGHT; + } + switch (flags) { + case CLIP_NONE: + + /* + * worldX, worldY + * | + * * 4+ . . +5 + * 3+ +6 + * . . + * . . + * 1+. . .2+---------+7 . . . .+8 + * 0+ +9 + * . . + * . . + *13+ +10 + * 12+ . . . . . . . . . . . . +11 + */ + + if (x < (left + nbPtr->corner)) { + NextPoint(left, top); + } else { + TopLeft(left, top); + } + if (nbPtr->slant & SLANT_LEFT) { + NextPoint(x, yTop); + NextPoint(x + nbPtr->tabHeight + nbPtr->yPad, y); + } else { + NextPoint(x, top); + TopLeft(x, y); + } + x += tabWidth; + if (nbPtr->slant & SLANT_RIGHT) { + NextPoint(x - nbPtr->tabHeight - nbPtr->yPad, y); + NextPoint(x, yTop); + } else { + TopRight(x, y); + NextPoint(x, top); + } + if (x > (right - nbPtr->corner)) { + NextPoint(right, top + nbPtr->corner); + } else { + TopRight(right, top); + } + BottomRight(right, bottom); + BottomLeft(left, bottom); + break; + + case CLIP_LEFT: + + /* + * worldX, worldY + * | + * * 4+ . . +5 + * 3+ +6 + * . . + * . . + * 2+--------+7 . . . .+8 + * 1+ . . . +0 +9 + * . . + * . . + * 13+ +10 + * 12+ . . . .+11 + */ + + NextPoint(left, yBot); + if (nbPtr->slant & SLANT_LEFT) { + NextPoint(x, yBot); + NextPoint(x, yTop); + NextPoint(x + nbPtr->tabHeight + nbPtr->yPad, y); + } else { + BottomLeft(x, yBot); + TopLeft(x, y); + } + + x += tabWidth; + if (nbPtr->slant & SLANT_RIGHT) { + NextPoint(x - nbPtr->tabHeight - nbPtr->yPad, y); + NextPoint(x, yTop); + NextPoint(x, top); + } else { + TopRight(x, y); + NextPoint(x, top); + } + if (x > (right - nbPtr->corner)) { + NextPoint(right, top + nbPtr->corner); + } else { + TopRight(right, top); + } + BottomRight(right, bottom); + BottomLeft(left, bottom); + break; + + case CLIP_RIGHT: + + /* + * worldX, worldY + * | + * * 9+ . . +10 + * 8+ +11 + * . . + * . . + * 6+ . . . .7+---------+12 + * 5+ 0+ . . . +13 + * . . + * . . + * 4+ +1 + * 3+ . . . +2 + */ + + NextPoint(right, yBot); + BottomRight(right, bottom); + BottomLeft(left, bottom); + if (x < (left + nbPtr->corner)) { + NextPoint(left, top); + } else { + TopLeft(left, top); + } + NextPoint(x, top); + + if (nbPtr->slant & SLANT_LEFT) { + NextPoint(x, yTop); + NextPoint(x + nbPtr->tabHeight + nbPtr->yPad, y); + } else { + TopLeft(x, y); + } + x += tabWidth; + if (nbPtr->slant & SLANT_RIGHT) { + NextPoint(x - nbPtr->tabHeight - nbPtr->yPad, y); + NextPoint(x, yTop); + NextPoint(x, yBot); + } else { + TopRight(x, y); + BottomRight(x, yBot); + } + break; + + case (CLIP_LEFT | CLIP_RIGHT): + + /* + * worldX, worldY + * | + * * 4+ . . . . . . . . +5 + * 3+ +6 + * . . + * . . + * 1+---------------------+7 + * 2+ 0+ +9 .+8 + * . . + * . . + * 13+ +10 + * 12+ . . . +11 + */ + + NextPoint(left, yBot); + if (nbPtr->slant & SLANT_LEFT) { + NextPoint(x, yBot); + NextPoint(x, yTop); + NextPoint(x + nbPtr->tabHeight + nbPtr->yPad, y); + } else { + BottomLeft(x, yBot); + TopLeft(x, y); + } + x += tabPtr->worldWidth; + if (nbPtr->slant & SLANT_RIGHT) { + NextPoint(x - nbPtr->tabHeight - nbPtr->yPad, y); + NextPoint(x, yTop); + NextPoint(x, yBot); + } else { + TopRight(x, y); + BottomRight(x, yBot); + } + NextPoint(right, yBot); + BottomRight(right, bottom); + BottomLeft(left, bottom); + break; + } + } + NextPoint(pointArr[0].x, pointArr[0].y); + for (i = 0; i < nPoints; i++) { + WorldToScreen(nbPtr, pointArr[i].x, pointArr[i].y, &x, &y); + pointArr[i].x = x; + pointArr[i].y = y; + } + Draw3DFolder(nbPtr, tabPtr, drawable, nbPtr->side, pointArr, nPoints); + DrawLabel(nbPtr, tabPtr, drawable); + if (tabPtr->container != NULL) { + XRectangle rect; + + /* Draw a rectangle covering the spot representing the window */ + GetWindowRectangle(tabPtr, nbPtr->tkwin, FALSE, &rect); + XFillRectangles(nbPtr->display, drawable, tabPtr->backGC, + &rect, 1); + } +} + +static void +DrawOuterBorders(nbPtr, drawable) + Notebook *nbPtr; + Drawable drawable; +{ + /* + * Draw 3D border just inside of the focus highlight ring. We + * draw the border even if the relief is flat so that any tabs + * that hang over the edge will be clipped. + */ + if (nbPtr->borderWidth > 0) { + Tk_Draw3DRectangle(nbPtr->tkwin, drawable, nbPtr->border, + nbPtr->highlightWidth, nbPtr->highlightWidth, + Tk_Width(nbPtr->tkwin) - 2 * nbPtr->highlightWidth, + Tk_Height(nbPtr->tkwin) - 2 * nbPtr->highlightWidth, + nbPtr->borderWidth, nbPtr->relief); + } + /* Draw focus highlight ring. */ + if (nbPtr->highlightWidth > 0) { + XColor *color; + GC gc; + + color = (nbPtr->flags & TNB_FOCUS) + ? nbPtr->highlightColor : nbPtr->highlightBgColor; + gc = Tk_GCForColor(color, drawable); + Tk_DrawFocusHighlight(nbPtr->tkwin, gc, nbPtr->highlightWidth, + drawable); + } +} + +/* + * ---------------------------------------------------------------------- + * + * DisplayNotebook -- + * + * This procedure is invoked to display the widget. + * + * Recomputes the layout of the widget if necessary. This is + * necessary if the world coordinate system has changed. + * Sets the vertical and horizontal scrollbars. This is done + * here since the window width and height are needed for the + * scrollbar calculations. + * + * Results: + * None. + * + * Side effects: + * The widget is redisplayed. + * + * ---------------------------------------------------------------------- + */ +static void +DisplayNotebook(clientData) + ClientData clientData; /* Information about widget. */ +{ + Notebook *nbPtr = clientData; + Pixmap drawable; + int width, height; + + nbPtr->flags &= ~TNB_REDRAW; + if (nbPtr->tkwin == NULL) { + return; /* Window has been destroyed. */ + } + if (nbPtr->flags & TNB_LAYOUT) { + ComputeLayout(nbPtr); + nbPtr->flags &= ~TNB_LAYOUT; + } + if ((nbPtr->reqHeight == 0) || (nbPtr->reqWidth == 0)) { + width = height = 0; + if (nbPtr->side & SIDE_VERTICAL) { + height = nbPtr->worldWidth; + } else { + width = nbPtr->worldWidth; + } + if (nbPtr->reqWidth > 0) { + width = nbPtr->reqWidth; + } else if (nbPtr->pageWidth > 0) { + width = nbPtr->pageWidth; + } + if (nbPtr->reqHeight > 0) { + height = nbPtr->reqHeight; + } else if (nbPtr->pageHeight > 0) { + height = nbPtr->pageHeight; + } + if (nbPtr->side & SIDE_VERTICAL) { + width += nbPtr->pageTop + nbPtr->inset + nbPtr->inset2; + height += nbPtr->inset + nbPtr->inset2; + } else { + height += nbPtr->pageTop + nbPtr->inset + nbPtr->inset2; + width += nbPtr->inset + nbPtr->inset2; + } + if ((Tk_ReqWidth(nbPtr->tkwin) != width) || + (Tk_ReqHeight(nbPtr->tkwin) != height)) { + Tk_GeometryRequest(nbPtr->tkwin, width, height); + } + } + if (nbPtr->flags & TNB_SCROLL) { + width = VPORTWIDTH(nbPtr); + nbPtr->scrollOffset = Blt_AdjustViewport(nbPtr->scrollOffset, + nbPtr->worldWidth, width, nbPtr->scrollUnits, + BLT_SCROLL_MODE_CANVAS); + if (nbPtr->scrollCmdPrefix != NULL) { + Blt_UpdateScrollbar(nbPtr->interp, nbPtr->scrollCmdPrefix, + (double)nbPtr->scrollOffset / nbPtr->worldWidth, + (double)(nbPtr->scrollOffset + width) / nbPtr->worldWidth); + } + ComputeVisibleTabs(nbPtr); + nbPtr->flags &= ~TNB_SCROLL; + } + if (!Tk_IsMapped(nbPtr->tkwin)) { + return; + } + height = Tk_Height(nbPtr->tkwin); + drawable = Tk_GetPixmap(nbPtr->display, Tk_WindowId(nbPtr->tkwin), + Tk_Width(nbPtr->tkwin), Tk_Height(nbPtr->tkwin), + Tk_Depth(nbPtr->tkwin)); + + /* + * Clear the background either by tiling a pixmap or filling with + * a solid color. Tiling takes precedence. + */ + if (nbPtr->tile != NULL) { + Blt_SetTileOrigin(nbPtr->tkwin, nbPtr->tile, 0, 0); + Blt_TileRectangle(nbPtr->tkwin, drawable, nbPtr->tile, 0, 0, + Tk_Width(nbPtr->tkwin), height); + } else { + Tk_Fill3DRectangle(nbPtr->tkwin, drawable, nbPtr->border, 0, 0, + Tk_Width(nbPtr->tkwin), height, 0, TK_RELIEF_FLAT); + } + + if (nbPtr->nVisible > 0) { + register int i; + register Tab *tabPtr; + Blt_ChainLink *linkPtr; + + linkPtr = nbPtr->startPtr->linkPtr; + for (i = 0; i < Blt_ChainGetLength(nbPtr->chainPtr); i++) { + linkPtr = Blt_ChainPrevLink(linkPtr); + if (linkPtr == NULL) { + linkPtr = Blt_ChainLastLink(nbPtr->chainPtr); + } + tabPtr = Blt_ChainGetValue(linkPtr); + if ((tabPtr != nbPtr->selectPtr) && + (tabPtr->flags & TAB_VISIBLE)) { + DrawFolder(nbPtr, tabPtr, drawable); + } + } + DrawFolder(nbPtr, nbPtr->selectPtr, drawable); + if (nbPtr->tearoff) { + DrawPerforation(nbPtr, nbPtr->selectPtr, drawable); + } + + if ((nbPtr->selectPtr->tkwin != NULL) && + (nbPtr->selectPtr->container == NULL)) { + XRectangle rect; + + GetWindowRectangle(nbPtr->selectPtr, nbPtr->tkwin, FALSE, &rect); + ArrangeWindow(nbPtr->selectPtr->tkwin, &rect, 0); + } + } + DrawOuterBorders(nbPtr, drawable); + XCopyArea(nbPtr->display, drawable, Tk_WindowId(nbPtr->tkwin), + nbPtr->highlightGC, 0, 0, Tk_Width(nbPtr->tkwin), height, 0, 0); + Tk_FreePixmap(nbPtr->display, drawable); +} + +/* + * From the left edge: + * + * |a|b|c|d|e| f |d|e|g|h| i |h|g|e|d|f| j |e|d|c|b|a| + * + * a. highlight ring + * b. notebook 3D border + * c. outer gap + * d. page border + * e. page corner + * f. gap + select pad + * g. label pad x (worldX) + * h. internal pad x + * i. label width + * j. rest of page width + * + * worldX, worldY + * | + * | + * * 4+ . . +5 + * 3+ +6 + * . . + * . . + * 1+. . .2+ +7 . . . .+8 + * 0+ +9 + * . . + * . . + *13+ +10 + * 12+-------------------------+11 + * + */ +static void +DisplayTearoff(clientData) + ClientData clientData; +{ + Notebook *nbPtr; + Tab *tabPtr; + Drawable drawable; + XPoint pointArr[16]; + XPoint *pointPtr; + int width, height; + int left, bottom, right, top; + int x, y; + int nPoints; + Tk_Window tkwin; + Tk_Window parent; + XRectangle rect; + + tabPtr = clientData; + if (tabPtr == NULL) { + return; + } + tabPtr->flags &= ~TAB_REDRAW; + nbPtr = tabPtr->nbPtr; + if (nbPtr->tkwin == NULL) { + return; + } + tkwin = tabPtr->container; + drawable = Tk_WindowId(tkwin); + + /* + * Clear the background either by tiling a pixmap or filling with + * a solid color. Tiling takes precedence. + */ + if (nbPtr->tile != NULL) { + Blt_SetTileOrigin(tkwin, nbPtr->tile, 0, 0); + Blt_TileRectangle(tkwin, drawable, nbPtr->tile, 0, 0, Tk_Width(tkwin), + Tk_Height(tkwin)); + } else { + Tk_Fill3DRectangle(tkwin, drawable, nbPtr->border, 0, 0, + Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); + } + + width = Tk_Width(tkwin) - 2 * nbPtr->inset; + height = Tk_Height(tkwin) - 2 * nbPtr->inset; + x = nbPtr->inset + nbPtr->gap + nbPtr->corner; + y = nbPtr->inset; + + left = nbPtr->inset; + right = nbPtr->inset + width; + top = nbPtr->inset + nbPtr->corner + nbPtr->xSelectPad; + bottom = nbPtr->inset + height; + + /* + * worldX, worldY + * | + * * 4+ . . +5 + * 3+ +6 + * . . + * . . + * 1+. . .2+ +7 . . . .+8 + * 0+ +9 + * . . + * . . + *13+ +10 + * 12+-------------------------+11 + */ + + nPoints = 0; + pointPtr = pointArr; + + TopLeft(left, top); + NextPoint(x, top); + TopLeft(x, y); + x += tabPtr->worldWidth; + TopRight(x, y); + NextPoint(x, top); + TopRight(right, top); + BottomRight(right, bottom); + BottomLeft(left, bottom); + NextPoint(pointArr[0].x, pointArr[0].y); + Draw3DFolder(nbPtr, tabPtr, drawable, SIDE_TOP, pointArr, nPoints); + + parent = (tabPtr->container == NULL) ? nbPtr->tkwin : tabPtr->container; + GetWindowRectangle(tabPtr, parent, TRUE, &rect); + ArrangeWindow(tabPtr->tkwin, &rect, TRUE); + + /* Draw 3D border. */ + if ((nbPtr->borderWidth > 0) && (nbPtr->relief != TK_RELIEF_FLAT)) { + Tk_Draw3DRectangle(tkwin, drawable, nbPtr->border, 0, 0, + Tk_Width(tkwin), Tk_Height(tkwin), nbPtr->borderWidth, + nbPtr->relief); + } +} + +/* + * -------------------------------------------------------------- + * + * NotebookCmd -- + * + * This procedure is invoked to process the "notebook" command. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + * -------------------------------------------------------------- + */ +static Blt_OpSpec notebookOps[] = +{ + {"activate", 1, (Blt_Op)ActivateOp, 3, 3, "index",}, + {"bind", 1, (Blt_Op)BindOp, 2, 5, "index ?sequence command?",}, + {"cget", 2, (Blt_Op)CgetOp, 3, 3, "option",}, + {"configure", 2, (Blt_Op)ConfigureOp, 2, 0, "?option value?...",}, + {"delete", 1, (Blt_Op)DeleteOp, 2, 0, "first ?last?",}, + {"focus", 1, (Blt_Op)FocusOp, 3, 3, "index",}, + {"highlight", 1, (Blt_Op)ActivateOp, 3, 3, "index",}, + {"id", 2, (Blt_Op)IdOp, 3, 3, "index",}, + {"index", 3, (Blt_Op)IndexOp, 3, 5, "string",}, + {"insert", 3, (Blt_Op)InsertOp, 3, 0, "index ?option value?",}, + {"invoke", 3, (Blt_Op)InvokeOp, 3, 3, "index",}, + {"move", 1, (Blt_Op)MoveOp, 5, 5, "name after|before index",}, + {"nearest", 1, (Blt_Op)NearestOp, 4, 4, "x y",}, + {"perforation", 1, (Blt_Op)PerforationOp, 2, 0, "args",}, + {"scan", 2, (Blt_Op)ScanOp, 5, 5, "dragto|mark x y",}, + {"see", 3, (Blt_Op)SeeOp, 3, 3, "index",}, + {"select", 3, (Blt_Op)SelectOp, 3, 3, "index",}, + {"size", 2, (Blt_Op)SizeOp, 2, 2, "",}, + {"tab", 1, (Blt_Op)TabOp, 2, 0, "oper args",}, + {"view", 1, (Blt_Op)ViewOp, 2, 5, + "?moveto fract? ?scroll number what?",}, +}; + +static int nNotebookOps = sizeof(notebookOps) / sizeof(Blt_OpSpec); + +static int +NotebookInstCmd(clientData, interp, argc, argv) + ClientData clientData; /* Information about the widget. */ + Tcl_Interp *interp; /* Interpreter to report errors back to. */ + int argc; /* Number of arguments. */ + char **argv; /* Vector of argument strings. */ +{ + Blt_Op proc; + Notebook *nbPtr = clientData; + int result; + + proc = Blt_GetOp(interp, nNotebookOps, notebookOps, BLT_OP_ARG1, argc, + argv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + Tcl_Preserve(nbPtr); + result = (*proc) (nbPtr, interp, argc, argv); + Tcl_Release(nbPtr); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * NotebookInstDeletedCmd -- + * + * This procedure can be called if the window was destroyed + * (tkwin will be NULL) and the command was deleted + * automatically. In this case, we need to do nothing. + * + * Otherwise this routine was called because the command was + * deleted. Then we need to clean-up and destroy the widget. + * + * Results: + * None. + * + * Side Effects: + * The widget is destroyed. + * + *---------------------------------------------------------------------- + */ +static void +NotebookInstDeletedCmd(clientData) + ClientData clientData; /* Pointer to widget record for widget. */ +{ + Notebook *nbPtr = clientData; + + if (nbPtr->tkwin != NULL) { + Tk_Window tkwin; + + tkwin = nbPtr->tkwin; + nbPtr->tkwin = NULL; + Tk_DestroyWindow(tkwin); +#ifdef ITCL_NAMESPACES + Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL); +#endif /* ITCL_NAMESPACES */ + } +} + +/* + * ------------------------------------------------------------------------ + * + * NotebookCmd -- + * + * This procedure 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. + * + * ----------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +NotebookCmd(clientData, interp, argc, argv) + ClientData clientData; /* Main window associated with interpreter. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. */ +{ + Notebook *nbPtr; + Tk_Window tkwin; + unsigned int mask; + Tcl_CmdInfo cmdInfo; + + if (argc < 2) { + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " pathName ?option value?...\"", (char *)NULL); + return TCL_ERROR; + } + tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), argv[1], + (char *)NULL); + if (tkwin == NULL) { + return TCL_ERROR; + } + nbPtr = CreateNotebook(interp, tkwin); + if (ConfigureNotebook(interp, nbPtr, argc - 2, argv + 2, 0) != TCL_OK) { + Tk_DestroyWindow(nbPtr->tkwin); + return TCL_ERROR; + } + mask = (ExposureMask | StructureNotifyMask | FocusChangeMask); + Tk_CreateEventHandler(tkwin, mask, NotebookEventProc, nbPtr); + nbPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], NotebookInstCmd, + nbPtr, NotebookInstDeletedCmd); +#ifdef ITCL_NAMESPACES + Itk_SetWidgetCommand(nbPtr->tkwin, nbPtr->cmdToken); +#endif + + /* + * Try to invoke a procedure to initialize various bindings on + * tabs. Source the file containing the procedure now if the + * procedure isn't currently defined. We deferred this to now so + * that the user could set the variable "blt_library" within the + * script. + */ + if (!Tcl_GetCommandInfo(interp, "blt::TabnotebookInit", &cmdInfo)) { + static char initCmd[] = + "source [file join $blt_library tabnotebook.tcl]"; + + if (Tcl_GlobalEval(interp, initCmd) != TCL_OK) { + char info[200]; + + sprintf(info, "\n (while loading bindings for %s)", argv[0]); + Tcl_AddErrorInfo(interp, info); + Tk_DestroyWindow(nbPtr->tkwin); + return TCL_ERROR; + } + } + if (Tcl_VarEval(interp, "blt::TabnotebookInit ", argv[1], (char *)NULL) + != TCL_OK) { + Tk_DestroyWindow(nbPtr->tkwin); + return TCL_ERROR; + } + Tcl_SetResult(interp, Tk_PathName(nbPtr->tkwin), TCL_VOLATILE); + return TCL_OK; +} + +int +Blt_TabnotebookInit(interp) + Tcl_Interp *interp; +{ + static Blt_CmdSpec cmdSpec = + { + "tabnotebook", NotebookCmd, + }; + + if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) { + return TCL_ERROR; + } + return TCL_OK; +} + +#endif /* NO_TABNOTEBOOK */ |