/* Part of XPCE --- The SWI-Prolog GUI toolkit Author: Jan Wielemaker and Anjo Anjewierden E-mail: jan@swi.psy.uva.nl WWW: http://www.swi.psy.uva.nl/projects/xpce/ Copyright (c) 1985-2002, University of Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #define HASH_DICT_THRESHOLD 50 static status initialiseDictv(Dict dict, int argc, Any *argv) { int i; assign(dict, members, newObject(ClassChain, EAV)); assign(dict, browser, NIL); assign(dict, table, NIL); for (i = 0; i < argc; i++) appendDict(dict, argv[i]); succeed; } static status unlinkDict(Dict dict) { if ( notNil(dict->browser) && !isFreeingObj(dict->browser) ) send(dict->browser, NAME_dict, NIL, EAV); clearDict(dict); if ( notNil(dict->table) ) { freeObject(dict->table); assign(dict, table, NIL); } succeed; } /******************************* * TABLE * *******************************/ static HashTable getTableDict(Dict dict) { if ( isNil(dict->table) ) { Cell cell; assign(dict, table, newObject(ClassHashTable, EAV)); for_cell(cell, dict->members) { DictItem di = cell->value; appendHashTable(dict->table, di->key, di); } } answer(dict->table); } /******************************** * RENUMBER * ********************************/ static status renumberDict(Dict dict) { int index = 0; Cell cell; for_cell(cell, dict->members) { DictItem di = cell->value; if ( di->index != toInt(index) ) assign(di, index, toInt(index)); index++; } succeed; } /******************************** * MEMBERSHIP * ********************************/ DictItem getMemberDict(Dict dict, Any obj) { if ( instanceOfObject(obj, ClassDictItem) ) { DictItem di = obj; if ( di->dict == dict ) answer(di); fail; } if ( instanceOfObject(obj, ClassCharArray) ) obj = toName(obj); if ( notNil(dict->table) ) answer(getMemberHashTable(dict->table, obj)); else if ( valInt(dict->members->size) > HASH_DICT_THRESHOLD ) answer(getMemberHashTable(getTableDict(dict), obj)); else { Cell cell; for_cell(cell, dict->members) { DictItem di = cell->value; if ( di->key == obj ) answer(di); } fail; } fail; } static status memberDict(Dict dict, Any obj) { if ( getMemberDict(dict, obj) ) succeed; fail; } /******************************** * APPEND/DELETE * ********************************/ status deleteDict(Dict dict, Any obj) { DictItem di; if ( !isFreeingObj(dict) ) { TRY( di = getMemberDict(dict, obj) ); addCodeReference(dict); if ( notNil(dict->browser) && !isFreeingObj(dict->browser) ) send(dict->browser, NAME_DeleteItem, di, EAV); if ( notNil(dict->table) ) deleteHashTable(dict->table, di->key); assign(di, dict, NIL); deleteChain(dict->members, di); renumberDict(dict); delCodeReference(dict); } succeed; } status appendDict(Dict dict, DictItem di) { if ( di->dict == dict ) succeed; if ( notNil(di->dict) ) { addCodeReference(di); deleteDict(di->dict, di); delCodeReference(di); } assign(di, dict, dict); assign(di, index, dict->members->size); if ( notNil(dict->table) ) appendHashTable(dict->table, di->key, di); appendChain(dict->members, di); if ( notNil(dict->browser) ) send(dict->browser, NAME_InsertItem, di, EAV); succeed; } static status membersDict(Dict dict, Chain members) { Cell cell; TRY(send(dict, NAME_clear, EAV)); for_cell(cell, members) TRY(send(dict, NAME_append, cell->value, EAV)); succeed; } static status insertAfterDict(Dict dict, DictItem di, Any after) { DictItem a; if ( notNil(after) ) { TRY(a = getMemberDict(dict, after)); } else a = NIL; if ( notNil(di->dict) ) { addCodeReference(di); deleteDict(di->dict, di); delCodeReference(di); } assign(di, dict, dict); if ( notNil(dict->table) ) appendHashTable(dict->table, di->key, di); insertAfterChain(dict->members, di, a); renumberDict(dict); if ( notNil(dict->browser) ) send(dict->browser, NAME_InsertItem, di, EAV); succeed; } DictItem getFindIndexDict(Dict dict, Int ln) { DictItem di; Cell cell; for_cell(cell, dict->members) { di = cell->value; if ( di->index == ln ) answer(di); } fail; } DictItem getFindPrefixDict(Dict dict, StringObj str, Int from, BoolObj ign_case) { Cell cell; if ( isDefault(ign_case) ) ign_case = OFF; TRY(cell = getNth0CellChain(dict->members, isDefault(from) ? ZERO : from)); for( ; notNil(cell); cell = cell->next ) { DictItem di = cell->value; CharArray label = getLabelDictItem(di); if ( label ) { if ( ign_case == OFF ) { if ( str_prefix(&label->data, &str->data) ) answer(di); } else { if ( str_icase_prefix(&label->data, &str->data) ) answer(di); } } } fail; } /******************************** * SORTING * ********************************/ static int sort_ignore_case = FALSE; static int sort_ignore_blanks = FALSE; static int compare_dict_items(const void *d1, const void *d2) { CharArray c1 = getLabelDictItem(*(DictItem *)d1); CharArray c2 = getLabelDictItem(*(DictItem *)d2); if ( c1 && c2 ) { PceString s1 = &c1->data; PceString s2 = &c2->data; if ( sort_ignore_blanks ) { LocalString(t1, s1->s_iswide, s1->s_size); LocalString(t2, s2->s_iswide, s2->s_size); str_cpy(t1, s1); str_cpy(t2, s2); str_strip(t1); str_strip(t2); if ( sort_ignore_case == TRUE ) return str_icase_cmp(t1, t2); else return str_cmp(t1, t2); } else { if ( sort_ignore_case == TRUE ) return str_icase_cmp(s1, s2); else return str_cmp(s1, s2); } } fail; } static status insertDict(Dict dict, DictItem di) { int codesort = FALSE; Cell cell; DictItem di2; int cmp; /* not sorted or empty: just append */ if ( isNil(dict->sort_by) || dict->members->size == ZERO ) return appendDict(dict, di); /* delete from possible old dict */ if ( notNil(di->dict) ) { addCodeReference(di); deleteDict(di->dict, di); delCodeReference(di); } /* find out sorting rules */ if ( isDefault(dict->sort_by) ) { sort_ignore_case = FALSE; sort_ignore_blanks = FALSE; } else { qsortCompareCode = dict->sort_by; codesort++; } /* after the last: use append */ di2 = getTailChain(dict->members); cmp = codesort ? qsortCompareObjects(&di, &di2) : compare_dict_items(&di, &di2); if ( cmp >= 0 ) return appendDict(dict, di); /* associate with the dict */ assign(di, dict, dict); if ( notNil(dict->table) ) appendHashTable(dict->table, di->key, di); /* find its place */ for_cell(cell, dict->members) { di2 = cell->value; cmp = codesort ? qsortCompareObjects(&di, &di2) : compare_dict_items(&di, &di2); if ( cmp < 0 ) { dict->members->current = cell; insertChain(dict->members, di); break; } } renumberDict(dict); if ( notNil(dict->browser) ) send(dict->browser, NAME_InsertItem, di, EAV); succeed; } static status sortDict(Dict dict, Any code_or_ign_case, BoolObj ign_blanks, BoolObj reverse) { int count, i=0; DictItem *items; Cell cell; Chain old; /* reference count */ int codesort = FALSE; int oldrev = qsortReverse; Code sortcode; if ( valInt(dict->members->size) <= 1 ) succeed; if ( instanceOfObject((sortcode=code_or_ign_case), ClassCode) || (isDefault(code_or_ign_case) && instanceOfObject((sortcode=dict->sort_by), ClassCode)) ) { qsortCompareCode = sortcode; codesort++; } else { if ( isDefault(code_or_ign_case) ) code_or_ign_case = getClassVariableValueObject(dict, NAME_sortIgnoreCase); if ( isDefault(ign_blanks) ) ign_blanks = getClassVariableValueObject(dict, NAME_sortIgnoreBlanks); sort_ignore_case = (code_or_ign_case == ON); sort_ignore_blanks = (ign_blanks == ON); } count = valInt(dict->members->size); items = pceMalloc((count*sizeof(DictItem))); for_cell(cell, dict->members) items[i++] = cell->value; qsortReverse = (reverse == ON); qsort(items, count, sizeof(DictItem), codesort ? qsortCompareObjects : compare_dict_items); qsortReverse = oldrev; /* see whether something changed */ for(i=0, cell = dict->members->head; i < count; i++, cell = cell->next) { if ( cell->value != items[i] ) break; } if ( i == count ) /* no change */ { pceFree(items); succeed; } if ( notNil(dict->browser) ) send(dict->browser, NAME_Clear, EAV); old = dict->members; lockObject(old, ON); assign(dict, members, newObject(ClassChain, EAV)); if ( notNil(dict->table) ) { clearHashTable(dict->table); assign(dict, table, NIL); } for (i=0; imembers) { DictItem di = cell->value; CharArray label = getLabelDictItem(di); if ( label && str_sub(&label->data, &name->data) ) appendChain(matching, di); } answer(matching); } status clearDict(Dict dict) { Cell cell; if ( dict->members->size == ZERO ) succeed; if ( notNil(dict->browser) && !isFreeingObj(dict->browser) ) send(dict->browser, NAME_Clear, EAV); if ( notNil(dict->table) ) { clearHashTable(dict->table); assign(dict, table, NIL); } for_cell(cell, dict->members) { DictItem di = cell->value; assign(di, dict, NIL); } clearChain(dict->members); succeed; } static status forAllDict(Dict d, Code code, BoolObj safe) { return forAllChain(d->members, code, safe); } static status forSomeDict(Dict d, Code code, BoolObj safe) { return forSomeChain(d->members, code, safe); } static Any getFindDict(Dict d, Code code) { return getFindChain(d->members, code); } static Chain getFindAllDict(Dict d, Code code) { return getFindAllChain(d->members, code); } static Any getBrowserDict(Dict d) { if ( notNil(d->browser) ) { ListBrowser lb = d->browser; if ( instanceOfObject(lb->device, ClassBrowser) ) answer(lb->device); else answer(lb); } fail; } /******************************* * VISUAL * *******************************/ static Chain getContainsDict(Dict d) { answer(d->members); } /******************************* * CLASS DECLARATION * *******************************/ /* Type declaractions */ static char *T_sort[] = { "[bool|code|function]", "ignore_blanks=[bool]", "reverse=[bool]" }; static char *T_actionAcode_safeADboolD[] = { "action=code", "safe=[bool]" }; static char *T_insertAfter[] = { "after=dict_item", "item=any|dict_item*" }; static char *T_findPrefix[] = { "for=string", "from=[int]", "no_exact_case=[bool]" }; /* Instance Variables */ static vardecl var_dict[] = { IV(NAME_browser, "list_browser*", IV_NONE, NAME_visualisation, "Associated browser (visualisation)"), IV(NAME_members, "chain", IV_GET, NAME_organisation, "Objects in the dictionary"), IV(NAME_table, "hash_table*", IV_NONE, NAME_hashing, "Hashtable for access on key"), SV(NAME_sortBy, "[code]*", IV_GET|IV_STORE, sortByDict, NAME_order, "Sorting rule to apply") }; /* Send Methods */ static senddecl send_dict[] = { SM(NAME_initialise, 1, "member=dict_item ...", initialiseDictv, DEFAULT, "Create a dict and append the arguments"), SM(NAME_unlink, 0, NULL, unlinkDict, DEFAULT, "Destroy hash-table and unlink from browser"), SM(NAME_append, 1, "item=dict_item", appendDict, NAME_add, "Append dict_item at the end"), SM(NAME_insert, 1, "item=dict_item", insertDict, NAME_add, "Insert dict_item according to <-sort_by"), SM(NAME_insertAfter, 2, T_insertAfter, insertAfterDict, NAME_add, "Insert item after 2nd (or prepend)"), SM(NAME_clear, 0, NULL, clearDict, NAME_delete, "Delete all members"), SM(NAME_delete, 1, "any|dict_item", deleteDict, NAME_delete, "Delete dict_item or name"), SM(NAME_forAll, 2, T_actionAcode_safeADboolD, forAllDict, NAME_iterate, "Run code on all dict_items, demand acceptance ([safe])"), SM(NAME_forSome, 2, T_actionAcode_safeADboolD, forSomeDict, NAME_iterate, "Run code on all dict_items ([safe])"), SM(NAME_sort, 3, T_sort, sortDict, NAME_order, "Sort contents"), SM(NAME_member, 1, "any|dict_item", memberDict, NAME_set, "Test if dict_item or name is member"), SM(NAME_members, 1, "chain", membersDict, NAME_set, "->clear and ->append elements of chain") }; /* Get Methods */ static getdecl get_dict[] = { GM(NAME_containedIn, 0, "list_browser|browser", NULL, getBrowserDict, DEFAULT, "Equivalent to <-browser"), GM(NAME_contains, 0, "chain", NULL, getContainsDict, DEFAULT, "Equivalent to <-members"), GM(NAME_table, 0, "hash_table", NULL, getTableDict, NAME_hashing, "Return hash-table key --> dict_item"), GM(NAME_member, 1, "dict_item", "any|dict_item", getMemberDict, NAME_lookup, "Find dict_item from <-key"), GM(NAME_find, 1, "dict_item", "test=code", getFindDict, NAME_search, "First dict_item accepted by code"), GM(NAME_findAll, 1, "chain", "test=code", getFindAllDict, NAME_search, "New chain with dict_items accepted"), GM(NAME_findPrefix, 3, "dict_item", T_findPrefix, getFindPrefixDict, NAME_search, "First item from index int that matches"), GM(NAME_match, 1, "chain", "char_array", getMatchDict, NAME_search, "New chain with items that match argument"), GM(NAME_browser, 0, "list_browser|browser", NULL, getBrowserDict, NAME_visualisation, "ListBrowser or Browser associated") }; /* Resources */ static classvardecl rc_dict[] = { RC(NAME_sortIgnoreBlanks, "bool", "@off", "@on: ignore leading blanks when sorting"), RC(NAME_sortIgnoreCase, "bool", "@off", "@on: ignore case when sorting") }; /* Class Declaration */ ClassDecl(dict_decls, var_dict, send_dict, get_dict, rc_dict, 0, NULL, "$Rev$"); status makeClassDict(Class class) { declareClass(class, &dict_decls); saveStyleVariableClass(class, NAME_table, NAME_nil); succeed; }