source: trunk/gui/src/RpCanvHotspot.c @ 3177

Last change on this file since 3177 was 3177, checked in by mmc, 10 years ago

Updated all of the copyright notices to reference the transfer to
the new HUBzero Foundation, LLC.

File size: 54.9 KB
Line 
1
2/*
3 * ----------------------------------------------------------------------
4 *  RpCanvHotspot - canvas item with text and background box
5 *
6 *  This canvas item makes it easy to create a box with text inside.
7 *  The box is normally stretched around the text, but can be given
8 *  a max size and causing text to be clipped.
9 *
10 *    .c create hotspot <x> <y> -anchor <nsew> \
11 *        -text <text>         << text to be displayed
12 *        -font <name>         << font used for text
13 *        -image <image>       << image displayed on the right of text
14 *        -maxwidth <size>     << maximum size
15 *        -textcolor <color>   << text color
16 *        -substtextcolor <color>   << substituted text color
17 *        -background <color>  << fill for rect behind text
18 *        -outline <color>     << outline around text
19 *        -borderwidth <size>  << for 3D border
20 *        -relief <value>      << for 3D border (drawn under -outline)
21 *        -tags <taglist>      << usual Tk canvas tags
22 *        -interp interp        Interpreter containing substitution variables.
23 *
24 * ======================================================================
25 *  AUTHOR:  Michael McLennan, Purdue University
26 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
27 *
28 *  See the file "license.terms" for information on usage and
29 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
30 * ======================================================================
31 */
32#include <string.h>
33#include <math.h>
34#include "tk.h"
35
36#define SEGMENT_TEXT    0
37#define SEGMENT_VALUE   1
38#define VAR_FLAGS (TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS)
39
40#define REDRAW_PENDING          (1<<0)
41#define LAYOUT_PENDING          (1<<1)
42#define SUBSTITUTIONS_PENDING   (1<<2)
43
44#define TRUE            1
45#define FALSE           0
46#undef MAX
47#define MAX(a,b)        (((a)>(b))?(a):(b))
48
49#undef ROUND
50#define ROUND(x)        ((int)(((double)(x)) + (((x)<0.0) ? -0.5 : 0.5)))
51
52#define IMAGE_PAD       3
53#define TEXT_PADX       3
54#define TEXT_PADY       1
55
56typedef struct _ItemSegment ItemSegment;
57typedef struct _HotspotItem HotspotItem;
58
59/*
60 * ItemSegment
61 *
62 */
63struct _ItemSegment {
64    HotspotItem *itemPtr;
65    ItemSegment *nextPtr;
66    int type;                           /* Image, normal text, or substituted
67                                         * value. */
68    short int x, y;                     /* Location of segment on canvas. */
69    short int width, height;            /* Dimension of segment. */
70    Tk_TextLayout layout;               /* Layout of normal or substituted
71                                         * value */
72    int length;                         /* # of bytes in below string. */
73    int lineNum;                        /* Line where segment if located. */
74    char *value;                        /* Substitututed value. */
75    char text[1];
76};
77
78/*
79 * Record for each hotspot item:
80 */
81struct _HotspotItem  {
82    Tk_Item base;                       /* Generic stuff that's the same for
83                                         * all types.  MUST BE FIRST IN
84                                         * STRUCTURE. */
85    unsigned int flags;
86    Tcl_Interp *interp;                 /* Interp that owns this item */
87    Display *display;
88    Tk_Window tkwin;                    /* Window that represents this
89                                         * canvas */
90    Tk_Canvas canvas;                   /* Canvas that owns this item */
91
92    /*
93     * Fields that are set by widget commands other than "configure":
94     */
95    double x, y;                        /* Positioning point for item. This in
96                                         * conjunction with -anchor determine
97                                         * where the item is drawn on the
98                                         * canvas. */
99    double x1, y1, x2, y2;
100    /*
101     * Configuration settings that are updated by Tk_ConfigureWidget:
102     */
103    Tcl_Interp *valueInterp;            /* If non-NULL, interpreter containing
104                                         * substituted variables.   */
105    const char *text;                   /* Text to be displayed */
106    Tk_Anchor anchor;                   /* Where to anchor text relative to
107                                         * (x,y) */
108    XColor *textColor;                  /* Color for normal text */
109    XColor *valueColor;                 /* Color for substitututed values. */
110    XColor *activeColor;                /* Color for active substitututed
111                                         * values */
112    Tk_3DBorder activeBorder;           /* Background color for active
113                                         * substitututed values */
114    XColor *outlineColor;               /* Color for outline of rectangle */
115    Tk_3DBorder border;                 /* If non-NULL, background color of
116                                         * rectangle enclosing the entire
117                                         * hotspot item. */
118    int borderWidth;                    /* 3D border width (drawn under
119                                         * -outline) */
120    int relief;                         /* Indicates whether hotspot as a
121                                         * whole is raised, sunken, or flat */
122    Tk_Font font;                       /* Font for drawing normal text. */
123    Tk_Font valueFont;                  /* Font for drawing substituted
124                                         * values. */
125
126    const char *imageName;              /* Name of normal hotspot image. */
127    const char *activeImageName;        /* Name of active hotspot image. */
128 
129    Tk_Image image;                     /* The normal icon for a hotspot. */
130    Tk_Image activeImage;               /* If non-NULL, active icon. */
131
132    /*
133     * Fields whose values are derived from the current values of the
134     * configuration settings above:
135     */
136    GC normalGC;                        /* Graphics context for drawing
137                                         * text. */
138    GC valueGC;                         /* GC for substituted values. */
139    GC activeGC;                        /* Gc for active substituted
140                                         * values. */
141    GC outlineGC;                       /* GC for outline? */
142    ItemSegment *firstPtr, *lastPtr;    /* List of hotspot segments. */
143    int width, height;                  /* Dimension of bounding box. */
144    const char *activeValue;            /* Active segment. */
145    short int maxImageWidth, maxImageHeight;
146    int numLines;
147    int showIcons;                      /* Indicates whether to draw icons. */
148};
149
150/*
151 * Information used for parsing configuration specs:
152 */
153static Tk_CustomOption tagsOption = {
154    (Tk_OptionParseProc *) Tk_CanvasTagsParseProc,
155    Tk_CanvasTagsPrintProc, (ClientData) NULL
156};
157
158static Tk_OptionParseProc ValueInterpParseProc;
159static Tk_OptionPrintProc ValueInterpPrintProc;
160static Tk_CustomOption interpOption = {
161    (Tk_OptionParseProc *)ValueInterpParseProc, ValueInterpPrintProc,
162    (ClientData) NULL
163};
164
165static Tk_ConfigSpec configSpecs[] = {
166    {TK_CONFIG_BORDER, "-activebackground", (char *)NULL, (char*)NULL,
167        "white", Tk_Offset(HotspotItem, activeBorder), TK_CONFIG_NULL_OK},
168    {TK_CONFIG_COLOR, "-activeforeground", (char*)NULL, (char*)NULL,
169        "black", Tk_Offset(HotspotItem, activeColor), 0},
170    {TK_CONFIG_STRING, "-activeimage", "activeImage", (char*)NULL, (char*)NULL,
171        Tk_Offset(HotspotItem, activeImageName), TK_CONFIG_NULL_OK},
172    {TK_CONFIG_STRING, "-activevalue", "activeValue", (char*)NULL, (char*)NULL,
173        Tk_Offset(HotspotItem, activeValue), TK_CONFIG_NULL_OK},
174    {TK_CONFIG_ANCHOR, "-anchor", "anchor", (char*)NULL,
175        "center", Tk_Offset(HotspotItem, anchor), TK_CONFIG_DONT_SET_DEFAULT},
176    {TK_CONFIG_BORDER, "-background", (char *)NULL, (char*)NULL,
177        "", Tk_Offset(HotspotItem, border), TK_CONFIG_NULL_OK},
178    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", (char*)NULL,
179        "0", Tk_Offset(HotspotItem, borderWidth), TK_CONFIG_DONT_SET_DEFAULT},
180    {TK_CONFIG_BORDER, "-fill", (char *)NULL, (char*)NULL,
181        "", Tk_Offset(HotspotItem, border), TK_CONFIG_NULL_OK},
182    {TK_CONFIG_FONT, "-font", "font", (char*)NULL,
183        "helvetica -12", Tk_Offset(HotspotItem, font), 0},
184    {TK_CONFIG_COLOR, "-foreground", "foreground", (char*)NULL,
185        "black", Tk_Offset(HotspotItem, textColor), 0},
186    {TK_CONFIG_CUSTOM, "-interp", (char*)NULL, (char*)NULL, (char*)NULL,
187        Tk_Offset(HotspotItem, valueInterp), TK_CONFIG_NULL_OK, &interpOption},
188    {TK_CONFIG_FONT, "-valuefont", (char*)NULL, (char*)NULL,
189        "helvetica -12 bold", Tk_Offset(HotspotItem, valueFont), 0},
190    {TK_CONFIG_COLOR, "-valueforeground", (char*)NULL, (char*)NULL,
191        "blue", Tk_Offset(HotspotItem, valueColor), 0},
192    {TK_CONFIG_STRING, "-image", (char*)NULL, (char*)NULL, (char*)NULL,
193        Tk_Offset(HotspotItem, imageName), 0},
194    {TK_CONFIG_COLOR, "-outline", (char*)NULL, (char*)NULL,
195        "", Tk_Offset(HotspotItem, outlineColor), TK_CONFIG_NULL_OK},
196    {TK_CONFIG_RELIEF, "-relief", (char*)NULL, (char*)NULL,
197        "flat", Tk_Offset(HotspotItem, relief), TK_CONFIG_DONT_SET_DEFAULT},
198    {TK_CONFIG_BOOLEAN, "-showicons", (char*)NULL, (char*)NULL,
199        "0", Tk_Offset(HotspotItem, showIcons), TK_CONFIG_DONT_SET_DEFAULT},
200    {TK_CONFIG_CUSTOM, "-tags", (char*)NULL, (char*)NULL,
201        (char*)NULL, 0, TK_CONFIG_NULL_OK, &tagsOption},
202    {TK_CONFIG_STRING, "-text", (char*)NULL, (char*)NULL,
203        "", Tk_Offset(HotspotItem, text), 0},
204    {TK_CONFIG_END, (char*)NULL, (char*)NULL, (char*)NULL,
205        (char*)NULL, 0, 0}
206};
207
208/*
209 * Prototypes for procedures defined in this file
210 */
211static Tk_ItemConfigureProc ConfigureProc;
212static Tk_ItemCreateProc CreateProc;
213static Tk_ItemDeleteProc DeleteProc;
214static Tk_ItemDisplayProc DisplayProc;
215static Tk_ItemScaleProc ScaleProc;
216static Tk_ItemTranslateProc TranslateProc;
217static Tk_ItemCoordProc CoordsProc;
218static Tk_ItemAreaProc AreaProc;
219static Tk_ItemPointProc PointProc;
220static Tk_ItemPostscriptProc PostscriptProc;
221
222static Tk_ImageChangedProc ImageChangedProc;
223
224/*
225 * The structures below defines the canvas item type:
226 */
227static Tk_ItemType hotspotType = {
228    "hotspot",                          /* name */
229    sizeof(HotspotItem),                /* itemSize */
230    CreateProc,
231    configSpecs,                        /* configSpecs */
232    ConfigureProc,
233    CoordsProc,                 
234    DeleteProc,                         
235    DisplayProc,                       
236    TK_CONFIG_OBJS,                     /* flags */
237    PointProc,                         
238    AreaProc,                           
239    PostscriptProc,                     
240    ScaleProc,                         
241    TranslateProc,                     
242    NULL,                               /* indexProc */
243    NULL,                               /* icursorProc */
244    NULL,                               /* selectionProc */
245    NULL,                               /* insertProc */
246    NULL,                               /* dTextProc */
247    NULL,                               /* nextPtr */
248};
249
250/*
251 * The record below describes a canvas widget.  It is made available
252 * to the item procedures so they can access certain shared fields such
253 * as the overall displacement and scale factor for the canvas.
254 */
255
256typedef struct TkCanvas {
257    Tk_Window tkwin;            /* Window that embodies the canvas.  NULL
258                                 * means that the window has been destroyed
259                                 * but the data structures haven't yet been
260                                 * cleaned up.*/
261    Display *display;           /* Display containing widget;  needed, among
262                                 * other things, to release resources after
263                                 * tkwin has already gone away. */
264    Tcl_Interp *interp;         /* Interpreter associated with canvas. */
265    Tcl_Command widgetCmd;      /* Token for canvas's widget command. */
266    Tk_Item *firstItemPtr;      /* First in list of all items in canvas,
267                                 * or NULL if canvas empty. */
268    Tk_Item *lastItemPtr;       /* Last in list of all items in canvas,
269                                 * or NULL if canvas empty. */
270
271    /*
272     * Information used when displaying widget:
273     */
274
275    int borderWidth;            /* Width of 3-D border around window. */
276    Tk_3DBorder border;         /* Used for canvas background. */
277    int relief;                 /* Indicates whether window as a whole is
278                                 * raised, sunken, or flat. */
279    int highlightWidth;         /* Width in pixels of highlight to draw
280                                 * around widget when it has the focus.
281                                 * <= 0 means don't draw a highlight. */
282    XColor *highlightBgColorPtr;
283                                /* Color for drawing traversal highlight
284                                 * area when highlight is off. */
285    XColor *highlightColorPtr;  /* Color for drawing traversal highlight. */
286    int inset;                  /* Total width of all borders, including
287                                 * traversal highlight and 3-D border.
288                                 * Indicates how much interior stuff must
289                                 * be offset from outside edges to leave
290                                 * room for borders. */
291    GC pixmapGC;                /* Used to copy bits from a pixmap to the
292                                 * screen and also to clear the pixmap. */
293    int width, height;          /* Dimensions to request for canvas window,
294                                 * specified in pixels. */
295    int redrawX1, redrawY1;     /* Upper left corner of area to redraw,
296                                 * in pixel coordinates.  Border pixels
297                                 * are included.  Only valid if
298                                 * REDRAW_PENDING flag is set. */
299    int redrawX2, redrawY2;     /* Lower right corner of area to redraw,
300                                 * in integer canvas coordinates.  Border
301                                 * pixels will *not* be redrawn. */
302    int confine;                /* Non-zero means constrain view to keep
303                                 * as much of canvas visible as possible. */
304
305    /*
306     * Information used to manage the selection and insertion cursor:
307     */
308
309    Tk_CanvasTextInfo textInfo; /* Contains lots of fields;  see tk.h for
310                                 * details.  This structure is shared with
311                                 * the code that implements individual items. */
312    int insertOnTime;           /* Number of milliseconds cursor should spend
313                                 * in "on" state for each blink. */
314    int insertOffTime;          /* Number of milliseconds cursor should spend
315                                 * in "off" state for each blink. */
316    Tcl_TimerToken insertBlinkHandler;
317                                /* Timer handler used to blink cursor on and
318                                 * off. */
319
320    /*
321     * Transformation applied to canvas as a whole:  to compute screen
322     * coordinates (X,Y) from canvas coordinates (x,y), do the following:
323     *
324     * X = x - xOrigin;
325     * Y = y - yOrigin;
326     */
327
328    int xOrigin, yOrigin;       /* Canvas coordinates corresponding to
329                                 * upper-left corner of window, given in
330                                 * canvas pixel units. */
331    int drawableXOrigin, drawableYOrigin;
332                                /* During redisplay, these fields give the
333                                 * canvas coordinates corresponding to
334                                 * the upper-left corner of the drawable
335                                 * where items are actually being drawn
336                                 * (typically a pixmap smaller than the
337                                 * whole window). */
338
339    /*
340     * Information used for event bindings associated with items.
341     */
342
343    Tk_BindingTable bindingTable;
344                                /* Table of all bindings currently defined
345                                 * for this canvas.  NULL means that no
346                                 * bindings exist, so the table hasn't been
347                                 * created.  Each "object" used for this
348                                 * table is either a Tk_Uid for a tag or
349                                 * the address of an item named by id. */
350    Tk_Item *currentItemPtr;    /* The item currently containing the mouse
351                                 * pointer, or NULL if none. */
352    Tk_Item *newCurrentPtr;     /* The item that is about to become the
353                                 * current one, or NULL.  This field is
354                                 * used to detect deletions  of the new
355                                 * current item pointer that occur during
356                                 * Leave processing of the previous current
357                                 * item.  */
358    double closeEnough;         /* The mouse is assumed to be inside an
359                                 * item if it is this close to it. */
360    XEvent pickEvent;           /* The event upon which the current choice
361                                 * of currentItem is based.  Must be saved
362                                 * so that if the currentItem is deleted,
363                                 * can pick another. */
364    int state;                  /* Last known modifier state.  Used to
365                                 * defer picking a new current object
366                                 * while buttons are down. */
367
368    /*
369     * Information used for managing scrollbars:
370     */
371
372    char *xScrollCmd;           /* Command prefix for communicating with
373                                 * horizontal scrollbar.  NULL means no
374                                 * horizontal scrollbar.  Malloc'ed*/
375    char *yScrollCmd;           /* Command prefix for communicating with
376                                 * vertical scrollbar.  NULL means no
377                                 * vertical scrollbar.  Malloc'ed*/
378    int scrollX1, scrollY1, scrollX2, scrollY2;
379                                /* These four coordinates define the region
380                                 * that is the 100% area for scrolling (i.e.
381                                 * these numbers determine the size and
382                                 * location of the sliders on scrollbars).
383                                 * Units are pixels in canvas coords. */
384    char *regionString;         /* The option string from which scrollX1
385                                 * etc. are derived.  Malloc'ed. */
386    int xScrollIncrement;       /* If >0, defines a grid for horizontal
387                                 * scrolling.  This is the size of the "unit",
388                                 * and the left edge of the screen will always
389                                 * lie on an even unit boundary. */
390    int yScrollIncrement;       /* If >0, defines a grid for horizontal
391                                 * scrolling.  This is the size of the "unit",
392                                 * and the left edge of the screen will always
393                                 * lie on an even unit boundary. */
394
395    /*
396     * Information used for scanning:
397     */
398
399    int scanX;                  /* X-position at which scan started (e.g.
400                                 * button was pressed here). */
401    int scanXOrigin;            /* Value of xOrigin field when scan started. */
402    int scanY;                  /* Y-position at which scan started (e.g.
403                                 * button was pressed here). */
404    int scanYOrigin;            /* Value of yOrigin field when scan started. */
405
406    /*
407     * Information used to speed up searches by remembering the last item
408     * created or found with an item id search.
409     */
410
411    Tk_Item *hotPtr;            /* Pointer to "hot" item (one that's been
412                                 * recently used.  NULL means there's no
413                                 * hot item. */
414    Tk_Item *hotPrevPtr;        /* Pointer to predecessor to hotPtr (NULL
415                                 * means item is first in list).  This is
416                                 * only a hint and may not really be hotPtr's
417                                 * predecessor. */
418
419    /*
420     * Miscellaneous information:
421     */
422
423    Tk_Cursor cursor;           /* Current cursor for window, or None. */
424    char *takeFocus;            /* Value of -takefocus option;  not used in
425                                 * the C code, but used by keyboard traversal
426                                 * scripts.  Malloc'ed, but may be NULL. */
427    double pixelsPerMM;         /* Scale factor between MM and pixels;
428                                 * used when converting coordinates. */
429    int flags;                  /* Various flags;  see below for
430                                 * definitions. */
431    int nextId;                 /* Number to use as id for next item
432                                 * created in widget. */
433    Tk_PostscriptInfo psInfo;
434                                /* Pointer to information used for generating
435                                 * Postscript for the canvas.  NULL means
436                                 * no Postscript is currently being
437                                 * generated. */
438    Tcl_HashTable idTable;      /* Table of integer indices. */
439    /*
440     * Additional information, added by the 'dash'-patch
441     */
442    VOID *reserved1;
443    Tk_State canvas_state;      /* state of canvas */
444    VOID *reserved2;
445    VOID *reserved3;
446    Tk_TSOffset tsoffset;
447#ifndef USE_OLD_TAG_SEARCH
448    void *bindTagExprs; /* linked list of tag expressions used in bindings */
449#endif
450} TkCanvas;
451
452/* Forward declarations. */
453static void ComputeBbox(HotspotItem *itemPtr);
454
455
456static void
457EventuallyRedraw(HotspotItem *itemPtr)
458{
459    if ((itemPtr->flags & REDRAW_PENDING) == 0) {
460        itemPtr->flags |= REDRAW_PENDING;
461        Tk_CanvasEventuallyRedraw(itemPtr->canvas, itemPtr->base.x1,
462                itemPtr->base.y1, itemPtr->base.x2, itemPtr->base.y2);
463    }
464}
465
466static int
467GetCoordFromObj(Tcl_Interp *interp, HotspotItem *itemPtr, Tcl_Obj *objPtr,
468                double *valuePtr)
469{
470    const char *string;
471
472    string = Tcl_GetString(objPtr);
473    return Tk_CanvasGetCoord(interp, itemPtr->canvas, string, valuePtr);
474}
475
476
477/*
478 *----------------------------------------------------------------------
479 *
480 * ValueInterpParseProc --
481 *
482 *      Converts the name of an axis to a pointer to its axis structure.
483 *
484 * Results:
485 *      The return value is a standard Tcl result.  The axis flags are
486 *      written into the widget record.
487 *
488 *----------------------------------------------------------------------
489 */
490/*ARGSUSED*/
491static int
492ValueInterpParseProc(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
493                     CONST84 char *string, char *widgRec, int offset)
494{
495    HotspotItem *itemPtr = (HotspotItem *)widgRec;
496
497    if (string[0] == '\0') {
498        itemPtr->valueInterp = NULL;
499        return TCL_OK;
500    }
501    itemPtr->valueInterp = Tcl_GetSlave(interp, string);
502    if (itemPtr->valueInterp == NULL) {
503        return TCL_ERROR;
504    }
505    return TCL_OK;
506}
507
508/*
509 *----------------------------------------------------------------------
510 *
511 * ValueInterpParseProc --
512 *
513 *      Convert the window coordinates into a string.
514 *
515 * Results:
516 *      The string representing the coordinate position is returned.
517 *
518 *----------------------------------------------------------------------
519 */
520/*ARGSUSED*/
521static char *
522ValueInterpPrintProc(ClientData clientData, Tk_Window tkwin, char *widgRec,
523                     int offset, Tcl_FreeProc **freeProcPtr)
524{
525    HotspotItem *itemPtr = (HotspotItem *)widgRec;
526
527    if (itemPtr->valueInterp != NULL) {
528        Tcl_Obj *objPtr;
529
530        Tcl_GetInterpPath(itemPtr->interp, itemPtr->valueInterp);
531        objPtr = Tcl_GetObjResult(itemPtr->interp);
532        *freeProcPtr = TCL_VOLATILE;
533        return Tcl_GetString(objPtr);
534    }
535    return "";
536}
537
538/*
539 *---------------------------------------------------------------------------
540 *
541 * TraceVarProc --
542 *
543 *      This procedure is invoked when someone changes the state variable
544 *      associated with a radiobutton or checkbutton entry.  The entry's
545 *      selected state is set to match the value of the variable.
546 *
547 * Results:
548 *      NULL is always returned.
549 *
550 * Side effects:
551 *      The combobox entry may become selected or deselected.
552 *
553 *---------------------------------------------------------------------------
554 */
555static char *
556TraceVarProc(
557    ClientData clientData,              /* Information about the item. */
558    Tcl_Interp *interp,                 /* Interpreter containing variable. */
559    const char *name1,                  /* First part of variable's name. */
560    const char *name2,                  /* Second part of variable's name. */
561    int flags)                          /* Describes what just happened. */
562{
563    HotspotItem *itemPtr = clientData;
564
565    if (flags & TCL_INTERP_DESTROYED) {
566        return NULL;                    /* Interpreter is going away. */
567
568    }
569    /*
570     * If the variable is being unset, then re-establish the trace.
571     */
572    if (flags & TCL_TRACE_UNSETS) {
573        if (flags & TCL_TRACE_DESTROYED) {
574            Tcl_TraceVar(interp, name1, VAR_FLAGS, TraceVarProc, itemPtr);
575        }
576    }
577    itemPtr->flags |= SUBSTITUTIONS_PENDING;
578    EventuallyRedraw(itemPtr);
579    return NULL;                        /* Done. */
580}
581
582/*
583 * ----------------------------------------------------------------------
584 *
585 * ImageChangedProc --
586 *
587 *      This procedure is invoked by the image code whenever the manager
588 *      for an image does something that affects the image's size or
589 *      how it is displayed.
590 *
591 * Results:
592 *      None.
593 *
594 * Side effects:
595 *      Arranges for the canvas to get redisplayed.
596 *
597 * ----------------------------------------------------------------------
598 */
599static void
600ImageChangedProc(clientData, x, y, width, height, imgWidth, imgHeight)
601    ClientData clientData;              /* Pointer to canvas item for image. */
602    int x, y;                           /* Upper left pixel (within image)
603                                         * that must be redisplayed. */
604    int width, height;                  /* Dimensions of area to redisplay
605                                         * (may be <= 0). */
606    int imgWidth, imgHeight;            /* New dimensions of image. */
607{     
608    HotspotItem *itemPtr = (HotspotItem *) clientData;
609   
610    itemPtr->flags |= LAYOUT_PENDING;
611    EventuallyRedraw(itemPtr);
612    return;
613}
614
615static void
616AddTextSegment(HotspotItem *itemPtr, const char *text, int length)
617{
618    ItemSegment *segPtr;
619
620    segPtr = (ItemSegment *)Tcl_Alloc(sizeof(ItemSegment) + length + 1);
621    memset(segPtr, 0, sizeof(ItemSegment) + length + 1);
622    segPtr->itemPtr = itemPtr;
623    segPtr->lineNum = itemPtr->numLines;
624    segPtr->type = SEGMENT_TEXT;
625    strncpy(segPtr->text, text, length);
626    segPtr->text[length] = '\0';
627    segPtr->length = length;
628    segPtr->x = segPtr->y = 0;
629    segPtr->nextPtr = NULL;
630    if (itemPtr->firstPtr == NULL) {
631        itemPtr->firstPtr = segPtr;
632    }
633    if (itemPtr->lastPtr == NULL) {
634        itemPtr->lastPtr = segPtr;
635    } else {
636        itemPtr->lastPtr->nextPtr = segPtr;
637        itemPtr->lastPtr = segPtr;
638    }
639}
640
641static void
642AddValueSegment(HotspotItem *itemPtr, const char *varName, int length)
643{
644    ItemSegment *segPtr;
645    Tcl_Interp *interp;
646
647    segPtr = (ItemSegment *)Tcl_Alloc(sizeof(ItemSegment) + length + 1);
648    memset(segPtr, 0, sizeof(ItemSegment) + length + 1);
649    segPtr->itemPtr = itemPtr;
650    segPtr->lineNum = itemPtr->numLines;
651    segPtr->type = SEGMENT_VALUE;
652    strncpy(segPtr->text, varName, length);
653    segPtr->text[length] = '\0';
654    segPtr->length = length;
655    interp = itemPtr->valueInterp;
656    if (interp == NULL) {
657        interp = itemPtr->interp;
658    }
659    Tcl_TraceVar(interp, varName, VAR_FLAGS, TraceVarProc, itemPtr);
660    segPtr->value = NULL;
661    segPtr->x = segPtr->y = 0;
662    segPtr->nextPtr = NULL;
663    if (itemPtr->firstPtr == NULL) {
664        itemPtr->firstPtr = segPtr;
665    }
666    if (itemPtr->lastPtr == NULL) {
667        itemPtr->lastPtr = segPtr;
668    } else {
669        itemPtr->lastPtr->nextPtr = segPtr;
670        itemPtr->lastPtr = segPtr;
671    }
672}
673
674static void
675DestroySegment(ItemSegment *segPtr)
676{
677    HotspotItem *itemPtr;
678
679    itemPtr = segPtr->itemPtr;
680    if (segPtr->layout != NULL) {
681        Tk_FreeTextLayout(segPtr->layout);
682    }
683    if (segPtr->type == SEGMENT_VALUE) {
684        Tcl_UntraceVar(itemPtr->interp, segPtr->text, VAR_FLAGS, TraceVarProc,
685                itemPtr);
686    }
687    if (segPtr->value != NULL) {
688        Tcl_Free((char *)segPtr->value);
689    }
690    Tcl_Free((char *)segPtr);
691}
692
693static void
694DestroySegments(HotspotItem *itemPtr)
695{
696    ItemSegment *segPtr, *nextPtr;
697
698    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = nextPtr) {
699        nextPtr = segPtr->nextPtr;
700        DestroySegment(segPtr);
701    }
702    itemPtr->firstPtr = itemPtr->lastPtr = NULL;
703}
704
705static void
706DoSubstitutions(HotspotItem *itemPtr)
707{
708    ItemSegment *segPtr;
709    Tcl_Interp *interp;
710
711    itemPtr->flags &= ~SUBSTITUTIONS_PENDING;
712    interp = itemPtr->valueInterp;
713    if (interp == NULL) {
714        interp = itemPtr->interp;
715    }
716    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
717        const char *value;
718
719        if (segPtr->type != SEGMENT_VALUE) {
720            continue;
721        }
722        value = Tcl_GetVar(interp, segPtr->text, TCL_GLOBAL_ONLY);
723        if (value == NULL) {
724            value = "???";
725        }
726        if (segPtr->value != NULL) {
727            Tcl_Free((char *)segPtr->value);
728        }
729        segPtr->value = Tcl_Alloc(strlen(value) + 1);
730        strcpy(segPtr->value, value);
731    }
732    itemPtr->flags |= LAYOUT_PENDING;
733}
734
735static void
736ComputeGeometry(HotspotItem *itemPtr)
737{
738    int x, y, w, h;
739    ItemSegment *segPtr;
740    int imgWidth, imgHeight;
741    int maxWidth, maxHeight;
742    int justify;
743    int wrapLength;
744    int flags;
745
746    justify = TK_JUSTIFY_LEFT;
747    wrapLength = 0;
748    flags = 0;
749    /* Collect the individual segment sizes. */
750    imgWidth = itemPtr->maxImageWidth + IMAGE_PAD;
751    imgHeight = itemPtr->maxImageHeight + 2 * IMAGE_PAD;
752    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
753        if (segPtr->layout != NULL) {
754            Tk_FreeTextLayout(segPtr->layout);
755        }
756        w = h = 0;
757        switch (segPtr->type) {
758        case SEGMENT_VALUE:
759            segPtr->layout = Tk_ComputeTextLayout(itemPtr->valueFont,
760                        segPtr->value, -1, wrapLength, justify, flags, &w, &h);
761            w += TEXT_PADX * 2;
762            h += TEXT_PADY * 2;
763            if (itemPtr->showIcons) {
764                w += imgWidth, h = MAX(h, imgHeight);
765            }
766            break;
767        case SEGMENT_TEXT:
768            segPtr->layout = Tk_ComputeTextLayout(itemPtr->font, segPtr->text,
769                        -1, wrapLength, justify, flags, &w, &h);
770            w += TEXT_PADX * 2;
771            h += TEXT_PADY * 2;
772            break;
773        default:
774            Tcl_Panic("unknown hotspot segment type");
775        }
776        segPtr->width = w;
777        segPtr->height = h;
778#ifdef notdef
779        fprintf(stderr, "ComputeGeometry: item=(%s) segment=%x (%s) type=%d w=%d h=%d lineNum=%d nextPtr=%x\n",
780                itemPtr->text, segPtr, segPtr->text, segPtr->type, segPtr->width,
781                segPtr->height, segPtr->lineNum, segPtr->nextPtr);
782#endif
783    }
784    /* Compute the relative positions of the segments. */
785    x = y = 0;
786    maxWidth = maxHeight = 0;
787    for (segPtr = itemPtr->firstPtr; segPtr != NULL; /*empty*/) {
788        int maxHeight, lineNum;
789        ItemSegment *startPtr;
790
791        maxHeight = 0;
792        lineNum = segPtr->lineNum;
793        startPtr = segPtr;
794        x = 0;
795
796        while ((segPtr != NULL) && (segPtr->lineNum == lineNum)) {
797            if (segPtr->height > maxHeight) {
798                maxHeight = segPtr->height;
799            }
800            segPtr->x = x;
801            x += segPtr->width;
802            segPtr = segPtr->nextPtr;
803        }
804        if (x > maxWidth) {
805            maxWidth = x;
806        }
807        segPtr = startPtr;
808        while ((segPtr != NULL) && (segPtr->lineNum == lineNum)) {
809            segPtr->y = y + (maxHeight - segPtr->height) / 2;
810            segPtr = segPtr->nextPtr;
811        }
812        y += maxHeight;
813    }
814    itemPtr->width = maxWidth;
815    itemPtr->height = y;
816    ComputeBbox(itemPtr);
817}
818
819static int
820ParseDescription(Tcl_Interp *interp, HotspotItem *itemPtr,
821                const char *description)
822{
823    const char *p, *start;
824
825    if (itemPtr->firstPtr != NULL) {
826        DestroySegments(itemPtr);
827    }
828    start = description;
829    itemPtr->numLines = 1;
830    for (p = description; *p != '\0'; p++) {
831        if ((p[0] == '$') && (p[1] == '{')) {
832            const char *varName;
833            const char *q;
834
835            if (p > start) {
836                /* Save the leading text. */
837                AddTextSegment(itemPtr, start, p - start);
838                start = p;
839            }
840            /* Start of substitution variable. */
841            varName = p + 2;
842            for (q = varName; q != '\0'; q++) {
843                if (*q == '}') {
844                    AddValueSegment(itemPtr, varName, q - varName);
845                    p = q;
846                    start = p + 1;
847                    goto next;
848                }
849            }
850            Tcl_AppendResult(interp, "back variable specification in \"",
851                             description, "\"", (char *)NULL);
852            return TCL_ERROR;
853        } else if (p[0] == '\n') {
854            if (p > start) {
855                AddTextSegment(itemPtr, start, p - start);
856                start = p + 1;
857                itemPtr->numLines++;
858            }
859        }
860    next:
861        ;
862    }
863    if (p > start) {
864        /* Save the trailing text. */
865        AddTextSegment(itemPtr, start, p - start);
866    }
867    itemPtr->flags |= SUBSTITUTIONS_PENDING;
868    return TCL_OK;
869}
870
871/*
872 * ------------------------------------------------------------------------
873 *
874 *  CreateProc --
875 *
876 *    This procedure is invoked to create a new hotspot item
877 *    in a canvas.  A hotspot is a text item with a box behind it.
878 *
879 *  Results:
880 *    A standard Tcl return name.  If an error occurred in creating
881 *    the item then an error message is left in the interp's result.
882 *    In this case itemPtr is left uninitialized so it can be safely
883 *    freed by the caller.
884 *
885 *  Side effects:
886 *    A new item is created.
887 *
888 * ------------------------------------------------------------------------
889 */
890static int
891CreateProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
892           int objc, Tcl_Obj *const *objv)
893{
894    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
895    double x, y;
896    Tk_Window tkwin;
897
898    tkwin = Tk_CanvasTkwin(canvas);
899    if (objc < 2) {
900        Tcl_AppendResult(interp, "wrong # args: should be \"",
901            Tk_PathName(tkwin), " create ", canvItemPtr->typePtr->name,
902            " x y ?options?\"", (char *)NULL);
903        return TCL_ERROR;
904    }
905
906    /* Initialize all HotspotItem fields, expect for the the base. */
907    memset((char *)itemPtr + sizeof(Tk_Item), 0,
908           sizeof(HotspotItem) - sizeof(Tk_Item));
909    /*
910     * Carry out initialization that is needed in order to clean up after
911     * errors during the the remainder of this procedure.
912     */
913    itemPtr->anchor = TK_ANCHOR_CENTER;
914    itemPtr->canvas = canvas;
915    itemPtr->display = Tk_Display(tkwin);
916    itemPtr->interp = interp;
917    itemPtr->relief = TK_RELIEF_FLAT;
918    itemPtr->tkwin = tkwin;
919    itemPtr->valueInterp = interp;
920    itemPtr->showIcons = FALSE;
921
922    /* Process the arguments to fill in the item record. */
923    if ((GetCoordFromObj(interp, itemPtr, objv[0], &x) != TCL_OK) ||
924        (GetCoordFromObj(interp, itemPtr, objv[1], &y) != TCL_OK)) {
925        DeleteProc(canvas, canvItemPtr, itemPtr->display);
926        return TCL_ERROR;
927    }
928    itemPtr->x = x;
929    itemPtr->y = y;
930    ComputeBbox(itemPtr);
931    if (ConfigureProc(interp, canvas, canvItemPtr, objc - 2, objv + 2, 0)
932        != TCL_OK) {
933        DeleteProc(canvas, canvItemPtr, itemPtr->display);
934        return TCL_ERROR;
935    }
936    return TCL_OK;
937}
938
939/*
940 * ------------------------------------------------------------------------
941 *
942 *  CoordsProc --
943 *
944 *    This procedure is invoked to process the "coords" widget command on
945 *    hotspot items.  See the user documentation for details on what
946 *    it does.
947 *
948 *  Results:
949 *    Returns TCL_OK or TCL_ERROR, and sets the interp's result.
950 *
951 *  Side effects:
952 *    The coordinates for the given item may be changed.
953 *
954 * ------------------------------------------------------------------------
955 */
956static int
957CoordsProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
958           int objc, Tcl_Obj *const *objv)
959{
960    HotspotItem *itemPtr = (HotspotItem *) canvItemPtr;
961    double x, y;
962
963    if (objc == 0) {
964        Tcl_Obj *listObjPtr;
965
966        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
967        Tcl_ListObjAppendElement(interp, listObjPtr,
968                Tcl_NewDoubleObj(itemPtr->x));
969        Tcl_ListObjAppendElement(interp, listObjPtr,
970                Tcl_NewDoubleObj(itemPtr->y));
971        Tcl_SetObjResult(interp, listObjPtr);
972        return TCL_OK;
973    }
974    if (objc == 1) {
975        int elc;
976        Tcl_Obj **elv;
977
978        if (Tcl_ListObjGetElements(interp, objv[0], &elc, &elv) != TCL_OK) {
979            return TCL_ERROR;
980        }
981        if ((GetCoordFromObj(interp, itemPtr, elv[0], &x) != TCL_OK) ||
982            (GetCoordFromObj(interp, itemPtr, elv[1], &y) != TCL_OK)) {
983            return TCL_ERROR;
984        }
985    } else if (objc == 3) {
986        if ((GetCoordFromObj(interp, itemPtr, objv[0], &x) != TCL_OK) ||
987            (GetCoordFromObj(interp, itemPtr, objv[1], &y) != TCL_OK)) {
988            return TCL_ERROR;
989        }
990    } else {
991        char buf[64 + TCL_INTEGER_SPACE];
992
993        sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", objc);
994        Tcl_AppendResult(interp, buf, (char *)NULL);
995        return TCL_ERROR;
996    }
997    itemPtr->x = x;
998    itemPtr->y = y;
999    ComputeBbox(itemPtr);
1000    return TCL_OK;
1001}
1002
1003/*
1004 * ------------------------------------------------------------------------
1005 *  ConfigureProc --
1006 *
1007 *    This procedure is invoked to configure various aspects of a
1008 *    hotspot item, such as its color and font.
1009 *
1010 *  Results:
1011 *    A standard Tcl result code.  If an error occurs, then an error
1012 *    message is left in the interp's result.
1013 *
1014 *  Side effects:
1015 *    Configuration information, such as colors and GCs, may be set
1016 *    for itemPtr.
1017 * ------------------------------------------------------------------------
1018 */
1019static int
1020ConfigureProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
1021              int objc, Tcl_Obj *CONST *objv, int flags)
1022{
1023    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1024    XGCValues gcValues;
1025    GC newGC;
1026    unsigned long mask;
1027    Tk_Window tkwin;
1028    Tk_Image tkImage;
1029
1030    tkwin = Tk_CanvasTkwin(canvas);
1031    if (Tk_ConfigureWidget(interp, tkwin, configSpecs, objc,
1032        (CONST char **)objv, (char*)itemPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
1033        return TCL_ERROR;
1034    }
1035
1036    /* Normal text GC. */
1037    mask = GCFont | GCForeground;
1038    gcValues.font = Tk_FontId(itemPtr->font);
1039    gcValues.foreground = itemPtr->textColor->pixel;
1040    newGC = Tk_GetGC(tkwin, mask, &gcValues);
1041    if (itemPtr->normalGC != None) {
1042        Tk_FreeGC(Tk_Display(tkwin), itemPtr->normalGC);
1043    }
1044    itemPtr->normalGC = newGC;
1045
1046    /* Outline GC */
1047    if (itemPtr->outlineColor != NULL) {
1048        mask = GCLineWidth | GCForeground;
1049        gcValues.foreground = itemPtr->outlineColor->pixel;
1050        gcValues.line_width = 1;
1051        newGC = Tk_GetGC(tkwin, mask, &gcValues);
1052        if (itemPtr->outlineGC != None) {
1053            Tk_FreeGC(Tk_Display(tkwin), itemPtr->outlineGC);
1054        }
1055        itemPtr->outlineGC = newGC;
1056    }
1057
1058    /* Substituted value GC. */
1059    mask = GCFont | GCForeground;
1060    gcValues.font = Tk_FontId(itemPtr->valueFont);
1061    gcValues.foreground = itemPtr->valueColor->pixel;
1062    newGC = Tk_GetGC(tkwin, mask, &gcValues);
1063    if (itemPtr->valueGC != None) {
1064        Tk_FreeGC(Tk_Display(tkwin), itemPtr->valueGC);
1065    }
1066    itemPtr->valueGC = newGC;
1067
1068    /* Active substituted value GC. */
1069    mask = GCFont | GCForeground;
1070    gcValues.font = Tk_FontId(itemPtr->valueFont);
1071    gcValues.foreground = itemPtr->activeColor->pixel;
1072    newGC = Tk_GetGC(tkwin, mask, &gcValues);
1073    if (itemPtr->activeGC != None) {
1074        Tk_FreeGC(Tk_Display(tkwin), itemPtr->activeGC);
1075    }
1076    itemPtr->activeGC = newGC;
1077
1078    itemPtr->maxImageWidth = itemPtr->maxImageHeight = 0;
1079    /* Normal hotspot image. */
1080    if (itemPtr->imageName != NULL) {
1081        int iw, ih;
1082
1083        tkImage = Tk_GetImage(interp, tkwin, itemPtr->imageName,
1084                ImageChangedProc, (ClientData)itemPtr);
1085        if (tkImage == NULL) {
1086            return TCL_ERROR;
1087        }
1088        Tk_SizeOfImage(tkImage, &iw, &ih);
1089        if (iw > itemPtr->maxImageWidth) {
1090            itemPtr->maxImageWidth = iw;
1091        }
1092        if (ih > itemPtr->maxImageHeight) {
1093            itemPtr->maxImageHeight = ih;
1094        }
1095    } else {
1096        tkImage = NULL;
1097    }
1098       
1099    if (itemPtr->image != NULL) {
1100        Tk_FreeImage(itemPtr->image);
1101    }
1102    itemPtr->image = tkImage;
1103
1104    /* Active hotspot image. */
1105    if (itemPtr->activeImageName != NULL) {
1106        int iw, ih;
1107       
1108        tkImage = Tk_GetImage(interp, tkwin, itemPtr->activeImageName,
1109                ImageChangedProc, (ClientData)itemPtr);
1110        if (tkImage == NULL) {
1111            return TCL_ERROR;
1112        }
1113        Tk_SizeOfImage(tkImage, &iw, &ih);
1114        if (iw > itemPtr->maxImageWidth) {
1115            itemPtr->maxImageWidth = iw;
1116        }
1117        if (ih > itemPtr->maxImageHeight) {
1118            itemPtr->maxImageHeight = ih;
1119        }
1120    } else {
1121        tkImage = NULL;
1122    }
1123    if (itemPtr->activeImage != NULL) {
1124        Tk_FreeImage(itemPtr->activeImage);
1125    }
1126    itemPtr->activeImage = tkImage;
1127
1128    if (itemPtr->text != NULL) {
1129        if (ParseDescription(interp, itemPtr, itemPtr->text) != TCL_OK) {
1130            return TCL_ERROR;
1131        }
1132    }
1133    ComputeBbox(itemPtr);
1134    return TCL_OK;
1135}
1136
1137/*
1138 * ------------------------------------------------------------------------
1139 *  DeleteHotspot --
1140 *
1141 *    This procedure is called to clean up the data structure
1142 *    associated with a hotspot item.
1143 *
1144 *  Results:
1145 *    None.
1146 *
1147 *  Side effects:
1148 *    Resources associated with itemPtr are released.
1149 * ------------------------------------------------------------------------
1150 */
1151static void
1152DeleteProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, Display *display)
1153{
1154    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1155
1156    if (itemPtr->textColor != NULL) {
1157        Tk_FreeColor(itemPtr->textColor);
1158    }
1159    if (itemPtr->valueColor != NULL) {
1160        Tk_FreeColor(itemPtr->valueColor);
1161    }
1162    if (itemPtr->activeColor != NULL) {
1163        Tk_FreeColor(itemPtr->activeColor);
1164    }
1165    if (itemPtr->outlineColor != NULL) {
1166        Tk_FreeColor(itemPtr->outlineColor);
1167    }
1168    if (itemPtr->border != NULL) {
1169        Tk_Free3DBorder(itemPtr->border);
1170    }
1171    if (itemPtr->activeBorder != NULL) {
1172        Tk_Free3DBorder(itemPtr->activeBorder);
1173    }
1174    if (itemPtr->text != NULL) {
1175        Tcl_Free((char *)itemPtr->text);
1176    }
1177    if (itemPtr->imageName != NULL) {
1178        Tcl_Free((char *)itemPtr->imageName);
1179    }
1180    if (itemPtr->activeImageName != NULL) {
1181        Tcl_Free((char *)itemPtr->activeImageName);
1182    }
1183    if (itemPtr->image != NULL ) {
1184        Tk_FreeImage(itemPtr->image);
1185    }
1186    if (itemPtr->activeImage != NULL ) {
1187        Tk_FreeImage(itemPtr->activeImage);
1188    }
1189    if (itemPtr->font != NULL) {
1190        Tk_FreeFont(itemPtr->font);
1191    }
1192    if (itemPtr->valueFont != NULL) {
1193        Tk_FreeFont(itemPtr->valueFont);
1194    }
1195    if (itemPtr->normalGC != None) {
1196        Tk_FreeGC(display, itemPtr->normalGC);
1197    }
1198    if (itemPtr->activeGC != None) {
1199        Tk_FreeGC(display, itemPtr->activeGC);
1200    }
1201    if (itemPtr->outlineGC != None) {
1202        Tk_FreeGC(display, itemPtr->outlineGC);
1203    }
1204    if (itemPtr->valueGC != None) {
1205        Tk_FreeGC(display, itemPtr->valueGC);
1206    }
1207    DestroySegments(itemPtr);
1208}
1209
1210
1211/*
1212 *---------------------------------------------------------------------------
1213 *
1214 * TranslateAnchor --
1215 *
1216 *      Translate the coordinates of a given bounding box based upon the
1217 *      anchor specified.  The anchor indicates where the given xy position
1218 *      is in relation to the bounding box.
1219 *
1220 *              nw --- n --- ne
1221 *              |            |     x,y ---+
1222 *              w   center   e      |     |
1223 *              |            |      +-----+
1224 *              sw --- s --- se
1225 *
1226 * Results:
1227 *      The translated coordinates of the bounding box are returned.
1228 *
1229 *---------------------------------------------------------------------------
1230 */
1231static void
1232TranslateAnchor(HotspotItem *itemPtr, double *xPtr, double *yPtr)
1233{
1234    double x, y;
1235
1236    x = *xPtr, y = *yPtr;
1237    switch (itemPtr->anchor) {
1238    case TK_ANCHOR_NW:          /* Upper left corner */
1239        break;
1240    case TK_ANCHOR_W:           /* Left center */
1241        y -= (itemPtr->height * 0.5);
1242        break;
1243    case TK_ANCHOR_SW:          /* Lower left corner */
1244        y -= itemPtr->height;
1245        break;
1246    case TK_ANCHOR_N:           /* Top center */
1247        x -= (itemPtr->width * 0.5);
1248        break;
1249    case TK_ANCHOR_CENTER:      /* Centered */
1250        x -= (itemPtr->width * 0.5);
1251        y -= (itemPtr->height * 0.5);
1252        break;
1253    case TK_ANCHOR_S:           /* Bottom center */
1254        x -= (itemPtr->width * 0.5);
1255        y -= itemPtr->height;
1256        break;
1257    case TK_ANCHOR_NE:          /* Upper right corner */
1258        x -= itemPtr->width;
1259        break;
1260    case TK_ANCHOR_E:           /* Right center */
1261        x -= itemPtr->width;
1262        y -= (itemPtr->height * 0.5);
1263        break;
1264    case TK_ANCHOR_SE:          /* Lower right corner */
1265        x -= itemPtr->width;
1266        y -= itemPtr->height;
1267        break;
1268    }
1269    *xPtr = x;
1270    *yPtr = y;
1271}
1272
1273/*
1274 * ------------------------------------------------------------------------
1275 *
1276 *  ComputeBbox --
1277 *
1278 *    This procedure is invoked to compute the bounding box of all the pixels
1279 *    that may be drawn as part of a text item.  In addition, it recomputes
1280 *    all of the geometry information used to display a text item or check for
1281 *    mouse hits.
1282 *
1283 *  Results:
1284 *    None.
1285 *
1286 *  Side effects:
1287 *    The fields x1, y1, x2, and y2 are updated in the base structure
1288 *    for itemPtr, and the linePtr structure is regenerated
1289 *    for itemPtr.
1290 *
1291 * ------------------------------------------------------------------------
1292 */
1293static void
1294ComputeBbox(HotspotItem *itemPtr)
1295{
1296    double x, y;
1297
1298    x = itemPtr->x, y = itemPtr->y;
1299    TranslateAnchor(itemPtr, &x, &y);
1300    itemPtr->x1 = x;
1301    itemPtr->x2 = x + itemPtr->width;
1302    itemPtr->y1 = y;
1303    itemPtr->y2 = y + itemPtr->height;
1304    itemPtr->base.x1 = ROUND(itemPtr->x1);
1305    itemPtr->base.y1 = ROUND(itemPtr->y1);
1306    itemPtr->base.x2 = ROUND(itemPtr->x2);
1307    itemPtr->base.y2 = ROUND(itemPtr->y2);
1308}
1309
1310static void
1311DrawSegment(ItemSegment *segPtr, Drawable drawable, int x, int y)
1312{
1313    HotspotItem *itemPtr;
1314    GC gc;
1315    int isActive;
1316
1317    itemPtr = segPtr->itemPtr;
1318    if (segPtr->type == SEGMENT_TEXT) {
1319        Tk_DrawTextLayout(Tk_Display(itemPtr->tkwin), drawable,
1320                itemPtr->normalGC, segPtr->layout, x + segPtr->x + TEXT_PADX,
1321                y + segPtr->y + TEXT_PADY, 0, -1);
1322        return;
1323    }
1324    isActive = ((itemPtr->activeValue != NULL) &&
1325                (strcmp(itemPtr->activeValue, segPtr->text) == 0));
1326       
1327    if ((itemPtr->activeBorder != NULL) && (isActive)) {
1328        Tk_Fill3DRectangle(itemPtr->tkwin, drawable, itemPtr->activeBorder,
1329                x + segPtr->x, y + segPtr->y, segPtr->width, segPtr->height,
1330                1, TK_RELIEF_SOLID);
1331    }
1332    if (itemPtr->showIcons) {
1333        Tk_Image tkImage;
1334        int w, h;
1335        int ix, iy, ih, iw;
1336
1337        ih = itemPtr->maxImageHeight * 2 * IMAGE_PAD;
1338        iw = itemPtr->maxImageWidth + IMAGE_PAD;
1339        ix = x + segPtr->x + IMAGE_PAD;
1340        iy = y + segPtr->y + IMAGE_PAD;
1341        if (segPtr->height > ih) {
1342            iy += (segPtr->height - ih) / 2;
1343        }
1344        tkImage = (isActive) ? itemPtr->activeImage : itemPtr->image;
1345        Tk_SizeOfImage(tkImage, &w, &h);
1346        Tk_RedrawImage(tkImage, 0, 0, w, h, drawable, ix, iy);
1347        x += iw;
1348    }
1349    gc = (isActive) ? itemPtr->activeGC : itemPtr->valueGC;
1350    Tk_DrawTextLayout(Tk_Display(itemPtr->tkwin), drawable, gc, segPtr->layout,
1351         x + segPtr->x + TEXT_PADX, y + segPtr->y + TEXT_PADY, 0, -1);
1352}
1353
1354/*
1355 * ------------------------------------------------------------------------
1356 *
1357 *  DisplayProc --
1358 *
1359 *    This procedure is invoked to draw a text item in a given drawable.
1360 *
1361 *  Results:
1362 *    None.
1363 *
1364 *  Side effects:
1365 *    ItemPtr is drawn in drawable using the transformation information
1366 *    in canvas.
1367 *
1368 * ------------------------------------------------------------------------
1369 */
1370static void
1371DisplayProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, Display *display,
1372            Drawable drawable, int x, int y, int width, int height)
1373{
1374    HotspotItem *itemPtr = (HotspotItem *) canvItemPtr;
1375    ItemSegment *segPtr;
1376    short int drawX, drawY;
1377    double tx, ty;
1378
1379    itemPtr->flags &= ~REDRAW_PENDING;
1380    if (itemPtr->flags & SUBSTITUTIONS_PENDING) {
1381        DoSubstitutions(itemPtr);
1382    }
1383    if (itemPtr->flags & LAYOUT_PENDING) {
1384        ComputeGeometry(itemPtr);
1385    }
1386    tx = itemPtr->x, ty = itemPtr->y;
1387    TranslateAnchor(itemPtr, &tx, &ty);
1388
1389    /* Convert canvas coordinates to drawable coordinates. */
1390    Tk_CanvasDrawableCoords(canvas, tx, ty, &drawX, &drawY);
1391    x = drawX, y = drawY;
1392
1393    if (itemPtr->border != NULL) {
1394        Tk_Fill3DRectangle(itemPtr->tkwin, drawable, itemPtr->border, x, y,
1395                itemPtr->width, itemPtr->height, itemPtr->borderWidth,
1396                itemPtr->relief);
1397    }
1398    if (itemPtr->outlineColor != NULL) {
1399        XDrawRectangle(display, drawable, itemPtr->outlineGC, x, y,
1400                itemPtr->width, itemPtr->height);
1401    }
1402    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1403        DrawSegment(segPtr, drawable, x, y);
1404    }
1405}
1406
1407/*
1408 * ------------------------------------------------------------------------
1409 *
1410 *  PointProc --
1411 *
1412 *    Computes the distance from a given point to a given hotspot item, in
1413 *    canvas units.
1414 *
1415 *  Results:
1416 *    The return value is 0 if the point whose x and y coordinates
1417 *    are pointPtr[0] and pointPtr[1] is inside the text item.  If
1418 *    the point isn't inside the text item then the return value
1419 *    is the distance from the point to the text item.
1420 *
1421 *  Side effects:
1422 *    None.
1423 *
1424 * ------------------------------------------------------------------------
1425 */
1426static double
1427PointProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double *pointPtr)
1428{
1429    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1430    double dx, dy;
1431
1432    if (pointPtr[0] < itemPtr->x1) {
1433        dx = itemPtr->x1 - pointPtr[0];
1434    } else if (pointPtr[0] >= itemPtr->x2)  {
1435        dx = pointPtr[0] + 1 - itemPtr->x2;
1436    } else {
1437        dx = 0;
1438    }
1439    if (pointPtr[1] < itemPtr->y1) {
1440        dy = itemPtr->y1 - pointPtr[1];
1441    } else if (pointPtr[1] >= itemPtr->y2)  {
1442        dy = pointPtr[1] + 1 - itemPtr->y2;
1443    } else {
1444        dy = 0;
1445    }
1446    return hypot(dx, dy);
1447}
1448
1449/*
1450 * ------------------------------------------------------------------------
1451 *
1452 *  AreaProc --
1453 *
1454 *    This procedure is called to determine whether an item lies
1455 *    entirely inside, entirely outside, or overlapping a given
1456 *    rectangle.
1457 *
1458 *  Results:
1459 *    -1 is returned if the item is entirely outside the area given
1460 *    by rectPtr, 0 if it overlaps, and 1 if it is entirely inside
1461 *    the given area.
1462 *
1463 *  Side effects:
1464 *    None.
1465 *
1466 * ------------------------------------------------------------------------
1467 */
1468static int
1469AreaProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double *rectPtr)
1470{
1471    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1472
1473    if ((rectPtr[2] <= itemPtr->x1) || (rectPtr[0] >= itemPtr->x2) ||
1474        (rectPtr[3] <= itemPtr->y1) || (rectPtr[1] >= itemPtr->y2)) {
1475        return -1;
1476    }
1477    if ((rectPtr[0] <= itemPtr->x1) && (rectPtr[1] <= itemPtr->y1) &&
1478        (rectPtr[2] >= itemPtr->x2) && (rectPtr[3] >= itemPtr->y2)) {
1479        return 1;
1480    }
1481    return 0;
1482}
1483
1484/*
1485 * ------------------------------------------------------------------------
1486 *
1487 *  ScaleProc --
1488 *
1489 *    This procedure is invoked to rescale a text item.
1490 *
1491 * Results:
1492 *    None.
1493 *
1494 * Side effects:
1495 *    Scales the position of the text, but not the size of the font
1496 *    for the text.
1497 *
1498 * ------------------------------------------------------------------------
1499 */
1500/* ARGSUSED */
1501static void
1502ScaleProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double x, double y,
1503          double scaleX, double scaleY)
1504{
1505    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1506
1507    itemPtr->x = x + scaleX * (itemPtr->x - x);
1508    itemPtr->y = y + scaleY * (itemPtr->y - y);
1509    ComputeBbox(itemPtr);
1510    return;
1511}
1512
1513/*
1514 * ------------------------------------------------------------------------
1515 *
1516 *  TranslateProc --
1517 *
1518 *    This procedure is called to move a text item by a given amount.
1519 *
1520 *  Results:
1521 *    None.
1522 *
1523 *  Side effects:
1524 *    The position of the text item is offset by (xDelta, yDelta),
1525 *    and the bounding box is updated in the generic part of the
1526 *    item structure.
1527 *
1528 * ------------------------------------------------------------------------
1529 */
1530static void
1531TranslateProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double dx,  double dy)
1532{
1533    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1534
1535    itemPtr->x += dx;
1536    itemPtr->y += dy;
1537    ComputeBbox(itemPtr);
1538}
1539
1540/*
1541 * ------------------------------------------------------------------------
1542 *
1543 *  PostscriptProc --
1544 *
1545 *    This procedure is called to generate Postscript for hotspot items.
1546 *
1547 *  Results:
1548 *    The return value is a standard Tcl result.  If an error occurs in
1549 *    generating Postscript then an error message is left in the interp's
1550 *    result, replacing whatever used to be there.  If no error occurs, then
1551 *    Postscript for the item is appended to the result.
1552 *
1553 *  Side effects:
1554 *    None.
1555 *
1556 * ------------------------------------------------------------------------
1557 */
1558static int
1559PostscriptProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
1560               int prepass)
1561{
1562#ifdef notdef
1563    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
1564    Tk_Window canvasWin = Tk_CanvasTkwin(canvas);
1565
1566    int x, y;
1567    double xpos;
1568    Tk_FontMetrics fm;
1569    char *justify;
1570    char buffer[500];
1571    int delta;
1572
1573    if (itemPtr->textColor == NULL || itemPtr->text == NULL
1574          || *itemPtr->text == 0) {
1575        return TCL_OK;
1576    }
1577
1578    if (Tk_CanvasPsFont(interp, canvas, itemPtr->font) != TCL_OK) {
1579        return TCL_ERROR;
1580    }
1581
1582
1583    if (!prepass &&
1584        Tk_CanvasPsColor(interp, canvas, itemPtr->textColor) != TCL_OK) {
1585        return TCL_ERROR;
1586    }
1587    xpos = itemPtr->x;
1588
1589    /*
1590     * Check for an image to print.
1591     */
1592
1593    if (itemPtr->imageLeft) {
1594      if(!prepass) {
1595        delta = (itemPtr->base.y2 - itemPtr->base.y1)/2 - itemPtr->imageLeftH/2;
1596        sprintf(buffer, "%.15g %.15g", xpos,
1597                Tk_CanvasPsY(canvas, itemPtr->y+delta+2));
1598        Tcl_AppendResult(interp, buffer, " translate\n", (char *) NULL);
1599      }
1600
1601      Tk_PostscriptImage(itemPtr->imageLeft, interp, canvasWin,
1602                         ((TkCanvas*)canvas)->psInfo, 0, 0,
1603                         itemPtr->imageLeftW, itemPtr->imageLeftH, prepass);
1604
1605      if ( !prepass ) {
1606        /*
1607         * This PS code is needed in order for the label to display
1608         * correctly
1609         */
1610       
1611        Tcl_AppendResult(interp,"grestore\ngsave\n",(char *)NULL);
1612      }
1613
1614      xpos += itemPtr->imageLeftW + itemPtr->padding;
1615    }
1616
1617   
1618    if (prepass != 0 ) {
1619      return TCL_OK;
1620    }
1621
1622    /*
1623     * Before drawing the text, reset the font and color information.
1624     * Otherwise the text won't appear.
1625     */
1626
1627    if ( itemPtr->imageLeft ){
1628      if (Tk_CanvasPsFont(interp, canvas, itemPtr->font) != TCL_OK) {
1629        return TCL_ERROR;
1630      }
1631     
1632      if (Tk_CanvasPsColor(interp, canvas, itemPtr->textColor) != TCL_OK) {
1633        return TCL_ERROR;
1634      }
1635    }
1636
1637
1638    /*
1639     * Draw the text for the hotspot.
1640     */
1641    sprintf(buffer, "%.15g %.15g [\n", xpos,
1642        Tk_CanvasPsY(canvas, itemPtr->y));
1643    Tcl_AppendResult(interp, buffer, (char *) NULL);
1644
1645    Tk_TextLayoutToPostscript(interp, itemPtr->textLayout);
1646
1647    x = 0;  y = 0;  justify = NULL;
1648    switch (itemPtr->anchor) {
1649        case TK_ANCHOR_NW:        x = 0; y = 0;        break;
1650        case TK_ANCHOR_N:         x = 1; y = 0;        break;
1651        case TK_ANCHOR_NE:        x = 2; y = 0;        break;
1652        case TK_ANCHOR_E:         x = 2; y = 1;        break;
1653        case TK_ANCHOR_SE:        x = 2; y = 2;        break;
1654        case TK_ANCHOR_S:         x = 1; y = 2;        break;
1655        case TK_ANCHOR_SW:        x = 0; y = 2;        break;
1656        case TK_ANCHOR_W:         x = 0; y = 1;        break;
1657        case TK_ANCHOR_CENTER:    x = 1; y = 1;        break;
1658    }
1659    justify = "0"; /* TK_JUSTIFY_LEFT */
1660
1661    Tk_GetFontMetrics(itemPtr->font, &fm);
1662    sprintf(buffer, "] %d %g %g %s %s DrawText\n",
1663            fm.linespace, x / -2.0, y / 2.0, justify,
1664            "false" /* stipple */);
1665    Tcl_AppendResult(interp, buffer, (char *) NULL);
1666#endif
1667    return TCL_OK;
1668}
1669
1670static HotspotItem *
1671GetHotspotItem(Tcl_Interp *interp, Tcl_Obj *canvasObjPtr, Tcl_Obj *itemObjPtr)
1672{
1673    const char *string;
1674    Tk_Item *canvItemPtr;
1675    HotspotItem *itemPtr;
1676    Tcl_CmdInfo cmdInfo;
1677    TkCanvas *canvasPtr;
1678    int id;
1679    Tcl_HashEntry *hPtr;
1680    Tk_Window tkwin;
1681
1682    /* See if we can find the canvas window associated by the name. */
1683    string = Tcl_GetString(canvasObjPtr);
1684    tkwin = Tk_NameToWindow(interp, string, Tk_MainWindow(interp));
1685    if (tkwin == NULL) {
1686        Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
1687                         (char *)NULL);
1688        return NULL;
1689    }
1690    if (strcmp(Tk_Class(tkwin), "Canvas") != 0) {
1691        Tcl_AppendResult(interp, "window \"", string, "\" isn't a canvas.",
1692                         (char *)NULL);
1693        return NULL;
1694    }
1695    /* If we're this far, then we can try to get the clientData. If the widget
1696    * command was renamed, we're out of luck. */
1697    if (!Tcl_GetCommandInfo(interp, string, &cmdInfo)) {
1698        Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
1699                         (char *)NULL);
1700        return NULL;
1701    }
1702    canvasPtr = cmdInfo.objClientData;
1703    if (Tcl_GetIntFromObj(interp, itemObjPtr, &id) != TCL_OK) {
1704        return NULL;
1705    }
1706    string = Tcl_GetString(itemObjPtr);
1707    hPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *)id);
1708    if (hPtr == NULL) {
1709        Tcl_AppendResult(interp, "can't find canvas item \"", string,
1710                "\"", (char *)NULL);
1711        return NULL;
1712
1713    }
1714    canvItemPtr = Tcl_GetHashValue(hPtr);
1715    /* The canvas item we find has to be a hotspot. */
1716    if (canvItemPtr->typePtr != &hotspotType) {
1717        Tcl_AppendResult(interp, "bad canvas item \"", string,
1718                "\": must be a hotspot", (char *)NULL);
1719        return NULL;
1720
1721    }
1722    return itemPtr = (HotspotItem *)canvItemPtr;
1723}
1724
1725static const char *
1726Identify(Tcl_Interp *interp, HotspotItem *itemPtr, double x, double y)
1727{
1728    ItemSegment *segPtr;
1729
1730    x -= itemPtr->x1;
1731    y -= itemPtr->y1;
1732    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1733        if (segPtr->type == SEGMENT_TEXT) {
1734            continue;                   /* Ignore text segments. */
1735        }
1736        if ((x >= segPtr->x) && (x < (segPtr->x + segPtr->width)) &&
1737            (y >= segPtr->y) && (y < (segPtr->y + segPtr->height))) {
1738            return segPtr->text;
1739        }
1740    }
1741    return "";
1742}
1743
1744static int
1745HotspotCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1746           Tcl_Obj *const *objv)
1747{
1748    const char *string;
1749    HotspotItem *itemPtr;
1750    int length;
1751    char c;
1752
1753    if (objc < 3) {
1754        return TCL_ERROR;
1755    }
1756    itemPtr = GetHotspotItem(interp, objv[2], objv[3]);
1757    if (itemPtr == NULL) {
1758        return TCL_ERROR;
1759    }
1760    string = Tcl_GetStringFromObj(objv[1], &length);
1761    c = string[0];
1762    if ((c == 'a') && (strncmp(string, "activate", length) == 0)) {
1763        /* hotspot activate .c 0 varName */
1764    } else if ((c == 'd') && (strncmp(string, "deactivate", length) == 0)) {
1765        /* hotspot deactivate .c 0 */
1766        itemPtr->activeValue = NULL;
1767    } else if ((c == 'i') && (strncmp(string, "identify", length) == 0)) {
1768        double x, y;
1769        const char *token;
1770        Tcl_Obj *objPtr;
1771
1772        /* hotspot identify .c 0 x y */
1773        if ((Tcl_GetDoubleFromObj(interp, objv[4], &x) != TCL_OK) ||
1774            (Tcl_GetDoubleFromObj(interp, objv[5], &y) != TCL_OK)) {
1775            return TCL_ERROR;
1776        }
1777        token = Identify(interp, itemPtr, x, y);
1778        objPtr = Tcl_NewStringObj(token, -1);
1779        Tcl_SetObjResult(interp, objPtr);
1780    } else if ((c == 'v') && (strncmp(string, "variables", length) == 0)) {
1781        Tcl_Obj *listObjPtr;
1782        ItemSegment *segPtr;
1783
1784        /* hotspot variables .c 0 */
1785        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1786        for (segPtr = itemPtr->firstPtr; segPtr != NULL;
1787             segPtr = segPtr->nextPtr) {
1788            if (segPtr->type == SEGMENT_VALUE) {
1789                Tcl_Obj *objPtr;
1790
1791                objPtr = Tcl_NewStringObj(segPtr->text, -1);
1792                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1793            }
1794        }
1795        Tcl_SetObjResult(interp, listObjPtr);
1796    } else {
1797        Tcl_AppendResult(interp, "unknown hotspot option \"", string,
1798                "\": should be either activate, deactivate, identity, ",
1799                "or variables", (char *)NULL);
1800        return TCL_ERROR;
1801    }
1802    return TCL_OK;
1803}
1804
1805/*
1806 * ------------------------------------------------------------------------
1807 *  RpCanvHotspot_Init --
1808 *
1809 *  Invoked when the Rappture GUI library is being initialized
1810 *  to install the "hotspot" item on the Tk canvas widget.
1811 *
1812 *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
1813 *  message in the interp) if anything goes wrong.
1814 * ------------------------------------------------------------------------
1815 */
1816int
1817RpCanvHotspot_Init(interp)
1818    Tcl_Interp *interp;         /* interpreter being initialized */
1819{
1820    Tk_CreateItemType(&hotspotType);
1821    Tcl_CreateObjCommand(interp, "Rappture::hotspot", HotspotCmd, NULL, NULL);
1822    return TCL_OK;
1823}
1824
Note: See TracBrowser for help on using the repository browser.