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

Last change on this file since 4503 was 3636, checked in by mmc, 8 years ago

Fixed the drawing widget to handle notifications from outside widgets, so
that canvas items react to value changes. Also added tooltips for drawing
items.

Nudged the next/back buttons for the pager in a bit, so they're a little
easier to press in the iPad app.

Fixed the Ruby template for the builder to include the overwrite/append flag.

File size: 55.7 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    long lid;
1680    Tcl_HashEntry *hPtr;
1681    Tk_Window tkwin;
1682
1683    /* See if we can find the canvas window associated by the name. */
1684    string = Tcl_GetString(canvasObjPtr);
1685    tkwin = Tk_NameToWindow(interp, string, Tk_MainWindow(interp));
1686    if (tkwin == NULL) {
1687        Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
1688                         (char *)NULL);
1689        return NULL;
1690    }
1691    if (strcmp(Tk_Class(tkwin), "Canvas") != 0) {
1692        Tcl_AppendResult(interp, "window \"", string, "\" isn't a canvas.",
1693                         (char *)NULL);
1694        return NULL;
1695    }
1696    /* If we're this far, then we can try to get the clientData. If the widget
1697    * command was renamed, we're out of luck. */
1698    if (!Tcl_GetCommandInfo(interp, string, &cmdInfo)) {
1699        Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
1700                         (char *)NULL);
1701        return NULL;
1702    }
1703    canvasPtr = cmdInfo.objClientData;
1704    if (Tcl_GetIntFromObj(interp, itemObjPtr, &id) != TCL_OK) {
1705        return NULL;
1706    }
1707    string = Tcl_GetString(itemObjPtr);
1708
1709    lid = id;
1710    hPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *)lid);
1711    if (hPtr == NULL) {
1712        Tcl_AppendResult(interp, "can't find canvas item \"", string,
1713                "\"", (char *)NULL);
1714        return NULL;
1715
1716    }
1717    canvItemPtr = Tcl_GetHashValue(hPtr);
1718    /* The canvas item we find has to be a hotspot. */
1719    if (canvItemPtr->typePtr != &hotspotType) {
1720        Tcl_AppendResult(interp, "bad canvas item \"", string,
1721                "\": must be a hotspot", (char *)NULL);
1722        return NULL;
1723
1724    }
1725    return itemPtr = (HotspotItem *)canvItemPtr;
1726}
1727
1728static Tcl_Obj *
1729Identify(Tcl_Interp *interp, HotspotItem *itemPtr, double x, double y)
1730{
1731    Tcl_Obj* resultPtr = NULL;
1732    ItemSegment *segPtr;
1733    Tcl_Obj* objPtr;
1734
1735    x -= itemPtr->x1;
1736    y -= itemPtr->y1;
1737    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1738        if (segPtr->type == SEGMENT_TEXT) {
1739            continue;                   /* Ignore text segments. */
1740        }
1741        if ((x >= segPtr->x) && (x < (segPtr->x + segPtr->width)) &&
1742            (y >= segPtr->y) && (y < (segPtr->y + segPtr->height))) {
1743
1744            /* build return list: {string x0 y0 x1 y1} */
1745            resultPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1746
1747            objPtr = Tcl_NewStringObj(segPtr->text, -1);
1748            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
1749
1750            objPtr = Tcl_NewIntObj(itemPtr->x1 + segPtr->x);
1751            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
1752            objPtr = Tcl_NewIntObj(itemPtr->y1 + segPtr->y);
1753            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
1754
1755            objPtr = Tcl_NewIntObj(itemPtr->x1 + segPtr->x + segPtr->width);
1756            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
1757            objPtr = Tcl_NewIntObj(itemPtr->y1 + segPtr->y + segPtr->height);
1758            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
1759
1760            return resultPtr;
1761        }
1762    }
1763    return NULL;
1764}
1765
1766static int
1767HotspotCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1768           Tcl_Obj *const *objv)
1769{
1770    const char *string;
1771    HotspotItem *itemPtr;
1772    int length;
1773    char c;
1774
1775    if (objc < 3) {
1776        return TCL_ERROR;
1777    }
1778    itemPtr = GetHotspotItem(interp, objv[2], objv[3]);
1779    if (itemPtr == NULL) {
1780        return TCL_ERROR;
1781    }
1782    string = Tcl_GetStringFromObj(objv[1], &length);
1783    c = string[0];
1784    if ((c == 'a') && (strncmp(string, "activate", length) == 0)) {
1785        /* hotspot activate .c 0 varName */
1786    } else if ((c == 'd') && (strncmp(string, "deactivate", length) == 0)) {
1787        /* hotspot deactivate .c 0 */
1788        itemPtr->activeValue = NULL;
1789    } else if ((c == 'i') && (strncmp(string, "identify", length) == 0)) {
1790        double x, y;
1791        Tcl_Obj *objPtr;
1792
1793        /* hotspot identify .c 0 x y */
1794        if ((Tcl_GetDoubleFromObj(interp, objv[4], &x) != TCL_OK) ||
1795            (Tcl_GetDoubleFromObj(interp, objv[5], &y) != TCL_OK)) {
1796            return TCL_ERROR;
1797        }
1798        objPtr = Identify(interp, itemPtr, x, y);
1799        if (objPtr != NULL) {
1800            Tcl_SetObjResult(interp, objPtr);
1801        }
1802    } else if ((c == 'v') && (strncmp(string, "variables", length) == 0)) {
1803        Tcl_Obj *listObjPtr;
1804        ItemSegment *segPtr;
1805
1806        /* hotspot variables .c 0 */
1807        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1808        for (segPtr = itemPtr->firstPtr; segPtr != NULL;
1809             segPtr = segPtr->nextPtr) {
1810            if (segPtr->type == SEGMENT_VALUE) {
1811                Tcl_Obj *objPtr;
1812
1813                objPtr = Tcl_NewStringObj(segPtr->text, -1);
1814                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1815            }
1816        }
1817        Tcl_SetObjResult(interp, listObjPtr);
1818    } else {
1819        Tcl_AppendResult(interp, "unknown hotspot option \"", string,
1820                "\": should be either activate, deactivate, identity, ",
1821                "or variables", (char *)NULL);
1822        return TCL_ERROR;
1823    }
1824    return TCL_OK;
1825}
1826
1827/*
1828 * ------------------------------------------------------------------------
1829 *  RpCanvHotspot_Init --
1830 *
1831 *  Invoked when the Rappture GUI library is being initialized
1832 *  to install the "hotspot" item on the Tk canvas widget.
1833 *
1834 *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
1835 *  message in the interp) if anything goes wrong.
1836 * ------------------------------------------------------------------------
1837 */
1838int
1839RpCanvHotspot_Init(interp)
1840    Tcl_Interp *interp;         /* interpreter being initialized */
1841{
1842    Tk_CreateItemType(&hotspotType);
1843    Tcl_CreateObjCommand(interp, "Rappture::hotspot", HotspotCmd, NULL, NULL);
1844    return TCL_OK;
1845}
1846
Note: See TracBrowser for help on using the repository browser.