/***** * map.c : XmHTML imagemap routines * * This file Version $Revision: 1.7 $ * * Creation date: Tue Feb 25 19:14:55 GMT+0100 1997 * Last modification: $Date: 1999/07/29 01:26:29 $ * By: $Author: sopwith $ * Current State: $State: Exp $ * * Author: newt * * Copyright (C) 1994-1997 by Ripley Software Development * All Rights Reserved * * This file is part of the XmHTML Widget Library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ /***** * ChangeLog * $Log: map.c,v $ * Revision 1.7 1999/07/29 01:26:29 sopwith * * * Fix all warnings. * * Revision 1.6 1999/02/25 01:05:07 unammx * Missing bit of the strtok patches from Ulrich * * Revision 1.5 1998/02/12 03:09:33 unammx * Merge to Koen's XmHTML 1.1.2 + following fixes: * * Wed Feb 11 20:27:19 1998 Miguel de Icaza * * * gtk-forms.c (freeForm): gtk_destroy_widget is no longer needed * with the refcounting changes. * * * gtk-xmhtml.c (gtk_xmhtml_remove): Only god knows why I was * adding the just removed widget. * * Revision 1.4 1997/12/29 22:16:31 unammx * This version does: * * - Sync with Koen to version Beta 1.1.2c of the XmHTML widget. * Includes various table fixes. * * - Callbacks are now properly checked for the Gtk edition (ie, * signals). * * Revision 1.3 1997/12/24 17:53:55 unammx * Fun stuff: * * The widget now handles mouse motion, mouse clicks, anchors can * be clicked. * * The widget emits signals for all of the interesting events * (the same events that were used by the Motif port, we just use * signals instead of XtCallbacks). * * Boring stuff: * * The widget now handles focusin/focusout/enternotif/leavenotify * * More code sharing between the Motif frontend an the Gtk * frontned; More portability macros; * * Cleaned up some more the privte widget header files. * * Revision 1.2 1997/12/18 23:00:16 unammx * More fixes and added PNG support. - Federico * * Revision 1.1 1997/11/28 03:38:57 gnomecvs * Work in progress port of XmHTML; No, it does not compile, don't even try -mig * * Revision 1.8 1997/08/30 01:11:52 newt * my_strdup -> strdup and _XmHTMLWarning proto changes. * * Revision 1.7 1997/08/01 13:02:49 newt * Performance enhancements + comment updating. * * Revision 1.6 1997/05/28 01:52:04 newt * ? * * Revision 1.5 1997/04/29 14:28:00 newt * Header files modifications. * * Revision 1.4 1997/03/11 19:56:01 newt * replaced XmHTMLAddImagemap call by XmHTMLImageAddImageMap * * Revision 1.3 1997/03/04 18:48:10 newt * _XmHTMLDrawImagemapSelection added * * Revision 1.2 1997/03/04 01:00:25 newt * Polygon and default shaped imagemaps are now working * * Revision 1.1 1997/03/02 23:02:42 newt * Initial Revision * *****/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "XmHTMLP.h" #include "XmHTMLfuncs.h" /*** External Function Prototype Declarations ***/ /*** Public Variable Declarations ***/ /*** Private Datatype Declarations ****/ typedef enum{ MAP_DEFAULT = 1, MAP_RECT, MAP_CIRCLE, MAP_POLY }MapShape; /*** Private Function Prototype Declarations ****/ static Region createPoly(int npoints, int *points); static void deleteArea(mapArea *area); static void freeImageMap(XmHTMLImageMap *map); static int* getCoordinates(String attributes, int *ncoords); /* * A point is in a rectangle if it's x coordinate is larger than the left * side and smaller than the right side and if it's y coordinate is larger * than the top side and smaller than the bottom side. */ #define PointInRect(X,Y,CRD) \ (((X) >= CRD[0] && (X) <= CRD[2]) && ((Y) >= CRD[1] && (Y) <= CRD[3])) /* * a point is in a circle if the distance from the circle's origin to the * point is less than the radius of the circle (plain ol' Pythagoras) */ #define PointInCircle(X,Y,XC,YC,R) \ (((((X)-(XC))*((X)-(XC))) + (((Y)-(YC))*((Y)-(YC))) ) <= (R*R)) /* seamingly easy */ #define PointInPoly(X,Y,REG) \ (XPointInRegion((REG),(X),(Y))) /*** Private Variable Declarations ***/ struct _mapArea{ String url; /* url to call when clicked */ String alt; /* alternative text */ Boolean nohref; /* obvious */ MapShape shape; /* type of area */ int ncoords; /* no of coordinates */ int *coords; /* array of coordinates */ Region region; /* Region for polygons */ XmHTMLAnchor *anchor; /* anchor object */ struct _mapArea *next; /* ptr to next area */ }; /***** * Name: createPoly * Return Type: Region * Description: creates a polygon region given the polygon's coordinates * In: * npoints: total no of points * points: array of points defining the polygon. * The last point automatically connects to the first. * Returns: * a newly created region *****/ static Region createPoly(int npoints, int *points) { static Region region; XPoint *xpoints; int i, half; /* create array of XPoint's required for region generation */ half = npoints/2.; xpoints = (XPoint*)calloc(half+1, sizeof(XPoint)); for(i = 0; i < half; i++) { xpoints[i].x = points[i*2]; xpoints[i].y = points[i*2+1]; } /* last point is same as first point */ xpoints[half].x = points[0]; xpoints[half].y = points[1]; /* create the region */ region = XPolygonRegion(xpoints, half+1, WindingRule); /* no longer needed, free it */ free(xpoints); return(region); } /***** * Name: deleteArea * Return Type: void * Description: frees all memory occupied by the given area * In: * area: area to free * Returns: * nothing *****/ static void deleteArea(mapArea *area) { /* sanity */ if(area == NULL) return; if(area->url) free(area->url); if(area->alt) free(area->alt); if(area->coords) free(area->coords); if(area->shape == MAP_POLY && area->region) XDestroyRegion(area->region); free(area); area = NULL; } /***** * Name: freeImageMap * Return Type: void * Description: frees the given imagemap and all areas defined for it. * In: * map: imagemap to free * Returns: * nothing *****/ static void freeImageMap(XmHTMLImageMap *map) { mapArea *area, *area_list; area_list = map->areas; while(area_list) { area = area_list->next; deleteArea(area_list); area_list = area; } if(map->name) free(map->name); free(map); map = NULL; } /***** * Name: getCoordinates * Return Type: int* * Description: returns array of map coordinates * In: * attributes: raw area specs * *ncoords: no of coordinates, filled upon return * Returns: * an array of integers representing the coordinates of an area. * returns NULL if no coords are found. *****/ static int* getCoordinates(String attributes, int *ncoords) { String chPtr, tmp; int *coords; int num; char *tokp; *ncoords = 0; coords = NULL; /* get coordinates and count how many there are */ chPtr = _XmHTMLTagGetValue(attributes, "coords"); if(!chPtr) return(NULL); /* count how many coordinates we have */ for(num = 0, tmp = strtok_r(chPtr, ",", &tokp); tmp != NULL; tmp = strtok_r(NULL, ",", &tokp), num++); free(chPtr); if(!num) return(NULL); _XmHTMLDebug(10, ("map.c: getCoordinates, counted %i numbers\n", num)); /* allocate memory for these coordinates */ coords = (int*)calloc(num, sizeof(int)); /* again get coordinates, but now convert to numbers */ chPtr = _XmHTMLTagGetValue(attributes, "coords"); for(num = 0, tmp = strtok_r(chPtr, ",", &tokp); tmp != NULL; tmp = strtok_r(NULL, ",", &tokp), num++) coords[num] = atoi(tmp); /* no longer needed */ free(chPtr); #ifdef DEBUG { int i; _XmHTMLDebug(10, ("map.c: getCoordinates: ")); for(i = 0; i < num; i++) _XmHTMLDebug(10, ("%i ", coords[i])); _XmHTMLDebug(10, ("\n")); } #endif *ncoords = num; return(coords); } static void drawSelectionRectangle(XmHTMLWidget html, XmHTMLImage *image, mapArea *area) { int x = image->owner->x - html->html.scroll_x + area->coords[0]; int y = image->owner->y - html->html.scroll_y + area->coords[1]; int width = area->coords[2] - area->coords[0]; int height = area->coords[3] - area->coords[1]; Toolkit_Set_Foreground(Toolkit_Display(html->html.work_area), html->html.gc, html->html.imagemap_fg); Toolkit_Draw_Rectangle(Toolkit_Display(html->html.work_area), Toolkit_Widget_Window(html->html.work_area), html->html.gc, x, y, width, height); } static void drawSelectionPolygon(XmHTMLWidget html, XmHTMLImage *image, mapArea *area) { TPoint *points; int i, npoints; int x = image->owner->x - html->html.scroll_x; int y = image->owner->y - html->html.scroll_y; npoints = area->ncoords/2; points = (TPoint*)calloc(npoints+1, sizeof(TPoint)); for(i = 0; i < npoints; i++) { points[i].x = area->coords[i*2] + x; points[i].y = area->coords[i*2+1] + y; } /* last point is same as first point */ points[npoints].x = points[0].x; points[npoints].y = points[0].y; Toolkit_Set_Foreground(Toolkit_Display(html->html.work_area), html->html.gc, html->html.imagemap_fg); Toolkit_Draw_Lines(Toolkit_Display(html->html.work_area), Toolkit_Widget_Window(html->html.work_area), html->html.gc, points, npoints+1, CoordModeOrigin); free(points); } static void drawSelectionArc(XmHTMLWidget html, XmHTMLImage *image, mapArea *area) { int x = image->owner->x - html->html.scroll_x + area->coords[0]; int y = image->owner->y - html->html.scroll_y + area->coords[1]; int radius = area->coords[2]; /* upper-left corner of bounding rectangle */ x -= radius; y -= radius; Toolkit_Set_Foreground(Toolkit_Display(html->html.work_area), html->html.gc, html->html.imagemap_fg); Toolkit_Draw_Arc(Toolkit_Display(html->html.work_area), Toolkit_Widget_Window(html->html.work_area), html->html.gc, x, y, 2*radius, 2*radius, 0, 23040); } /******** ****** Public Functions ********/ /***** * Name: _XmHTMLAddAreaToMap * Return Type: void * Description: adds the given area specification to the given imagemap * In: * map: XmHTMLImageMap * object: raw area data * Returns: * nothing *****/ void _XmHTMLAddAreaToMap(XmHTMLWidget html, XmHTMLImageMap *map, XmHTMLObject *object) { static mapArea *area; mapArea *tmp; String chPtr; /* sanity */ if(map == NULL || object->attributes == NULL) return; area = (mapArea*)malloc(sizeof(mapArea)); (void)memset(area, 0, sizeof(mapArea)); area->url = _XmHTMLTagGetValue(object->attributes, "href"); area->alt = _XmHTMLTagGetValue(object->attributes, "alt"); area->nohref = _XmHTMLTagCheck(object->attributes, "nohref"); chPtr = _XmHTMLTagGetValue(object->attributes, "shape"); /* get specified coordinates */ area->coords = getCoordinates(object->attributes, &area->ncoords); /* * No shape given, try to figure it out using the number of specified * coordinates */ if(chPtr == NULL) { switch(area->ncoords) { case 0: /* no coords given => default area */ area->shape = MAP_DEFAULT; break; case 3: /* 3 coords => circle */ area->shape = MAP_CIRCLE; break; case 4: /* 4 coords => assume rectangle */ area->shape = MAP_RECT; break; default: /* assume poly */ area->shape = MAP_POLY; } } else { switch(tolower(chPtr[0])) { case 'c': area->shape = MAP_CIRCLE; break; case 'r': area->shape = MAP_RECT; break; case 'p': area->shape = MAP_POLY; break; default: area->shape = MAP_DEFAULT; } free(chPtr); } /* check if all coordinates specs are valid for the given shape */ switch(area->shape) { case MAP_RECT: /* too bad if coords are bad */ if(area->ncoords != 4) { _XmHTMLWarning(__WFUNC__(html, "_XmHTMLAddAreaToImagemap"), "Imagemap shape = RECT but " "I have %i coordinates instead of 4. Area ignored.", area->ncoords); deleteArea(area); return; } break; case MAP_CIRCLE: /* too bad if coords are bad */ if(area->ncoords != 3) { _XmHTMLWarning(__WFUNC__(html, "_XmHTMLAddAreaToImagemap"), "Imagemap shape = CIRCLE " "but I have %i coordinates instead of 3. Area ignored.", area->ncoords); deleteArea(area); return; } break; case MAP_POLY: if(!area->coords) { _XmHTMLWarning(__WFUNC__(html, "_XmHTMLAddAreaToImagemap"), "Imagemap shape = POLY but" " I have no coordinates!. Area ignored.", area->ncoords); deleteArea(area); return; } if(area->ncoords % 2) { _XmHTMLWarning(__WFUNC__(html, "_XmHTMLAddAreaToImagemap"), "Imagemap shape = POLY " "but I have oddsized polygon coordinates (%i found).\n" " Skipping last coordinate.", area->ncoords); area->ncoords--; } area->region = createPoly(area->ncoords, area->coords); break; default: break; } /* gets automagically added to the list of anchors for this widget */ if(!area->nohref) area->anchor = _XmHTMLNewAnchor(html, object); /* add this area to the list of areas for this imagemap */ if(map->areas == NULL) { map->nareas = 1; map->areas = area; return; } for(tmp = map->areas; tmp != NULL && tmp->next != NULL ; tmp = tmp->next); map->nareas++; tmp->next = area; _XmHTMLDebug(10, ("map.c: _XmHTMLAddAreaToMap, stored href %s, map now " "contains %i areas.\n", area->url, map->nareas)); } /***** * Name: _XmHTMLCreateImagemap * Return Type: XmHTMLImageMap * Description: initializes a new imagemap * In: * name: name for this map * Returns: * the newly created imagemap *****/ XmHTMLImageMap* _XmHTMLCreateImagemap(String name) { static XmHTMLImageMap *map; map = (XmHTMLImageMap*)malloc(sizeof(XmHTMLImageMap)); (void)memset(map, 0, sizeof(XmHTMLImageMap)); map->name = strdup(name); return(map); } /***** * Name: _XmHTMLStoreImagemap * Return Type: void * Description: stores the given imagemap in the given html widget * In: * html: XmHTMLWidget * map: map to store * Returns: * nothing, but appends the given map to the list of imagemaps of the * given HTML widget. *****/ void _XmHTMLStoreImagemap(XmHTMLWidget html, XmHTMLImageMap *map) { XmHTMLImageMap *tmp; /* head of the list */ if(html->html.image_maps == NULL) { html->html.image_maps = map; return; } /* walk to the one but last map in the list and insert it */ for(tmp = html->html.image_maps; tmp != NULL && tmp->next != NULL; tmp = tmp->next); tmp->next = map; } /***** * Name: _XmHTMLGetImagemap * Return Type: XmHTMLImageMap* * Description: retrieves the imagemap with the given name. * In: * html: XmHTMLWidget * name: name of map to retrieve * Returns: * named map if found, NULL otherwise. *****/ XmHTMLImageMap* _XmHTMLGetImagemap(XmHTMLWidget html, String name) { XmHTMLImageMap *tmp; if(!name || *name == '\0') return(NULL); for(tmp = html->html.image_maps; tmp != NULL && strcasecmp(tmp->name, &name[1]); tmp = tmp->next); _XmHTMLFullDebug(10, ("map.c: _XmHTMLGetImageMap, found %s match for " "named imagemap %s\n", (tmp ? "a" : "no"), name)); return(tmp); } /***** * Name: _XmHTMLDrawImagemapSelection * Return Type: void * Description: draws a bounding box around each area in an imagemap * In: * html: XmHTMLWidget id * image: image for which to paint bounding boxes * Returns: * nothing *****/ void _XmHTMLDrawImagemapSelection(XmHTMLWidget html, XmHTMLImage *image) { XmHTMLImageMap *map; int xs, ys; mapArea *area; if((map = _XmHTMLGetImagemap(html, image->map_url)) == NULL) return; /* map coordinates to upperleft corner of image */ xs = html->html.scroll_x - image->owner->x; ys = html->html.scroll_y - image->owner->y; area = map->areas; while(area) { switch(area->shape) { case MAP_RECT: drawSelectionRectangle(html, image, area); break; case MAP_CIRCLE: drawSelectionArc(html, image, area); break; case MAP_POLY: drawSelectionPolygon(html, image, area); break; default: break; } area = area->next; } } /***** * Name: _XmHTMLGetImagemapAnchor * Return Type: XmHTMLAnchor* * Description: checks whether the given coordinates lie somewhere within * the given imagemap. * In: * html: XmHTMLWidget * x,y: point coordinates, relative to upper-left corner of the * html widget * image: current image data, required to make x and y coordinates * relative to upper-left corner of the image. * map: imagemap to check * Returns: * anchor data if successfull, NULL otherwise *****/ XmHTMLAnchor* _XmHTMLGetAnchorFromMap(XmHTMLWidget html, int x, int y, XmHTMLImage *image, XmHTMLImageMap *map) { int xs, ys; mapArea *area, *def_area; XmHTMLAnchor *anchor = NULL; Boolean found = False; /* map coordinates to upperleft corner of image */ xs = x + html->html.scroll_x - image->owner->x; ys = y + html->html.scroll_y - image->owner->y; _XmHTMLFullDebug(10, ("map.c: _XmHTMLGetAnchorFromMap, x = %i, y = %i, " "relative x = %i, relative y = %i\n", x, y, xs, ys)); area = map->areas; def_area = NULL; /* * We test against found instead of anchor becoming non-NULL: * areas with the NOHREF attribute set don't have an anchor but * should be taken into account as well. */ while(area && !found) { switch(area->shape) { case MAP_RECT: if(PointInRect(xs, ys, area->coords)) { anchor = area->anchor; found = True; } break; case MAP_CIRCLE: if(PointInCircle(xs, ys, area->coords[0], area->coords[1], area->coords[2])) { anchor = area->anchor; found = True; } break; case MAP_POLY: if(PointInPoly(xs, ys, area->region)) { anchor = area->anchor; found = True; } break; /* * just save default area info; it's only needed if nothing * else matches. */ case MAP_DEFAULT: def_area = area; break; } area = area->next; } if(!found && def_area) anchor = def_area->anchor; _XmHTMLFullDebug(10, ("map.c: _XmHTMLGetAnchorFromMap, %s anchor found\n", (anchor ? "an" : "no"))); return(anchor); } /***** * Name: _XmHTMLFreeImageMaps * Return Type: void * Description: frees all imagemaps for the given widget * In: * html: XmHTMLWidget * Returns: * nothing *****/ void _XmHTMLFreeImageMaps(XmHTMLWidget html) { XmHTMLImageMap *map, *map_list; map_list = html->html.image_maps; while(map_list != NULL) { map = map_list->next; freeImageMap(map_list); map_list = NULL; map_list = map; } html->html.image_maps = NULL; } /***** * Name: _XmHTMLCheckImagemaps * Return Type: void * Description: checks whether an image requires an external imagemap * In: * html: XmHTMLWidget containing images to check * Returns: * nothing * Note: * this routine is only effective when a XmNimagemapCallback callback * is installed. When an external imagemap is required, this routine * triggers this callback and will load an imagemap when the map_contents * field is non-null after the callback returns. We make a copy of this * map and use it to parse and load the imagemap. *****/ void _XmHTMLCheckImagemaps(XmHTMLWidget html) { XmHTMLImage *image; XmHTMLImageMap *imagemap; XmHTMLImagemapCallbackStruct cbs; String map; _XmHTMLDebug(10, ("map.c: _XmHTMLCheckImagemaps Start\n")); if(html->html.images == NULL || !CHECK_CALLBACK (html, imagemap_callback, IMAGEMAP)) { _XmHTMLDebug(10, ("map.c: _XmHTMLCheckImagemaps End: %s.\n", (html->html.images ? "no imagemap_callback" : "no images in document"))); return; } for(image = html->html.images; image != NULL; image = image->next) { if(image->map_url != NULL) { if((imagemap = _XmHTMLGetImagemap(html, image->map_url)) == NULL) { /* set to zero */ (void)memset(&cbs, 0, sizeof(XmHTMLImagemapCallbackStruct)); cbs.reason = XmCR_HTML_IMAGEMAP; cbs.map_name = image->map_url; cbs.image_name = image->html_image->url; _XmHTMLDebug(10, ("map.c: _XmHTMLCheckImagemaps, calling " "imagemap_callback for imagemap %s used in image %s\n", cbs.map_name, cbs.image_name)); /* trigger the imagemap callback */ Toolkit_Call_Callback((TWidget)html, html->html.imagemap_callback, IMAGEMAP, &cbs); _XmHTMLDebug(10, ("map.c: _XmHTMLCheckImagemaps, return from " "imagemap_callback, %s imagemap.\n", cbs.map_contents ? "loading" : "not loading")); /* parse and add this imagemap */ if(cbs.map_contents != NULL) { map = strdup(cbs.map_contents); XmHTMLImageAddImageMap((TWidget)html, map); free(map); } } } } _XmHTMLDebug(10, ("map.c: _XmHTMLCheckImagemaps End\n")); }