Blame | Last modification | View Log | Download | RSS feed
/** 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 PatrickThis file is part of GraphApp, a cross-platform C graphics library.GraphApp is free software; you can redistribute it and/or modify itunder the terms of the GNU Library General Public License.GraphApp is distributed in the hope that it will be useful, butWITHOUT ANY WARRANTY.See the file COPYLIB.TXT for details.*/#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.*/PROTECTEDvoid 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 DEBUGprint_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.*/PROTECTEDvoid 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).*/PROTECTEDvoid 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).*/PROTECTEDvoid 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.*/PROTECTEDvoid 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.*/PROTECTEDvoid deletion_traversal(void){static int level = 0;object obj;static void del_object(object obj); /* declaration */level++;if (level == 1) {while (del_base) {obj = del_base->obj;if (obj) {if (obj->refcount == 0)del_object(obj);elseremove_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 DEBUGprint_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);}PROTECTEDvoid 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).*/PROTECTEDobject 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.*/static 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->parent) {first_object = obj->parent->child;obj = obj->next;}elsebreak;}}return found;}PROTECTEDobject 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 DEBUGstatic 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 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);}PROTECTEDvoid print_object_list(void){static int start_again = 1;char *mode = "a";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