/* * Group.c -- Implementation of Group item. * * Authors : Patrick Lecoanet. * Creation date : Wed Jun 23 10:09:20 1999 * * $Id$ */ /* * Copyright (c) 1999 - 1999 CENA, Patrick Lecoanet -- * * This code 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 code 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 code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "Types.h" #include "WidgetInfo.h" #include "Item.h" #include "Group.h" #include "Geo.h" #include "tkZinc.h" #include #include static const char rcsid[] = "$Id$"; static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Group item special record. */ typedef struct _GroupItemStruct { ItemStruct header; /* Public data */ Item clip; unsigned char alpha; /* Private data */ Item head; /* Doubly linked list of all items. */ Item tail; ZnList dependents; /* List of dependent items. */ #ifdef OM /* Overlap manager variables. * These variables are valid *only* if the overlap * manager is active. */ ZnBool call_om; /* Tell if there is a need to call the */ /* overlap manager. */ #endif } GroupItemStruct, *GroupItem; /* ********************************************************************************** * * Specific Group item record * ********************************************************************************** */ static ZnAttrConfig group_attrs[] = { { ZN_CONFIG_ALPHA, "-alpha", NULL, Tk_Offset(GroupItemStruct, alpha), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-atomic", NULL, Tk_Offset(GroupItemStruct, header.flags), ATOMIC_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_ITEM, "-clip", NULL, Tk_Offset(GroupItemStruct, clip), 0, ZN_COORDS_FLAG|ZN_ITEM_FLAG, False }, { ZN_CONFIG_BOOL, "-composealpha", NULL, Tk_Offset(GroupItemStruct, header.flags), COMPOSE_ALPHA_BIT, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(GroupItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_TRANSFO_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(GroupItemStruct, header.flags), COMPOSE_SCALE_BIT, ZN_TRANSFO_FLAG, False }, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(GroupItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(GroupItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_TAG_LIST, "-tags", NULL, Tk_Offset(GroupItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(GroupItemStruct, header.flags), VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0 } }; /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(Item item, int *argc, Tcl_Obj *CONST *args[]) { GroupItem group = (GroupItem) item; group->head = ZN_NO_ITEM; group->tail = ZN_NO_ITEM; group->clip = ZN_NO_ITEM; group->alpha = 100; group->dependents = NULL; SET(item->flags, VISIBLE_BIT); SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ALPHA_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); CLEAR(item->flags, ATOMIC_BIT); item->priority = DEFAULT_GROUP_PRIORITY; #ifdef OM group->call_om = False; #endif return ZN_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(Item item) { GroupItem group = (GroupItem) item; ZnList dependents; Item connected, current_item, new_item; Item *items; Tcl_HashTable mapping; Tcl_HashEntry *entry; int new, num_items, i; current_item = group->tail; group->head = group->tail = ZN_NO_ITEM; #ifdef OM group->call_om = False; #endif dependents = group->dependents; if (dependents) { Tcl_InitHashTable(&mapping, TCL_ONE_WORD_KEYS); } /* * First clone all the children, and build a mapping * table if there is some attachments to relink. */ while (current_item != ZN_NO_ITEM) { connected = current_item->connected_item; new_item = ITEM.CloneItem(current_item); new_item->connected_item = connected; ITEM.InsertItem(new_item, item, ZN_NO_ITEM, True); if (dependents) { entry = Tcl_CreateHashEntry(&mapping, (char *) current_item, &new); Tcl_SetHashValue(entry, (ClientData) new_item); } if (current_item == group->clip) { group->clip = new_item; } current_item = current_item->previous; } /* * Then rebuild the dependency list with * the new items. */ if (dependents) { /*printf("rebuilding dependents\n");*/ group->dependents = NULL; items = (Item *) ZnListArray(dependents); num_items = ZnListSize(dependents); for (i = 0; i < num_items; i++, items++) { entry = Tcl_FindHashEntry(&mapping, (char *) *items); if (entry == NULL) { ZnWarning("Can't find item correspondance in Group Clone\n"); abort(); } else { current_item = (Item) Tcl_GetHashValue(entry); } entry = Tcl_FindHashEntry(&mapping, (char *) current_item->connected_item); if (entry == NULL) { ZnWarning("Can't found item correspondance in Group Clone\n"); abort(); } else { /*printf("item %d correspond to ", current_item->connected_item->id);*/ current_item->connected_item = (Item) Tcl_GetHashValue(entry); /*printf("%d\n", current_item->connected_item->id);*/ ZnInsertDependentItem(current_item); } } Tcl_DeleteHashTable(&mapping); } } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(Item item) { GroupItem group = (GroupItem) item; Item current_item, next_item; current_item = group->head; while (current_item != ZN_NO_ITEM) { next_item = current_item->next; ITEM.DestroyItem(current_item); current_item = next_item; } if (group->dependents) { ZnListFree(group->dependents); } } /* ********************************************************************************** * * SetXShape -- * ********************************************************************************** */ static void SetXShape(Item grp) { #ifdef SHAPE WidgetInfo *wi = grp->wi; Item clip = ((GroupItem) grp)->clip; int i, j, num_pts, max_num_pts; ZnPos min_x, min_y, max_x, max_y; ZnTriStrip tristrip; ZnPoint *p; ZnBool simple; ZnDim width, height; XPoint xpts[3], *xp2, *xpts2; Region reg, reg_op, reg_to; if (!wi->has_x_shape) { return; } if ((clip == ZN_NO_ITEM) || !wi->reshape) { /* * Reset both clip just to be sure (the application can have * changed wi->full_reshape while resetting wi->reshape). */ XShapeCombineMask(wi->dpy, ZnWindowId(wi->win), ShapeBounding, 0, 0, None, ShapeSet); XShapeCombineMask(wi->dpy, wi->real_top, ShapeBounding, 0, 0, None, ShapeSet); } else { /* * Get the clip shape. */ tristrip.num_strips = 0; simple = clip->class->GetClipVertices(clip, &tristrip); if (simple || (tristrip.num_strips == 0)) { /* * Nothing to do: after normalisation the rectangular shape will * fit exactly the window. We may test here if a shape is currently * active and reset the mask only in this case (need a flag in wi). */ XShapeCombineMask(wi->dpy, ZnWindowId(wi->win), ShapeBounding, 0, 0, None, ShapeSet); XShapeCombineMask(wi->dpy, wi->real_top, ShapeBounding, 0, 0, None, ShapeSet); } else { /* * First make the vertices start at zero. * In case of a fan we benefit from the fact that * ALL the contour vertices are included in * the tristrip, so we dont need to consider the * center (arc in pie slice mode). */ max_x = min_x = tristrip.strips[0].points[0].x; max_y = min_y = tristrip.strips[0].points[0].y; max_num_pts = tristrip.strips[0].num_points; for (j = 0; j < tristrip.num_strips; j++) { p = tristrip.strips[j].points; num_pts = tristrip.strips[j].num_points; if (num_pts > max_num_pts) { max_num_pts = num_pts; } for (i = 0; i < num_pts; p++, i++) { if (p->x < min_x) { min_x = p->x; } if (p->y < min_y) { min_y = p->y; } if (p->x > max_x) { max_x = p->x; } if (p->y > max_y) { max_y = p->y; } } } max_x -= min_x; max_y -= min_y; XShapeCombineMask(wi->dpy, wi->full_reshape?ZnWindowId(wi->win):wi->real_top, ShapeBounding, 0, 0, None, ShapeSet); reg = XCreateRegion(); /* * Now normalize the shape and map it to the window size, * then Translate it in a region and apply this region to * the window. */ width = wi->width; height = wi->height; for (j = 0; j < tristrip.num_strips; j++) { p = tristrip.strips[j].points; num_pts = tristrip.strips[j].num_points; /* * In case of a fan we benefit from the fact that * ALL the contour vertices are included in * the tristrip, so we can use the corresponding * polygon instead of going through all the triangles. */ if (tristrip.strips[j].fan) { /* Skip the center */ p++; num_pts--; xp2 = xpts2 = ZnMalloc(num_pts*sizeof(XPoint)); for (i = 0 ; i < num_pts; i++, p++, xp2++) { xp2->x = (short) ((p->x - min_x) * width / max_x); xp2->y = (short) ((p->y - min_y) * height / max_y); } reg_op = XPolygonRegion(xpts2, num_pts, EvenOddRule); reg_to = XCreateRegion(); XUnionRegion(reg, reg_op, reg_to); XDestroyRegion(reg); XDestroyRegion(reg_op); reg = reg_to; ZnFree(xpts2); } else { xpts[0].x = (short) ((p->x - min_x) * width / max_x); xpts[0].y = (short) ((p->y - min_y) * height / max_y); p++; xpts[1].x = (short) ((p->x - min_x) * width / max_x); xpts[1].y = (short) ((p->y - min_y) * height / max_y); p++; for (i = 2 ; i < num_pts; i++, p++) { xpts[2].x = (short) ((p->x - min_x) * width / max_x); xpts[2].y = (short) ((p->y - min_y) * height / max_y); reg_op = XPolygonRegion(xpts, 3, EvenOddRule); reg_to = XCreateRegion(); XUnionRegion(reg, reg_op, reg_to); XDestroyRegion(reg); XDestroyRegion(reg_op); reg = reg_to; xpts[0] = xpts[1]; xpts[1] = xpts[2]; } } } XShapeCombineRegion(wi->dpy, wi->full_reshape?wi->real_top:ZnWindowId(wi->win), ShapeBounding, 0, 0, reg, ShapeSet); XDestroyRegion(reg); } } #endif } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(Item item, int argc, Tcl_Obj *CONST argv[], int *flags) { GroupItem group = (GroupItem) item; WidgetInfo *wi = item->wi; if (ZnConfigureAttributes(wi, item, group_attrs, argc, argv, flags) == ZN_ERROR) { return ZN_ERROR; } /* * If the clip item changed, check if it is a legal * item type that is inside this group. */ if (ISSET(*flags, ZN_ITEM_FLAG)) { if (group->clip && (!group->clip->class->GetClipVertices || (group->clip->parent != item))) { group->clip = ZN_NO_ITEM; Tcl_AppendResult(wi->interp, " clip item must be a child of the group", NULL); return ZN_ERROR; } if (!group->clip && (item == wi->top_group)) { SetXShape(item); } } return ZN_OK; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(Item item, int argc, Tcl_Obj *CONST argv[]) { if (ZnQueryAttribute(item->wi, item, group_attrs, argv[0]) == ZN_ERROR) { return ZN_ERROR; } return ZN_OK; } /* ********************************************************************************** * * PushClip -- * Save the current clip shape and current clipbox if needed. * Intersect the previous shape and the local to obtain the * new current shape. Use this shape to compute the current * clipbox and if set_gc is True compute the current region. * ********************************************************************************** */ static void PushClip(GroupItem group, ZnBool set_gc) { WidgetInfo *wi = ((Item) group)->wi; ZnTriStrip tristrip; ZnBool simple; if ((group->clip != ZN_NO_ITEM) && ((((Item) group) != wi->top_group) || !wi->reshape)) { simple = group->clip->class->GetClipVertices(group->clip, &tristrip); /*printf("Group: PushClip group %d\n", ((Item) group)->id);*/ ZnPushClip(wi, &tristrip, simple, set_gc); } } /* ********************************************************************************** * * PopClip -- * Re-install the previous clip shape if any (stack can be empty). * ********************************************************************************** */ static void PopClip(GroupItem group, ZnBool set_gc) { WidgetInfo *wi = ((Item) group)->wi; if ((group->clip != ZN_NO_ITEM) && ((((Item) group) != wi->top_group) || !wi->reshape)) { /*printf("Group: PopClip group %d\n", ((Item) group)->id);*/ ZnPopClip(wi, set_gc); } } /* ********************************************************************************** * * PushTransform -- * Save the current transform then concatenate the item transform to * form the new current transform. * ********************************************************************************** */ static void PushTransform(Item item) { if (!item->transfo && ISSET(item->flags, COMPOSE_SCALE_BIT) && ISSET(item->flags, COMPOSE_ROTATION_BIT)) { return; } ZnPushTransform(item->wi, item->transfo, ISSET(item->flags, COMPOSE_SCALE_BIT), ISSET(item->flags, COMPOSE_ROTATION_BIT)); /*printf("Pushing transfo for item: %d\n;", item->id); ZnPrintTransfo(wi->current_transfo);*/ } /* ********************************************************************************** * * PopTransform -- * Restore the previously saved transform from the stack. * ********************************************************************************** */ static void PopTransform(Item item) { if (!item->transfo && ISSET(item->flags, COMPOSE_SCALE_BIT) && ISSET(item->flags, COMPOSE_ROTATION_BIT)) { return; } ZnPopTransform(item->wi); /*printf("Popping transfo for item: %d\n", item->id); ZnPrintTransfo(wi->current_transfo);*/ } /* ********************************************************************************** * * ComputeCoordinates -- * Compute the geometrical elements of a group. First of all save the current * transform and combine it with the item transform. Then call the item * ComputeCoordinates method. * For regular child items (not groups) some of the code of the item * itself is factored out in CallRegularCC. * ********************************************************************************** */ static void CallRegularCC(Item item) { WidgetInfo *wi = item->wi; /*ZnBBox *clip_box;*/ /* * Do some generic pre-work in behalf of the (regular) children. */ if (ISSET(item->flags, VISIBLE_BIT)) { ZnDamage(wi, &item->item_bounding_box); } PushTransform(item); /*printf("calling cc on regular item %d\n", item->id);*/ /*ZnPrintTransfo(wi->current_transfo);*/ item->class->ComputeCoordinates(item, False); /* * If a current clipbox exists adjust the item * bounding box accordingly. When computing coordinates * the damaged area is not pushed onto the clipstack, * the following predicate is thus valid for testing * a clipbox. */ /* Tue Nov 14 15:21:05 2000 Suppressed to have a real bbox to align tiles (i.e if an object is larger than its enclosing clipping, the bbox is equal to the clip area and the tiling will not move with the object until it partially uncovered the clip area. Have to watch any possible breakage. if (ZnCurrentClip(wi, NULL, &clip_box, NULL)) { ZnBBox inter; IntersectBBox(&item->item_bounding_box, clip_box, &inter); item->item_bounding_box = inter; }*/ /* * Do some generic post-work in behalf of the (regular) children. */ if (ISSET(item->inv_flags, ZN_REPICK_FLAG)) { SET(wi->events_flags, INTERNAL_NEED_REPICK); } if (ISSET(item->inv_flags, ZN_COORDS_FLAG) && (ISSET(item->flags, SENSITIVE_BIT) || ISSET(item->flags, VISIBLE_BIT))) { SET(wi->events_flags, INTERNAL_NEED_REPICK); } /* * Damage if the item is visible or if it is * a group clipper. */ if (ISSET(item->flags, VISIBLE_BIT) || (item == ((GroupItem) item->parent)->clip)) { ZnDamage(wi, &item->item_bounding_box); } PopTransform(item); item->inv_flags = 0; /*printf("Done cc on regular item %d\n", item->id);*/ } static void ComputeCoordinates(Item item, ZnBool force) { GroupItem group = (GroupItem) item; Item current_item; Item *deps; int num_deps, i; PushTransform(item); /* printf("Group.c\n"); ZnPrintTransfo(item->wi->current_transfo); printf("\n");*/ force |= ISSET(item->inv_flags, ZN_TRANSFO_FLAG); /* * If the clip item changed or there is no clip anymore * force an update. */ force |= ISSET(item->inv_flags, ZN_ITEM_FLAG); /* * Clip shape is computed in the group's local * coordinates. */ if (group->clip != ZN_NO_ITEM) { /* * Update the geometry of the clip item if needed. * Its bounding box will be clipped by the current * clipbox (i.e the clipbox of the group's parent). */ if (force || ISSET(group->clip->inv_flags, ZN_COORDS_FLAG) || ISSET(group->clip->inv_flags, ZN_TRANSFO_FLAG)) { /*printf("calling cc on clip item %d for group %d\n", group->clip->id, item->id);*/ CallRegularCC(group->clip); if (item == item->wi->top_group) { SetXShape(item); } /* * If the clip item has changed we need to compute * new clipped bounding boxes for all the children. */ force = True; } } PushClip(group, False); for (current_item = group->head; current_item != ZN_NO_ITEM; current_item = current_item->next) { /* * Skip the clip item, it has been already updated. * Skip as well items with a dependency, they will * be updated later. */ if ((current_item == group->clip) || (current_item->connected_item != ZN_NO_ITEM)) { continue; } if (force || ISSET(current_item->inv_flags, ZN_COORDS_FLAG) || ISSET(current_item->inv_flags, ZN_TRANSFO_FLAG)) { if (current_item->class != ZnGroup) { /*printf("calling cc on item %d\n", current_item->id);*/ CallRegularCC(current_item); } else { /*printf("calling cc on group %d\n", current_item->id);*/ current_item->class->ComputeCoordinates(current_item, force); } } } /* * Update coordinates and bounding boxes following * a possible change in connected items. Only regular * items can be concerned. */ if (group->dependents) { num_deps = ZnListSize(group->dependents); deps = (Item *) ZnListArray(group->dependents); for (i = 0; i < num_deps; i++) { current_item = deps[i]; if (force || ISSET(current_item->inv_flags, ZN_COORDS_FLAG) || ISSET(current_item->inv_flags, ZN_TRANSFO_FLAG) || ISSET(current_item->connected_item->flags, UPDATE_DEPENDENT_BIT)) { /*printf("Updating dependent: %d\n", current_item->id);*/ CallRegularCC(current_item); } } /* * Now, we must reset the update_dependent flag */ for (i = 0; i < num_deps; i++) { CLEAR(deps[i]->connected_item->flags, UPDATE_DEPENDENT_BIT); } /*printf("... done\n");*/ } /* * Compute the bounding box. */ ResetBBox(&item->item_bounding_box); current_item = group->head; while (current_item != ZN_NO_ITEM) { AddBBoxToBBox(&item->item_bounding_box, ¤t_item->item_bounding_box); current_item = current_item->next; } item->inv_flags = 0; PopClip(group, False); PopTransform(item); } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(Item item, ZnToArea ta) { GroupItem group = (GroupItem) item; Item current_item; ZnBBox enclosing, inter; int result = -1; ZnBool outside, inside; ZnBool atomic, report, empty = True; PushTransform(item); report = ta->report; /* * Is this group the target group ? */ if ((ta->in_group != ZN_NO_ITEM) && (ta->in_group != item)) { /* No, try the subgroups. */ for (current_item = group->head; current_item != ZN_NO_ITEM; current_item = current_item->next) { if (current_item->class != ZnGroup) { continue; } result = current_item->class->ToArea(current_item, ta); if (ta->in_group == ZN_NO_ITEM) { /* The target group has been found, return its result. */ goto out; } } /* No group found in this subtree. */ goto out; } /* * At this point we are either in the target group * or one of its sub-groups. If in the target group, * erase the target in the call struct to remember * the fact. */ if (ta->in_group == item) { /* * We are in the target group, mark the fact and bypass the group * atomicity. */ ta->in_group = ZN_NO_ITEM; atomic = False; } else { /* * We are below the start group, If this group is ATOMIC, * ask the child groups to report instead of adding their * children to the result. */ atomic = ISSET(item->flags, ATOMIC_BIT); ta->report |= atomic; } enclosing.orig.x = ta->area->orig.x - 1; enclosing.orig.y = ta->area->orig.y - 1; enclosing.corner.x = ta->area->corner.x + 1; enclosing.corner.y = ta->area->corner.y + 1; outside = inside = True; /* * Process each item and proceed with subtrees if * asked for by the recursive flag. */ /* printf("searching in group %d\n", item?item->id:0);*/ for (current_item = group->head; current_item != ZN_NO_ITEM; current_item = current_item->next) { if (ISCLEAR(current_item->flags, VISIBLE_BIT) && ISCLEAR(current_item->flags, SENSITIVE_BIT)) { continue; } /*printf("visible&sensitive %d\n", current_item?current_item->id:0);*/ IntersectBBox(&enclosing, ¤t_item->item_bounding_box, &inter); if (IsEmptyBBox(&inter)) { continue; } /*printf("bbox test passed %d\n", current_item?current_item->id:0);*/ if ((current_item->class != ZnGroup) || atomic || ta->recursive) { if (current_item->class != ZnGroup) { /*printf("testing %d\n", current_item?current_item->id:0);*/ PushTransform(current_item); result = current_item->class->ToArea(current_item, ta); PopTransform(current_item); } else { result = current_item->class->ToArea(current_item, ta); } outside &= (result == -1); inside &= (result == 1); empty = False; /* * If this group is ATOMIC, it must report itself as matching * if a/ the request is 'enclosed' and all the children are * enclosed or b/ the request is 'overlapping' and at least one * child overlaps (or is enclosed). * So here we can do early tests to shortcut the search when * the most stringent conditions are met. */ if (atomic) { if (!ta->enclosed && (result >= 0)) { result = 0; goto out; } else if (ta->enclosed && (result == 0)) { goto out; } } if (!ta->report && (result >= ta->enclosed)) { /*printf("Doing %d\n", current_item?current_item->id:0);*/ ZnDoItem(item->wi->interp, current_item, ZN_NO_PART, ta->tag_uid); } } } /* * If there are no items or only sub-groups in this group and * the search is not recursive we must report outside. */ if (empty) { result = -1; } else { if (atomic) { result = outside ? -1 : 1; } else if (ta->report) { /* Need to report matching children to ancestor */ if (outside && inside) { result = 0; } else { result = outside ? -1 : 1; } } else { result = -1; } } out: ta->report = report; PopTransform(item); return result; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(Item item) { GroupItem group = (GroupItem) item; WidgetInfo *wi = item->wi; Item current_item; ZnBBox bbox, old_damaged_area, *clip_box; PushTransform(item); PushClip(group, True); if (group->clip != ZN_NO_ITEM) { ZnCurrentClip(wi, NULL, &clip_box, NULL); old_damaged_area = wi->damaged_area; IntersectBBox(&wi->damaged_area, clip_box, &bbox); wi->damaged_area = bbox; } current_item = group->tail; while (current_item != ZN_NO_ITEM) { if (ISSET(current_item->flags, VISIBLE_BIT)) { IntersectBBox(&wi->damaged_area, ¤t_item->item_bounding_box, &bbox); if (!IsEmptyBBox(&bbox)) { if (current_item->class != ZnGroup) { PushTransform(current_item); } current_item->class->Draw(current_item); if (wi->draw_bboxes) { XGCValues values; values.foreground = ZnPixel(ZnGetGradientColor(wi->bbox_color, 0, NULL)); values.fill_style = FillSolid; values.line_width = 1; values.line_style = (current_item->class==ZnGroup)?LineOnOffDash:LineSolid; XChangeGC(wi->dpy, wi->gc, GCForeground|GCLineStyle|GCLineWidth|GCFillStyle, &values); XDrawRectangle(wi->dpy, wi->draw_buffer, wi->gc, current_item->item_bounding_box.orig.x, current_item->item_bounding_box.orig.y, current_item->item_bounding_box.corner.x - current_item->item_bounding_box.orig.x, current_item->item_bounding_box.corner.y - current_item->item_bounding_box.orig.y); } if (current_item->class != ZnGroup) { PopTransform(current_item); } } } current_item = current_item->previous; } if (group->clip != ZN_NO_ITEM) { wi->damaged_area = old_damaged_area; } PopClip(group, True); PopTransform(item); } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ static void Render(Item item) { #ifdef GLX GroupItem group = (GroupItem) item; Item current_item; WidgetInfo *wi = item->wi; #ifdef GLX_DAMAGE ZnBBox *clip_box; ZnBBox bbox, old_damaged_area; #endif unsigned char save_alpha = wi->alpha; unsigned char save_alpha2; if (ISSET(item->flags, COMPOSE_ALPHA_BIT)) { wi->alpha = wi->alpha * group->alpha / 100; } else { wi->alpha = group->alpha; } save_alpha2 = wi->alpha; PushTransform(item); PushClip(group, True); #ifdef GLX_DAMAGE if (group->clip != ZN_NO_ITEM) { ZnCurrentClip(wi, NULL, &clip_box, NULL); old_damaged_area = wi->damaged_area; IntersectBBox(&wi->damaged_area, clip_box, &bbox); wi->damaged_area = bbox; } #endif current_item = group->tail; while (current_item != ZN_NO_ITEM) { if (ISSET(current_item->flags, VISIBLE_BIT)) { #ifdef GLX_DAMAGE IntersectBBox(&wi->damaged_area, ¤t_item->item_bounding_box, &bbox); if (!IsEmptyBBox(&bbox)) { #endif if (current_item->class != ZnGroup) { PushTransform(current_item); if (ISCLEAR(current_item->flags, COMPOSE_ALPHA_BIT)) { wi->alpha = 100; } } current_item->class->Render(current_item); if (current_item->class != ZnGroup) { PopTransform(current_item); wi->alpha = save_alpha2; } #ifdef GLX_DAMAGE } #endif } current_item = current_item->previous; } #ifdef GLX_DAMAGE if (group->clip != ZN_NO_ITEM) { wi->damaged_area = old_damaged_area; } #endif PopClip(group, True); PopTransform(item); wi->alpha = save_alpha; #endif } /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(Item item, int item_part) { ZnBool sensitive = ISSET(item->flags, SENSITIVE_BIT); Item parent = item->parent; while (sensitive && (parent != ZN_NO_ITEM)) { sensitive &= ISSET(parent->flags, SENSITIVE_BIT); parent = parent->parent; } return sensitive; } /* ********************************************************************************** * * Pick -- * Given a point an an aperture, find the topmost group item/part * that is (a) within the pick_aperture * (b) the top most * (c) has either its sensibility or its visibility set. * * Results: * The return value is the distance of the picked item/part if one * has been found or a really big distance if not. a_item and a_part * are set to point the picked item/part or to ZN_NO_ITEM/ZN_NO_PART. * If the group is ATOMIC, a_item points the group instead of the * actual item. * * Side effects: * None. * ********************************************************************************** */ static double Pick(Item item, ZnPick ps) { GroupItem group = (GroupItem) item; Item p_item=ZN_NO_ITEM, current_item; WidgetInfo *wi = item->wi; int p_part=0, aperture = ps->aperture; double dist, best = 1e10; ZnBBox bbox, inter, *clip_box; ZnPoint *p = ps->point; ZnBool atomic; ps->a_item= ZN_NO_ITEM; ps->a_part = ZN_NO_PART; if (group->head == ZN_NO_ITEM) { return best; } PushTransform(item); PushClip(group, False); /* * Is this group the target group ? */ if ((ps->in_group != ZN_NO_ITEM) && (ps->in_group != item)) { /* No, try the subgroups. */ for (current_item = group->head; current_item != ZN_NO_ITEM; current_item = current_item->next) { if (current_item->class != ZnGroup) { continue; } best = current_item->class->Pick(current_item, ps); if (ps->in_group == ZN_NO_ITEM) { /* The target group has been found, return its result. */ goto out; } } /* No group found in this subtree. */ goto out; } /* * At this point we are either in the target group * or one of its sub-groups. If in the target group, * erase the target in the call struct to remember * the fact. */ if (ps->in_group == item) { ps->in_group = ZN_NO_ITEM; } bbox.orig.x = p->x - aperture; bbox.orig.y = p->y - aperture; bbox.corner.x = p->x + (aperture?aperture:1); bbox.corner.y = p->y + (aperture?aperture:1); if (ZnCurrentClip(wi, NULL, &clip_box, NULL)) { IntersectBBox(&bbox, clip_box, &inter); if (IsEmptyBBox(&inter)) { goto out; } } current_item = (ps->start_item == ZN_NO_ITEM) ? group->head : ps->start_item; atomic = ISSET(item->flags, ATOMIC_BIT); for ( ; current_item != ZN_NO_ITEM; current_item = current_item->next) { /* * Sensitive item must be reported even if they are invisible. * It is legal to fire bindings on invisible sensitive items. * This is _not_ a bug do _not_ modify the test below. */ if (ISCLEAR(current_item->flags, SENSITIVE_BIT) && ISCLEAR(current_item->flags, VISIBLE_BIT)) { continue; } if (current_item->class != ZnGroup) { PushTransform(current_item); p_item = ps->a_item; p_part = ps->a_part; ps->a_item = current_item; ps->a_part = ZN_NO_PART; dist = current_item->class->Pick(current_item, ps); dist -= aperture; PopTransform(current_item); } else if (!atomic && !ps->recursive) { continue; } else { dist = current_item->class->Pick(current_item, ps); } if (dist < 0.0) { dist = 0.0; } if (dist >= best) { /* Not a good one, restore the previous best and try again. */ ps->a_item = p_item; ps->a_part = p_part; continue; } if (atomic) { /* If ATOMIC, this group is the item to be reported. */ ps->a_item = item; ps->a_part = ZN_NO_PART; } best = dist; /*printf("found %d:%d, at %g\n", (ps->a_item)->id, ps->a_part, dist);*/ if (dist == 0.0) { /* No need to look further, the item found is the topmost * closest. */ break; } } out: PopClip(group, False); PopTransform(item); return best; } /* ********************************************************************************** * * Coords -- * Return or edit the group translation (can be also interpreted as the * position of the group origin in the group's parent). * ********************************************************************************** */ static int Coords(Item item, int contour, int index, int cmd, ZnPoint **pts, char **controls, int *num_pts) { if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST) || (cmd == COORDS_REMOVE)) { Tcl_AppendResult(item->wi->interp, " groups can't add or remove vertices", NULL); return ZN_ERROR; } else if ((cmd == COORDS_REPLACE) || (cmd == COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, " coords command need 1 point on groups", NULL); return ZN_ERROR; } if (!item->transfo) { item->transfo = ZnTransfoNew(); } ZnSetTranslation(item->transfo, (*pts)[0].x, (*pts)[0].y); ITEM.Invalidate(item, ZN_TRANSFO_FLAG); } else if ((cmd == COORDS_READ) || (cmd == COORDS_READ_ALL)) { ZnPoint *p; ZnListAssertSize(item->wi->work_pts, 1); p = (ZnPoint *) ZnListArray(item->wi->work_pts); ZnTransfoDecompose(item->transfo, NULL, p, NULL, NULL); *num_pts = 1; *pts = p; } return ZN_OK; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static void PostScript(Item item, PostScriptInfo ps_info) { } Item ZnGroupHead(Item group) { if (group->class != ZnGroup) { return ZN_NO_ITEM; } return ((GroupItem) group)->head; } Item ZnGroupTail(Item group) { if (group->class != ZnGroup) { return ZN_NO_ITEM; } return ((GroupItem) group)->tail; } ZnBool ZnGroupCallOm(Item group) { if (group->class != ZnGroup) { return False; } return ((GroupItem) group)->call_om; } ZnBool ZnGroupAtomic(Item group) { if (group->class != ZnGroup) { return True; } return ISSET(group->flags, ATOMIC_BIT); } void ZnGroupSetCallOm(Item group, ZnBool set) { if (group->class != ZnGroup) { return; } ((GroupItem) group)->call_om = set; } void ZnGroupRemoveClip(Item group, Item clip) { GroupItem grp = (GroupItem) group; if (grp->clip == clip) { grp->clip = ZN_NO_ITEM; ITEM.Invalidate(group, ZN_COORDS_FLAG); } } /* ********************************************************************************** * * ZnInsertDependentItem -- * ********************************************************************************** */ void ZnInsertDependentItem(Item item) { GroupItem group = (GroupItem) item->parent; if (!group) { return; } if (!group->dependents) { group->dependents = ZnListNew(2, sizeof(Item)); } ZnListAdd(group->dependents, &item, ZnListTail); } /* ********************************************************************************** * * ZnExtractDependentItem -- * ********************************************************************************** */ void ZnExtractDependentItem(Item item) { GroupItem group = (GroupItem) item->parent; int index, num_items; Item *deps; if (!group || !group->dependents) { return; } num_items = ZnListSize(group->dependents); deps = (Item *) ZnListArray(group->dependents); for (index = 0; index < num_items; index++) { if (deps[index]->id == item->id) { ZnListDelete(group->dependents, index); if (ZnListSize(group->dependents) == 0) { ZnListFree(group->dependents); group->dependents = NULL; break; } } } } /* ********************************************************************************** * * ZnDisconnectDependentItems -- * * ********************************************************************************** */ void ZnDisconnectDependentItems(Item item) { Item current_item; GroupItem group = (GroupItem) item->parent; Item *deps; int num_deps, i; if (!group || !group->dependents) { return; } deps = (Item *) ZnListArray(group->dependents); num_deps = ZnListSize(group->dependents); for (i = num_deps-1; i >= 0; i--) { current_item = deps[i]; if (current_item->connected_item == item) { current_item->connected_item = ZN_NO_ITEM; ZnListDelete(group->dependents, i); ITEM.Invalidate(current_item, ZN_COORDS_FLAG); } } if (ZnListSize(group->dependents) == 0) { ZnListFree(group->dependents); group->dependents = NULL; } } /* ********************************************************************************** * * ZnGroupExtractItem -- * ********************************************************************************** */ void ZnGroupExtractItem(Item item) { GroupItem group; if (!item->parent) { return; } group = (GroupItem) item->parent; if (item->previous != ZN_NO_ITEM) { item->previous->next = item->next; } else { group->head = item->next; } if (item->next != ZN_NO_ITEM) { item->next->previous = item->previous; } else { group->tail = item->previous; } ITEM.Invalidate((Item) group, ZN_COORDS_FLAG); item->previous = ZN_NO_ITEM; item->next = ZN_NO_ITEM; item->parent = NULL; } /* ********************************************************************************** * * ZnGroupInsertItem -- * ********************************************************************************** */ void ZnGroupInsertItem(Item group, Item item, Item mark_item, ZnBool before) { GroupItem grp = (GroupItem) group; /* * Empty list, add the first item. */ if (grp->head == ZN_NO_ITEM) { grp->head = grp->tail = item; item->previous = item->next = ZN_NO_ITEM; return; } if (mark_item != ZN_NO_ITEM) { /* * Better leave here, mark_item will not * have the links set right. */ if (mark_item == item) { return; } /* * Force the priority to be the same as the reference * item; */ item->priority = mark_item->priority; } else { mark_item = grp->head; while ((mark_item != ZN_NO_ITEM) && (mark_item->priority > item->priority)) { mark_item = mark_item->next; } before = True; } if (before && (mark_item != ZN_NO_ITEM)) { /* * Insert before mark. */ item->next = mark_item; item->previous = mark_item->previous; if (mark_item->previous == ZN_NO_ITEM) { grp->head = item; } else { mark_item->previous->next = item; } mark_item->previous = item; } else { /* * Insert after mark either because 'before' is False * and mark_item valid or because the right place is at * the end of the list and mark_item is ZN_NO_ITEM. */ if (mark_item == ZN_NO_ITEM) { grp->tail->next = item; item->previous = grp->tail; grp->tail = item; } else { item->previous = mark_item; item->next = mark_item->next; if (item->next == ZN_NO_ITEM) { grp->tail = item; } else { item->next->previous = item; } mark_item->next = item; } } ITEM.Invalidate(group, ZN_COORDS_FLAG); } /* ********************************************************************************** * * Exported functions struct -- * ********************************************************************************** */ static ItemClassStruct GROUP_ITEM_CLASS = { sizeof(GroupItemStruct), 0, /* num_parts */ False, /* has_anchors */ "group", group_attrs, Init, Clone, Destroy, Configure, Query, NULL, /* GetFieldSet */ NULL, /* GetAnchor */ NULL, /* GetClipVertices */ NULL, /* GetContours */ Coords, NULL, /* InsertChars */ NULL, /* DeleteChars */ NULL, /* Cursor */ NULL, /* Index */ NULL, /* Part */ NULL, /* Selection */ NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, NULL, /* PickVertex */ PostScript }; ZnItemClassId ZnGroup = (ZnItemClassId) &GROUP_ITEM_CLASS;