| /* | |
| * GraphApp - Cross-Platform Graphics Programming Library. | |
| * | |
| * File: objects.c -- maintain internal info about graphical objects. | |
| * Platform: Windows Version: 2.35 Date: 1998/04/04 | |
| * | |
| * Version: 1.00 Changes: Original version by Lachlan Patrick. | |
| * Version: 2.00 Changes: Total overhaul - new object class system. | |
| * Version: 2.15 Changes: Objects are Transparent by default. | |
| * Version: 2.20 Changes: Faster and better exiting. | |
| * Version: 2.30 Changes: Now uses array(), not malloc(). | |
| * Version: 2.35 Changes: New delayed deletion technique. | |
| */ | |
| /* Copyright (C) 1993-1998 Lachlan Patrick | |
| This file is part of GraphApp, a cross-platform C graphics library. | |
| GraphApp is free software; you can redistribute it and/or modify it | |
| under the terms of the GNU Library General Public License. | |
| GraphApp is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY. | |
| See the file COPYLIB.TXT for details. | |
| Copyright (C) 2004 The R Foundation | |
| */ | |
| #include "internal.h" | |
| /* | |
| * The current implementation of the list of objects | |
| * uses a linked list of primary objects, each object | |
| * potentially having a list of children associated with it | |
| * | |
| * e.g. window | |
| * +-> menubar -> button -> scrollbar -> textfield | |
| * +-> menu | |
| * +-> menuitem -> menuitem -> menuitem ... | |
| * | |
| * The base_object is at the top of the hierarchy. | |
| */ | |
| static object base_object = NULL; | |
| /* | |
| * Initialise the base_object and set the list to be empty. | |
| */ | |
| PROTECTED | |
| void init_objects(void) | |
| { | |
| if (base_object) | |
| return; | |
| base_object = create(objinfo); | |
| if (! base_object) | |
| apperror("Out of memory (init_objects)!"); | |
| base_object->kind = BaseObject; | |
| base_object->next = base_object->prev = base_object; | |
| base_object->parent = base_object->child = NULL; | |
| } | |
| static void add_object(object obj, object parent) | |
| { | |
| /* All objects have a parent. */ | |
| if (! parent) | |
| parent = base_object; | |
| if (parent->child) | |
| { /* add to end of child circularly linked list */ | |
| obj->prev = parent->child->prev; | |
| obj->next = parent->child; | |
| obj->prev->next = obj; | |
| obj->next->prev = obj; | |
| } else { | |
| /* create new child list */ | |
| obj->next = obj->prev = obj; | |
| parent->child = obj; | |
| } | |
| obj->parent = parent; | |
| #if DEBUG | |
| print_object_list(); | |
| #endif | |
| } | |
| static void remove_object(object obj) | |
| { | |
| if ((obj->next) && (obj->next != obj)) { | |
| obj->prev->next = obj->next; | |
| obj->next->prev = obj->prev; | |
| } else { | |
| obj->next = obj->prev = NULL; | |
| } | |
| if (obj->parent) | |
| if (obj->parent->child == obj) | |
| obj->parent->child = obj->next; | |
| } | |
| /* | |
| * Bring an object to the front of its sibling list. | |
| * Useful for keeping track of which window is up front. | |
| */ | |
| PROTECTED | |
| void move_to_front(object obj) | |
| { | |
| object parent = obj->parent; | |
| remove_object(obj); /* Remove it so as to reorder list. */ | |
| add_object(obj, parent); /* Add to end of list. */ | |
| parent->child = obj; /* Move to front. */ | |
| } | |
| /* | |
| * Call a function on all objects in a sibling list. | |
| * Only applies up to the end of the list, i.e. up to and | |
| * including the last element in the circular list. | |
| * Used to disable or enable windows when modal windows are used. | |
| */ | |
| void apply_to_list(object first, actionfn fn) | |
| { | |
| object start = first->parent->child; | |
| object obj = first; | |
| do { | |
| fn(obj); | |
| obj = obj->next; | |
| } while (obj != start); | |
| } | |
| /* | |
| * When objects are deleted using del() they are added to | |
| * the end of a circular list, which is traversed at the end | |
| * of each call to doevent(). This traversal actually deletes | |
| * the objects in that list. The del_base object is at | |
| * the head of this list and is the first object to be | |
| * deleted. | |
| */ | |
| typedef struct delnode delnode; | |
| struct delnode { | |
| object obj; /* object to be deleted */ | |
| delnode * next; /* next object in the list */ | |
| delnode * prev; /* previous object in the list */ | |
| }; | |
| static delnode * del_base = NULL; | |
| /* | |
| * Reduce the reference count of an object: | |
| * A by-product of this might be that the object is added | |
| * to the list of objects to be deleted. If the refcount | |
| * of an object is already at zero, the object is not | |
| * added to the list, nor is the refcount reduced any further. | |
| * If the refcount of the object is less than zero, the | |
| * object is a special object which cannot be deleted under | |
| * any circumstances (e.g. default fonts and cursors). | |
| */ | |
| PROTECTED | |
| void decrease_refcount(object obj) | |
| { | |
| delnode *new_node; | |
| if (! obj) | |
| return; | |
| if (obj->refcount <= 0) | |
| return; /* cannot delete this object */ | |
| obj->refcount--; | |
| if (obj->refcount != 0) | |
| return; /* don't delete this object */ | |
| /* the refcount must now be zero, so add to list */ | |
| new_node = create(delnode); | |
| new_node->obj = obj; | |
| if (del_base) { | |
| /* add to end of circular list */ | |
| new_node->prev = del_base->prev; | |
| new_node->next = del_base; | |
| new_node->prev->next = new_node; | |
| new_node->next->prev = new_node; | |
| } | |
| else { | |
| /* create new circular list */ | |
| new_node->next = new_node->prev = new_node; | |
| del_base = new_node; | |
| } | |
| } | |
| /* | |
| * Increase the reference count of an object. | |
| * Avoids increases a negative number (they're special). | |
| */ | |
| PROTECTED | |
| void increase_refcount(object obj) | |
| { | |
| if (obj && (obj->refcount >= 0)) | |
| obj->refcount ++; | |
| } | |
| /* | |
| * Protect an object from being deleted. This function is | |
| * used to protect default fonts and cursors from deletion. | |
| * It achieves this by simply setting the refcount to the | |
| * special value -1. | |
| */ | |
| PROTECTED | |
| void protect_object(object obj) | |
| { | |
| if (obj) | |
| obj->refcount = -1; | |
| } | |
| /* | |
| * Remove a delnode from the deletion list. | |
| * Fix del_base to point correctly. | |
| */ | |
| static void remove_delnode(delnode *n) | |
| { | |
| /* remove and destroy this node */ | |
| n->prev->next = n->next; | |
| n->next->prev = n->prev; | |
| if (n == n->next) /* nothing in list */ | |
| del_base = NULL; | |
| else if (n == del_base) /* move head */ | |
| del_base = n->next; | |
| discard(n); | |
| } | |
| /* | |
| * Remove all copies of an object from the deletion list | |
| * after that object has been deleted. | |
| * This occurs as a by-product of object deletion, and | |
| * ensures that an object cannot be deleted twice. | |
| */ | |
| static void remove_deleted_object(object obj) | |
| { | |
| delnode *last; | |
| delnode *next; | |
| delnode *n; | |
| if (! del_base) | |
| return; | |
| next = del_base; | |
| last = del_base->prev; | |
| do { | |
| n = next; | |
| next = n->next; | |
| if (n->obj == obj) { | |
| /* remove and destroy this node */ | |
| remove_delnode(n); | |
| } | |
| } while (n != last); | |
| } | |
| /* | |
| * Traverse the deletion list and delete every object found there. | |
| * This function repeatedly deletes del_base until that value is null. | |
| * This traversal occurs at the end of every doevent call. | |
| */ | |
| static void del_object(object obj); /* declaration */ | |
| PROTECTED | |
| void deletion_traversal(void) | |
| { | |
| static int level = 0; | |
| object obj; | |
| level++; | |
| if (level == 1) { | |
| while (del_base) { | |
| obj = del_base->obj; | |
| if (obj) { | |
| if (obj->refcount == 0) | |
| del_object(obj); | |
| else | |
| remove_deleted_object(obj); | |
| } | |
| } | |
| } | |
| level--; | |
| } | |
| /* | |
| * Keep application global variables up-to-date. | |
| */ | |
| static void update_app_globals(object obj) | |
| { | |
| if (obj->drawstate == current) | |
| current = & app_drawstate; | |
| if (obj == current->dest) | |
| current->dest = NULL; | |
| if (obj == current_window) | |
| current_window = NULL; | |
| if (obj == current_menubar) | |
| current_menubar = NULL; | |
| if (obj == current_menu) | |
| current_menu = NULL; | |
| if (obj == current->crsr) | |
| current->crsr = ArrowCursor; | |
| if (obj == current->fnt) | |
| current->fnt = SystemFont; | |
| } | |
| /* | |
| * Object deletion functions: | |
| * | |
| * The free_private function destroys the operating-system | |
| * graphics handles, also calling the user-defined and | |
| * internally defined deletion functions. This function | |
| * also removes the object from the internal deletion list. | |
| * | |
| * The free_object function does this too, additionally | |
| * releasing the memory used by the object from the heap. | |
| */ | |
| static void free_private(object obj) | |
| { | |
| #if DEBUG | |
| print_object_list(); | |
| #endif | |
| /* Remove from object hierachy. */ | |
| remove_object(obj); | |
| /* Call user destructor first. */ | |
| if ((obj->call) && (obj->call->die)) | |
| obj->call->die(obj); | |
| /* Fix application variables. */ | |
| update_app_globals(obj); | |
| /* Free drawing context. */ | |
| del_context(obj); | |
| /* Then call private destructor. */ | |
| if (obj->die) | |
| obj->die(obj); | |
| /* Remove object from deletion list. */ | |
| remove_deleted_object(obj); | |
| } | |
| static void free_object(object obj) | |
| { | |
| free_private(obj); | |
| /* Free any extra internal info. */ | |
| if (obj->drawstate) | |
| discard(obj->drawstate); | |
| if (obj->text) | |
| discard(obj->text); | |
| if (obj->call) | |
| discard(obj->call); | |
| discard(obj); | |
| } | |
| /* | |
| * Private recursive object destructor. Deletes child objects | |
| * first and then the top-most object specified. | |
| * This function is called during the deletion_traversal function, | |
| * which is called at the end of each doevent call. | |
| */ | |
| static void del_object(object obj) | |
| { | |
| while (obj->child) | |
| del_object(obj->child); | |
| free_object(obj); | |
| } | |
| /* | |
| * Quick destruction function when terminating application. | |
| * Make Windows happy by releasing memory which it otherwise | |
| * would not automatically release (silly Windows). | |
| * Leave application variables on the heap (by not calling | |
| * del_object or free_object). This works fine, and avoids | |
| * problems when calling exitapp() in callbacks. | |
| */ | |
| static void free_memory(object obj) | |
| { | |
| if (obj->kind == WindowObject) | |
| hide(obj); | |
| while(obj->child) | |
| free_memory(obj->child); | |
| free_private(obj); | |
| } | |
| PROTECTED | |
| void finish_objects(void) | |
| { | |
| free_memory(base_object); | |
| base_object = NULL; | |
| } | |
| /* | |
| * Create and return a new object with a refcount of 1. | |
| * The object is Transparent and may have some other | |
| * structures associated with it (depending on its kind). | |
| */ | |
| PROTECTED | |
| object new_object(int kind, HANDLE handle, object parent) | |
| { | |
| object obj; | |
| /* create the object */ | |
| obj = create(objinfo); | |
| if (! obj) | |
| return NULL; | |
| obj->menubar = obj->popup = obj->toolbar = NULL; | |
| obj->status[0] = '\0'; | |
| if (kind & ControlObject) | |
| { | |
| obj->call = create(callinfo); | |
| if (! obj->call) { | |
| discard(obj); | |
| return NULL; | |
| } | |
| } | |
| obj->refcount = 1; | |
| obj->kind = kind; | |
| obj->handle = handle; | |
| obj->bg = Transparent; | |
| add_object(obj, parent); | |
| return obj; | |
| } | |
| /* | |
| * Return the object if it matches the spec. | |
| */ | |
| static object match_object(object obj, HANDLE handle, int id, int key) | |
| { | |
| if ((handle != 0) && (obj->handle == handle)) | |
| return obj; | |
| if ((id != 0) && (obj->id == id)) | |
| return obj; | |
| if ((key != 0) && (obj->key == key) | |
| && (obj->kind == MenuitemObject)) | |
| return obj; | |
| return NULL; | |
| } | |
| /* | |
| * Perform the multi-child tree traversal. | |
| */ | |
| object tree_search(object top, HANDLE handle, int id, int key) | |
| { | |
| object obj, first_object, found = NULL; | |
| if ((! top) || (! top->child)) | |
| return NULL; | |
| first_object = top->child; | |
| obj = first_object; | |
| while (obj != top) | |
| { | |
| found = match_object(obj, handle, id, key); | |
| if (found) | |
| break; | |
| if (obj->child) | |
| { /* object has children - descend tree */ | |
| first_object = obj->child; | |
| obj = first_object; | |
| continue; | |
| } | |
| else | |
| { /* object is childless - go to next object */ | |
| obj = obj->next; | |
| } | |
| while (obj == first_object) | |
| { /* back at first object in sibling list */ | |
| /* climb the tree */ | |
| obj = obj->parent; | |
| if (obj == top) | |
| break; | |
| if (obj->parent) { | |
| first_object = obj->parent->child; | |
| obj = obj->next; | |
| } | |
| else | |
| break; | |
| } | |
| } | |
| return found; | |
| } | |
| PROTECTED | |
| object find_object(HANDLE handle, int id, int key) | |
| { | |
| return tree_search(base_object, handle, id, key); | |
| } | |
| /* | |
| * Write the tree structure to disk to examine it. | |
| */ | |
| #if DEBUG | |
| static void print_drawstate(FILE *f, drawstate d, object obj) | |
| { | |
| fprintf(f, "%lu: [", ((unsigned long)d)); | |
| if (!d) | |
| return; | |
| fprintf(f, "drawto=%s", (! d->drawing) ? "null" : | |
| (d->drawing == obj) ? "this" : "??"); | |
| fprintf(f, ", 0x%8.8lX", (unsigned long)d->rgb); | |
| fprintf(f, ", %s", getname(d->font)); | |
| fprintf(f, ", %s", getname(d->cursor)); | |
| fprintf(f, ", (%d,%d)", d->point.x, d->point.y); | |
| fprintf(f, ", width=%d", d->linewidth); | |
| fprintf(f, "]"); | |
| } | |
| static void print_objects(FILE *f, object obj, int indent) | |
| { | |
| object child; | |
| char *s; | |
| int i; | |
| for (i=0; i<indent; i++) | |
| fprintf(f, " "); | |
| if (! obj) { | |
| fprintf(f, "Null Object\n"); | |
| return; | |
| } | |
| switch(obj->kind) { | |
| case BaseObject: s = "BaseObject"; break; | |
| case ControlObject: s = "ControlObject"; break; | |
| case WindowObject: s = "WindowObject"; break; | |
| case BitmapObject: s = "BitmapObject"; break; | |
| case CursorObject: s = "CursorObject"; break; | |
| case FontObject: s = "FontObject"; break; | |
| case UserObject: s = "UserObject"; break; | |
| case LabelObject: s = "LabelObject"; break; | |
| case ButtonObject: s = "ButtonObject"; break; | |
| case CheckboxObject: s = "CheckboxObject"; break; | |
| case RadioObject: s = "RadioObject"; break; | |
| case ScrollbarObject: s = "ScrollbarObject"; break; | |
| case FieldObject: s = "FieldObject"; break; | |
| case TextboxObject: s = "TextboxObject"; break; | |
| case ListboxObject: s = "ListboxObject"; break; | |
| case ProgressbarObject: s = "ProgressbarObject"; break; | |
| case MultilistObject: s = "MultilistObject"; break; | |
| case DroplistObject: s = "DroplistObject"; break; | |
| case DropfieldObject: s = "DropfieldObject"; break; | |
| case MenubarObject: s = "MenubarObject"; break; | |
| case MenuObject: s = "MenuObject"; break; | |
| case MenuitemObject: s = "MenuitemObject"; break; | |
| default: s = "Unknown Object???"; break; | |
| } | |
| fprintf(f, "%s", s); | |
| if (obj->text) | |
| fprintf(f, ", text: \"%s\"", obj->text); | |
| /* | |
| if (! obj->child) | |
| fprintf(f, ", child: null"); | |
| */ | |
| if (! obj->handle) | |
| fprintf(f, ", handle: null"); | |
| if (obj->drawstate) { | |
| fprintf(f, ", drawstate "); | |
| print_drawstate(f, obj->drawstate, obj); | |
| } | |
| fprintf(f, "\n"); | |
| indent += 2; | |
| child = obj->child; | |
| while (child) { | |
| print_objects(f, child, indent); /* recursive */ | |
| child = child->next; | |
| if (child == obj->child) /* if back at start of list */ | |
| break; | |
| } | |
| fflush(f); | |
| } | |
| PROTECTED | |
| void print_object_list(void) | |
| { | |
| static int start_again = 1; | |
| char *mode = "at"; /* to ensure text mode */ | |
| FILE *f; | |
| if (! app_initialised) | |
| return; | |
| if (start_again) { | |
| mode = "w"; | |
| start_again = 0; | |
| } | |
| f = fopen("obj-list.txt", mode); | |
| if (current) { | |
| fprintf(f, "current drawstate "); | |
| print_drawstate(f, current, NULL); | |
| fprintf(f, "\n"); | |
| } | |
| fprintf(f, "global drawstate "); | |
| print_drawstate(f, & app_drawstate, NULL); | |
| fprintf(f, "\n"); | |
| print_objects(f, base_object, 0); | |
| fprintf(f, "------------\n"); | |
| fclose(f); | |
| } | |
| #endif | |
| void remove_menu_item(object obj) | |
| { | |
| /* Must call private destructor first! */ | |
| if (obj->die) obj->die(obj); | |
| remove_object(obj); | |
| remove_deleted_object(obj); | |
| } |