diff options
Diffstat (limited to 'tk8.6/generic/tkCanvas.c')
-rw-r--r-- | tk8.6/generic/tkCanvas.c | 5984 |
1 files changed, 5984 insertions, 0 deletions
diff --git a/tk8.6/generic/tkCanvas.c b/tk8.6/generic/tkCanvas.c new file mode 100644 index 0000000..ecabe22 --- /dev/null +++ b/tk8.6/generic/tkCanvas.c @@ -0,0 +1,5984 @@ +/* + * tkCanvas.c -- + * + * This module implements canvas widgets for the Tk toolkit. A canvas + * displays a background and a collection of graphical objects such as + * rectangles, lines, and texts. + * + * Copyright (c) 1991-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 1998-1999 by Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* #define USE_OLD_TAG_SEARCH 1 */ + +#include "default.h" +#include "tkInt.h" +#include "tkCanvas.h" +#ifdef TK_NO_DOUBLE_BUFFERING +#ifdef MAC_OSX_TK +#include "tkMacOSXInt.h" +#endif +#endif /* TK_NO_DOUBLE_BUFFERING */ + +/* + * See tkCanvas.h for key data structures used to implement canvases. + */ + +#ifdef USE_OLD_TAG_SEARCH +/* + * The structure defined below is used to keep track of a tag search in + * progress. No field should be accessed by anyone other than StartTagSearch + * and NextItem. + */ + +typedef struct TagSearch { + TkCanvas *canvasPtr; /* Canvas widget being searched. */ + Tk_Uid tag; /* Tag to search for. 0 means return all + * items. */ + Tk_Item *currentPtr; /* Pointer to last item returned. */ + Tk_Item *lastPtr; /* The item right before the currentPtr is + * tracked so if the currentPtr is deleted we + * don't have to start from the beginning. */ + int searchOver; /* Non-zero means NextItem should always + * return NULL. */ +} TagSearch; + +#else /* USE_OLD_TAG_SEARCH */ +/* + * The structure defined below is used to keep track of a tag search in + * progress. No field should be accessed by anyone other than TagSearchScan, + * TagSearchFirst, TagSearchNext, TagSearchScanExpr, TagSearchEvalExpr, + * TagSearchExprInit, TagSearchExprDestroy, TagSearchDestroy. + * ( + * Not quite accurate: the TagSearch structure is also accessed from: + * CanvasWidgetCmd, FindItems, RelinkItems + * The only instances of the structure are owned by: + * CanvasWidgetCmd + * CanvasWidgetCmd is the only function that calls: + * FindItems, RelinkItems + * CanvasWidgetCmd, FindItems, RelinkItems, are the only functions that call + * TagSearch* + * ) + */ + +typedef struct TagSearch { + TkCanvas *canvasPtr; /* Canvas widget being searched. */ + Tk_Item *currentPtr; /* Pointer to last item returned. */ + Tk_Item *lastPtr; /* The item right before the currentPtr is + * tracked so if the currentPtr is deleted we + * don't have to start from the beginning. */ + int searchOver; /* Non-zero means NextItem should always + * return NULL. */ + int type; /* Search type (see #defs below) */ + int id; /* Item id for searches by id */ + const char *string; /* Tag expression string */ + int stringIndex; /* Current position in string scan */ + int stringLength; /* Length of tag expression string */ + char *rewritebuffer; /* Tag string (after removing escapes) */ + unsigned int rewritebufferAllocated; + /* Available space for rewrites. */ + TagSearchExpr *expr; /* Compiled tag expression. */ +} TagSearch; + +/* + * Values for the TagSearch type field. + */ + +#define SEARCH_TYPE_EMPTY 0 /* Looking for empty tag */ +#define SEARCH_TYPE_ID 1 /* Looking for an item by id */ +#define SEARCH_TYPE_ALL 2 /* Looking for all items */ +#define SEARCH_TYPE_TAG 3 /* Looking for an item by simple tag */ +#define SEARCH_TYPE_EXPR 4 /* Compound search */ + +#endif /* USE_OLD_TAG_SEARCH */ + +/* + * Custom option for handling "-state" and "-offset" + */ + +static const Tk_CustomOption stateOption = { + TkStateParseProc, TkStatePrintProc, + NULL /* Only "normal" and "disabled". */ +}; + +static const Tk_CustomOption offsetOption = { + TkOffsetParseProc, TkOffsetPrintProc, INT2PTR(TK_OFFSET_RELATIVE) +}; + +/* + * Information used for argv parsing. + */ + +static const Tk_ConfigSpec configSpecs[] = { + {TK_CONFIG_BORDER, "-background", "background", "Background", + DEF_CANVAS_BG_COLOR, Tk_Offset(TkCanvas, bgBorder), + TK_CONFIG_COLOR_ONLY, NULL}, + {TK_CONFIG_BORDER, "-background", "background", "Background", + DEF_CANVAS_BG_MONO, Tk_Offset(TkCanvas, bgBorder), + TK_CONFIG_MONO_ONLY, NULL}, + {TK_CONFIG_SYNONYM, "-bd", "borderWidth", NULL, NULL, 0, 0, NULL}, + {TK_CONFIG_SYNONYM, "-bg", "background", NULL, NULL, 0, 0, NULL}, + {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + DEF_CANVAS_BORDER_WIDTH, Tk_Offset(TkCanvas, borderWidth), 0, NULL}, + {TK_CONFIG_DOUBLE, "-closeenough", "closeEnough", "CloseEnough", + DEF_CANVAS_CLOSE_ENOUGH, Tk_Offset(TkCanvas, closeEnough), 0, NULL}, + {TK_CONFIG_BOOLEAN, "-confine", "confine", "Confine", + DEF_CANVAS_CONFINE, Tk_Offset(TkCanvas, confine), 0, NULL}, + {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", + DEF_CANVAS_CURSOR, Tk_Offset(TkCanvas, cursor), TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_PIXELS, "-height", "height", "Height", + DEF_CANVAS_HEIGHT, Tk_Offset(TkCanvas, height), 0, NULL}, + {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", DEF_CANVAS_HIGHLIGHT_BG, + Tk_Offset(TkCanvas, highlightBgColorPtr), 0, NULL}, + {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", + DEF_CANVAS_HIGHLIGHT, Tk_Offset(TkCanvas, highlightColorPtr), 0, NULL}, + {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", + "HighlightThickness", + DEF_CANVAS_HIGHLIGHT_WIDTH, Tk_Offset(TkCanvas, highlightWidth), 0, NULL}, + {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground", + DEF_CANVAS_INSERT_BG, Tk_Offset(TkCanvas, textInfo.insertBorder), 0, NULL}, + {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", + DEF_CANVAS_INSERT_BD_COLOR, + Tk_Offset(TkCanvas, textInfo.insertBorderWidth), TK_CONFIG_COLOR_ONLY, NULL}, + {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", + DEF_CANVAS_INSERT_BD_MONO, + Tk_Offset(TkCanvas, textInfo.insertBorderWidth), TK_CONFIG_MONO_ONLY, NULL}, + {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", + DEF_CANVAS_INSERT_OFF_TIME, Tk_Offset(TkCanvas, insertOffTime), 0, NULL}, + {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", + DEF_CANVAS_INSERT_ON_TIME, Tk_Offset(TkCanvas, insertOnTime), 0, NULL}, + {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", + DEF_CANVAS_INSERT_WIDTH, Tk_Offset(TkCanvas, textInfo.insertWidth), 0, NULL}, + {TK_CONFIG_CUSTOM, "-offset", "offset", "Offset", "0,0", + Tk_Offset(TkCanvas, tsoffset),TK_CONFIG_DONT_SET_DEFAULT, + &offsetOption}, + {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_CANVAS_RELIEF, Tk_Offset(TkCanvas, relief), 0, NULL}, + {TK_CONFIG_STRING, "-scrollregion", "scrollRegion", "ScrollRegion", + DEF_CANVAS_SCROLL_REGION, Tk_Offset(TkCanvas, regionString), + TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_CANVAS_SELECT_COLOR, Tk_Offset(TkCanvas, textInfo.selBorder), + TK_CONFIG_COLOR_ONLY, NULL}, + {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_CANVAS_SELECT_MONO, Tk_Offset(TkCanvas, textInfo.selBorder), + TK_CONFIG_MONO_ONLY, NULL}, + {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth", + DEF_CANVAS_SELECT_BD_COLOR, + Tk_Offset(TkCanvas, textInfo.selBorderWidth), TK_CONFIG_COLOR_ONLY, NULL}, + {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth", + DEF_CANVAS_SELECT_BD_MONO, Tk_Offset(TkCanvas, textInfo.selBorderWidth), + TK_CONFIG_MONO_ONLY, NULL}, + {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_CANVAS_SELECT_FG_COLOR, Tk_Offset(TkCanvas, textInfo.selFgColorPtr), + TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_CANVAS_SELECT_FG_MONO, Tk_Offset(TkCanvas, textInfo.selFgColorPtr), + TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_CUSTOM, "-state", "state", "State", + "normal", Tk_Offset(TkCanvas, canvas_state), TK_CONFIG_DONT_SET_DEFAULT, + &stateOption}, + {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_CANVAS_TAKE_FOCUS, Tk_Offset(TkCanvas, takeFocus), + TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_PIXELS, "-width", "width", "Width", + DEF_CANVAS_WIDTH, Tk_Offset(TkCanvas, width), 0, NULL}, + {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", + DEF_CANVAS_X_SCROLL_CMD, Tk_Offset(TkCanvas, xScrollCmd), + TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_PIXELS, "-xscrollincrement", "xScrollIncrement", + "ScrollIncrement", + DEF_CANVAS_X_SCROLL_INCREMENT, Tk_Offset(TkCanvas, xScrollIncrement), + 0, NULL}, + {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", + DEF_CANVAS_Y_SCROLL_CMD, Tk_Offset(TkCanvas, yScrollCmd), + TK_CONFIG_NULL_OK, NULL}, + {TK_CONFIG_PIXELS, "-yscrollincrement", "yScrollIncrement", + "ScrollIncrement", + DEF_CANVAS_Y_SCROLL_INCREMENT, Tk_Offset(TkCanvas, yScrollIncrement), + 0, NULL}, + {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0, NULL} +}; + +/* + * List of all the item types known at present. This is *global* and is + * protected by typeListMutex. + */ + +static Tk_ItemType *typeList = NULL; + /* NULL means initialization hasn't been done + * yet. */ +TCL_DECLARE_MUTEX(typeListMutex) + +#ifndef USE_OLD_TAG_SEARCH +/* + * Uids for operands in compiled advanced tag search expressions. + * Initialization is done by GetStaticUids() + */ + +typedef struct { + Tk_Uid allUid; + Tk_Uid currentUid; + Tk_Uid andUid; + Tk_Uid orUid; + Tk_Uid xorUid; + Tk_Uid parenUid; + Tk_Uid negparenUid; + Tk_Uid endparenUid; + Tk_Uid tagvalUid; + Tk_Uid negtagvalUid; +} SearchUids; + +static Tcl_ThreadDataKey dataKey; +static SearchUids * GetStaticUids(void); +#endif /* USE_OLD_TAG_SEARCH */ + +/* + * Prototypes for functions defined later in this file: + */ + +static void CanvasBindProc(ClientData clientData, + XEvent *eventPtr); +static void CanvasBlinkProc(ClientData clientData); +static void CanvasCmdDeletedProc(ClientData clientData); +static void CanvasDoEvent(TkCanvas *canvasPtr, XEvent *eventPtr); +static void CanvasEventProc(ClientData clientData, + XEvent *eventPtr); +static int CanvasFetchSelection(ClientData clientData, int offset, + char *buffer, int maxBytes); +static Tk_Item * CanvasFindClosest(TkCanvas *canvasPtr, + double coords[2]); +static void CanvasFocusProc(TkCanvas *canvasPtr, int gotFocus); +static void CanvasLostSelection(ClientData clientData); +static void CanvasSelectTo(TkCanvas *canvasPtr, + Tk_Item *itemPtr, int index); +static void CanvasSetOrigin(TkCanvas *canvasPtr, + int xOrigin, int yOrigin); +static void CanvasUpdateScrollbars(TkCanvas *canvasPtr); +static int CanvasWidgetCmd(ClientData clientData, + Tcl_Interp *interp, int argc, + Tcl_Obj *const *argv); +static void CanvasWorldChanged(ClientData instanceData); +static int ConfigureCanvas(Tcl_Interp *interp, + TkCanvas *canvasPtr, int argc, + Tcl_Obj *const *argv, int flags); +static void DestroyCanvas(char *memPtr); +static void DisplayCanvas(ClientData clientData); +static void DoItem(Tcl_Obj *accumObj, + Tk_Item *itemPtr, Tk_Uid tag); +static void EventuallyRedrawItem(TkCanvas *canvasPtr, + Tk_Item *itemPtr); +#ifdef USE_OLD_TAG_SEARCH +static int FindItems(Tcl_Interp *interp, TkCanvas *canvasPtr, + int argc, Tcl_Obj *const *argv, + Tcl_Obj *newTagObj, int first); +#else /* USE_OLD_TAG_SEARCH */ +static int FindItems(Tcl_Interp *interp, TkCanvas *canvasPtr, + int argc, Tcl_Obj *const *argv, + Tcl_Obj *newTagObj, int first, + TagSearch **searchPtrPtr); +#endif /* USE_OLD_TAG_SEARCH */ +static int FindArea(Tcl_Interp *interp, TkCanvas *canvasPtr, + Tcl_Obj *const *argv, Tk_Uid uid, int enclosed); +static double GridAlign(double coord, double spacing); +static const char** TkGetStringsFromObjs(int argc, Tcl_Obj *const *objv); +static void InitCanvas(void); +#ifdef USE_OLD_TAG_SEARCH +static Tk_Item * NextItem(TagSearch *searchPtr); +#endif /* USE_OLD_TAG_SEARCH */ +static void PickCurrentItem(TkCanvas *canvasPtr, XEvent *eventPtr); +static Tcl_Obj * ScrollFractions(int screen1, + int screen2, int object1, int object2); +#ifdef USE_OLD_TAG_SEARCH +static void RelinkItems(TkCanvas *canvasPtr, + Tcl_Obj *tag, Tk_Item *prevPtr); +static Tk_Item * StartTagSearch(TkCanvas *canvasPtr, + Tcl_Obj *tag, TagSearch *searchPtr); +#else /* USE_OLD_TAG_SEARCH */ +static int RelinkItems(TkCanvas *canvasPtr, Tcl_Obj *tag, + Tk_Item *prevPtr, TagSearch **searchPtrPtr); +static void TagSearchExprInit(TagSearchExpr **exprPtrPtr); +static void TagSearchExprDestroy(TagSearchExpr *expr); +static void TagSearchDestroy(TagSearch *searchPtr); +static int TagSearchScan(TkCanvas *canvasPtr, + Tcl_Obj *tag, TagSearch **searchPtrPtr); +static int TagSearchScanExpr(Tcl_Interp *interp, + TagSearch *searchPtr, TagSearchExpr *expr); +static int TagSearchEvalExpr(TagSearchExpr *expr, + Tk_Item *itemPtr); +static Tk_Item * TagSearchFirst(TagSearch *searchPtr); +static Tk_Item * TagSearchNext(TagSearch *searchPtr); +#endif /* USE_OLD_TAG_SEARCH */ + +/* + * The structure below defines canvas class behavior by means of functions + * that can be invoked from generic window code. + */ + +static const Tk_ClassProcs canvasClass = { + sizeof(Tk_ClassProcs), /* size */ + CanvasWorldChanged, /* worldChangedProc */ + NULL, /* createProc */ + NULL /* modalProc */ +}; + +/* + * Macros that significantly simplify all code that finds items. + */ + +#ifdef USE_OLD_TAG_SEARCH +#define FIRST_CANVAS_ITEM_MATCHING(objPtr,searchPtrPtr,errorExitClause) \ + itemPtr = StartTagSearch(canvasPtr,(objPtr),&search) +#define FOR_EVERY_CANVAS_ITEM_MATCHING(objPtr,searchPtrPtr,errorExitClause) \ + for (itemPtr = StartTagSearch(canvasPtr, (objPtr), &search); \ + itemPtr != NULL; itemPtr = NextItem(&search)) +#define FIND_ITEMS(objPtr, n) \ + FindItems(interp, canvasPtr, objc, objv, (objPtr), (n)) +#define RELINK_ITEMS(objPtr, itemPtr) \ + RelinkItems(canvasPtr, (objPtr), (itemPtr)) +#else /* USE_OLD_TAG_SEARCH */ +#define FIRST_CANVAS_ITEM_MATCHING(objPtr,searchPtrPtr,errorExitClause) \ + if ((result=TagSearchScan(canvasPtr,(objPtr),(searchPtrPtr))) != TCL_OK){ \ + errorExitClause; \ + } \ + itemPtr = TagSearchFirst(*(searchPtrPtr)); +#define FOR_EVERY_CANVAS_ITEM_MATCHING(objPtr,searchPtrPtr,errorExitClause) \ + if ((result=TagSearchScan(canvasPtr,(objPtr),(searchPtrPtr))) != TCL_OK){ \ + errorExitClause; \ + } \ + for (itemPtr = TagSearchFirst(*(searchPtrPtr)); \ + itemPtr != NULL; itemPtr = TagSearchNext(*(searchPtrPtr))) +#define FIND_ITEMS(objPtr, n) \ + FindItems(interp, canvasPtr, objc, objv, (objPtr), (n), &searchPtr) +#define RELINK_ITEMS(objPtr, itemPtr) \ + result = RelinkItems(canvasPtr, (objPtr), (itemPtr), &searchPtr) +#endif /* USE_OLD_TAG_SEARCH */ + +/* + * ---------------------------------------------------------------------- + * + * AlwaysRedraw, ItemConfigure, ItemCoords, etc. -- + * + * Helper functions that make access to canvas item functions simpler. + * Note that these are all inline functions. + * + * ---------------------------------------------------------------------- + */ + +static inline int +AlwaysRedraw( + Tk_Item *itemPtr) +{ + return itemPtr->typePtr->alwaysRedraw & 1; +} + +static inline int +ItemConfigure( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Interp *interp = canvasPtr->interp; + int result; + + if (itemPtr->typePtr->alwaysRedraw & TK_CONFIG_OBJS) { + result = itemPtr->typePtr->configProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc, objv, TK_CONFIG_ARGV_ONLY); + } else { + const char **args = TkGetStringsFromObjs(objc, objv); + + result = itemPtr->typePtr->configProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc, (Tcl_Obj **) args, TK_CONFIG_ARGV_ONLY); + if (args != NULL) { + ckfree(args); + } + } + return result; +} + +static inline int +ItemConfigInfo( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + Tcl_Obj *fieldName) +{ + return Tk_ConfigureInfo(canvasPtr->interp, canvasPtr->tkwin, + itemPtr->typePtr->configSpecs, (char *) itemPtr, + (fieldName ? Tcl_GetString(fieldName) : NULL), 0); +} + +static inline int +ItemConfigValue( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + Tcl_Obj *fieldName) +{ + return Tk_ConfigureValue(canvasPtr->interp, canvasPtr->tkwin, + itemPtr->typePtr->configSpecs, (char *) itemPtr, + Tcl_GetString(fieldName), 0); +} + +static inline int +ItemCoords( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Interp *interp = canvasPtr->interp; + int result; + + if (itemPtr->typePtr->coordProc == NULL) { + result = TCL_OK; + } else if (itemPtr->typePtr->alwaysRedraw & TK_CONFIG_OBJS) { + result = itemPtr->typePtr->coordProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc, objv); + } else { + const char **args = TkGetStringsFromObjs(objc, objv); + + result = itemPtr->typePtr->coordProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc, (Tcl_Obj **) args); + if (args != NULL) { + ckfree(args); + } + } + return result; +} + +static inline int +ItemCreate( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, /* Warning: incomplete! typePtr field must be + * set by this point. */ + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Interp *interp = canvasPtr->interp; + int result; + + if (itemPtr->typePtr->alwaysRedraw & TK_CONFIG_OBJS) { + result = itemPtr->typePtr->createProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc-3, objv+3); + } else { + const char **args = TkGetStringsFromObjs(objc-3, objv+3); + + result = itemPtr->typePtr->createProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objc-3, (Tcl_Obj **) args); + if (args != NULL) { + ckfree(args); + } + } + return result; +} + +static inline void +ItemCursor( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int index) +{ + itemPtr->typePtr->icursorProc((Tk_Canvas) canvasPtr, itemPtr, index); +} + +static inline void +ItemDelChars( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int first, + int last) +{ + itemPtr->typePtr->dCharsProc((Tk_Canvas) canvasPtr, itemPtr, first, last); +} + +static inline void +ItemDelete( + TkCanvas *canvasPtr, + Tk_Item *itemPtr) +{ + itemPtr->typePtr->deleteProc((Tk_Canvas) canvasPtr, itemPtr, + canvasPtr->display); +} + +static inline void +ItemDisplay( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + Pixmap pixmap, + int screenX1, int screenY1, + int width, int height) +{ + itemPtr->typePtr->displayProc((Tk_Canvas) canvasPtr, itemPtr, + canvasPtr->display, pixmap, screenX1, screenY1, width, height); +} + +static inline int +ItemIndex( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + Tcl_Obj *objPtr, + int *indexPtr) +{ + Tcl_Interp *interp = canvasPtr->interp; + + if (itemPtr->typePtr->indexProc == NULL) { + return TCL_OK; + } else if (itemPtr->typePtr->alwaysRedraw & TK_CONFIG_OBJS) { + return itemPtr->typePtr->indexProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, objPtr, indexPtr); + } else { + return itemPtr->typePtr->indexProc(interp, (Tk_Canvas) canvasPtr, + itemPtr, (Tcl_Obj *) Tcl_GetString(objPtr), indexPtr); + } +} + +static inline void +ItemInsert( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int beforeThis, + Tcl_Obj *toInsert) +{ + if (itemPtr->typePtr->alwaysRedraw & TK_CONFIG_OBJS) { + itemPtr->typePtr->insertProc((Tk_Canvas) canvasPtr, itemPtr, + beforeThis, toInsert); + } else { + itemPtr->typePtr->insertProc((Tk_Canvas) canvasPtr, itemPtr, + beforeThis, (Tcl_Obj *) Tcl_GetString(toInsert)); + } +} + +static inline int +ItemOverlap( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double rect[]) +{ + return itemPtr->typePtr->areaProc((Tk_Canvas) canvasPtr, itemPtr, rect); +} + +static inline double +ItemPoint( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double coords[], + double halo) +{ + double dist; + + dist = itemPtr->typePtr->pointProc((Tk_Canvas) canvasPtr, itemPtr, + coords) - halo; + return (dist < 0.0) ? 0.0 : dist; +} + +static inline void +ItemScale( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double xOrigin, double yOrigin, + double xScale, double yScale) +{ + itemPtr->typePtr->scaleProc((Tk_Canvas) canvasPtr, itemPtr, + xOrigin, yOrigin, xScale, yScale); +} + +static inline int +ItemSelection( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + int offset, + char *buffer, + int maxBytes) +{ + if (itemPtr == NULL || itemPtr->typePtr->selectionProc == NULL) { + return -1; + } + + return itemPtr->typePtr->selectionProc((Tk_Canvas) canvasPtr, itemPtr, + offset, buffer, maxBytes); +} + +static inline void +ItemTranslate( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double xDelta, + double yDelta) +{ + itemPtr->typePtr->translateProc((Tk_Canvas) canvasPtr, itemPtr, + xDelta, yDelta); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasObjCmd -- + * + * This function is invoked to process the "canvas" Tcl command. See the + * user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +Tk_CanvasObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + Tcl_Obj *const argv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + TkCanvas *canvasPtr; + Tk_Window newWin; + + if (typeList == NULL) { + InitCanvas(); + } + + if (argc < 2) { + Tcl_WrongNumArgs(interp, 1, argv, "pathName ?-option value ...?"); + return TCL_ERROR; + } + + newWin = Tk_CreateWindowFromPath(interp,tkwin,Tcl_GetString(argv[1]),NULL); + if (newWin == NULL) { + return TCL_ERROR; + } + + /* + * Initialize fields that won't be initialized by ConfigureCanvas, or + * which ConfigureCanvas expects to have reasonable values (e.g. resource + * pointers). + */ + + canvasPtr = ckalloc(sizeof(TkCanvas)); + canvasPtr->tkwin = newWin; + canvasPtr->display = Tk_Display(newWin); + canvasPtr->interp = interp; + canvasPtr->widgetCmd = Tcl_CreateObjCommand(interp, + Tk_PathName(canvasPtr->tkwin), CanvasWidgetCmd, canvasPtr, + CanvasCmdDeletedProc); + canvasPtr->firstItemPtr = NULL; + canvasPtr->lastItemPtr = NULL; + canvasPtr->borderWidth = 0; + canvasPtr->bgBorder = NULL; + canvasPtr->relief = TK_RELIEF_FLAT; + canvasPtr->highlightWidth = 0; + canvasPtr->highlightBgColorPtr = NULL; + canvasPtr->highlightColorPtr = NULL; + canvasPtr->inset = 0; + canvasPtr->pixmapGC = None; + canvasPtr->width = None; + canvasPtr->height = None; + canvasPtr->confine = 0; + canvasPtr->textInfo.selBorder = NULL; + canvasPtr->textInfo.selBorderWidth = 0; + canvasPtr->textInfo.selFgColorPtr = NULL; + canvasPtr->textInfo.selItemPtr = NULL; + canvasPtr->textInfo.selectFirst = -1; + canvasPtr->textInfo.selectLast = -1; + canvasPtr->textInfo.anchorItemPtr = NULL; + canvasPtr->textInfo.selectAnchor = 0; + canvasPtr->textInfo.insertBorder = NULL; + canvasPtr->textInfo.insertWidth = 0; + canvasPtr->textInfo.insertBorderWidth = 0; + canvasPtr->textInfo.focusItemPtr = NULL; + canvasPtr->textInfo.gotFocus = 0; + canvasPtr->textInfo.cursorOn = 0; + canvasPtr->insertOnTime = 0; + canvasPtr->insertOffTime = 0; + canvasPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; + canvasPtr->xOrigin = canvasPtr->yOrigin = 0; + canvasPtr->drawableXOrigin = canvasPtr->drawableYOrigin = 0; + canvasPtr->bindingTable = NULL; + canvasPtr->currentItemPtr = NULL; + canvasPtr->newCurrentPtr = NULL; + canvasPtr->closeEnough = 0.0; + canvasPtr->pickEvent.type = LeaveNotify; + canvasPtr->pickEvent.xcrossing.x = 0; + canvasPtr->pickEvent.xcrossing.y = 0; + canvasPtr->state = 0; + canvasPtr->xScrollCmd = NULL; + canvasPtr->yScrollCmd = NULL; + canvasPtr->scrollX1 = 0; + canvasPtr->scrollY1 = 0; + canvasPtr->scrollX2 = 0; + canvasPtr->scrollY2 = 0; + canvasPtr->regionString = NULL; + canvasPtr->xScrollIncrement = 0; + canvasPtr->yScrollIncrement = 0; + canvasPtr->scanX = 0; + canvasPtr->scanXOrigin = 0; + canvasPtr->scanY = 0; + canvasPtr->scanYOrigin = 0; + canvasPtr->hotPtr = NULL; + canvasPtr->hotPrevPtr = NULL; + canvasPtr->cursor = None; + canvasPtr->takeFocus = NULL; + canvasPtr->pixelsPerMM = WidthOfScreen(Tk_Screen(newWin)); + canvasPtr->pixelsPerMM /= WidthMMOfScreen(Tk_Screen(newWin)); + canvasPtr->flags = 0; + canvasPtr->nextId = 1; + canvasPtr->psInfo = NULL; + canvasPtr->canvas_state = TK_STATE_NORMAL; + canvasPtr->tsoffset.flags = 0; + canvasPtr->tsoffset.xoffset = 0; + canvasPtr->tsoffset.yoffset = 0; +#ifndef USE_OLD_TAG_SEARCH + canvasPtr->bindTagExprs = NULL; +#endif + Tcl_InitHashTable(&canvasPtr->idTable, TCL_ONE_WORD_KEYS); + + Tk_SetClass(canvasPtr->tkwin, "Canvas"); + Tk_SetClassProcs(canvasPtr->tkwin, &canvasClass, canvasPtr); + Tk_CreateEventHandler(canvasPtr->tkwin, + ExposureMask|StructureNotifyMask|FocusChangeMask, + CanvasEventProc, canvasPtr); + Tk_CreateEventHandler(canvasPtr->tkwin, KeyPressMask|KeyReleaseMask + |ButtonPressMask|ButtonReleaseMask|EnterWindowMask + |LeaveWindowMask|PointerMotionMask|VirtualEventMask, + CanvasBindProc, canvasPtr); + Tk_CreateSelHandler(canvasPtr->tkwin, XA_PRIMARY, XA_STRING, + CanvasFetchSelection, canvasPtr, XA_STRING); + if (ConfigureCanvas(interp, canvasPtr, argc-2, argv+2, 0) != TCL_OK) { + goto error; + } + + Tcl_SetObjResult(interp, TkNewWindowObj(canvasPtr->tkwin)); + return TCL_OK; + + error: + Tk_DestroyWindow(canvasPtr->tkwin); + return TCL_ERROR; +} + +/* + *-------------------------------------------------------------- + * + * CanvasWidgetCmd -- + * + * This function is invoked to process the Tcl command that corresponds + * to a widget managed by this module. See the user documentation for + * details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +CanvasWidgetCmd( + ClientData clientData, /* Information about canvas widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + TkCanvas *canvasPtr = clientData; + int c, result; + Tk_Item *itemPtr = NULL; /* Initialization needed only to prevent + * compiler warning. */ +#ifdef USE_OLD_TAG_SEARCH + TagSearch search; +#else /* USE_OLD_TAG_SEARCH */ + TagSearch *searchPtr = NULL;/* Allocated by first TagSearchScan, freed by + * TagSearchDestroy */ +#endif /* USE_OLD_TAG_SEARCH */ + + int index; + static const char *const optionStrings[] = { + "addtag", "bbox", "bind", "canvasx", + "canvasy", "cget", "configure", "coords", + "create", "dchars", "delete", "dtag", + "find", "focus", "gettags", "icursor", + "imove", "index", "insert", "itemcget", + "itemconfigure", + "lower", "move", "moveto", "postscript", + "raise", "rchars", "scale", "scan", + "select", "type", "xview", "yview", + NULL + }; + enum options { + CANV_ADDTAG, CANV_BBOX, CANV_BIND, CANV_CANVASX, + CANV_CANVASY, CANV_CGET, CANV_CONFIGURE, CANV_COORDS, + CANV_CREATE, CANV_DCHARS, CANV_DELETE, CANV_DTAG, + CANV_FIND, CANV_FOCUS, CANV_GETTAGS, CANV_ICURSOR, + CANV_IMOVE, CANV_INDEX, CANV_INSERT, CANV_ITEMCGET, + CANV_ITEMCONFIGURE, + CANV_LOWER, CANV_MOVE, CANV_MOVETO, CANV_POSTSCRIPT, + CANV_RAISE, CANV_RCHARS, CANV_SCALE, CANV_SCAN, + CANV_SELECT, CANV_TYPE, CANV_XVIEW, CANV_YVIEW + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + Tcl_Preserve(canvasPtr); + + result = TCL_OK; + switch ((enum options) index) { + case CANV_ADDTAG: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "tag searchCommand ?arg ...?"); + result = TCL_ERROR; + goto done; + } + result = FIND_ITEMS(objv[2], 3); + break; + + case CANV_BBOX: { + int i, gotAny; + int x1 = 0, y1 = 0, x2 = 0, y2 = 0; /* Initializations needed only + * to prevent overcautious + * compiler warnings. */ + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?tagOrId ...?"); + result = TCL_ERROR; + goto done; + } + gotAny = 0; + for (i = 2; i < objc; i++) { + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[i], &searchPtr, goto done) { + if ((itemPtr->x1 >= itemPtr->x2) + || (itemPtr->y1 >= itemPtr->y2)) { + continue; + } + if (!gotAny) { + x1 = itemPtr->x1; + y1 = itemPtr->y1; + x2 = itemPtr->x2; + y2 = itemPtr->y2; + gotAny = 1; + } else { + if (itemPtr->x1 < x1) { + x1 = itemPtr->x1; + } + if (itemPtr->y1 < y1) { + y1 = itemPtr->y1; + } + if (itemPtr->x2 > x2) { + x2 = itemPtr->x2; + } + if (itemPtr->y2 > y2) { + y2 = itemPtr->y2; + } + } + } + } + if (gotAny) { + Tcl_Obj *resultObjs[4]; + + resultObjs[0] = Tcl_NewIntObj(x1); + resultObjs[1] = Tcl_NewIntObj(y1); + resultObjs[2] = Tcl_NewIntObj(x2); + resultObjs[3] = Tcl_NewIntObj(y2); + Tcl_SetObjResult(interp, Tcl_NewListObj(4, resultObjs)); + } + break; + } + case CANV_BIND: { + ClientData object; + + if ((objc < 3) || (objc > 5)) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?sequence? ?command?"); + result = TCL_ERROR; + goto done; + } + + /* + * Figure out what object to use for the binding (individual item vs. + * tag). + */ + + object = NULL; +#ifdef USE_OLD_TAG_SEARCH + if (isdigit(UCHAR(Tcl_GetString(objv[2])[0]))) { + int id; + char *end; + Tcl_HashEntry *entryPtr; + + id = strtoul(Tcl_GetString(objv[2]), &end, 0); + if (*end != 0) { + goto bindByTag; + } + entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *) id); + if (entryPtr != NULL) { + itemPtr = Tcl_GetHashValue(entryPtr); + object = itemPtr; + } + + if (object == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "item \"%s\" doesn't exist", Tcl_GetString(objv[2]))); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "CANVAS_ITEM", + Tcl_GetString(objv[2]), NULL); + result = TCL_ERROR; + goto done; + } + } else { + bindByTag: + object = Tk_GetUid(Tcl_GetString(objv[2])); + } +#else /* USE_OLD_TAG_SEARCH */ + result = TagSearchScan(canvasPtr, objv[2], &searchPtr); + if (result != TCL_OK) { + goto done; + } + if (searchPtr->type == SEARCH_TYPE_ID) { + Tcl_HashEntry *entryPtr; + + entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, + (char *) INT2PTR(searchPtr->id)); + if (entryPtr != NULL) { + itemPtr = Tcl_GetHashValue(entryPtr); + object = itemPtr; + } + + if (object == 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "item \"%s\" doesn't exist", Tcl_GetString(objv[2]))); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "CANVAS_ITEM", + Tcl_GetString(objv[2]), NULL); + result = TCL_ERROR; + goto done; + } + } else { + object = (ClientData) searchPtr->expr->uid; + } +#endif /* USE_OLD_TAG_SEARCH */ + + /* + * Make a binding table if the canvas doesn't already have one. + */ + + if (canvasPtr->bindingTable == NULL) { + canvasPtr->bindingTable = Tk_CreateBindingTable(interp); + } + + if (objc == 5) { + int append = 0; + unsigned long mask; + const char *argv4 = Tcl_GetString(objv[4]); + + if (argv4[0] == 0) { + result = Tk_DeleteBinding(interp, canvasPtr->bindingTable, + object, Tcl_GetString(objv[3])); + goto done; + } +#ifndef USE_OLD_TAG_SEARCH + if (searchPtr->type == SEARCH_TYPE_EXPR) { + /* + * If new tag expression, then insert in linked list. + */ + + TagSearchExpr *expr, **lastPtr; + + lastPtr = &(canvasPtr->bindTagExprs); + while ((expr = *lastPtr) != NULL) { + if (expr->uid == searchPtr->expr->uid) { + break; + } + lastPtr = &(expr->next); + } + if (!expr) { + /* + * Transfer ownership of expr to bindTagExprs list. + */ + + *lastPtr = searchPtr->expr; + searchPtr->expr->next = NULL; + + /* + * Flag in TagSearch that expr has changed ownership so + * that TagSearchDestroy doesn't try to free it. + */ + + searchPtr->expr = NULL; + } + } +#endif /* not USE_OLD_TAG_SEARCH */ + if (argv4[0] == '+') { + argv4++; + append = 1; + } + mask = Tk_CreateBinding(interp, canvasPtr->bindingTable, + object, Tcl_GetString(objv[3]), argv4, append); + if (mask == 0) { + result = TCL_ERROR; + goto done; + } + if (mask & (unsigned) ~(ButtonMotionMask|Button1MotionMask + |Button2MotionMask|Button3MotionMask|Button4MotionMask + |Button5MotionMask|ButtonPressMask|ButtonReleaseMask + |EnterWindowMask|LeaveWindowMask|KeyPressMask + |KeyReleaseMask|PointerMotionMask|VirtualEventMask)) { + Tk_DeleteBinding(interp, canvasPtr->bindingTable, + object, Tcl_GetString(objv[3])); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "requested illegal events; only key, button, motion," + " enter, leave, and virtual events may be used", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "BAD_EVENTS", NULL); + result = TCL_ERROR; + goto done; + } + } else if (objc == 4) { + const char *command; + + command = Tk_GetBinding(interp, canvasPtr->bindingTable, + object, Tcl_GetString(objv[3])); + if (command == NULL) { + const char *string = Tcl_GetString(Tcl_GetObjResult(interp)); + + /* + * Ignore missing binding errors. This is a special hack that + * relies on the error message returned by FindSequence in + * tkBind.c. + */ + + if (string[0] != '\0') { + result = TCL_ERROR; + goto done; + } + Tcl_ResetResult(interp); + } else { + Tcl_SetObjResult(interp, Tcl_NewStringObj(command, -1)); + } + } else { + Tk_GetAllBindings(interp, canvasPtr->bindingTable, object); + } + break; + } + case CANV_CANVASX: { + int x; + double grid; + + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "screenx ?gridspacing?"); + result = TCL_ERROR; + goto done; + } + if (Tk_GetPixelsFromObj(interp, canvasPtr->tkwin, objv[2], + &x) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + if (objc == 4) { + if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[3], &grid) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + } else { + grid = 0.0; + } + x += canvasPtr->xOrigin; + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(GridAlign((double)x,grid))); + break; + } + case CANV_CANVASY: { + int y; + double grid; + + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "screeny ?gridspacing?"); + result = TCL_ERROR; + goto done; + } + if (Tk_GetPixelsFromObj(interp, canvasPtr->tkwin, objv[2], + &y) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + if (objc == 4) { + if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[3], &grid) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + } else { + grid = 0.0; + } + y += canvasPtr->yOrigin; + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(GridAlign((double)y,grid))); + break; + } + case CANV_CGET: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option"); + result = TCL_ERROR; + goto done; + } + result = Tk_ConfigureValue(interp, canvasPtr->tkwin, configSpecs, + (char *) canvasPtr, Tcl_GetString(objv[2]), 0); + break; + case CANV_CONFIGURE: + if (objc == 2) { + result = Tk_ConfigureInfo(interp, canvasPtr->tkwin, configSpecs, + (char *) canvasPtr, NULL, 0); + } else if (objc == 3) { + result = Tk_ConfigureInfo(interp, canvasPtr->tkwin, configSpecs, + (char *) canvasPtr, Tcl_GetString(objv[2]), 0); + } else { + result = ConfigureCanvas(interp, canvasPtr, objc-2, objv+2, + TK_CONFIG_ARGV_ONLY); + } + break; + case CANV_COORDS: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?x y x y ...?"); + result = TCL_ERROR; + goto done; + } + FIRST_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done); + if (itemPtr != NULL) { + if (objc != 3) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } + result = ItemCoords(canvasPtr, itemPtr, objc-3, objv+3); + if (objc != 3) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } + } + break; + case CANV_IMOVE: { + double ignored; + Tcl_Obj *tmpObj; + + if (objc != 6) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId index x y"); + result = TCL_ERROR; + goto done; + } + if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[4], &ignored) != TCL_OK + || Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[5], &ignored) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + /* + * Make a temporary object here that we can reuse for all the + * modifications in the loop. + */ + + tmpObj = Tcl_NewListObj(2, objv+4); + + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto doneImove) { + int index; + int x1, x2, y1, y2; + int dontRedraw1, dontRedraw2; + + /* + * The TK_MOVABLE_POINTS flag should only be set for types that + * support the same semantics of index, dChars and insert methods + * as lines and canvases. + */ + + if (itemPtr == NULL || + !(itemPtr->typePtr->alwaysRedraw & TK_MOVABLE_POINTS)) { + continue; + } + + result = ItemIndex(canvasPtr, itemPtr, objv[3], &index); + if (result != TCL_OK) { + break; + } + + /* + * Redraw both item's old and new areas: it's possible that a + * replace could result in a new area larger than the old area. + * Except if the dCharsProc or insertProc sets the + * TK_ITEM_DONT_REDRAW flag, nothing more needs to be done. + */ + + x1 = itemPtr->x1; y1 = itemPtr->y1; + x2 = itemPtr->x2; y2 = itemPtr->y2; + + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemDelChars(canvasPtr, itemPtr, index, index); + dontRedraw1 = itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW; + + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemInsert(canvasPtr, itemPtr, index, tmpObj); + dontRedraw2 = itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW; + + if (!(dontRedraw1 && dontRedraw2)) { + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + x1, y1, x2, y2); + EventuallyRedrawItem(canvasPtr, itemPtr); + } + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + } + + doneImove: + Tcl_DecrRefCount(tmpObj); + break; + } + case CANV_CREATE: { + Tk_ItemType *typePtr; + Tk_ItemType *matchPtr = NULL; + Tk_Item *itemPtr; + int isNew = 0; + Tcl_HashEntry *entryPtr; + const char *arg; + size_t length; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "type coords ?arg ...?"); + result = TCL_ERROR; + goto done; + } + arg = Tcl_GetString(objv[2]); + length = objv[2]->length; + c = arg[0]; + + /* + * Lock because the list of types is a global resource that could be + * updated by another thread. That's fairly unlikely, but not + * impossible. + */ + + Tcl_MutexLock(&typeListMutex); + for (typePtr = typeList; typePtr != NULL; typePtr = typePtr->nextPtr){ + if ((c == typePtr->name[0]) + && (!strncmp(arg, typePtr->name, length))) { + if (matchPtr != NULL) { + Tcl_MutexUnlock(&typeListMutex); + goto badType; + } + matchPtr = typePtr; + } + } + + /* + * Can unlock now because we no longer look at the fields of the + * matched item type that are potentially modified by other threads. + */ + + Tcl_MutexUnlock(&typeListMutex); + if (matchPtr == NULL) { + badType: + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unknown or ambiguous item type \"%s\"", arg)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "CANVAS_ITEM_TYPE", arg, + NULL); + result = TCL_ERROR; + goto done; + } + if (objc < 4) { + /* + * Allow more specific error return. + */ + + Tcl_WrongNumArgs(interp, 3, objv, "coords ?arg ...?"); + result = TCL_ERROR; + goto done; + } + + typePtr = matchPtr; + itemPtr = ckalloc(typePtr->itemSize); + itemPtr->id = canvasPtr->nextId; + canvasPtr->nextId++; + itemPtr->tagPtr = itemPtr->staticTagSpace; + itemPtr->tagSpace = TK_TAG_SPACE; + itemPtr->numTags = 0; + itemPtr->typePtr = typePtr; + itemPtr->state = TK_STATE_NULL; + itemPtr->redraw_flags = 0; + + if (ItemCreate(canvasPtr, itemPtr, objc, objv) != TCL_OK) { + ckfree(itemPtr); + result = TCL_ERROR; + goto done; + } + + itemPtr->nextPtr = NULL; + entryPtr = Tcl_CreateHashEntry(&canvasPtr->idTable, + (char *) INT2PTR(itemPtr->id), &isNew); + Tcl_SetHashValue(entryPtr, itemPtr); + itemPtr->prevPtr = canvasPtr->lastItemPtr; + canvasPtr->hotPtr = itemPtr; + canvasPtr->hotPrevPtr = canvasPtr->lastItemPtr; + if (canvasPtr->lastItemPtr == NULL) { + canvasPtr->firstItemPtr = itemPtr; + } else { + canvasPtr->lastItemPtr->nextPtr = itemPtr; + } + canvasPtr->lastItemPtr = itemPtr; + itemPtr->redraw_flags |= FORCE_REDRAW; + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + Tcl_SetObjResult(interp, Tcl_NewIntObj(itemPtr->id)); + break; + } + case CANV_DCHARS: { + int first, last; + int x1, x2, y1, y2; + + if ((objc != 4) && (objc != 5)) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId first ?last?"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if ((itemPtr->typePtr->indexProc == NULL) + || (itemPtr->typePtr->dCharsProc == NULL)) { + continue; + } + result = ItemIndex(canvasPtr, itemPtr, objv[3], &first); + if (result != TCL_OK) { + goto done; + } + if (objc == 5) { + result = ItemIndex(canvasPtr, itemPtr, objv[4], &last); + if (result != TCL_OK) { + goto done; + } + } else { + last = first; + } + + /* + * Redraw both item's old and new areas: it's possible that a + * delete could result in a new area larger than the old area. + * Except if the dCharsProc sets the TK_ITEM_DONT_REDRAW flag, + * nothing more needs to be done. + */ + + x1 = itemPtr->x1; y1 = itemPtr->y1; + x2 = itemPtr->x2; y2 = itemPtr->y2; + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemDelChars(canvasPtr, itemPtr, first, last); + if (!(itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW)) { + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + x1, y1, x2, y2); + EventuallyRedrawItem(canvasPtr, itemPtr); + } + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + } + break; + } + case CANV_DELETE: { + int i; + Tcl_HashEntry *entryPtr; + + for (i = 2; i < objc; i++) { + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[i], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + if (canvasPtr->bindingTable != NULL) { + Tk_DeleteAllBindings(canvasPtr->bindingTable, itemPtr); + } + ItemDelete(canvasPtr, itemPtr); + if (itemPtr->tagPtr != itemPtr->staticTagSpace) { + ckfree(itemPtr->tagPtr); + } + entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, + (char *) INT2PTR(itemPtr->id)); + Tcl_DeleteHashEntry(entryPtr); + if (itemPtr->nextPtr != NULL) { + itemPtr->nextPtr->prevPtr = itemPtr->prevPtr; + } + if (itemPtr->prevPtr != NULL) { + itemPtr->prevPtr->nextPtr = itemPtr->nextPtr; + } + if (canvasPtr->firstItemPtr == itemPtr) { + canvasPtr->firstItemPtr = itemPtr->nextPtr; + if (canvasPtr->firstItemPtr == NULL) { + canvasPtr->lastItemPtr = NULL; + } + } + if (canvasPtr->lastItemPtr == itemPtr) { + canvasPtr->lastItemPtr = itemPtr->prevPtr; + } + ckfree(itemPtr); + if (itemPtr == canvasPtr->currentItemPtr) { + canvasPtr->currentItemPtr = NULL; + canvasPtr->flags |= REPICK_NEEDED; + } + if (itemPtr == canvasPtr->newCurrentPtr) { + canvasPtr->newCurrentPtr = NULL; + canvasPtr->flags |= REPICK_NEEDED; + } + if (itemPtr == canvasPtr->textInfo.focusItemPtr) { + canvasPtr->textInfo.focusItemPtr = NULL; + } + if (itemPtr == canvasPtr->textInfo.selItemPtr) { + canvasPtr->textInfo.selItemPtr = NULL; + } + if ((itemPtr == canvasPtr->hotPtr) + || (itemPtr == canvasPtr->hotPrevPtr)) { + canvasPtr->hotPtr = NULL; + } + } + } + break; + } + case CANV_DTAG: { + Tk_Uid tag; + int i; + + if ((objc != 3) && (objc != 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?tagToDelete?"); + result = TCL_ERROR; + goto done; + } + if (objc == 4) { + tag = Tk_GetUid(Tcl_GetString(objv[3])); + } else { + tag = Tk_GetUid(Tcl_GetString(objv[2])); + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + for (i = itemPtr->numTags-1; i >= 0; i--) { + if (itemPtr->tagPtr[i] == tag) { + itemPtr->tagPtr[i] = itemPtr->tagPtr[itemPtr->numTags-1]; + itemPtr->numTags--; + } + } + } + break; + } + case CANV_FIND: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "searchCommand ?arg ...?"); + result = TCL_ERROR; + goto done; + } + result = FIND_ITEMS(NULL, 2); + break; + case CANV_FOCUS: + if (objc > 3) { + Tcl_WrongNumArgs(interp, 2, objv, "?tagOrId?"); + result = TCL_ERROR; + goto done; + } + itemPtr = canvasPtr->textInfo.focusItemPtr; + if (objc == 2) { + if (itemPtr != NULL) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(itemPtr->id)); + } + goto done; + } + if (canvasPtr->textInfo.gotFocus) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } + if (Tcl_GetString(objv[2])[0] == 0) { + canvasPtr->textInfo.focusItemPtr = NULL; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if (itemPtr->typePtr->icursorProc != NULL) { + break; + } + } + if (itemPtr == NULL) { + goto done; + } + canvasPtr->textInfo.focusItemPtr = itemPtr; + if (canvasPtr->textInfo.gotFocus) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } + break; + case CANV_GETTAGS: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId"); + result = TCL_ERROR; + goto done; + } + FIRST_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done); + if (itemPtr != NULL) { + int i; + Tcl_Obj *resultObj = Tcl_NewObj(); + + for (i = 0; i < itemPtr->numTags; i++) { + Tcl_ListObjAppendElement(NULL, resultObj, + Tcl_NewStringObj(itemPtr->tagPtr[i], -1)); + } + Tcl_SetObjResult(interp, resultObj); + } + break; + case CANV_ICURSOR: { + int index; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId index"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if ((itemPtr->typePtr->indexProc == NULL) + || (itemPtr->typePtr->icursorProc == NULL)) { + goto done; + } + result = ItemIndex(canvasPtr, itemPtr, objv[3], &index); + if (result != TCL_OK) { + goto done; + } + ItemCursor(canvasPtr, itemPtr, index); + if ((itemPtr == canvasPtr->textInfo.focusItemPtr) + && (canvasPtr->textInfo.cursorOn)) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } + } + break; + } + case CANV_INDEX: { + int index; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId string"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if (itemPtr->typePtr->indexProc != NULL) { + break; + } + } + if (itemPtr == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't find an indexable item \"%s\"", + Tcl_GetString(objv[2]))); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "INDEXABLE_ITEM", NULL); + result = TCL_ERROR; + goto done; + } + result = ItemIndex(canvasPtr, itemPtr, objv[3], &index); + if (result != TCL_OK) { + goto done; + } + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); + break; + } + case CANV_INSERT: { + int beforeThis; + int x1, x2, y1, y2; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId beforeThis string"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if ((itemPtr->typePtr->indexProc == NULL) + || (itemPtr->typePtr->insertProc == NULL)) { + continue; + } + result = ItemIndex(canvasPtr, itemPtr, objv[3], &beforeThis); + if (result != TCL_OK) { + goto done; + } + + /* + * Redraw both item's old and new areas: it's possible that an + * insertion could result in a new area either larger or smaller + * than the old area. Except if the insertProc sets the + * TK_ITEM_DONT_REDRAW flag, nothing more needs to be done. + */ + + x1 = itemPtr->x1; y1 = itemPtr->y1; + x2 = itemPtr->x2; y2 = itemPtr->y2; + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemInsert(canvasPtr, itemPtr, beforeThis, objv[4]); + if (!(itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW)) { + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + x1, y1, x2, y2); + EventuallyRedrawItem(canvasPtr, itemPtr); + } + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + } + break; + } + case CANV_ITEMCGET: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId option"); + result = TCL_ERROR; + goto done; + } + FIRST_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done); + if (itemPtr != NULL) { + result = ItemConfigValue(canvasPtr, itemPtr, objv[3]); + } + break; + case CANV_ITEMCONFIGURE: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?-option value ...?"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if (objc == 3) { + result = ItemConfigInfo(canvasPtr, itemPtr, NULL); + } else if (objc == 4) { + result = ItemConfigInfo(canvasPtr, itemPtr, objv[3]); + } else { + EventuallyRedrawItem(canvasPtr, itemPtr); + result = ItemConfigure(canvasPtr, itemPtr, objc-3, objv+3); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + if ((result != TCL_OK) || (objc < 5)) { + break; + } + } + break; + case CANV_LOWER: { + Tk_Item *itemPtr; + + if ((objc != 3) && (objc != 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?belowThis?"); + result = TCL_ERROR; + goto done; + } + + /* + * First find the item just after which we'll insert the named items. + */ + + if (objc == 3) { + itemPtr = NULL; + } else { + FIRST_CANVAS_ITEM_MATCHING(objv[3], &searchPtr, goto done); + if (itemPtr == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "tagOrId \"%s\" doesn't match any items", + Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "ITEM", NULL); + result = TCL_ERROR; + goto done; + } + itemPtr = itemPtr->prevPtr; + } + RELINK_ITEMS(objv[2], itemPtr); + break; + } + case CANV_MOVE: { + double xAmount, yAmount; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId xAmount yAmount"); + result = TCL_ERROR; + goto done; + } + if ((Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, objv[3], + &xAmount) != TCL_OK) || (Tk_CanvasGetCoordFromObj(interp, + (Tk_Canvas) canvasPtr, objv[4], &yAmount) != TCL_OK)) { + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + ItemTranslate(canvasPtr, itemPtr, xAmount, yAmount); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + break; + } + case CANV_MOVETO: { + int xBlank, yBlank; + double xAmount, yAmount; + double oldX = 0, oldY = 0, newX, newY; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId x y"); + result = TCL_ERROR; + goto done; + } + + xBlank = 0; + if (Tcl_GetString(objv[3])[0] == '\0') { + xBlank = 1; + } else if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[3], &newX) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + yBlank = 0; + if (Tcl_GetString(objv[4])[0] == '\0') { + yBlank = 1; + } else if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[4], &newY) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + + FIRST_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done); + if (itemPtr != NULL) { + oldX = itemPtr->x1; + oldY = itemPtr->y1; + + /* + * Calculate the displacement. + */ + + if (xBlank) { + xAmount = 0; + } else { + xAmount = newX - oldX; + } + + if (yBlank) { + yAmount = 0; + } else { + yAmount = newY - oldY; + } + + /* + * Move the object(s). + */ + + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + ItemTranslate(canvasPtr, itemPtr, xAmount, yAmount); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + } + break; + } + case CANV_POSTSCRIPT: { + const char **args = TkGetStringsFromObjs(objc, objv); + + result = TkCanvPostscriptCmd(canvasPtr, interp, objc, args); + if (args != NULL) { + ckfree(args); + } + break; + } + case CANV_RAISE: { + Tk_Item *prevPtr; + + if ((objc != 3) && (objc != 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId ?aboveThis?"); + result = TCL_ERROR; + goto done; + } + + /* + * First find the item just after which we'll insert the named items. + */ + + if (objc == 3) { + prevPtr = canvasPtr->lastItemPtr; + } else { + prevPtr = NULL; + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[3], &searchPtr, goto done) { + prevPtr = itemPtr; + } + if (prevPtr == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "tagOrId \"%s\" doesn't match any items", + Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "ITEM", NULL); + result = TCL_ERROR; + goto done; + } + } + RELINK_ITEMS(objv[2], prevPtr); + break; + } + case CANV_RCHARS: { + int first, last; + int x1, x2, y1, y2; + int dontRedraw1, dontRedraw2; + + if (objc != 6) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId first last string"); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + if ((itemPtr->typePtr->indexProc == NULL) + || (itemPtr->typePtr->dCharsProc == NULL) + || (itemPtr->typePtr->insertProc == NULL)) { + continue; + } + result = ItemIndex(canvasPtr, itemPtr, objv[3], &first); + if (result != TCL_OK) { + goto done; + } + result = ItemIndex(canvasPtr, itemPtr, objv[4], &last); + if (result != TCL_OK) { + goto done; + } + + /* + * Redraw both item's old and new areas: it's possible that a + * replace could result in a new area larger than the old area. + * Except if the dCharsProc or insertProc sets the + * TK_ITEM_DONT_REDRAW flag, nothing more needs to be done. + */ + + x1 = itemPtr->x1; y1 = itemPtr->y1; + x2 = itemPtr->x2; y2 = itemPtr->y2; + + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemDelChars(canvasPtr, itemPtr, first, last); + dontRedraw1 = itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW; + + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + ItemInsert(canvasPtr, itemPtr, first, objv[5]); + dontRedraw2 = itemPtr->redraw_flags & TK_ITEM_DONT_REDRAW; + + if (!(dontRedraw1 && dontRedraw2)) { + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + x1, y1, x2, y2); + EventuallyRedrawItem(canvasPtr, itemPtr); + } + itemPtr->redraw_flags &= ~TK_ITEM_DONT_REDRAW; + } + break; + } + case CANV_SCALE: { + double xOrigin, yOrigin, xScale, yScale; + + if (objc != 7) { + Tcl_WrongNumArgs(interp, 2, objv, + "tagOrId xOrigin yOrigin xScale yScale"); + result = TCL_ERROR; + goto done; + } + if ((Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[3], &xOrigin) != TCL_OK) + || (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[4], &yOrigin) != TCL_OK) + || (Tcl_GetDoubleFromObj(interp, objv[5], &xScale)!=TCL_OK) + || (Tcl_GetDoubleFromObj(interp, objv[6], &yScale)!=TCL_OK)) { + result = TCL_ERROR; + goto done; + } + if ((xScale == 0.0) || (yScale == 0.0)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "scale factor cannot be zero", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "BAD_SCALE", NULL); + result = TCL_ERROR; + goto done; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + ItemScale(canvasPtr, itemPtr, xOrigin, yOrigin, xScale, yScale); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + break; + } + case CANV_SCAN: { + int x, y, gain = 10; + static const char *const optionStrings[] = { + "mark", "dragto", NULL + }; + + if (objc < 5) { + Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y ?dragGain?"); + result = TCL_ERROR; + } else if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, + "scan option", 0, &index) != TCL_OK) { + result = TCL_ERROR; + } else if ((objc != 5) && (objc != 5+index)) { + Tcl_WrongNumArgs(interp, 3, objv, index?"x y ?gain?":"x y"); + result = TCL_ERROR; + } else if ((Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)){ + result = TCL_ERROR; + } else if ((objc == 6) && + (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) { + result = TCL_ERROR; + } else if (!index) { + canvasPtr->scanX = x; + canvasPtr->scanXOrigin = canvasPtr->xOrigin; + canvasPtr->scanY = y; + canvasPtr->scanYOrigin = canvasPtr->yOrigin; + } else { + int newXOrigin, newYOrigin, tmp; + + /* + * Compute a new view origin for the canvas, amplifying the + * mouse motion. + */ + + tmp = canvasPtr->scanXOrigin - gain*(x - canvasPtr->scanX) + - canvasPtr->scrollX1; + newXOrigin = canvasPtr->scrollX1 + tmp; + tmp = canvasPtr->scanYOrigin - gain*(y - canvasPtr->scanY) + - canvasPtr->scrollY1; + newYOrigin = canvasPtr->scrollY1 + tmp; + CanvasSetOrigin(canvasPtr, newXOrigin, newYOrigin); + } + break; + } + case CANV_SELECT: { + int index, optionindex; + static const char *const optionStrings[] = { + "adjust", "clear", "from", "item", "to", NULL + }; + enum options { + CANV_ADJUST, CANV_CLEAR, CANV_FROM, CANV_ITEM, CANV_TO + }; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option ?tagOrId? ?arg?"); + result = TCL_ERROR; + goto done; + } + if (objc >= 4) { + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[3], &searchPtr, goto done) { + if ((itemPtr->typePtr->indexProc != NULL) + && (itemPtr->typePtr->selectionProc != NULL)){ + break; + } + } + if (itemPtr == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't find an indexable and selectable item \"%s\"", + Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SELECTABLE_ITEM", + NULL); + result = TCL_ERROR; + goto done; + } + } + if (objc == 5) { + result = ItemIndex(canvasPtr, itemPtr, objv[4], &index); + if (result != TCL_OK) { + goto done; + } + } + if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, + "select option", 0, &optionindex) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + switch ((enum options) optionindex) { + case CANV_ADJUST: + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "tagOrId index"); + result = TCL_ERROR; + goto done; + } + if (canvasPtr->textInfo.selItemPtr == itemPtr) { + if (index < (canvasPtr->textInfo.selectFirst + + canvasPtr->textInfo.selectLast)/2) { + canvasPtr->textInfo.selectAnchor = + canvasPtr->textInfo.selectLast + 1; + } else { + canvasPtr->textInfo.selectAnchor = + canvasPtr->textInfo.selectFirst; + } + } + CanvasSelectTo(canvasPtr, itemPtr, index); + break; + case CANV_CLEAR: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + result = TCL_ERROR; + goto done; + } + EventuallyRedrawItem(canvasPtr, canvasPtr->textInfo.selItemPtr); + canvasPtr->textInfo.selItemPtr = NULL; + break; + case CANV_FROM: + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "tagOrId index"); + result = TCL_ERROR; + goto done; + } + canvasPtr->textInfo.anchorItemPtr = itemPtr; + canvasPtr->textInfo.selectAnchor = index; + break; + case CANV_ITEM: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + result = TCL_ERROR; + goto done; + } + if (canvasPtr->textInfo.selItemPtr != NULL) { + Tcl_SetObjResult(interp, + Tcl_NewIntObj(canvasPtr->textInfo.selItemPtr->id)); + } + break; + case CANV_TO: + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId index"); + result = TCL_ERROR; + goto done; + } + CanvasSelectTo(canvasPtr, itemPtr, index); + break; + } + break; + } + case CANV_TYPE: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "tag"); + result = TCL_ERROR; + goto done; + } + FIRST_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done); + if (itemPtr != NULL) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj(itemPtr->typePtr->name, -1)); + } + break; + case CANV_XVIEW: { + int count, type; + int newX = 0; /* Initialization needed only to prevent gcc + * warnings. */ + double fraction; + const char **args; + + if (objc == 2) { + Tcl_SetObjResult(interp, ScrollFractions( + canvasPtr->xOrigin + canvasPtr->inset, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin) + - canvasPtr->inset, canvasPtr->scrollX1, + canvasPtr->scrollX2)); + break; + } + + args = TkGetStringsFromObjs(objc, objv); + type = Tk_GetScrollInfo(interp, objc, args, &fraction, &count); + if (args != NULL) { + ckfree(args); + } + switch (type) { + case TK_SCROLL_ERROR: + result = TCL_ERROR; + goto done; + case TK_SCROLL_MOVETO: + newX = canvasPtr->scrollX1 - canvasPtr->inset + + (int) (fraction * (canvasPtr->scrollX2 + - canvasPtr->scrollX1) + 0.5); + break; + case TK_SCROLL_PAGES: + newX = (int) (canvasPtr->xOrigin + count * .9 + * (Tk_Width(canvasPtr->tkwin) - 2*canvasPtr->inset)); + break; + case TK_SCROLL_UNITS: + if (canvasPtr->xScrollIncrement > 0) { + newX = canvasPtr->xOrigin + count*canvasPtr->xScrollIncrement; + } else { + newX = (int) (canvasPtr->xOrigin + count * .1 + * (Tk_Width(canvasPtr->tkwin) - 2*canvasPtr->inset)); + } + break; + } + CanvasSetOrigin(canvasPtr, newX, canvasPtr->yOrigin); + break; + } + case CANV_YVIEW: { + int count, type; + int newY = 0; /* Initialization needed only to prevent gcc + * warnings. */ + double fraction; + const char **args; + + if (objc == 2) { + Tcl_SetObjResult(interp, ScrollFractions( + canvasPtr->yOrigin + canvasPtr->inset, + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin) + - canvasPtr->inset, + canvasPtr->scrollY1, canvasPtr->scrollY2)); + break; + } + + args = TkGetStringsFromObjs(objc, objv); + type = Tk_GetScrollInfo(interp, objc, args, &fraction, &count); + if (args != NULL) { + ckfree(args); + } + switch (type) { + case TK_SCROLL_ERROR: + result = TCL_ERROR; + goto done; + case TK_SCROLL_MOVETO: + newY = canvasPtr->scrollY1 - canvasPtr->inset + (int) ( + fraction*(canvasPtr->scrollY2-canvasPtr->scrollY1) + 0.5); + break; + case TK_SCROLL_PAGES: + newY = (int) (canvasPtr->yOrigin + count * .9 + * (Tk_Height(canvasPtr->tkwin) - 2*canvasPtr->inset)); + break; + case TK_SCROLL_UNITS: + if (canvasPtr->yScrollIncrement > 0) { + newY = canvasPtr->yOrigin + count*canvasPtr->yScrollIncrement; + } else { + newY = (int) (canvasPtr->yOrigin + count * .1 + * (Tk_Height(canvasPtr->tkwin) - 2*canvasPtr->inset)); + } + break; + } + CanvasSetOrigin(canvasPtr, canvasPtr->xOrigin, newY); + break; + } + } + + done: +#ifndef USE_OLD_TAG_SEARCH + TagSearchDestroy(searchPtr); +#endif /* not USE_OLD_TAG_SEARCH */ + Tcl_Release(canvasPtr); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * DestroyCanvas -- + * + * This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean + * up the internal structure of a canvas at a safe time (when no-one is + * using it anymore). + * + * Results: + * None. + * + * Side effects: + * Everything associated with the canvas is freed up. + * + *---------------------------------------------------------------------- + */ + +static void +DestroyCanvas( + char *memPtr) /* Info about canvas widget. */ +{ + TkCanvas *canvasPtr = (TkCanvas *) memPtr; + Tk_Item *itemPtr; +#ifndef USE_OLD_TAG_SEARCH + TagSearchExpr *expr, *next; +#endif + + /* + * Free up all of the items in the canvas. + */ + + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = canvasPtr->firstItemPtr) { + canvasPtr->firstItemPtr = itemPtr->nextPtr; + ItemDelete(canvasPtr, itemPtr); + if (itemPtr->tagPtr != itemPtr->staticTagSpace) { + ckfree(itemPtr->tagPtr); + } + ckfree(itemPtr); + } + + /* + * Free up all the stuff that requires special handling, then let + * Tk_FreeOptions handle all the standard option-related stuff. + */ + + Tcl_DeleteHashTable(&canvasPtr->idTable); + if (canvasPtr->pixmapGC != None) { + Tk_FreeGC(canvasPtr->display, canvasPtr->pixmapGC); + } +#ifndef USE_OLD_TAG_SEARCH + expr = canvasPtr->bindTagExprs; + while (expr) { + next = expr->next; + TagSearchExprDestroy(expr); + expr = next; + } +#endif /* USE_OLD_TAG_SEARCH */ + Tcl_DeleteTimerHandler(canvasPtr->insertBlinkHandler); + if (canvasPtr->bindingTable != NULL) { + Tk_DeleteBindingTable(canvasPtr->bindingTable); + } + Tk_FreeOptions(configSpecs, (char *) canvasPtr, canvasPtr->display, 0); + canvasPtr->tkwin = NULL; + ckfree(canvasPtr); +} + +/* + *---------------------------------------------------------------------- + * + * ConfigureCanvas -- + * + * This function is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a canvas + * widget. + * + * Results: + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. + * + * Side effects: + * Configuration information, such as colors, border width, etc. get set + * for canvasPtr; old resources get freed, if there were any. + * + *---------------------------------------------------------------------- + */ + +static int +ConfigureCanvas( + Tcl_Interp *interp, /* Used for error reporting. */ + TkCanvas *canvasPtr, /* Information about widget; may or may not + * already have values for some fields. */ + int objc, /* Number of valid entries in objv. */ + Tcl_Obj *const objv[], /* Argument objects. */ + int flags) /* Flags to pass to Tk_ConfigureWidget. */ +{ + XGCValues gcValues; + GC newGC; + Tk_State old_canvas_state=canvasPtr->canvas_state; + + if (Tk_ConfigureWidget(interp, canvasPtr->tkwin, configSpecs, + objc, (const char **) objv, (char *) canvasPtr, + flags|TK_CONFIG_OBJS) != TCL_OK) { + return TCL_ERROR; + } + + /* + * A few options need special processing, such as setting the background + * from a 3-D border and creating a GC for copying bits to the screen. + */ + + Tk_SetBackgroundFromBorder(canvasPtr->tkwin, canvasPtr->bgBorder); + + if (canvasPtr->highlightWidth < 0) { + canvasPtr->highlightWidth = 0; + } + canvasPtr->inset = canvasPtr->borderWidth + canvasPtr->highlightWidth; + + gcValues.function = GXcopy; + gcValues.graphics_exposures = False; + gcValues.foreground = Tk_3DBorderColor(canvasPtr->bgBorder)->pixel; + newGC = Tk_GetGC(canvasPtr->tkwin, + GCFunction|GCGraphicsExposures|GCForeground, &gcValues); + if (canvasPtr->pixmapGC != None) { + Tk_FreeGC(canvasPtr->display, canvasPtr->pixmapGC); + } + canvasPtr->pixmapGC = newGC; + + /* + * Reconfigure items to reflect changed state disabled/normal. + */ + + if ( old_canvas_state != canvasPtr->canvas_state ) { + Tk_Item *itemPtr; + int result; + + for ( itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if ( itemPtr->state == TK_STATE_NULL ) { + result = (*itemPtr->typePtr->configProc)(canvasPtr->interp, + (Tk_Canvas) canvasPtr, itemPtr, 0, NULL, + TK_CONFIG_ARGV_ONLY); + if (result != TCL_OK) { + Tcl_ResetResult(canvasPtr->interp); + } + } + } + } + + /* + * Reset the desired dimensions for the window. + */ + + Tk_GeometryRequest(canvasPtr->tkwin, canvasPtr->width + 2*canvasPtr->inset, + canvasPtr->height + 2*canvasPtr->inset); + + /* + * Restart the cursor timing sequence in case the on-time or off-time just + * changed. + */ + + if (canvasPtr->textInfo.gotFocus) { + CanvasFocusProc(canvasPtr, 1); + } + + /* + * Recompute the scroll region. + */ + + canvasPtr->scrollX1 = 0; + canvasPtr->scrollY1 = 0; + canvasPtr->scrollX2 = 0; + canvasPtr->scrollY2 = 0; + if (canvasPtr->regionString != NULL) { + int argc2; + const char **argv2; + + if (Tcl_SplitList(canvasPtr->interp, canvasPtr->regionString, + &argc2, &argv2) != TCL_OK) { + return TCL_ERROR; + } + if (argc2 != 4) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad scrollRegion \"%s\"", canvasPtr->regionString)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SCROLL_REGION", NULL); + badRegion: + ckfree(canvasPtr->regionString); + ckfree(argv2); + canvasPtr->regionString = NULL; + return TCL_ERROR; + } + if ((Tk_GetPixels(canvasPtr->interp, canvasPtr->tkwin, + argv2[0], &canvasPtr->scrollX1) != TCL_OK) + || (Tk_GetPixels(canvasPtr->interp, canvasPtr->tkwin, + argv2[1], &canvasPtr->scrollY1) != TCL_OK) + || (Tk_GetPixels(canvasPtr->interp, canvasPtr->tkwin, + argv2[2], &canvasPtr->scrollX2) != TCL_OK) + || (Tk_GetPixels(canvasPtr->interp, canvasPtr->tkwin, + argv2[3], &canvasPtr->scrollY2) != TCL_OK)) { + goto badRegion; + } + ckfree(argv2); + } + + flags = canvasPtr->tsoffset.flags; + if (flags & TK_OFFSET_LEFT) { + canvasPtr->tsoffset.xoffset = 0; + } else if (flags & TK_OFFSET_CENTER) { + canvasPtr->tsoffset.xoffset = canvasPtr->width/2; + } else if (flags & TK_OFFSET_RIGHT) { + canvasPtr->tsoffset.xoffset = canvasPtr->width; + } + if (flags & TK_OFFSET_TOP) { + canvasPtr->tsoffset.yoffset = 0; + } else if (flags & TK_OFFSET_MIDDLE) { + canvasPtr->tsoffset.yoffset = canvasPtr->height/2; + } else if (flags & TK_OFFSET_BOTTOM) { + canvasPtr->tsoffset.yoffset = canvasPtr->height; + } + + /* + * Reset the canvas's origin (this is a no-op unless confine mode has just + * been turned on or the scroll region has changed). + */ + + CanvasSetOrigin(canvasPtr, canvasPtr->xOrigin, canvasPtr->yOrigin); + canvasPtr->flags |= UPDATE_SCROLLBARS|REDRAW_BORDERS; + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + canvasPtr->xOrigin, canvasPtr->yOrigin, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin), + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin)); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * CanvasWorldChanged -- + * + * This function is called when the world has changed in some way and the + * widget needs to recompute all its graphics contexts and determine its + * new geometry. + * + * Results: + * None. + * + * Side effects: + * Configures all items in the canvas with a empty argc/argv, for the + * side effect of causing all the items to recompute their geometry and + * to be redisplayed. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasWorldChanged( + ClientData instanceData) /* Information about widget. */ +{ + TkCanvas *canvasPtr = instanceData; + Tk_Item *itemPtr; + + itemPtr = canvasPtr->firstItemPtr; + for ( ; itemPtr != NULL; itemPtr = itemPtr->nextPtr) { + if (ItemConfigure(canvasPtr, itemPtr, 0, NULL) != TCL_OK) { + Tcl_ResetResult(canvasPtr->interp); + } + } + canvasPtr->flags |= REPICK_NEEDED; + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + canvasPtr->xOrigin, canvasPtr->yOrigin, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin), + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin)); +} + +/* + *---------------------------------------------------------------------- + * + * DisplayCanvas -- + * + * This function redraws the contents of a canvas window. It is invoked + * as a do-when-idle handler, so it only runs when there's nothing else + * for the application to do. + * + * Results: + * None. + * + * Side effects: + * Information appears on the screen. + * + *---------------------------------------------------------------------- + */ + +static void +DisplayCanvas( + ClientData clientData) /* Information about widget. */ +{ + TkCanvas *canvasPtr = clientData; + Tk_Window tkwin = canvasPtr->tkwin; + Tk_Item *itemPtr; + Pixmap pixmap; + int screenX1, screenX2, screenY1, screenY2, width, height; + + if (canvasPtr->tkwin == NULL) { + return; + } + + if (!Tk_IsMapped(tkwin)) { + goto done; + } + +#ifdef MAC_OSX_TK + /* + * If drawing is disabled, all we need to do is + * clear the REDRAW_PENDING flag. + */ + TkWindow *winPtr = (TkWindow *)(canvasPtr->tkwin); + MacDrawable *macWin = winPtr->privatePtr; + if (macWin && (macWin->flags & TK_DO_NOT_DRAW)){ + canvasPtr->flags &= ~REDRAW_PENDING; + return; + } +#endif + + /* + * Choose a new current item if that is needed (this could cause event + * handlers to be invoked). + */ + + while (canvasPtr->flags & REPICK_NEEDED) { + Tcl_Preserve(canvasPtr); + canvasPtr->flags &= ~REPICK_NEEDED; + PickCurrentItem(canvasPtr, &canvasPtr->pickEvent); + tkwin = canvasPtr->tkwin; + Tcl_Release(canvasPtr); + if (tkwin == NULL) { + return; + } + } + + /* + * Scan through the item list, registering the bounding box for all items + * that didn't do that for the final coordinates yet. This can be + * determined by the FORCE_REDRAW flag. + */ + + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if (itemPtr->redraw_flags & FORCE_REDRAW) { + itemPtr->redraw_flags &= ~FORCE_REDRAW; + EventuallyRedrawItem(canvasPtr, itemPtr); + itemPtr->redraw_flags &= ~FORCE_REDRAW; + } + } + + /* + * Compute the intersection between the area that needs redrawing and the + * area that's visible on the screen. + */ + + if ((canvasPtr->redrawX1 < canvasPtr->redrawX2) + && (canvasPtr->redrawY1 < canvasPtr->redrawY2)) { + screenX1 = canvasPtr->xOrigin + canvasPtr->inset; + screenY1 = canvasPtr->yOrigin + canvasPtr->inset; + screenX2 = canvasPtr->xOrigin + Tk_Width(tkwin) - canvasPtr->inset; + screenY2 = canvasPtr->yOrigin + Tk_Height(tkwin) - canvasPtr->inset; + if (canvasPtr->redrawX1 > screenX1) { + screenX1 = canvasPtr->redrawX1; + } + if (canvasPtr->redrawY1 > screenY1) { + screenY1 = canvasPtr->redrawY1; + } + if (canvasPtr->redrawX2 < screenX2) { + screenX2 = canvasPtr->redrawX2; + } + if (canvasPtr->redrawY2 < screenY2) { + screenY2 = canvasPtr->redrawY2; + } + if ((screenX1 >= screenX2) || (screenY1 >= screenY2)) { + goto borders; + } + + width = screenX2 - screenX1; + height = screenY2 - screenY1; + +#ifndef TK_NO_DOUBLE_BUFFERING + /* + * Redrawing is done in a temporary pixmap that is allocated here and + * freed at the end of the function. All drawing is done to the + * pixmap, and the pixmap is copied to the screen at the end of the + * function. The temporary pixmap serves two purposes: + * + * 1. It provides a smoother visual effect (no clearing and gradual + * redraw will be visible to users). + * 2. It allows us to redraw only the objects that overlap the redraw + * area. Otherwise incorrect results could occur from redrawing + * things that stick outside of the redraw area (we'd have to + * redraw everything in order to make the overlaps look right). + * + * Some tricky points about the pixmap: + * + * 1. We only allocate a large enough pixmap to hold the area that has + * to be redisplayed. This saves time in in the X server for large + * objects that cover much more than the area being redisplayed: + * only the area of the pixmap will actually have to be redrawn. + * 2. Some X servers (e.g. the one for DECstations) have troubles with + * with characters that overlap an edge of the pixmap (on the DEC + * servers, as of 8/18/92, such characters are drawn one pixel too + * far to the right). To handle this problem, make the pixmap a bit + * larger than is absolutely needed so that for normal-sized fonts + * the characters that overlap the edge of the pixmap will be + * outside the area we care about. + */ + + canvasPtr->drawableXOrigin = screenX1 - 30; + canvasPtr->drawableYOrigin = screenY1 - 30; + pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), + (screenX2 + 30 - canvasPtr->drawableXOrigin), + (screenY2 + 30 - canvasPtr->drawableYOrigin), + Tk_Depth(tkwin)); +#else + canvasPtr->drawableXOrigin = canvasPtr->xOrigin; + canvasPtr->drawableYOrigin = canvasPtr->yOrigin; + pixmap = Tk_WindowId(tkwin); + TkpClipDrawableToRect(Tk_Display(tkwin), pixmap, + screenX1 - canvasPtr->xOrigin, screenY1 - canvasPtr->yOrigin, + width, height); +#endif /* TK_NO_DOUBLE_BUFFERING */ + + /* + * Clear the area to be redrawn. + */ + + XFillRectangle(Tk_Display(tkwin), pixmap, canvasPtr->pixmapGC, + screenX1 - canvasPtr->drawableXOrigin, + screenY1 - canvasPtr->drawableYOrigin, (unsigned int) width, + (unsigned int) height); + + /* + * Scan through the item list, redrawing those items that need it. An + * item must be redraw if either (a) it intersects the smaller + * on-screen area or (b) it intersects the full canvas area and its + * type requests that it be redrawn always (e.g. so subwindows can be + * unmapped when they move off-screen). + */ + + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if ((itemPtr->x1 >= screenX2) + || (itemPtr->y1 >= screenY2) + || (itemPtr->x2 < screenX1) + || (itemPtr->y2 < screenY1)) { + if (!AlwaysRedraw(itemPtr) + || (itemPtr->x1 >= canvasPtr->redrawX2) + || (itemPtr->y1 >= canvasPtr->redrawY2) + || (itemPtr->x2 < canvasPtr->redrawX1) + || (itemPtr->y2 < canvasPtr->redrawY1)) { + continue; + } + } + if (itemPtr->state == TK_STATE_HIDDEN || + (itemPtr->state == TK_STATE_NULL && + canvasPtr->canvas_state == TK_STATE_HIDDEN)) { + continue; + } + ItemDisplay(canvasPtr, itemPtr, pixmap, screenX1, screenY1, width, + height); + } + +#ifndef TK_NO_DOUBLE_BUFFERING + /* + * Copy from the temporary pixmap to the screen, then free up the + * temporary pixmap. + */ + + XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), + canvasPtr->pixmapGC, + screenX1 - canvasPtr->drawableXOrigin, + screenY1 - canvasPtr->drawableYOrigin, + (unsigned int) width, (unsigned int) height, + screenX1 - canvasPtr->xOrigin, screenY1 - canvasPtr->yOrigin); + Tk_FreePixmap(Tk_Display(tkwin), pixmap); +#else + TkpClipDrawableToRect(Tk_Display(tkwin), pixmap, 0, 0, -1, -1); +#endif /* TK_NO_DOUBLE_BUFFERING */ + } + + /* + * Draw the window borders, if needed. + */ + + borders: + if (canvasPtr->flags & REDRAW_BORDERS) { + canvasPtr->flags &= ~REDRAW_BORDERS; + if (canvasPtr->borderWidth > 0) { + Tk_Draw3DRectangle(tkwin, Tk_WindowId(tkwin), + canvasPtr->bgBorder, canvasPtr->highlightWidth, + canvasPtr->highlightWidth, + Tk_Width(tkwin) - 2*canvasPtr->highlightWidth, + Tk_Height(tkwin) - 2*canvasPtr->highlightWidth, + canvasPtr->borderWidth, canvasPtr->relief); + } + if (canvasPtr->highlightWidth != 0) { + GC fgGC, bgGC; + + bgGC = Tk_GCForColor(canvasPtr->highlightBgColorPtr, + Tk_WindowId(tkwin)); + if (canvasPtr->textInfo.gotFocus) { + fgGC = Tk_GCForColor(canvasPtr->highlightColorPtr, + Tk_WindowId(tkwin)); + TkpDrawHighlightBorder(tkwin, fgGC, bgGC, + canvasPtr->highlightWidth, Tk_WindowId(tkwin)); + } else { + TkpDrawHighlightBorder(tkwin, bgGC, bgGC, + canvasPtr->highlightWidth, Tk_WindowId(tkwin)); + } + } + } + + done: + canvasPtr->flags &= ~(REDRAW_PENDING|BBOX_NOT_EMPTY); + canvasPtr->redrawX1 = canvasPtr->redrawX2 = 0; + canvasPtr->redrawY1 = canvasPtr->redrawY2 = 0; + if (canvasPtr->flags & UPDATE_SCROLLBARS) { + CanvasUpdateScrollbars(canvasPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * CanvasEventProc -- + * + * This function is invoked by the Tk dispatcher for various events on + * canvases. + * + * Results: + * None. + * + * Side effects: + * When the window gets deleted, internal structures get cleaned up. When + * it gets exposed, it is redisplayed. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasEventProc( + ClientData clientData, /* Information about window. */ + XEvent *eventPtr) /* Information about event. */ +{ + TkCanvas *canvasPtr = clientData; + + if (eventPtr->type == Expose) { + int x, y; + + x = eventPtr->xexpose.x + canvasPtr->xOrigin; + y = eventPtr->xexpose.y + canvasPtr->yOrigin; + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, x, y, + x + eventPtr->xexpose.width, + y + eventPtr->xexpose.height); + if ((eventPtr->xexpose.x < canvasPtr->inset) + || (eventPtr->xexpose.y < canvasPtr->inset) + || ((eventPtr->xexpose.x + eventPtr->xexpose.width) + > (Tk_Width(canvasPtr->tkwin) - canvasPtr->inset)) + || ((eventPtr->xexpose.y + eventPtr->xexpose.height) + > (Tk_Height(canvasPtr->tkwin) - canvasPtr->inset))) { + canvasPtr->flags |= REDRAW_BORDERS; + } + } else if (eventPtr->type == DestroyNotify) { + if (canvasPtr->tkwin != NULL) { + canvasPtr->tkwin = NULL; + Tcl_DeleteCommandFromToken(canvasPtr->interp, + canvasPtr->widgetCmd); + } + if (canvasPtr->flags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayCanvas, canvasPtr); + } + Tcl_EventuallyFree(canvasPtr, (Tcl_FreeProc *) DestroyCanvas); + } else if (eventPtr->type == ConfigureNotify) { + canvasPtr->flags |= UPDATE_SCROLLBARS; + + /* + * The call below is needed in order to recenter the canvas if it's + * confined and its scroll region is smaller than the window. + */ + + CanvasSetOrigin(canvasPtr, canvasPtr->xOrigin, canvasPtr->yOrigin); + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, canvasPtr->xOrigin, + canvasPtr->yOrigin, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin), + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin)); + canvasPtr->flags |= REDRAW_BORDERS; + } else if (eventPtr->type == FocusIn) { + if (eventPtr->xfocus.detail != NotifyInferior) { + CanvasFocusProc(canvasPtr, 1); + } + } else if (eventPtr->type == FocusOut) { + if (eventPtr->xfocus.detail != NotifyInferior) { + CanvasFocusProc(canvasPtr, 0); + } + } else if (eventPtr->type == UnmapNotify) { + Tk_Item *itemPtr; + + /* + * Special hack: if the canvas is unmapped, then must notify all items + * with "alwaysRedraw" set, so that they know that they are no longer + * displayed. + */ + + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if (AlwaysRedraw(itemPtr)) { + ItemDisplay(canvasPtr, itemPtr, None, 0, 0, 0, 0); + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * CanvasCmdDeletedProc -- + * + * This function is invoked when a widget command is deleted. If the + * widget isn't already in the process of being destroyed, this command + * destroys it. + * + * Results: + * None. + * + * Side effects: + * The widget is destroyed. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasCmdDeletedProc( + ClientData clientData) /* Pointer to widget record for widget. */ +{ + TkCanvas *canvasPtr = clientData; + Tk_Window tkwin = canvasPtr->tkwin; + + /* + * This function could be invoked either because the window was destroyed + * and the command was then deleted (in which case tkwin is NULL) or + * because the command was deleted, and then this function destroys the + * widget. + */ + + if (tkwin != NULL) { + canvasPtr->tkwin = NULL; + Tk_DestroyWindow(tkwin); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tk_CanvasEventuallyRedraw -- + * + * Arrange for part or all of a canvas widget to redrawn at some + * convenient time in the future. + * + * Results: + * None. + * + * Side effects: + * The screen will eventually be refreshed. + * + *---------------------------------------------------------------------- + */ + +void +Tk_CanvasEventuallyRedraw( + Tk_Canvas canvas, /* Information about widget. */ + int x1, int y1, /* Upper left corner of area to redraw. Pixels + * on edge are redrawn. */ + int x2, int y2) /* Lower right corner of area to redraw. + * Pixels on edge are not redrawn. */ +{ + TkCanvas *canvasPtr = Canvas(canvas); + + /* + * If tkwin is NULL, the canvas has been destroyed, so we can't really + * redraw it. + */ + + if (canvasPtr->tkwin == NULL) { + return; + } + + if ((x1 >= x2) || (y1 >= y2) || + (x2 < canvasPtr->xOrigin) || (y2 < canvasPtr->yOrigin) || + (x1 >= canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin)) || + (y1 >= canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin))) { + return; + } + if (canvasPtr->flags & BBOX_NOT_EMPTY) { + if (x1 <= canvasPtr->redrawX1) { + canvasPtr->redrawX1 = x1; + } + if (y1 <= canvasPtr->redrawY1) { + canvasPtr->redrawY1 = y1; + } + if (x2 >= canvasPtr->redrawX2) { + canvasPtr->redrawX2 = x2; + } + if (y2 >= canvasPtr->redrawY2) { + canvasPtr->redrawY2 = y2; + } + } else { + canvasPtr->redrawX1 = x1; + canvasPtr->redrawY1 = y1; + canvasPtr->redrawX2 = x2; + canvasPtr->redrawY2 = y2; + canvasPtr->flags |= BBOX_NOT_EMPTY; + } + if (!(canvasPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayCanvas, canvasPtr); + canvasPtr->flags |= REDRAW_PENDING; + } +} + +/* + *---------------------------------------------------------------------- + * + * EventuallyRedrawItem -- + * + * Arrange for part or all of a canvas widget to redrawn at some + * convenient time in the future. + * + * Results: + * None. + * + * Side effects: + * The screen will eventually be refreshed. + * + *---------------------------------------------------------------------- + */ + +static void +EventuallyRedrawItem( + TkCanvas *canvasPtr, /* Information about widget. */ + Tk_Item *itemPtr) /* Item to be redrawn. May be NULL, in which + * case nothing happens. */ +{ + if (itemPtr == NULL) { + return; + } + if ((itemPtr->x1 >= itemPtr->x2) || (itemPtr->y1 >= itemPtr->y2) || + (itemPtr->x2 < canvasPtr->xOrigin) || + (itemPtr->y2 < canvasPtr->yOrigin) || + (itemPtr->x1 >= canvasPtr->xOrigin+Tk_Width(canvasPtr->tkwin)) || + (itemPtr->y1 >= canvasPtr->yOrigin+Tk_Height(canvasPtr->tkwin))) { + if (!AlwaysRedraw(itemPtr)) { + return; + } + } + if (!(itemPtr->redraw_flags & FORCE_REDRAW)) { + if (canvasPtr->flags & BBOX_NOT_EMPTY) { + if (itemPtr->x1 <= canvasPtr->redrawX1) { + canvasPtr->redrawX1 = itemPtr->x1; + } + if (itemPtr->y1 <= canvasPtr->redrawY1) { + canvasPtr->redrawY1 = itemPtr->y1; + } + if (itemPtr->x2 >= canvasPtr->redrawX2) { + canvasPtr->redrawX2 = itemPtr->x2; + } + if (itemPtr->y2 >= canvasPtr->redrawY2) { + canvasPtr->redrawY2 = itemPtr->y2; + } + } else { + canvasPtr->redrawX1 = itemPtr->x1; + canvasPtr->redrawY1 = itemPtr->y1; + canvasPtr->redrawX2 = itemPtr->x2; + canvasPtr->redrawY2 = itemPtr->y2; + canvasPtr->flags |= BBOX_NOT_EMPTY; + } + itemPtr->redraw_flags |= FORCE_REDRAW; + } + if (!(canvasPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayCanvas, canvasPtr); + canvasPtr->flags |= REDRAW_PENDING; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tk_CreateItemType -- + * + * This function may be invoked to add a new kind of canvas element to + * the core item types supported by Tk. + * + * Results: + * None. + * + * Side effects: + * From now on, the new item type will be useable in canvas widgets + * (e.g. typePtr->name can be used as the item type in "create" widget + * commands). If there was already a type with the same name as in + * typePtr, it is replaced with the new type. + * + *---------------------------------------------------------------------- + */ + +void +Tk_CreateItemType( + Tk_ItemType *typePtr) /* Information about item type; storage must + * be statically allocated (must live + * forever). */ +{ + Tk_ItemType *typePtr2, *prevPtr; + + if (typeList == NULL) { + InitCanvas(); + } + + /* + * If there's already an item type with the given name, remove it. + */ + + Tcl_MutexLock(&typeListMutex); + for (typePtr2 = typeList, prevPtr = NULL; typePtr2 != NULL; + prevPtr = typePtr2, typePtr2 = typePtr2->nextPtr) { + if (strcmp(typePtr2->name, typePtr->name) == 0) { + if (prevPtr == NULL) { + typeList = typePtr2->nextPtr; + } else { + prevPtr->nextPtr = typePtr2->nextPtr; + } + break; + } + } + typePtr->nextPtr = typeList; + typeList = typePtr; + Tcl_MutexUnlock(&typeListMutex); +} + +/* + *---------------------------------------------------------------------- + * + * Tk_GetItemTypes -- + * + * This function returns a pointer to the list of all item types. Note + * that this is inherently thread-unsafe, but since item types are only + * ever registered very rarely this is unlikely to be a problem in + * practice. + * + * Results: + * The return value is a pointer to the first in the list of item types + * currently supported by canvases. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tk_ItemType * +Tk_GetItemTypes(void) +{ + if (typeList == NULL) { + InitCanvas(); + } + return typeList; +} + +/* + *---------------------------------------------------------------------- + * + * InitCanvas -- + * + * This function is invoked to perform once-only-ever initialization for + * the module, such as setting up the type table. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +InitCanvas(void) +{ + Tcl_MutexLock(&typeListMutex); + if (typeList != NULL) { + Tcl_MutexUnlock(&typeListMutex); + return; + } + typeList = &tkRectangleType; + tkRectangleType.nextPtr = &tkTextType; + tkTextType.nextPtr = &tkLineType; + tkLineType.nextPtr = &tkPolygonType; + tkPolygonType.nextPtr = &tkImageType; + tkImageType.nextPtr = &tkOvalType; + tkOvalType.nextPtr = &tkBitmapType; + tkBitmapType.nextPtr = &tkArcType; + tkArcType.nextPtr = &tkWindowType; + tkWindowType.nextPtr = NULL; + Tcl_MutexUnlock(&typeListMutex); +} + +#ifdef USE_OLD_TAG_SEARCH +/* + *-------------------------------------------------------------- + * + * StartTagSearch -- + * + * This function is called to initiate an enumeration of all items in a + * given canvas that contain a given tag. + * + * Results: + * The return value is a pointer to the first item in canvasPtr that + * matches tag, or NULL if there is no such item. The information at + * *searchPtr is initialized such that successive calls to NextItem will + * return successive items that match tag. + * + * Side effects: + * SearchPtr is linked into a list of searches in progress on canvasPtr, + * so that elements can safely be deleted while the search is in + * progress. EndTagSearch must be called at the end of the search to + * unlink searchPtr from this list. + * + *-------------------------------------------------------------- + */ + +static Tk_Item * +StartTagSearch( + TkCanvas *canvasPtr, /* Canvas whose items are to be searched. */ + Tcl_Obj *tagObj, /* Object giving tag value. */ + TagSearch *searchPtr) /* Record describing tag search; will be + * initialized here. */ +{ + int id; + Tk_Item *itemPtr, *lastPtr; + Tk_Uid *tagPtr; + Tk_Uid uid; + char *tag = Tcl_GetString(tagObj); + int count; + TkWindow *tkwin = (TkWindow *) canvasPtr->tkwin; + TkDisplay *dispPtr = tkwin->dispPtr; + + /* + * Initialize the search. + */ + + searchPtr->canvasPtr = canvasPtr; + searchPtr->searchOver = 0; + + /* + * Find the first matching item in one of several ways. If the tag is a + * number then it selects the single item with the matching identifier. + * In this case see if the item being requested is the hot item, in which + * case the search can be skipped. + */ + + if (isdigit(UCHAR(*tag))) { + char *end; + Tcl_HashEntry *entryPtr; + + dispPtr->numIdSearches++; + id = strtoul(tag, &end, 0); + if (*end == 0) { + itemPtr = canvasPtr->hotPtr; + lastPtr = canvasPtr->hotPrevPtr; + if ((itemPtr == NULL) || (itemPtr->id != id) || (lastPtr == NULL) + || (lastPtr->nextPtr != itemPtr)) { + dispPtr->numSlowSearches++; + entryPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char*) id); + if (entryPtr != NULL) { + itemPtr = Tcl_GetHashValue(entryPtr); + lastPtr = itemPtr->prevPtr; + } else { + lastPtr = itemPtr = NULL; + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + canvasPtr->hotPtr = itemPtr; + canvasPtr->hotPrevPtr = lastPtr; + return itemPtr; + } + } + + searchPtr->tag = uid = Tk_GetUid(tag); + if (uid == Tk_GetUid("all")) { + /* + * All items match. + */ + + searchPtr->tag = NULL; + searchPtr->lastPtr = NULL; + searchPtr->currentPtr = canvasPtr->firstItemPtr; + return canvasPtr->firstItemPtr; + } + + /* + * None of the above. Search for an item with a matching tag. + */ + + for (lastPtr = NULL, itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + lastPtr = itemPtr, itemPtr = itemPtr->nextPtr) { + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + return NULL; +} + +/* + *-------------------------------------------------------------- + * + * NextItem -- + * + * This function returns successive items that match a given tag; it + * should be called only after StartTagSearch has been used to begin a + * search. + * + * Results: + * The return value is a pointer to the next item that matches the tag + * specified to StartTagSearch, or NULL if no such item exists. + * *SearchPtr is updated so that the next call to this function will + * return the next item. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static Tk_Item * +NextItem( + TagSearch *searchPtr) /* Record describing search in progress. */ +{ + Tk_Item *itemPtr, *lastPtr; + int count; + Tk_Uid uid; + Tk_Uid *tagPtr; + + /* + * Find next item in list (this may not actually be a suitable one to + * return), and return if there are no items left. + */ + + lastPtr = searchPtr->lastPtr; + if (lastPtr == NULL) { + itemPtr = searchPtr->canvasPtr->firstItemPtr; + } else { + itemPtr = lastPtr->nextPtr; + } + if ((itemPtr == NULL) || (searchPtr->searchOver)) { + searchPtr->searchOver = 1; + return NULL; + } + if (itemPtr != searchPtr->currentPtr) { + /* + * The structure of the list has changed. Probably the previously- + * returned item was removed from the list. In this case, don't + * advance lastPtr; just return its new successor (i.e. do nothing + * here). + */ + } else { + lastPtr = itemPtr; + itemPtr = lastPtr->nextPtr; + } + + /* + * Handle special case of "all" search by returning next item. + */ + + uid = searchPtr->tag; + if (uid == NULL) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + + /* + * Look for an item with a particular tag. + */ + + for ( ; itemPtr != NULL; lastPtr = itemPtr, itemPtr = itemPtr->nextPtr) { + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + return NULL; +} + +#else /* !USE_OLD_TAG_SEARCH */ +/* + *---------------------------------------------------------------------- + * + * GetStaticUids -- + * + * This function is invoked to return a structure filled with the Uids + * used when doing tag searching. If it was never before called in the + * current thread, it initializes the structure for that thread (uids are + * only ever local to one thread [Bug 1114977]). + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static SearchUids * +GetStaticUids(void) +{ + SearchUids *searchUids = + Tcl_GetThreadData(&dataKey, sizeof(SearchUids)); + + if (searchUids->allUid == NULL) { + searchUids->allUid = Tk_GetUid("all"); + searchUids->currentUid = Tk_GetUid("current"); + searchUids->andUid = Tk_GetUid("&&"); + searchUids->orUid = Tk_GetUid("||"); + searchUids->xorUid = Tk_GetUid("^"); + searchUids->parenUid = Tk_GetUid("("); + searchUids->endparenUid = Tk_GetUid(")"); + searchUids->negparenUid = Tk_GetUid("!("); + searchUids->tagvalUid = Tk_GetUid("!!"); + searchUids->negtagvalUid = Tk_GetUid("!"); + } + return searchUids; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchExprInit -- + * + * This function allocates and initializes one TagSearchExpr struct. + * + * Results: + * + * Side effects: + * + *-------------------------------------------------------------- + */ + +static void +TagSearchExprInit( + TagSearchExpr **exprPtrPtr) +{ + TagSearchExpr *expr = *exprPtrPtr; + + if (expr == NULL) { + expr = ckalloc(sizeof(TagSearchExpr)); + expr->allocated = 0; + expr->uids = NULL; + expr->next = NULL; + } + expr->uid = NULL; + expr->index = 0; + expr->length = 0; + *exprPtrPtr = expr; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchExprDestroy -- + * + * This function destroys one TagSearchExpr structure. + * + * Results: + * + * Side effects: + * + *-------------------------------------------------------------- + */ + +static void +TagSearchExprDestroy( + TagSearchExpr *expr) +{ + if (expr != NULL) { + if (expr->uids) { + ckfree(expr->uids); + } + ckfree(expr); + } +} + +/* + *-------------------------------------------------------------- + * + * TagSearchScan -- + * + * This function is called to initiate an enumeration of all items in a + * given canvas that contain a tag that matches the tagOrId expression. + * + * Results: + * The return value indicates if the tagOrId expression was successfully + * scanned (syntax). The information at *searchPtr is initialized such + * that a call to TagSearchFirst, followed by successive calls to + * TagSearchNext will return items that match tag. + * + * Side effects: + * SearchPtr is linked into a list of searches in progress on canvasPtr, + * so that elements can safely be deleted while the search is in + * progress. + * + *-------------------------------------------------------------- + */ + +static int +TagSearchScan( + TkCanvas *canvasPtr, /* Canvas whose items are to be searched. */ + Tcl_Obj *tagObj, /* Object giving tag value. */ + TagSearch **searchPtrPtr) /* Record describing tag search; will be + * initialized here. */ +{ + const char *tag = Tcl_GetString(tagObj); + int i; + TagSearch *searchPtr; + + /* + * Initialize the search. + */ + + if (*searchPtrPtr != NULL) { + searchPtr = *searchPtrPtr; + } else { + /* + * Allocate primary search struct on first call. + */ + + *searchPtrPtr = searchPtr = ckalloc(sizeof(TagSearch)); + searchPtr->expr = NULL; + + /* + * Allocate buffer for rewritten tags (after de-escaping). + */ + + searchPtr->rewritebufferAllocated = 100; + searchPtr->rewritebuffer = ckalloc(searchPtr->rewritebufferAllocated); + } + TagSearchExprInit(&searchPtr->expr); + + /* + * How long is the tagOrId? + */ + + searchPtr->stringLength = strlen(tag); + + /* + * Make sure there is enough buffer to hold rewritten tags. + */ + + if ((unsigned) searchPtr->stringLength >= + searchPtr->rewritebufferAllocated) { + searchPtr->rewritebufferAllocated = searchPtr->stringLength + 100; + searchPtr->rewritebuffer = + ckrealloc(searchPtr->rewritebuffer, + searchPtr->rewritebufferAllocated); + } + + /* + * Initialize search. + */ + + searchPtr->canvasPtr = canvasPtr; + searchPtr->searchOver = 0; + searchPtr->type = SEARCH_TYPE_EMPTY; + + /* + * Find the first matching item in one of several ways. If the tag is a + * number then it selects the single item with the matching identifier. + * In this case see if the item being requested is the hot item, in which + * case the search can be skipped. + */ + + if (searchPtr->stringLength && isdigit(UCHAR(*tag))) { + char *end; + + searchPtr->id = strtoul(tag, &end, 0); + if (*end == 0) { + searchPtr->type = SEARCH_TYPE_ID; + return TCL_OK; + } + } + + /* + * For all other tags and tag expressions convert to a UID. This UID is + * kept forever, but this should be thought of as a cache rather than as a + * memory leak. + */ + + searchPtr->expr->uid = Tk_GetUid(tag); + + /* + * Short circuit impossible searches for null tags. + */ + + if (searchPtr->stringLength == 0) { + return TCL_OK; + } + + /* + * Pre-scan tag for at least one unquoted "&&" "||" "^" "!"; if not found + * then use string as simple tag. + */ + + for (i = 0; i < searchPtr->stringLength ; i++) { + if (tag[i] == '"') { + i++; + for ( ; i < searchPtr->stringLength; i++) { + if (tag[i] == '\\') { + i++; + continue; + } + if (tag[i] == '"') { + break; + } + } + } else if ((tag[i] == '&' && tag[i+1] == '&') + || (tag[i] == '|' && tag[i+1] == '|') + || (tag[i] == '^') + || (tag[i] == '!')) { + searchPtr->type = SEARCH_TYPE_EXPR; + break; + } + } + + searchPtr->string = tag; + searchPtr->stringIndex = 0; + if (searchPtr->type == SEARCH_TYPE_EXPR) { + /* + * An operator was found in the prescan, so now compile the tag + * expression into array of Tk_Uid flagging any syntax errors found. + */ + + if (TagSearchScanExpr(canvasPtr->interp, searchPtr, + searchPtr->expr) != TCL_OK) { + /* + * Syntax error in tag expression. The result message was set by + * TagSearchScanExpr. + */ + + return TCL_ERROR; + } + searchPtr->expr->length = searchPtr->expr->index; + } else if (searchPtr->expr->uid == GetStaticUids()->allUid) { + /* + * All items match. + */ + + searchPtr->type = SEARCH_TYPE_ALL; + } else { + /* + * Optimized single-tag search + */ + + searchPtr->type = SEARCH_TYPE_TAG; + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchDestroy -- + * + * This function destroys any dynamic structures that may have been + * allocated by TagSearchScan. + * + * Results: + * None + * + * Side effects: + * Deallocates memory. + * + *-------------------------------------------------------------- + */ + +static void +TagSearchDestroy( + TagSearch *searchPtr) /* Record describing tag search. */ +{ + if (searchPtr) { + TagSearchExprDestroy(searchPtr->expr); + ckfree(searchPtr->rewritebuffer); + ckfree(searchPtr); + } +} + +/* + *-------------------------------------------------------------- + * + * TagSearchScanExpr -- + * + * This recursive function is called to scan a tag expression and compile + * it into an array of Tk_Uids. + * + * Results: + * The return value indicates if the tagOrId expression was successfully + * scanned (syntax). The information at *searchPtr is initialized such + * that a call to TagSearchFirst, followed by successive calls to + * TagSearchNext will return items that match tag. + * + * Side effects: + * + *-------------------------------------------------------------- + */ + +static int +TagSearchScanExpr( + Tcl_Interp *interp, /* Current interpreter. */ + TagSearch *searchPtr, /* Search data. */ + TagSearchExpr *expr) /* Compiled expression result. */ +{ + int looking_for_tag; /* When true, scanner expects next char(s) to + * be a tag, else operand expected. */ + int found_tag; /* One or more tags found. */ + int found_endquote; /* For quoted tag string parsing. */ + int negate_result; /* Pending negation of next tag value. */ + char *tag; /* Tag from tag expression string. */ + char c; + SearchUids *searchUids; /* Collection of uids for basic search + * expression terms. */ + + searchUids = GetStaticUids(); + negate_result = 0; + found_tag = 0; + looking_for_tag = 1; + while (searchPtr->stringIndex < searchPtr->stringLength) { + c = searchPtr->string[searchPtr->stringIndex++]; + + /* + * Need two slots free at this point, not one. [Bug 2931374] + */ + + if (expr->index >= expr->allocated-1) { + expr->allocated += 15; + if (expr->uids) { + expr->uids = ckrealloc(expr->uids, + expr->allocated * sizeof(Tk_Uid)); + } else { + expr->uids = ckalloc(expr->allocated * sizeof(Tk_Uid)); + } + } + + if (looking_for_tag) { + switch (c) { + case ' ': /* Ignore unquoted whitespace */ + case '\t': + case '\n': + case '\r': + break; + + case '!': /* Negate next tag or subexpr */ + if (looking_for_tag > 1) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "too many '!' in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "COMPLEXITY", NULL); + return TCL_ERROR; + } + looking_for_tag++; + negate_result = 1; + break; + + case '(': /* Scan (negated) subexpr recursively */ + if (negate_result) { + expr->uids[expr->index++] = searchUids->negparenUid; + negate_result = 0; + } else { + expr->uids[expr->index++] = searchUids->parenUid; + } + if (TagSearchScanExpr(interp, searchPtr, expr) != TCL_OK) { + /* + * Result string should be already set by nested call to + * tag_expr_scan() + */ + + return TCL_ERROR; + } + looking_for_tag = 0; + found_tag = 1; + break; + + case '"': /* Quoted tag string */ + if (negate_result) { + expr->uids[expr->index++] = searchUids->negtagvalUid; + negate_result = 0; + } else { + expr->uids[expr->index++] = searchUids->tagvalUid; + } + tag = searchPtr->rewritebuffer; + found_endquote = 0; + while (searchPtr->stringIndex < searchPtr->stringLength) { + c = searchPtr->string[searchPtr->stringIndex++]; + if (c == '\\') { + c = searchPtr->string[searchPtr->stringIndex++]; + } + if (c == '"') { + found_endquote = 1; + break; + } + *tag++ = c; + } + if (!found_endquote) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "missing endquote in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "ENDQUOTE", NULL); + return TCL_ERROR; + } + if (!(tag - searchPtr->rewritebuffer)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "null quoted tag string in tag search expression", + -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "EMPTY", NULL); + return TCL_ERROR; + } + *tag++ = '\0'; + expr->uids[expr->index++] = + Tk_GetUid(searchPtr->rewritebuffer); + looking_for_tag = 0; + found_tag = 1; + break; + + case '&': /* Illegal chars when looking for tag */ + case '|': + case '^': + case ')': + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "unexpected operator in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "UNEXPECTED", NULL); + return TCL_ERROR; + + default: /* Unquoted tag string */ + if (negate_result) { + expr->uids[expr->index++] = searchUids->negtagvalUid; + negate_result = 0; + } else { + expr->uids[expr->index++] = searchUids->tagvalUid; + } + tag = searchPtr->rewritebuffer; + *tag++ = c; + + /* + * Copy rest of tag, including any embedded whitespace. + */ + + while (searchPtr->stringIndex < searchPtr->stringLength) { + c = searchPtr->string[searchPtr->stringIndex]; + if (c == '!' || c == '&' || c == '|' || c == '^' + || c == '(' || c == ')' || c == '"') { + break; + } + *tag++ = c; + searchPtr->stringIndex++; + } + + /* + * Remove trailing whitespace. + */ + + while (1) { + c = *--tag; + + /* + * There must have been one non-whitespace char, so this + * will terminate. + */ + + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { + break; + } + } + *++tag = '\0'; + expr->uids[expr->index++] = + Tk_GetUid(searchPtr->rewritebuffer); + looking_for_tag = 0; + found_tag = 1; + } + + } else { /* ! looking_for_tag */ + switch (c) { + case ' ': /* Ignore whitespace */ + case '\t': + case '\n': + case '\r': + break; + + case '&': /* AND operator */ + c = searchPtr->string[searchPtr->stringIndex++]; + if (c != '&') { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "singleton '&' in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "INCOMPLETE_OP", NULL); + return TCL_ERROR; + } + expr->uids[expr->index++] = searchUids->andUid; + looking_for_tag = 1; + break; + + case '|': /* OR operator */ + c = searchPtr->string[searchPtr->stringIndex++]; + if (c != '|') { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "singleton '|' in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", + "INCOMPLETE_OP", NULL); + return TCL_ERROR; + } + expr->uids[expr->index++] = searchUids->orUid; + looking_for_tag = 1; + break; + + case '^': /* XOR operator */ + expr->uids[expr->index++] = searchUids->xorUid; + looking_for_tag = 1; + break; + + case ')': /* End subexpression */ + expr->uids[expr->index++] = searchUids->endparenUid; + goto breakwhile; + + default: /* syntax error */ + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "invalid boolean operator in tag search expression", + -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", "BAD_OP", + NULL); + return TCL_ERROR; + } + } + } + + breakwhile: + if (found_tag && !looking_for_tag) { + return TCL_OK; + } + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "missing tag in tag search expression", -1)); + Tcl_SetErrorCode(interp, "TK", "CANVAS", "SEARCH", "NO_TAG", NULL); + return TCL_ERROR; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchEvalExpr -- + * + * This recursive function is called to eval a tag expression. + * + * Results: + * The return value indicates if the tagOrId expression successfully + * matched the tags of the current item. + * + * Side effects: + * + *-------------------------------------------------------------- + */ + +static int +TagSearchEvalExpr( + TagSearchExpr *expr, /* Search expression */ + Tk_Item *itemPtr) /* Item being test for match */ +{ + int looking_for_tag; /* When true, scanner expects next char(s) to + * be a tag, else operand expected. */ + int negate_result; /* Pending negation of next tag value */ + Tk_Uid uid; + Tk_Uid *tagPtr; + int count; + int result; /* Value of expr so far */ + int parendepth; + SearchUids *searchUids; /* Collection of uids for basic search + * expression terms. */ + + searchUids = GetStaticUids(); + result = 0; /* Just to keep the compiler quiet. */ + + negate_result = 0; + looking_for_tag = 1; + while (expr->index < expr->length) { + uid = expr->uids[expr->index++]; + if (looking_for_tag) { + if (uid == searchUids->tagvalUid) { +/* + * assert(expr->index < expr->length); + */ + uid = expr->uids[expr->index++]; + result = 0; + + /* + * set result 1 if tag is found in item's tags + */ + + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + result = 1; + break; + } + } + + } else if (uid == searchUids->negtagvalUid) { + negate_result = ! negate_result; +/* + * assert(expr->index < expr->length); + */ + uid = expr->uids[expr->index++]; + result = 0; + + /* + * set result 1 if tag is found in item's tags. + */ + + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + result = 1; + break; + } + } + + } else if (uid == searchUids->parenUid) { + /* + * Evaluate subexpressions with recursion. + */ + + result = TagSearchEvalExpr(expr, itemPtr); + + } else if (uid == searchUids->negparenUid) { + negate_result = !negate_result; + + /* + * Evaluate subexpressions with recursion. + */ + + result = TagSearchEvalExpr(expr, itemPtr); + } + if (negate_result) { + result = ! result; + negate_result = 0; + } + looking_for_tag = 0; + } else { /* ! looking_for_tag */ + if (((uid == searchUids->andUid) && (!result)) || + ((uid == searchUids->orUid) && result)) { + /* + * Short circuit expression evaluation. + * + * if result before && is 0, or result before || is 1, then + * the expression is decided and no further evaluation is + * needed. + */ + + parendepth = 0; + while (expr->index < expr->length) { + uid = expr->uids[expr->index++]; + if (uid == searchUids->tagvalUid || + uid == searchUids->negtagvalUid) { + expr->index++; + continue; + } + if (uid == searchUids->parenUid || + uid == searchUids->negparenUid) { + parendepth++; + continue; + } + if (uid == searchUids->endparenUid) { + parendepth--; + if (parendepth < 0) { + break; + } + } + } + return result; + + } else if (uid == searchUids->xorUid) { + /* + * If the previous result was 1 then negate the next result. + */ + + negate_result = result; + + } else if (uid == searchUids->endparenUid) { + return result; + } + looking_for_tag = 1; + } + } +/* + * assert(!looking_for_tag); + */ + return result; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchFirst -- + * + * This function is called to get the first item item that matches a + * preestablished search predicate that was set by TagSearchScan. + * + * Results: + * The return value is a pointer to the first item, or NULL if there is + * no such item. The information at *searchPtr is updated such that + * successive calls to TagSearchNext will return successive items. + * + * Side effects: + * SearchPtr is linked into a list of searches in progress on canvasPtr, + * so that elements can safely be deleted while the search is in + * progress. + * + *-------------------------------------------------------------- + */ + +static Tk_Item * +TagSearchFirst( + TagSearch *searchPtr) /* Record describing tag search */ +{ + Tk_Item *itemPtr, *lastPtr; + Tk_Uid uid, *tagPtr; + int count; + + /* + * Short circuit impossible searches for null tags. + */ + + if (searchPtr->stringLength == 0) { + return NULL; + } + + /* + * Find the first matching item in one of several ways. If the tag is a + * number then it selects the single item with the matching identifier. + * In this case see if the item being requested is the hot item, in which + * case the search can be skipped. + */ + + if (searchPtr->type == SEARCH_TYPE_ID) { + Tcl_HashEntry *entryPtr; + + itemPtr = searchPtr->canvasPtr->hotPtr; + lastPtr = searchPtr->canvasPtr->hotPrevPtr; + if ((itemPtr == NULL) || (itemPtr->id != searchPtr->id) + || (lastPtr == NULL) || (lastPtr->nextPtr != itemPtr)) { + entryPtr = Tcl_FindHashEntry(&searchPtr->canvasPtr->idTable, + (char *) INT2PTR(searchPtr->id)); + if (entryPtr != NULL) { + itemPtr = Tcl_GetHashValue(entryPtr); + lastPtr = itemPtr->prevPtr; + } else { + lastPtr = itemPtr = NULL; + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + searchPtr->canvasPtr->hotPtr = itemPtr; + searchPtr->canvasPtr->hotPrevPtr = lastPtr; + return itemPtr; + } + + if (searchPtr->type == SEARCH_TYPE_ALL) { + /* + * All items match. + */ + + searchPtr->lastPtr = NULL; + searchPtr->currentPtr = searchPtr->canvasPtr->firstItemPtr; + return searchPtr->canvasPtr->firstItemPtr; + } + + if (searchPtr->type == SEARCH_TYPE_TAG) { + /* + * Optimized single-tag search + */ + + uid = searchPtr->expr->uid; + for (lastPtr = NULL, itemPtr = searchPtr->canvasPtr->firstItemPtr; + itemPtr != NULL; lastPtr=itemPtr, itemPtr=itemPtr->nextPtr) { + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + } + } else { + /* + * None of the above. Search for an item matching the tag expression. + */ + + for (lastPtr = NULL, itemPtr = searchPtr->canvasPtr->firstItemPtr; + itemPtr != NULL; lastPtr = itemPtr, itemPtr = itemPtr->nextPtr) { + searchPtr->expr->index = 0; + if (TagSearchEvalExpr(searchPtr->expr, itemPtr)) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + return NULL; +} + +/* + *-------------------------------------------------------------- + * + * TagSearchNext -- + * + * This function returns successive items that match a given tag; it + * should be called only after TagSearchFirst has been used to begin a + * search. + * + * Results: + * The return value is a pointer to the next item that matches the tag + * expr specified to TagSearchScan, or NULL if no such item exists. + * *SearchPtr is updated so that the next call to this function will + * return the next item. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static Tk_Item * +TagSearchNext( + TagSearch *searchPtr) /* Record describing search in progress. */ +{ + Tk_Item *itemPtr, *lastPtr; + Tk_Uid uid, *tagPtr; + int count; + + /* + * Find next item in list (this may not actually be a suitable one to + * return), and return if there are no items left. + */ + + lastPtr = searchPtr->lastPtr; + if (lastPtr == NULL) { + itemPtr = searchPtr->canvasPtr->firstItemPtr; + } else { + itemPtr = lastPtr->nextPtr; + } + if ((itemPtr == NULL) || (searchPtr->searchOver)) { + searchPtr->searchOver = 1; + return NULL; + } + if (itemPtr != searchPtr->currentPtr) { + /* + * The structure of the list has changed. Probably the previously- + * returned item was removed from the list. In this case, don't + * advance lastPtr; just return its new successor (i.e. do nothing + * here). + */ + } else { + lastPtr = itemPtr; + itemPtr = lastPtr->nextPtr; + } + + if (searchPtr->type == SEARCH_TYPE_ALL) { + /* + * All items match. + */ + + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + + if (searchPtr->type == SEARCH_TYPE_TAG) { + /* + * Optimized single-tag search + */ + + uid = searchPtr->expr->uid; + for (; itemPtr != NULL; lastPtr = itemPtr, itemPtr = itemPtr->nextPtr) { + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (*tagPtr == uid) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + return NULL; + } + + /* + * Else.... evaluate tag expression + */ + + for ( ; itemPtr != NULL; lastPtr = itemPtr, itemPtr = itemPtr->nextPtr) { + searchPtr->expr->index = 0; + if (TagSearchEvalExpr(searchPtr->expr, itemPtr)) { + searchPtr->lastPtr = lastPtr; + searchPtr->currentPtr = itemPtr; + return itemPtr; + } + } + searchPtr->lastPtr = lastPtr; + searchPtr->searchOver = 1; + return NULL; +} +#endif /* USE_OLD_TAG_SEARCH */ + +/* + *-------------------------------------------------------------- + * + * DoItem -- + * + * This is a utility function called by FindItems. It either adds + * itemPtr's id to the list being constructed, or it adds a new tag to + * itemPtr, depending on the value of tag. + * + * Results: + * None. + * + * Side effects: + * If tag is NULL then itemPtr's id is added as an element to the + * supplied object; otherwise tag is added to itemPtr's list of tags. + * + *-------------------------------------------------------------- + */ + +static void +DoItem( + Tcl_Obj *accumObj, /* Object in which to (possibly) record item + * id. */ + Tk_Item *itemPtr, /* Item to (possibly) modify. */ + Tk_Uid tag) /* Tag to add to those already present for + * item, or NULL. */ +{ + Tk_Uid *tagPtr; + int count; + + /* + * Handle the "add-to-result" case and return, if appropriate. + */ + + if (tag == NULL) { + Tcl_ListObjAppendElement(NULL, accumObj, Tcl_NewIntObj(itemPtr->id)); + return; + } + + for (tagPtr = itemPtr->tagPtr, count = itemPtr->numTags; + count > 0; tagPtr++, count--) { + if (tag == *tagPtr) { + return; + } + } + + /* + * Grow the tag space if there's no more room left in the current block. + */ + + if (itemPtr->tagSpace == itemPtr->numTags) { + Tk_Uid *newTagPtr; + + itemPtr->tagSpace += 5; + newTagPtr = ckalloc(itemPtr->tagSpace * sizeof(Tk_Uid)); + memcpy((void *) newTagPtr, itemPtr->tagPtr, + itemPtr->numTags * sizeof(Tk_Uid)); + if (itemPtr->tagPtr != itemPtr->staticTagSpace) { + ckfree(itemPtr->tagPtr); + } + itemPtr->tagPtr = newTagPtr; + tagPtr = &itemPtr->tagPtr[itemPtr->numTags]; + } + + /* + * Add in the new tag. + */ + + *tagPtr = tag; + itemPtr->numTags++; +} + +/* + *-------------------------------------------------------------- + * + * FindItems -- + * + * This function does all the work of implementing the "find" and + * "addtag" options of the canvas widget command, which locate items that + * have certain features (location, tags, position in display list, etc.) + * + * Results: + * A standard Tcl return value. If newTag is NULL, then a list of ids + * from all the items that match objc/objv is returned in the interp's + * result. If newTag is NULL, then the normal the interp's result is an + * empty string. If an error occurs, then the interp's result will hold + * an error message. + * + * Side effects: + * If newTag is non-NULL, then all the items that match the information + * in objc/objv have that tag added to their lists of tags. + * + *-------------------------------------------------------------- + */ + +static int +FindItems( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + TkCanvas *canvasPtr, /* Canvas whose items are to be searched. */ + int objc, /* Number of entries in argv. Must be greater + * than zero. */ + Tcl_Obj *const *objv, /* Arguments that describe what items to + * search for (see user doc on "find" and + * "addtag" options). */ + Tcl_Obj *newTag, /* If non-NULL, gives new tag to set on all + * found items; if NULL, then ids of found + * items are returned in the interp's + * result. */ + int first /* For error messages: gives number of + * elements of objv which are already + * handled. */ +#ifndef USE_OLD_TAG_SEARCH + ,TagSearch **searchPtrPtr /* From CanvasWidgetCmd local vars*/ +#endif /* not USE_OLD_TAG_SEARCH */ + ) +{ +#ifdef USE_OLD_TAG_SEARCH + TagSearch search; +#endif /* USE_OLD_TAG_SEARCH */ + Tk_Item *itemPtr; + Tk_Uid uid; + int index, result; + Tcl_Obj *resultObj; + static const char *const optionStrings[] = { + "above", "all", "below", "closest", + "enclosed", "overlapping", "withtag", NULL + }; + enum options { + CANV_ABOVE, CANV_ALL, CANV_BELOW, CANV_CLOSEST, + CANV_ENCLOSED, CANV_OVERLAPPING, CANV_WITHTAG + }; + + if (newTag != NULL) { + uid = Tk_GetUid(Tcl_GetString(newTag)); + } else { + uid = NULL; + } + if (Tcl_GetIndexFromObj(interp, objv[first], optionStrings, + "search command", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum options) index) { + case CANV_ABOVE: { + Tk_Item *lastPtr = NULL; + + if (objc != first+2) { + Tcl_WrongNumArgs(interp, first+1, objv, "tagOrId"); + return TCL_ERROR; + } + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[first+1], searchPtrPtr, + return TCL_ERROR) { + lastPtr = itemPtr; + } + if ((lastPtr != NULL) && (lastPtr->nextPtr != NULL)) { + resultObj = Tcl_NewObj(); + DoItem(resultObj, lastPtr->nextPtr, uid); + Tcl_SetObjResult(interp, resultObj); + } + break; + } + case CANV_ALL: + if (objc != first+1) { + Tcl_WrongNumArgs(interp, first+1, objv, NULL); + return TCL_ERROR; + } + + resultObj = Tcl_NewObj(); + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + DoItem(resultObj, itemPtr, uid); + } + Tcl_SetObjResult(interp, resultObj); + break; + + case CANV_BELOW: + if (objc != first+2) { + Tcl_WrongNumArgs(interp, first+1, objv, "tagOrId"); + return TCL_ERROR; + } + FIRST_CANVAS_ITEM_MATCHING(objv[first+1], searchPtrPtr, + return TCL_ERROR); + if ((itemPtr != NULL) && (itemPtr->prevPtr != NULL)) { + resultObj = Tcl_NewObj(); + DoItem(resultObj, itemPtr->prevPtr, uid); + Tcl_SetObjResult(interp, resultObj); + } + break; + case CANV_CLOSEST: { + double closestDist; + Tk_Item *startPtr, *closestPtr; + double coords[2], halo; + int x1, y1, x2, y2; + + if ((objc < first+3) || (objc > first+5)) { + Tcl_WrongNumArgs(interp, first+1, objv, "x y ?halo? ?start?"); + return TCL_ERROR; + } + if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[first+1], &coords[0]) != TCL_OK + || Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[first+2], &coords[1]) != TCL_OK) { + return TCL_ERROR; + } + if (objc > first+3) { + if (Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, + objv[first+3], &halo) != TCL_OK) { + return TCL_ERROR; + } + if (halo < 0.0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't have negative halo value \"%f\"", halo)); + return TCL_ERROR; + } + } else { + halo = 0.0; + } + + /* + * Find the item at which to start the search. + */ + + startPtr = canvasPtr->firstItemPtr; + if (objc == first+5) { + FIRST_CANVAS_ITEM_MATCHING(objv[first+4], searchPtrPtr, + return TCL_ERROR); + if (itemPtr != NULL) { + startPtr = itemPtr; + } + } + + /* + * The code below is optimized so that it can eliminate most items + * without having to call their item-specific functions. This is done + * by keeping a bounding box (x1, y1, x2, y2) that an item's bbox must + * overlap if the item is to have any chance of being closer than the + * closest so far. + */ + + itemPtr = startPtr; + while(itemPtr && (itemPtr->state == TK_STATE_HIDDEN || + (itemPtr->state == TK_STATE_NULL && + canvasPtr->canvas_state == TK_STATE_HIDDEN))) { + itemPtr = itemPtr->nextPtr; + } + if (itemPtr == NULL) { + return TCL_OK; + } + closestDist = ItemPoint(canvasPtr, itemPtr, coords, halo); + while (1) { + double newDist; + + /* + * Update the bounding box using itemPtr, which is the new closest + * item. + */ + + x1 = (int) (coords[0] - closestDist - halo - 1); + y1 = (int) (coords[1] - closestDist - halo - 1); + x2 = (int) (coords[0] + closestDist + halo + 1); + y2 = (int) (coords[1] + closestDist + halo + 1); + closestPtr = itemPtr; + + /* + * Search for an item that beats the current closest one. Work + * circularly through the canvas's item list until getting back to + * the starting item. + */ + + while (1) { + itemPtr = itemPtr->nextPtr; + if (itemPtr == NULL) { + itemPtr = canvasPtr->firstItemPtr; + } + if (itemPtr == startPtr) { + resultObj = Tcl_NewObj(); + DoItem(resultObj, closestPtr, uid); + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; + } + if (itemPtr->state == TK_STATE_HIDDEN || + (itemPtr->state == TK_STATE_NULL && + canvasPtr->canvas_state == TK_STATE_HIDDEN)) { + continue; + } + if ((itemPtr->x1 >= x2) || (itemPtr->x2 <= x1) + || (itemPtr->y1 >= y2) || (itemPtr->y2 <= y1)) { + continue; + } + newDist = ItemPoint(canvasPtr, itemPtr, coords, halo); + if (newDist <= closestDist) { + closestDist = newDist; + break; + } + } + } + break; + } + case CANV_ENCLOSED: + if (objc != first+5) { + Tcl_WrongNumArgs(interp, first+1, objv, "x1 y1 x2 y2"); + return TCL_ERROR; + } + return FindArea(interp, canvasPtr, objv+first+1, uid, 1); + case CANV_OVERLAPPING: + if (objc != first+5) { + Tcl_WrongNumArgs(interp, first+1, objv, "x1 y1 x2 y2"); + return TCL_ERROR; + } + return FindArea(interp, canvasPtr, objv+first+1, uid, 0); + case CANV_WITHTAG: + if (objc != first+2) { + Tcl_WrongNumArgs(interp, first+1, objv, "tagOrId"); + return TCL_ERROR; + } + resultObj = Tcl_NewObj(); + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[first+1], searchPtrPtr, + goto badWithTagSearch) { + DoItem(resultObj, itemPtr, uid); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; + badWithTagSearch: + Tcl_DecrRefCount(resultObj); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * FindArea -- + * + * This function implements area searches for the "find" and "addtag" + * options. + * + * Results: + * A standard Tcl return value. If newTag is NULL, then a list of ids + * from all the items overlapping or enclosed by the rectangle given by + * objc is returned in the interp's result. If newTag is NULL, then the + * normal the interp's result is an empty string. If an error occurs, + * then the interp's result will hold an error message. + * + * Side effects: + * If uid is non-NULL, then all the items overlapping or enclosed by the + * area in objv have that tag added to their lists of tags. + * + *-------------------------------------------------------------- + */ + +static int +FindArea( + Tcl_Interp *interp, /* Interpreter for error reporting and result + * storing. */ + TkCanvas *canvasPtr, /* Canvas whose items are to be searched. */ + Tcl_Obj *const *objv, /* Array of four arguments that give the + * coordinates of the rectangular area to + * search. */ + Tk_Uid uid, /* If non-NULL, gives new tag to set on all + * found items; if NULL, then ids of found + * items are returned in the interp's + * result. */ + int enclosed) /* 0 means overlapping or enclosed items are + * OK, 1 means only enclosed items are OK. */ +{ + double rect[4], tmp; + int x1, y1, x2, y2; + Tk_Item *itemPtr; + Tcl_Obj *resultObj; + + if ((Tk_CanvasGetCoordFromObj(interp, (Tk_Canvas) canvasPtr, objv[0], + &rect[0]) != TCL_OK) + || (Tk_CanvasGetCoordFromObj(interp,(Tk_Canvas)canvasPtr,objv[1], + &rect[1]) != TCL_OK) + || (Tk_CanvasGetCoordFromObj(interp,(Tk_Canvas)canvasPtr,objv[2], + &rect[2]) != TCL_OK) + || (Tk_CanvasGetCoordFromObj(interp,(Tk_Canvas)canvasPtr,objv[3], + &rect[3]) != TCL_OK)) { + return TCL_ERROR; + } + if (rect[0] > rect[2]) { + tmp = rect[0]; rect[0] = rect[2]; rect[2] = tmp; + } + if (rect[1] > rect[3]) { + tmp = rect[1]; rect[1] = rect[3]; rect[3] = tmp; + } + + /* + * Use an integer bounding box for a quick test, to avoid calling + * item-specific code except for items that are close. + */ + + x1 = (int) (rect[0] - 1.0); + y1 = (int) (rect[1] - 1.0); + x2 = (int) (rect[2] + 1.0); + y2 = (int) (rect[3] + 1.0); + resultObj = Tcl_NewObj(); + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if (itemPtr->state == TK_STATE_HIDDEN || + (itemPtr->state == TK_STATE_NULL + && canvasPtr->canvas_state == TK_STATE_HIDDEN)) { + continue; + } + if ((itemPtr->x1 >= x2) || (itemPtr->x2 <= x1) + || (itemPtr->y1 >= y2) || (itemPtr->y2 <= y1)) { + continue; + } + if (ItemOverlap(canvasPtr, itemPtr, rect) >= enclosed) { + DoItem(resultObj, itemPtr, uid); + } + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * RelinkItems -- + * + * Move one or more items to a different place in the display order for a + * canvas. + * + * Results: + * None. + * + * Side effects: + * The items identified by "tag" are moved so that they are all together + * in the display list and immediately after prevPtr. The order of the + * moved items relative to each other is not changed. + * + *-------------------------------------------------------------- + */ + +#ifdef USE_OLD_TAG_SEARCH +static void +RelinkItems( + TkCanvas *canvasPtr, /* Canvas to be modified. */ + Tcl_Obj *tag, /* Tag identifying items to be moved in the + * redisplay list. */ + Tk_Item *prevPtr) /* Reposition the items so that they go just + * after this item (NULL means put at + * beginning of list). */ +#else /* USE_OLD_TAG_SEARCH */ +static int +RelinkItems( + TkCanvas *canvasPtr, /* Canvas to be modified. */ + Tcl_Obj *tag, /* Tag identifying items to be moved in the + * redisplay list. */ + Tk_Item *prevPtr, /* Reposition the items so that they go just + * after this item (NULL means put at + * beginning of list). */ + TagSearch **searchPtrPtr) /* From CanvasWidgetCmd local vars */ +#endif /* USE_OLD_TAG_SEARCH */ +{ + Tk_Item *itemPtr; +#ifdef USE_OLD_TAG_SEARCH + TagSearch search; +#endif /* USE_OLD_TAG_SEARCH */ + Tk_Item *firstMovePtr, *lastMovePtr; + int result; + + /* + * Find all of the items to be moved and remove them from the list, making + * an auxiliary list running from firstMovePtr to lastMovePtr. Record + * their areas for redisplay. + */ + + firstMovePtr = lastMovePtr = NULL; + FOR_EVERY_CANVAS_ITEM_MATCHING(tag, searchPtrPtr, return TCL_ERROR) { + if (itemPtr == prevPtr) { + /* + * Item after which insertion is to occur is being moved! Switch + * to insert after its predecessor. + */ + + prevPtr = prevPtr->prevPtr; + } + if (itemPtr->prevPtr == NULL) { + if (itemPtr->nextPtr != NULL) { + itemPtr->nextPtr->prevPtr = NULL; + } + canvasPtr->firstItemPtr = itemPtr->nextPtr; + } else { + if (itemPtr->nextPtr != NULL) { + itemPtr->nextPtr->prevPtr = itemPtr->prevPtr; + } + itemPtr->prevPtr->nextPtr = itemPtr->nextPtr; + } + if (canvasPtr->lastItemPtr == itemPtr) { + canvasPtr->lastItemPtr = itemPtr->prevPtr; + } + if (firstMovePtr == NULL) { + itemPtr->prevPtr = NULL; + firstMovePtr = itemPtr; + } else { + itemPtr->prevPtr = lastMovePtr; + lastMovePtr->nextPtr = itemPtr; + } + lastMovePtr = itemPtr; + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + + /* + * Insert the list of to-be-moved items back into the canvas's at the + * desired position. + */ + + if (firstMovePtr == NULL) { +#ifdef USE_OLD_TAG_SEARCH + return; +#else /* USE_OLD_TAG_SEARCH */ + return TCL_OK; +#endif /* USE_OLD_TAG_SEARCH */ + } + if (prevPtr == NULL) { + if (canvasPtr->firstItemPtr != NULL) { + canvasPtr->firstItemPtr->prevPtr = lastMovePtr; + } + lastMovePtr->nextPtr = canvasPtr->firstItemPtr; + canvasPtr->firstItemPtr = firstMovePtr; + } else { + if (prevPtr->nextPtr != NULL) { + prevPtr->nextPtr->prevPtr = lastMovePtr; + } + lastMovePtr->nextPtr = prevPtr->nextPtr; + if (firstMovePtr != NULL) { + firstMovePtr->prevPtr = prevPtr; + } + prevPtr->nextPtr = firstMovePtr; + } + if (canvasPtr->lastItemPtr == prevPtr) { + canvasPtr->lastItemPtr = lastMovePtr; + } +#ifndef USE_OLD_TAG_SEARCH + return TCL_OK; +#endif /* not USE_OLD_TAG_SEARCH */ +} + +/* + *-------------------------------------------------------------- + * + * CanvasBindProc -- + * + * This function is invoked by the Tk dispatcher to handle events + * associated with bindings on items. + * + * Results: + * None. + * + * Side effects: + * Depends on the command invoked as part of the binding (if there was + * any). + * + *-------------------------------------------------------------- + */ + +static void +CanvasBindProc( + ClientData clientData, /* Pointer to canvas structure. */ + XEvent *eventPtr) /* Pointer to X event that just happened. */ +{ + TkCanvas *canvasPtr = clientData; + int mask; + + Tcl_Preserve(canvasPtr); + + /* + * This code below keeps track of the current modifier state in + * canvasPtr>state. This information is used to defer repicks of the + * current item while buttons are down. + */ + + switch (eventPtr->type) { + case ButtonPress: + case ButtonRelease: + switch (eventPtr->xbutton.button) { + case Button1: + mask = Button1Mask; + break; + case Button2: + mask = Button2Mask; + break; + case Button3: + mask = Button3Mask; + break; + case Button4: + mask = Button4Mask; + break; + case Button5: + mask = Button5Mask; + break; + default: + mask = 0; + break; + } + + /* + * For button press events, repick the current item using the button + * state before the event, then process the event. For button release + * events, first process the event, then repick the current item using + * the button state *after* the event (the button has logically gone + * up before we change the current item). + */ + + if (eventPtr->type == ButtonPress) { + /* + * On a button press, first repick the current item using the + * button state before the event, the process the event. + */ + + canvasPtr->state = eventPtr->xbutton.state; + PickCurrentItem(canvasPtr, eventPtr); + canvasPtr->state ^= mask; + CanvasDoEvent(canvasPtr, eventPtr); + } else { + /* + * Button release: first process the event, with the button still + * considered to be down. Then repick the current item under the + * assumption that the button is no longer down. + */ + + canvasPtr->state = eventPtr->xbutton.state; + CanvasDoEvent(canvasPtr, eventPtr); + eventPtr->xbutton.state ^= mask; + canvasPtr->state = eventPtr->xbutton.state; + PickCurrentItem(canvasPtr, eventPtr); + eventPtr->xbutton.state ^= mask; + } + break; + case EnterNotify: + case LeaveNotify: + canvasPtr->state = eventPtr->xcrossing.state; + PickCurrentItem(canvasPtr, eventPtr); + break; + case MotionNotify: + canvasPtr->state = eventPtr->xmotion.state; + PickCurrentItem(canvasPtr, eventPtr); + /* fallthrough */ + default: + CanvasDoEvent(canvasPtr, eventPtr); + } + + Tcl_Release(canvasPtr); +} + +/* + *-------------------------------------------------------------- + * + * PickCurrentItem -- + * + * Find the topmost item in a canvas that contains a given location and + * mark the the current item. If the current item has changed, generate a + * fake exit event on the old current item, a fake enter event on the new + * current item item and force a redraw of the two items. Canvas items + * that are hidden or disabled are ignored. + * + * Results: + * None. + * + * Side effects: + * The current item for canvasPtr may change. If it does, then the + * commands associated with item entry and exit could do just about + * anything. A binding script could delete the canvas, so callers should + * protect themselves with Tcl_Preserve and Tcl_Release. + * + *-------------------------------------------------------------- + */ + +static void +PickCurrentItem( + TkCanvas *canvasPtr, /* Canvas widget in which to select current + * item. */ + XEvent *eventPtr) /* Event describing location of mouse cursor. + * Must be EnterWindow, LeaveWindow, + * ButtonRelease, or MotionNotify. */ +{ + double coords[2]; + int buttonDown; + Tk_Item *prevItemPtr; +#ifndef USE_OLD_TAG_SEARCH + SearchUids *searchUids = GetStaticUids(); +#endif + + /* + * Check whether or not a button is down. If so, we'll log entry and exit + * into and out of the current item, but not entry into any other item. + * This implements a form of grabbing equivalent to what the X server does + * for windows. + */ + + buttonDown = canvasPtr->state + & (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask); + + /* + * Save information about this event in the canvas. The event in the + * canvas is used for two purposes: + * + * 1. Event bindings: if the current item changes, fake events are + * generated to allow item-enter and item-leave bindings to trigger. + * 2. Reselection: if the current item gets deleted, can use the saved + * event to find a new current item. + * + * Translate MotionNotify events into EnterNotify events, since that's + * what gets reported to item handlers. + */ + + if (eventPtr != &canvasPtr->pickEvent) { + if ((eventPtr->type == MotionNotify) + || (eventPtr->type == ButtonRelease)) { + canvasPtr->pickEvent.xcrossing.type = EnterNotify; + canvasPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; + canvasPtr->pickEvent.xcrossing.send_event + = eventPtr->xmotion.send_event; + canvasPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; + canvasPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; + canvasPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; + canvasPtr->pickEvent.xcrossing.subwindow = None; + canvasPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; + canvasPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; + canvasPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; + canvasPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; + canvasPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; + canvasPtr->pickEvent.xcrossing.mode = NotifyNormal; + canvasPtr->pickEvent.xcrossing.detail = NotifyNonlinear; + canvasPtr->pickEvent.xcrossing.same_screen = + eventPtr->xmotion.same_screen; + canvasPtr->pickEvent.xcrossing.focus = False; + canvasPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; + } else { + canvasPtr->pickEvent = *eventPtr; + } + } + + /* + * If this is a recursive call (there's already a partially completed call + * pending on the stack; it's in the middle of processing a Leave event + * handler for the old current item) then just return; the pending call + * will do everything that's needed. + */ + + if (canvasPtr->flags & REPICK_IN_PROGRESS) { + return; + } + + /* + * A LeaveNotify event automatically means that there's no current object, + * so the check for closest item can be skipped. + */ + + coords[0] = canvasPtr->pickEvent.xcrossing.x + canvasPtr->xOrigin; + coords[1] = canvasPtr->pickEvent.xcrossing.y + canvasPtr->yOrigin; + if (canvasPtr->pickEvent.type != LeaveNotify) { + canvasPtr->newCurrentPtr = CanvasFindClosest(canvasPtr, coords); + } else { + canvasPtr->newCurrentPtr = NULL; + } + + if ((canvasPtr->newCurrentPtr == canvasPtr->currentItemPtr) + && !(canvasPtr->flags & LEFT_GRABBED_ITEM)) { + /* + * Nothing to do: the current item hasn't changed. + */ + + return; + } + + if (!buttonDown) { + canvasPtr->flags &= ~LEFT_GRABBED_ITEM; + } + + /* + * Simulate a LeaveNotify event on the previous current item and an + * EnterNotify event on the new current item. Remove the "current" tag + * from the previous current item and place it on the new current item. + */ + + if ((canvasPtr->newCurrentPtr != canvasPtr->currentItemPtr) + && (canvasPtr->currentItemPtr != NULL) + && !(canvasPtr->flags & LEFT_GRABBED_ITEM)) { + XEvent event; + Tk_Item *itemPtr = canvasPtr->currentItemPtr; + int i; + + event = canvasPtr->pickEvent; + event.type = LeaveNotify; + + /* + * If the event's detail happens to be NotifyInferior the binding + * mechanism will discard the event. To be consistent, always use + * NotifyAncestor. + */ + + event.xcrossing.detail = NotifyAncestor; + canvasPtr->flags |= REPICK_IN_PROGRESS; + CanvasDoEvent(canvasPtr, &event); + canvasPtr->flags &= ~REPICK_IN_PROGRESS; + + /* + * The check below is needed because there could be an event handler + * for <LeaveNotify> that deletes the current item. + */ + + if ((itemPtr == canvasPtr->currentItemPtr) && !buttonDown) { + for (i = itemPtr->numTags-1; i >= 0; i--) { +#ifdef USE_OLD_TAG_SEARCH + if (itemPtr->tagPtr[i] == Tk_GetUid("current")) +#else /* USE_OLD_TAG_SEARCH */ + if (itemPtr->tagPtr[i] == searchUids->currentUid) +#endif /* USE_OLD_TAG_SEARCH */ + /* then */ { + itemPtr->tagPtr[i] = itemPtr->tagPtr[itemPtr->numTags-1]; + itemPtr->numTags--; + break; + } + } + } + + /* + * Note: during CanvasDoEvent above, it's possible that + * canvasPtr->newCurrentPtr got reset to NULL because the item was + * deleted. + */ + } + if ((canvasPtr->newCurrentPtr!=canvasPtr->currentItemPtr) && buttonDown) { + canvasPtr->flags |= LEFT_GRABBED_ITEM; + return; + } + + /* + * Special note: it's possible that canvasPtr->newCurrentPtr == + * canvasPtr->currentItemPtr here. This can happen, for example, if + * LEFT_GRABBED_ITEM was set. + */ + + prevItemPtr = canvasPtr->currentItemPtr; + canvasPtr->flags &= ~LEFT_GRABBED_ITEM; + canvasPtr->currentItemPtr = canvasPtr->newCurrentPtr; + if (prevItemPtr != NULL && prevItemPtr != canvasPtr->currentItemPtr && + (prevItemPtr->redraw_flags & TK_ITEM_STATE_DEPENDANT)) { + EventuallyRedrawItem(canvasPtr, prevItemPtr); + ItemConfigure(canvasPtr, prevItemPtr, 0, NULL); + } + if (canvasPtr->currentItemPtr != NULL) { + XEvent event; + +#ifdef USE_OLD_TAG_SEARCH + DoItem(NULL, canvasPtr->currentItemPtr, Tk_GetUid("current")); +#else /* USE_OLD_TAG_SEARCH */ + DoItem(NULL, canvasPtr->currentItemPtr, searchUids->currentUid); +#endif /* USE_OLD_TAG_SEARCH */ + if ((canvasPtr->currentItemPtr->redraw_flags & TK_ITEM_STATE_DEPENDANT + && prevItemPtr != canvasPtr->currentItemPtr)) { + ItemConfigure(canvasPtr, canvasPtr->currentItemPtr, 0, NULL); + EventuallyRedrawItem(canvasPtr, canvasPtr->currentItemPtr); + } + event = canvasPtr->pickEvent; + event.type = EnterNotify; + event.xcrossing.detail = NotifyAncestor; + CanvasDoEvent(canvasPtr, &event); + } +} + +/* + *---------------------------------------------------------------------- + * + * CanvasFindClosest -- + * + * Given x and y coordinates, find the topmost canvas item that is + * "close" to the coordinates. Canvas items that are hidden or disabled + * are ignored. + * + * Results: + * The return value is a pointer to the topmost item that is close to + * (x,y), or NULL if no item is close. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static Tk_Item * +CanvasFindClosest( + TkCanvas *canvasPtr, /* Canvas widget to search. */ + double coords[2]) /* Desired x,y position in canvas, not screen, + * coordinates.) */ +{ + Tk_Item *itemPtr; + Tk_Item *bestPtr; + int x1, y1, x2, y2; + + x1 = (int) (coords[0] - canvasPtr->closeEnough); + y1 = (int) (coords[1] - canvasPtr->closeEnough); + x2 = (int) (coords[0] + canvasPtr->closeEnough); + y2 = (int) (coords[1] + canvasPtr->closeEnough); + + bestPtr = NULL; + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if (itemPtr->state == TK_STATE_HIDDEN || + itemPtr->state==TK_STATE_DISABLED || + (itemPtr->state == TK_STATE_NULL && + (canvasPtr->canvas_state == TK_STATE_HIDDEN || + canvasPtr->canvas_state == TK_STATE_DISABLED))) { + continue; + } + if ((itemPtr->x1 > x2) || (itemPtr->x2 < x1) + || (itemPtr->y1 > y2) || (itemPtr->y2 < y1)) { + continue; + } + if (ItemPoint(canvasPtr,itemPtr,coords,0) <= canvasPtr->closeEnough) { + bestPtr = itemPtr; + } + } + return bestPtr; +} + +/* + *-------------------------------------------------------------- + * + * CanvasDoEvent -- + * + * This function is called to invoke binding processing for a new event + * that is associated with the current item for a canvas. + * + * Results: + * None. + * + * Side effects: + * Depends on the bindings for the canvas. A binding script could delete + * the canvas, so callers should protect themselves with Tcl_Preserve and + * Tcl_Release. + * + *-------------------------------------------------------------- + */ + +static void +CanvasDoEvent( + TkCanvas *canvasPtr, /* Canvas widget in which event occurred. */ + XEvent *eventPtr) /* Real or simulated X event that is to be + * processed. */ +{ +#define NUM_STATIC 3 + ClientData staticObjects[NUM_STATIC]; + ClientData *objectPtr; + int numObjects, i; + Tk_Item *itemPtr; +#ifndef USE_OLD_TAG_SEARCH + TagSearchExpr *expr; + int numExprs; + SearchUids *searchUids = GetStaticUids(); +#endif /* not USE_OLD_TAG_SEARCH */ + + if (canvasPtr->bindingTable == NULL) { + return; + } + + itemPtr = canvasPtr->currentItemPtr; + if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) { + itemPtr = canvasPtr->textInfo.focusItemPtr; + } + if (itemPtr == NULL) { + return; + } + +#ifdef USE_OLD_TAG_SEARCH + /* + * Set up an array with all the relevant objects for processing this + * event. The relevant objects are (a) the event's item, (b) the tags + * associated with the event's item, and (c) the tag "all". If there are a + * lot of tags then malloc an array to hold all of the objects. + */ + + numObjects = itemPtr->numTags + 2; +#else /* USE_OLD_TAG_SEARCH */ + /* + * Set up an array with all the relevant objects for processing this + * event. The relevant objects are: + * (a) the event's item, + * (b) the tags associated with the event's item, + * (c) the expressions that are true for the event's item's tags, and + * (d) the tag "all". + * + * If there are a lot of tags then malloc an array to hold all of the + * objects. + */ + + /* + * Flag and count all expressions that match item's tags. + */ + + numExprs = 0; + expr = canvasPtr->bindTagExprs; + while (expr) { + expr->index = 0; + expr->match = TagSearchEvalExpr(expr, itemPtr); + if (expr->match) { + numExprs++; + } + expr = expr->next; + } + + numObjects = itemPtr->numTags + numExprs + 2; +#endif /* not USE_OLD_TAG_SEARCH */ + if (numObjects <= NUM_STATIC) { + objectPtr = staticObjects; + } else { + objectPtr = ckalloc(numObjects * sizeof(ClientData)); + } +#ifdef USE_OLD_TAG_SEARCH + objectPtr[0] = (ClientData) Tk_GetUid("all"); +#else /* USE_OLD_TAG_SEARCH */ + objectPtr[0] = (ClientData) searchUids->allUid; +#endif /* USE_OLD_TAG_SEARCH */ + for (i = itemPtr->numTags-1; i >= 0; i--) { + objectPtr[i+1] = (ClientData) itemPtr->tagPtr[i]; + } + objectPtr[itemPtr->numTags+1] = itemPtr; + +#ifndef USE_OLD_TAG_SEARCH + /* + * Copy uids of matching expressions into object array + */ + + i = itemPtr->numTags+2; + expr = canvasPtr->bindTagExprs; + while (expr) { + if (expr->match) { + objectPtr[i++] = (int *) expr->uid; + } + expr = expr->next; + } +#endif /* not USE_OLD_TAG_SEARCH */ + + /* + * Invoke the binding system, then free up the object array if it was + * malloc-ed. + */ + + if (canvasPtr->tkwin != NULL) { + Tk_BindEvent(canvasPtr->bindingTable, eventPtr, canvasPtr->tkwin, + numObjects, objectPtr); + } + if (objectPtr != staticObjects) { + ckfree(objectPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * CanvasBlinkProc -- + * + * This function is called as a timer handler to blink the insertion + * cursor off and on. + * + * Results: + * None. + * + * Side effects: + * The cursor gets turned on or off, redisplay gets invoked, and this + * function reschedules itself. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasBlinkProc( + ClientData clientData) /* Pointer to record describing entry. */ +{ + TkCanvas *canvasPtr = clientData; + + if (!canvasPtr->textInfo.gotFocus || (canvasPtr->insertOffTime == 0)) { + return; + } + if (canvasPtr->textInfo.cursorOn) { + canvasPtr->textInfo.cursorOn = 0; + canvasPtr->insertBlinkHandler = Tcl_CreateTimerHandler( + canvasPtr->insertOffTime, CanvasBlinkProc, canvasPtr); + } else { + canvasPtr->textInfo.cursorOn = 1; + canvasPtr->insertBlinkHandler = Tcl_CreateTimerHandler( + canvasPtr->insertOnTime, CanvasBlinkProc, canvasPtr); + } + EventuallyRedrawItem(canvasPtr, canvasPtr->textInfo.focusItemPtr); +} + +/* + *---------------------------------------------------------------------- + * + * CanvasFocusProc -- + * + * This function is called whenever a canvas gets or loses the input + * focus. It's also called whenever the window is reconfigured while it + * has the focus. + * + * Results: + * None. + * + * Side effects: + * The cursor gets turned on or off. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasFocusProc( + TkCanvas *canvasPtr, /* Canvas that just got or lost focus. */ + int gotFocus) /* 1 means window is getting focus, 0 means + * it's losing it. */ +{ + Tcl_DeleteTimerHandler(canvasPtr->insertBlinkHandler); + if (gotFocus) { + canvasPtr->textInfo.gotFocus = 1; + canvasPtr->textInfo.cursorOn = 1; + if (canvasPtr->insertOffTime != 0) { + canvasPtr->insertBlinkHandler = Tcl_CreateTimerHandler( + canvasPtr->insertOffTime, CanvasBlinkProc, canvasPtr); + } + } else { + canvasPtr->textInfo.gotFocus = 0; + canvasPtr->textInfo.cursorOn = 0; + canvasPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; + } + EventuallyRedrawItem(canvasPtr, canvasPtr->textInfo.focusItemPtr); + if (canvasPtr->highlightWidth > 0) { + canvasPtr->flags |= REDRAW_BORDERS; + if (!(canvasPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayCanvas, canvasPtr); + canvasPtr->flags |= REDRAW_PENDING; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * CanvasSelectTo -- + * + * Modify the selection by moving its un-anchored end. This could make + * the selection either larger or smaller. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasSelectTo( + TkCanvas *canvasPtr, /* Information about widget. */ + Tk_Item *itemPtr, /* Item that is to hold selection. */ + int index) /* Index of element that is to become the + * "other" end of the selection. */ +{ + int oldFirst, oldLast; + Tk_Item *oldSelPtr; + + oldFirst = canvasPtr->textInfo.selectFirst; + oldLast = canvasPtr->textInfo.selectLast; + oldSelPtr = canvasPtr->textInfo.selItemPtr; + + /* + * Grab the selection if we don't own it already. + */ + + if (canvasPtr->textInfo.selItemPtr == NULL) { + Tk_OwnSelection(canvasPtr->tkwin, XA_PRIMARY, CanvasLostSelection, + canvasPtr); + } else if (canvasPtr->textInfo.selItemPtr != itemPtr) { + EventuallyRedrawItem(canvasPtr, canvasPtr->textInfo.selItemPtr); + } + canvasPtr->textInfo.selItemPtr = itemPtr; + + if (canvasPtr->textInfo.anchorItemPtr != itemPtr) { + canvasPtr->textInfo.anchorItemPtr = itemPtr; + canvasPtr->textInfo.selectAnchor = index; + } + if (canvasPtr->textInfo.selectAnchor <= index) { + canvasPtr->textInfo.selectFirst = canvasPtr->textInfo.selectAnchor; + canvasPtr->textInfo.selectLast = index; + } else { + canvasPtr->textInfo.selectFirst = index; + canvasPtr->textInfo.selectLast = canvasPtr->textInfo.selectAnchor - 1; + } + if ((canvasPtr->textInfo.selectFirst != oldFirst) + || (canvasPtr->textInfo.selectLast != oldLast) + || (itemPtr != oldSelPtr)) { + EventuallyRedrawItem(canvasPtr, itemPtr); + } +} + +/* + *-------------------------------------------------------------- + * + * CanvasFetchSelection -- + * + * This function is invoked by Tk to return part or all of the selection, + * when the selection is in a canvas widget. This function always returns + * the selection as a STRING. + * + * Results: + * The return value is the number of non-NULL bytes stored at buffer. + * Buffer is filled (or partially filled) with a NULL-terminated string + * containing part or all of the selection, as given by offset and + * maxBytes. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static int +CanvasFetchSelection( + ClientData clientData, /* Information about canvas widget. */ + int offset, /* Offset within selection of first character + * to be returned. */ + char *buffer, /* Location in which to place selection. */ + int maxBytes) /* Maximum number of bytes to place at buffer, + * not including terminating NULL + * character. */ +{ + TkCanvas *canvasPtr = clientData; + + return ItemSelection(canvasPtr, canvasPtr->textInfo.selItemPtr, offset, + buffer, maxBytes); +} + +/* + *---------------------------------------------------------------------- + * + * CanvasLostSelection -- + * + * This function is called back by Tk when the selection is grabbed away + * from a canvas widget. + * + * Results: + * None. + * + * Side effects: + * The existing selection is unhighlighted, and the window is marked as + * not containing a selection. + * + *---------------------------------------------------------------------- + */ + +static void +CanvasLostSelection( + ClientData clientData) /* Information about entry widget. */ +{ + TkCanvas *canvasPtr = clientData; + + EventuallyRedrawItem(canvasPtr, canvasPtr->textInfo.selItemPtr); + canvasPtr->textInfo.selItemPtr = NULL; +} + +/* + *-------------------------------------------------------------- + * + * GridAlign -- + * + * Given a coordinate and a grid spacing, this function computes the + * location of the nearest grid line to the coordinate. + * + * Results: + * The return value is the location of the grid line nearest to coord. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static double +GridAlign( + double coord, /* Coordinate to grid-align. */ + double spacing) /* Spacing between grid lines. If <= 0 then no + * alignment is done. */ +{ + if (spacing <= 0.0) { + return coord; + } + if (coord < 0) { + return -((int) ((-coord)/spacing + 0.5)) * spacing; + } + return ((int) (coord/spacing + 0.5)) * spacing; +} + +/* + *---------------------------------------------------------------------- + * + * ScrollFractions -- + * + * Given the range that's visible in the window and the "100% range" for + * what's in the canvas, return a list of two doubles representing the + * scroll fractions. This function is used for both x and y scrolling. + * + * Results: + * A List Tcl_Obj with two real numbers (Double Tcl_Objs) containing the + * scroll fractions (between 0 and 1) corresponding to the other + * arguments. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +ScrollFractions( + int screen1, /* Lowest coordinate visible in the window. */ + int screen2, /* Highest coordinate visible in the window. */ + int object1, /* Lowest coordinate in the object. */ + int object2) /* Highest coordinate in the object. */ +{ + Tcl_Obj *buffer[2]; + double range, f1, f2; + + range = object2 - object1; + if (range <= 0) { + f1 = 0; + f2 = 1.0; + } else { + f1 = (screen1 - object1)/range; + if (f1 < 0) { + f1 = 0.0; + } + f2 = (screen2 - object1)/range; + if (f2 > 1.0) { + f2 = 1.0; + } + if (f2 < f1) { + f2 = f1; + } + } + buffer[0] = Tcl_NewDoubleObj(f1); + buffer[1] = Tcl_NewDoubleObj(f2); + return Tcl_NewListObj(2, buffer); +} + +/* + *-------------------------------------------------------------- + * + * CanvasUpdateScrollbars -- + * + * This function is invoked whenever a canvas has changed in a way that + * requires scrollbars to be redisplayed (e.g. the view in the canvas has + * changed). + * + * Results: + * None. + * + * Side effects: + * If there are scrollbars associated with the canvas, then their + * scrolling commands are invoked to cause them to redisplay. If errors + * occur, additional Tcl commands may be invoked to process the errors. + * + *-------------------------------------------------------------- + */ + +static void +CanvasUpdateScrollbars( + TkCanvas *canvasPtr) /* Information about canvas. */ +{ + int result; + Tcl_Interp *interp; + int xOrigin, yOrigin, inset, width, height; + int scrollX1, scrollX2, scrollY1, scrollY2; + char *xScrollCmd, *yScrollCmd; + Tcl_DString buf; + + /* + * Save all the relevant values from the canvasPtr, because it might be + * deleted as part of either of the two calls to Tcl_VarEval below. + */ + + interp = canvasPtr->interp; + Tcl_Preserve(interp); + xScrollCmd = canvasPtr->xScrollCmd; + if (xScrollCmd != NULL) { + Tcl_Preserve(xScrollCmd); + } + yScrollCmd = canvasPtr->yScrollCmd; + if (yScrollCmd != NULL) { + Tcl_Preserve(yScrollCmd); + } + xOrigin = canvasPtr->xOrigin; + yOrigin = canvasPtr->yOrigin; + inset = canvasPtr->inset; + width = Tk_Width(canvasPtr->tkwin); + height = Tk_Height(canvasPtr->tkwin); + scrollX1 = canvasPtr->scrollX1; + scrollX2 = canvasPtr->scrollX2; + scrollY1 = canvasPtr->scrollY1; + scrollY2 = canvasPtr->scrollY2; + canvasPtr->flags &= ~UPDATE_SCROLLBARS; + if (canvasPtr->xScrollCmd != NULL) { + Tcl_Obj *fractions = ScrollFractions(xOrigin + inset, + xOrigin + width - inset, scrollX1, scrollX2); + + Tcl_DStringInit(&buf); + Tcl_DStringAppend(&buf, xScrollCmd, -1); + Tcl_DStringAppend(&buf, " ", -1); + Tcl_DStringAppend(&buf, Tcl_GetString(fractions), -1); + result = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0); + Tcl_DStringFree(&buf); + Tcl_DecrRefCount(fractions); + if (result != TCL_OK) { + Tcl_BackgroundException(interp, result); + } + Tcl_ResetResult(interp); + Tcl_Release(xScrollCmd); + } + + if (yScrollCmd != NULL) { + Tcl_Obj *fractions = ScrollFractions(yOrigin + inset, + yOrigin + height - inset, scrollY1, scrollY2); + + Tcl_DStringInit(&buf); + Tcl_DStringAppend(&buf, yScrollCmd, -1); + Tcl_DStringAppend(&buf, " ", -1); + Tcl_DStringAppend(&buf, Tcl_GetString(fractions), -1); + result = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0); + Tcl_DStringFree(&buf); + Tcl_DecrRefCount(fractions); + if (result != TCL_OK) { + Tcl_BackgroundException(interp, result); + } + Tcl_ResetResult(interp); + Tcl_Release(yScrollCmd); + } + Tcl_Release(interp); +} + +/* + *-------------------------------------------------------------- + * + * CanvasSetOrigin -- + * + * This function is invoked to change the mapping between canvas + * coordinates and screen coordinates in the canvas window. + * + * Results: + * None. + * + * Side effects: + * The canvas will be redisplayed to reflect the change in view. In + * addition, scrollbars will be updated if there are any. + * + *-------------------------------------------------------------- + */ + +static void +CanvasSetOrigin( + TkCanvas *canvasPtr, /* Information about canvas. */ + int xOrigin, /* New X origin for canvas (canvas x-coord + * corresponding to left edge of canvas + * window). */ + int yOrigin) /* New Y origin for canvas (canvas y-coord + * corresponding to top edge of canvas + * window). */ +{ + int left, right, top, bottom, delta; + + /* + * If scroll increments have been set, round the window origin to the + * nearest multiple of the increments. Remember, the origin is the place + * just inside the borders, not the upper left corner. + */ + + if (canvasPtr->xScrollIncrement > 0) { + if (xOrigin >= 0) { + xOrigin += canvasPtr->xScrollIncrement/2; + xOrigin -= (xOrigin + canvasPtr->inset) + % canvasPtr->xScrollIncrement; + } else { + xOrigin = (-xOrigin) + canvasPtr->xScrollIncrement/2; + xOrigin = -(xOrigin - (xOrigin - canvasPtr->inset) + % canvasPtr->xScrollIncrement); + } + } + if (canvasPtr->yScrollIncrement > 0) { + if (yOrigin >= 0) { + yOrigin += canvasPtr->yScrollIncrement/2; + yOrigin -= (yOrigin + canvasPtr->inset) + % canvasPtr->yScrollIncrement; + } else { + yOrigin = (-yOrigin) + canvasPtr->yScrollIncrement/2; + yOrigin = -(yOrigin - (yOrigin - canvasPtr->inset) + % canvasPtr->yScrollIncrement); + } + } + + /* + * Adjust the origin if necessary to keep as much as possible of the + * canvas in the view. The variables left, right, etc. keep track of how + * much extra space there is on each side of the view before it will stick + * out past the scroll region. If one side sticks out past the edge of the + * scroll region, adjust the view to bring that side back to the edge of + * the scrollregion (but don't move it so much that the other side sticks + * out now). If scroll increments are in effect, be sure to adjust only by + * full increments. + */ + + if ((canvasPtr->confine) && (canvasPtr->regionString != NULL)) { + left = xOrigin + canvasPtr->inset - canvasPtr->scrollX1; + right = canvasPtr->scrollX2 + - (xOrigin + Tk_Width(canvasPtr->tkwin) - canvasPtr->inset); + top = yOrigin + canvasPtr->inset - canvasPtr->scrollY1; + bottom = canvasPtr->scrollY2 + - (yOrigin + Tk_Height(canvasPtr->tkwin) - canvasPtr->inset); + if ((left < 0) && (right > 0)) { + delta = (right > -left) ? -left : right; + if (canvasPtr->xScrollIncrement > 0) { + delta -= delta % canvasPtr->xScrollIncrement; + } + xOrigin += delta; + } else if ((right < 0) && (left > 0)) { + delta = (left > -right) ? -right : left; + if (canvasPtr->xScrollIncrement > 0) { + delta -= delta % canvasPtr->xScrollIncrement; + } + xOrigin -= delta; + } + if ((top < 0) && (bottom > 0)) { + delta = (bottom > -top) ? -top : bottom; + if (canvasPtr->yScrollIncrement > 0) { + delta -= delta % canvasPtr->yScrollIncrement; + } + yOrigin += delta; + } else if ((bottom < 0) && (top > 0)) { + delta = (top > -bottom) ? -bottom : top; + if (canvasPtr->yScrollIncrement > 0) { + delta -= delta % canvasPtr->yScrollIncrement; + } + yOrigin -= delta; + } + } + + if ((xOrigin == canvasPtr->xOrigin) && (yOrigin == canvasPtr->yOrigin)) { + return; + } + + /* + * Tricky point: must redisplay not only everything that's visible in the + * window's final configuration, but also everything that was visible in + * the initial configuration. This is needed because some item types, like + * windows, need to know when they move off-screen so they can explicitly + * undisplay themselves. + */ + + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + canvasPtr->xOrigin, canvasPtr->yOrigin, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin), + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin)); + canvasPtr->xOrigin = xOrigin; + canvasPtr->yOrigin = yOrigin; + canvasPtr->flags |= UPDATE_SCROLLBARS; + Tk_CanvasEventuallyRedraw((Tk_Canvas) canvasPtr, + canvasPtr->xOrigin, canvasPtr->yOrigin, + canvasPtr->xOrigin + Tk_Width(canvasPtr->tkwin), + canvasPtr->yOrigin + Tk_Height(canvasPtr->tkwin)); +} + +/* + *---------------------------------------------------------------------- + * + * TkGetStringsFromObjs -- + * + * Results: + * Converts object list into string list. + * + * Side effects: + * Memory is allocated for the objv array, which must be freed using + * ckfree() when no longer needed. + * + *---------------------------------------------------------------------- + */ + +/* ARGSUSED */ +static const char ** +TkGetStringsFromObjs( + int objc, + Tcl_Obj *const objv[]) +{ + register int i; + const char **argv; + + if (objc <= 0) { + return NULL; + } + argv = ckalloc((objc+1) * sizeof(char *)); + for (i = 0; i < objc; i++) { + argv[i] = Tcl_GetString(objv[i]); + } + argv[objc] = 0; + return argv; +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsColor -- + * + * This function is called by individual canvas items when they want to + * set a color value for output. Given information about an X color, this + * function will generate Postscript commands to set up an appropriate + * color in Postscript. + * + * Results: + * Returns a standard Tcl return value. If an error occurs then an error + * message will be left in interp->result. If no error occurs, then + * additional Postscript will be appended to interp->result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +Tk_CanvasPsColor( + Tcl_Interp *interp, /* Interpreter for returning Postscript or + * error message. */ + Tk_Canvas canvas, /* Information about canvas. */ + XColor *colorPtr) /* Information about color. */ +{ + return Tk_PostscriptColor(interp, Canvas(canvas)->psInfo, colorPtr); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsFont -- + * + * This function is called by individual canvas items when they want to + * output text. Given information about an X font, this function will + * generate Postscript commands to set up an appropriate font in + * Postscript. + * + * Results: + * Returns a standard Tcl return value. If an error occurs then an error + * message will be left in interp->result. If no error occurs, then + * additional Postscript will be appended to the interp->result. + * + * Side effects: + * The Postscript font name is entered into psInfoPtr->fontTable if it + * wasn't already there. + * + *-------------------------------------------------------------- + */ + +int +Tk_CanvasPsFont( + Tcl_Interp *interp, /* Interpreter for returning Postscript or + * error message. */ + Tk_Canvas canvas, /* Information about canvas. */ + Tk_Font tkfont) /* Information about font in which text is to + * be printed. */ +{ + return Tk_PostscriptFont(interp, Canvas(canvas)->psInfo, tkfont); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsBitmap -- + * + * This function is called to output the contents of a sub-region of a + * bitmap in proper image data format for Postscript (i.e. data between + * angle brackets, one bit per pixel). + * + * Results: + * Returns a standard Tcl return value. If an error occurs then an error + * message will be left in interp->result. If no error occurs, then + * additional Postscript will be appended to interp->result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +Tk_CanvasPsBitmap( + Tcl_Interp *interp, /* Interpreter for returning Postscript or + * error message. */ + Tk_Canvas canvas, /* Information about canvas. */ + Pixmap bitmap, /* Bitmap for which to generate Postscript. */ + int startX, int startY, /* Coordinates of upper-left corner of + * rectangular region to output. */ + int width, int height) /* Size of rectangular region. */ +{ + return Tk_PostscriptBitmap(interp, Canvas(canvas)->tkwin, + Canvas(canvas)->psInfo, bitmap, startX, startY, width, height); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsStipple -- + * + * This function is called by individual canvas items when they have + * created a path that they'd like to be filled with a stipple pattern. + * Given information about an X bitmap, this function will generate + * Postscript commands to fill the current clip region using a stipple + * pattern defined by the bitmap. + * + * Results: + * Returns a standard Tcl return value. If an error occurs then an error + * message will be left in interp->result. If no error occurs, then + * additional Postscript will be appended to interp->result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +Tk_CanvasPsStipple( + Tcl_Interp *interp, /* Interpreter for returning Postscript or + * error message. */ + Tk_Canvas canvas, /* Information about canvas. */ + Pixmap bitmap) /* Bitmap to use for stippling. */ +{ + return Tk_PostscriptStipple(interp, Canvas(canvas)->tkwin, + Canvas(canvas)->psInfo, bitmap); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsY -- + * + * Given a y-coordinate in canvas coordinates, this function returns a + * y-coordinate to use for Postscript output. + * + * Results: + * Returns the Postscript coordinate that corresponds to "y". + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +double +Tk_CanvasPsY( + Tk_Canvas canvas, /* Token for canvas on whose behalf Postscript + * is being generated. */ + double y) /* Y-coordinate in canvas coords. */ +{ + return Tk_PostscriptY(y, Canvas(canvas)->psInfo); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CanvasPsPath -- + * + * Given an array of points for a path, generate Postscript commands to + * create the path. + * + * Results: + * Postscript commands get appended to what's in interp->result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +void +Tk_CanvasPsPath( + Tcl_Interp *interp, /* Put generated Postscript in this + * interpreter's result field. */ + Tk_Canvas canvas, /* Canvas on whose behalf Postscript is being + * generated. */ + double *coordPtr, /* Pointer to first in array of 2*numPoints + * coordinates giving points for path. */ + int numPoints) /* Number of points at *coordPtr. */ +{ + Tk_PostscriptPath(interp, Canvas(canvas)->psInfo, coordPtr, numPoints); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |