/* File: treedisp.c * Author: Richard Durbin (rd@mrc-lmb.cam.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1991 * ------------------------------------------------------------------- * Acedb is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr * * Description: display/editor for generic tree objects * Exported functions: treeDisplay * HISTORY: * Last edited: Jan 9 15:03 2004 (rnc) * * * May 17 13:54 1999 (edgrif): Set blocking mode for tree chooser graph. * * Sep 28 14:24 1998 (fw): make timestamp boxes inactive, this prevents seg-faults and invalid boxNum errors * - extra 'bs->up' tests in lookKbd() to prevent NULL pointer references? * * Aug 12 16:56 1996 (srk) * * Jun 3 12:42 1996 (rd) * * May 8 20:22 1993 (mieg): ON/OFF tag expansion * * May 8 20:21 1993 (mieg): updtateMode, to prevent expandText or ON/OFF * * Sep 20 01:05 1992 (rd): fixPath() handles new UNIQUE correctly * * Aug 16 10:59 1992 (mieg): tagChooser * * May 3 22:29 1992 (rd): redid MENU_PRESERVE (nl1 version lost, sorry) * * Feb 11 13:09 1992 (mieg): Introduced graphCompletionEntry * Created: Wed Oct 16 17:41:46 1991 (rd) * CVS info: $Id: treedisp.c,v 1.151 2004/01/09 15:04:04 rnc Exp $ *------------------------------------------------------------------- */ #define ACEDB_MAILER #include #include #include #include #include #include #include /* to remember open tags */ #include #include #include /* for E_mail */ #include #include #include #include #include #include #include #include /* for helpOn() fw-981103 */ #include #include #include /* for fpDisplay() fw-980928 */ #include /* for parseBuffer() fw-980928 */ #include /* for ksetClassComplete */ #include #include #include #include #include #include BITSET_MAKE_BITFIELD /* define bitField for bitset.h ops */ /************************************************************/ typedef struct TreeDispStruct { magic_t *magic ; /* == &TreeDisp_MAGIC */ KEY key ; OBJ objet ; Graph graph ; int activebox ; Array content ; /* content of each box: BS */ Array attachedObjs ; Associator bs2box ; Associator tag2box; /* maps tags to the display */ Array names ; /* Find: text as it appears on the screen */ Array boxes ; /* boxes for the names */ STORE_HANDLE handle; /* used by Find function */ BS bsWait ; /* for use during update */ Stack textStack ; /* for typing in things */ int classe ; /* for completion mechanism */ BOOL updateMode, tagChooserMode ; Array longTextArray ; void *editor; char *tagWarp; /* search pattern stripped of wildcards */ char *pattern; /* stores original search pattern */ int keyClicked; /* last key clicked in Find function */ } *TreeDisp ; static Array bbSetArray = 0 ; static BOOL autoPreserve = TRUE ; static magic_t TreeDisp_MAGIC = "TreeDisp_MAGIC"; static magic_t GRAPH2TreeDisp_ASSOC = "TreeDisp_ASSOC"; typedef enum { UNDEFINED, LEADING, TRAILING, BOTH, NEITHER } AceWild; static FREEOPT treeMenu[] = { { 14,"Tree display" }, { 99,"Quit" }, { 98,"Help"} , { 13,"Print"} , { 0,""}, { 9,"Update ^U"} , { 20,"Preserve"} , { 21,"Set AutoPreserve"} , { 22,"Unset AutoPreserve"} , { 14,"Export data"} , /* { 23,"Biblio"} , see keySet menu { 24,"Neighbours"} , see keySet query { 25,"Contract"} , useless, just touch a tag { 26,"Expand"}, useless, just touch a tag */ { 27,"Collapse Display"}, /* revert to default display */ { 17,"Toggle Timestamps"} , { 18,"Toggle TagCount"} , { 41,"Find ^F/^B"}, { 5,"Show as Text"} } ; static FREEOPT updateMenu[] = { { 11,"Tree Edit"} , { 99,"Quit"} , { 98,"Help"} , { 0,""}, { 3,"Save ^U"} , /* { 4,"Tree Display ^T"} , */ { 7,"Add Comment"} , { 10,"Cancel"} , { 8,"Delete"} , { 11,"Edit"} , { 25,"Contract"} , { 26,"Expand"}, { 41,"Find ^F/^B"}, } ; extern void lookBiblio (OBJ objet, KEY k) ; static void lookDestroy (void) ; static void lookRedraw (void) ; static void lookMenu (KEY k) ; static void lookPick (int k, double x_unused, double y_unused, int modifier_unused) ; static void lookKbd (int k, int modifier_unused) ; static void lookDraw (TreeDisp look); static void updatePick (int k, double x_unused, double y_unused, int modifier_unused) ; static void updateKbd (int k, int modifier_unused) ; static void defuse (BS bs) ; static void addComment (char *string) ; static void addData (char *string) ; static void addKeyText (char *string) ; static void addKey (KEY key) ; static void editEntry (char *text) ; static void editorOK(void *ep, void *data); static void editorCancel(void *ep, void *data); static void treeDump (TreeDisp look) ; static int countSiblings(BS bs) ; static void detach1 (TreeDisp look) ; static void treeDispMailer (KEY key) ; /* at end of file */ static char* drawBStext (BS bs) ; static void revertDisplayToDefault(TreeDisp look); /* does what it says on the tin */ static void FindObject (TreeDisp look, /* prompts for search pattern */ AceFindDirection direction); static BOOL FindBox (TreeDisp look, /* locates the box within the tree */ AceFindDirection direction, AceWild wildcard); static BOOL examineElement (char *element, char *pattern, AceWild wildcard ); static int intOrder (void *a, void *b); /* needed for arrayFind */ static void updateSave (TreeDisp look, BitSet bb); static BOOL showTimeStamps = FALSE ; static BOOL showTagCount = FALSE ; static int doAttachActionMenu; static int doHorizontalLayout; #define LINE_LENGTH 40 #define SELECT 0 #define FIND 1 /**************************************************************/ static TreeDisp currentTreeDisp (char *caller) { TreeDisp tree; Graph g = graphActive(); #ifdef DEBUG printf("currentTreeDisp %s\n", caller); #endif if (!graphAssFind (GRAPH2TreeDisp_ASSOC,&tree)) messcrash("%s() could not find TreeDisp on graph", caller); if (!tree) messcrash("%s() received NULL TreeDisp pointer", caller); if (tree->magic != &TreeDisp_MAGIC) messcrash("%s() received non-magic TreeDisp pointer", caller); if (g != tree->graph) messcrash("%s() found look from another graph", caller); return tree; } /* currentTreeDisp */ static int tagColour = BLACK ; static void colourBox (int box, BS bs, BOOL isActive) { if (!bs) return ; if (isActive) { if (bs->size & (MODEL_FLAG | UNIQUE_FLAG)) { if (bs->size & SUBTYPE_FLAG) graphBoxDraw (box, WHITE, GREEN) ; else graphBoxDraw (box, WHITE, BLUE) ; } else if (bsIsTag(bs)) graphBoxDraw (box, WHITE, tagColour) ; else graphBoxDraw (box, WHITE, BLACK) ; } else { if (bs->size & MODEL_FLAG) { if (bs->size & SUBTYPE_FLAG) graphBoxDraw (box, BLACK, LIGHTGREEN) ; else if (bsIsTag(bs)) graphBoxDraw (box, tagColour, CYAN) ; else graphBoxDraw (box, BLACK, CYAN) ; } else { if (bsIsComment(bs)) graphBoxDraw (box, BLACK, LIGHTGRAY) ; else if (bsIsTag(bs)) graphBoxDraw (box, tagColour, WHITE) ; else graphBoxDraw (box, BLACK, WHITE) ; } } } static void colourActive (TreeDisp look, BOOL isOn) { if (look->activebox > 0) colourBox (look->activebox, arr(look->content,look->activebox,BS), isOn) ; } /************************************/ static void clearFlags (BS bs) { bs->size = 0 ; if (bs->right) clearFlags (bs->right) ; if (bs->down) clearFlags (bs->down) ; } static void setOnFlag (BS bs) { if (bs->right) { if (countSiblings(bs->right) > 6 || (pickExpandTag (bs->right->key) && countSiblings(bs->right) > 3)) bs->size |= ON_FLAG ; setOnFlag (bs->right) ; } if (bs->down) setOnFlag (bs->down) ; } static void setAllOnFlags(BS bs) { if (bs) { bs->size &= ~ON_FLAG ; setAllOnFlags(bs->right); setAllOnFlags(bs->down); } } static void setOnFlagModelOnly (BS bs) { if (bs->right) { if ((bs->size & MODEL_FLAG) && (countSiblings(bs->right) > 6 || (pickExpandTag (bs->right->key) && countSiblings(bs->right) > 3))) bs->size |= ON_FLAG ; setOnFlagModelOnly (bs->right) ; } if (bs->down) setOnFlagModelOnly (bs->down) ; } static void getOnFlagSet (BS bs, BitSet bb) { if (bs->right) { if (!class(bs->key) && bs->key >= _Date) { if (bs->size & ON_FLAG) bitSet (bb, bs->key) ; else bitUnSet (bb, bs->key) ; } getOnFlagSet (bs->right, bb) ; } if (bs->down) getOnFlagSet (bs->down, bb) ; } static void setOnFlagSet (BS bs, BitSet bb) { if (bs->right) { if (!class(bs->key) && bs->key >= _Date) { if (bit(bb,bs->key)) bs->size |= ON_FLAG ; else bs->size &= ~ON_FLAG ; } setOnFlagSet (bs->right, bb) ; } if (bs->down) setOnFlagSet (bs->down, bb) ; } /************************************/ BOOL treeDisplay(KEY key, KEY from, BOOL isOldGraph, void *unused) { TreeDisp look=(TreeDisp)messalloc(sizeof(struct TreeDispStruct)); #ifdef DEBUG_TREEDISP printf("treeDisplay\n"); #endif look->magic = &TreeDisp_MAGIC; look->key = key; if (pickType (key) != 'B' || !(look->objet = bsCreateCopy(key))) goto abort ; clearFlags (look->objet->root) ; setOnFlag (look->objet->root) ; tagColour = prefInt ("TAG_COLOUR_IN_TREE_DISPLAY") ; if (!tagColour) tagColour = BLACK ; /* fw, 990501 Jean likes this menu, but most people find it confusing, so it is now configurable, but switched off by default */ doAttachActionMenu = prefValue("ACTION_MENU_IN_TREE_DISPLAY"); /* mieg, may 30 96, try to lay out in a more vertical way without breaking inside data values - much better and is now the default */ doHorizontalLayout = prefValue("HORIZONTAL_TREE"); if (isOldGraph) { lookDestroy () ; graphClear () ; graphGoto (0,0) ; graphRetitle (messprintf("%s: %s", className(key),name (key))) ; } else { displayCreate ("TREE"); graphRetitle (messprintf("%s: %s", className(key),name (key))) ; graphRegister (DESTROY, lookDestroy) ; graphRegister (PICK, lookPick) ; graphRegister (KEYBOARD, lookKbd) ; graphRegister (RESIZE, lookRedraw) ; graphFreeMenu (lookMenu, treeMenu) ; } look->graph = graphActive() ; graphAssociate (GRAPH2TreeDisp_ASSOC, look) ; look->content = arrayCreate(32,BS); look->bs2box = assCreate () ; look->tag2box = assCreate(); look->handle = handleCreate(); /* use a handle so we can destroy all in one go */ look->names = arrayHandleCreate(32, char*, look->handle); look->boxes = arrayHandleCreate(32, int, look->handle); look->activebox = 0 ; look->updateMode = FALSE ; look->tagChooserMode = FALSE ; lookDraw (look) ; return TRUE ; abort: bsDestroy (look->objet) ; messfree (look) ; return FALSE ; } /************************************************************/ static void lookDestroy (void) { TreeDisp look = currentTreeDisp("lookDestroy") ; #ifdef DEBUG_TREEDISP printf("lookDestroy\n"); #endif if (look->bsWait && (look->bsWait->size & ADD_KEY_FLAG)) displayUnBlock() ; if (look->editor) messfree(look->editor); if (look->tagWarp) messfree(look->tagWarp); detach1 (look) ; if (look->objet && isCacheModified(look->objet->x) && ( !strcmp("Tag-chooser", name(look->key)) || messQuery (messprintf("%s not saved, Save ?",name(look->key))))) { defuse (look->objet->root) ; bsSave (look->objet); } else bsDestroy (look->objet); arrayDestroy (look->content) ; assDestroy (look->bs2box) ; handleDestroy(look->handle); /* clear boxpointers array & ** associated linked lists */ if (look->textStack) stackDestroy (look->textStack) ; if (look->longTextArray) arrayDestroy (look->longTextArray); look->magic = 0 ; messfree (look) ; graphAssRemove (GRAPH2TreeDisp_ASSOC) ; } /************************************************************/ static void lookRedraw (void) { TreeDisp look = currentTreeDisp("lookRedraw") ; lookDraw(look) ; } /*********************************************************/ static void lookPick (int box, double x_unused, double y_unused, int modifier_unused) { BS bs ; TreeDisp look = currentTreeDisp("lookPick") ; #ifdef DEBUG_TREEDISP printf("lookPick\n"); #endif /* just testing fw - may99 { float x1, x2, y1, y2; graphWhere (&x1, &y1, &x2, &y2); printf ("(%.2f %.2f) (%.2f %.2f)\n", x1, y1, x2, y2); } */ if (autoPreserve) displayPreserve () ; if (!box) { colourActive (look, FALSE) ; look->activebox = 0 ; return ; } else if ( box >= arrayMax (look->content)) { messerror("LookPick received a wrong box number"); return ; } bs = arr(look->content,box,BS) ; if (!bs) /* timestamp boxes have NULL bs attached, so they are inactive */ return; /* the text of the currently picked box */ graphPostBuffer (drawBStext (bs)) ; /* mhmp 11/04/97 */ bs = arr(look->content,look->activebox,BS); if (box == look->activebox) /* a second hit - follow it */ { if (bs->size & ON_FLAG) /* if hidden, show branch */ { bs->size ^= ON_FLAG ; lookDraw(look) ; } else if (bs->key == _Pick_me_to_call && !look->updateMode && !look->tagChooserMode) externalDisplay(look->key) ; else if (bs->key == _Gel && !look->updateMode && !look->tagChooserMode) fpDisplay (look->key) ; else if (bs->key == _E_mail && !look->updateMode && !look->tagChooserMode) treeDispMailer (look->key) ; else if (class(bs->key)) display (bs->key,look->key, 0) ; else if (bs->right && !look->updateMode && !look->tagChooserMode) { bs->size ^= ON_FLAG ; lookDraw(look) ; } } else /* change now */ { colourActive (look, FALSE) ; look->activebox = box ; look->keyClicked = SELECT; colourActive (look, TRUE) ; } } /*****************/ static void lookKbd (int k, int modifier_unused) { char *vp ; BS bs ; int box ; int nn = 3 ; TreeDisp look = currentTreeDisp("lookKbd") ; #ifdef DEBUG_TREEDISP printf("lookKbd\n"); #endif if (isDisplayBlocked()) return ; if (autoPreserve) displayPreserve () ; /* Find: enable Control/B to activate FindObject (backward)*/ if (k == B_KEY && (modifier_unused & CNTL_MODKEY)) FindObject(look, FIND_BACKWARD); /* Find: enable Control/F to activate FindObject (forward)*/ if (k == F_KEY && (modifier_unused & CNTL_MODKEY)) FindObject(look, FIND_FORWARD); /* Find Next: enable Control/RightChevron to activate FindBox (forward)*/ if (k == DOT_KEY && (modifier_unused & CNTL_MODKEY)) FindBox(look, FIND_FORWARD, UNDEFINED); /* Find Previous: enable Control/LeftChevron to activate FindBox (backward)*/ if (k == COMMA_KEY && (modifier_unused & CNTL_MODKEY)) FindBox(look, FIND_BACKWARD, UNDEFINED); /* Update Mode: enable Control/U to switch to Update Mode */ if (k == U_KEY && (modifier_unused & CNTL_MODKEY)) treeUpdate(); if (!look->activebox) return ; colourActive (look, FALSE) ; if (!(bs = arr(look->content,look->activebox,BS))) return ; retry: /* may be a hidden bs->box is hit, try nn times */ switch (k) { case LEFT_KEY : while (bs->up && bs->up->down == bs) bs = bs->up ; if (bs->up && bs->up->up) bs = bs->up ; break ; case RIGHT_KEY : if (bs->right) bs = bs->right ; break ; case DOWN_KEY : if (bs->down) bs = bs->down ; else while (bs->up && bs->up->down == bs) bs = bs->up ; break ; case UP_KEY : if (bs->up && bs->up->down == bs) bs = bs->up ; else while (bs->down) bs = bs->down ; break ; case SPACE_KEY : if (bs->right) bs = bs->right ; else if (bs->down) bs = bs->down ; else while (bs->up && bs->up->up) { while (bs->up && bs->up->down == bs) bs = bs->up ; if (bs->up && !bs->up->up) break ; bs = bs->up ; if (bs->down) { bs = bs->down ; break ; } } break ; } if (assFind (look->bs2box, bs, &vp)) { box = assInt(vp) ; colourBox (box, bs, TRUE) ; if (box != look->activebox && class(bs->key) && iskey(bs->key) == 2) display (bs->key,look->key,0) ; look->activebox = box ; } else if (nn-- >0) goto retry ; } /*************************************************************/ void treeUpdate (void) { BitSet bb = 0 ; TreeDisp look = currentTreeDisp("treeUpdate") ; KEY tag; void *vp; #ifdef DEBUG_TREEDISP printf("treeUpdate\n"); #endif if(!checkWriteAccess()) return ; if (!KEYKEY(look->key)) { messout ("Sorry, you can not update models this way") ; return ; } if (pickList[class(look->key) & 255 ].protected) { messout ("Sorry, this is a protected class, you cannot update it interactively") ; return ; } if(!checkWriteAccess()) return ; if (look->activebox && bsIsTag(arr(look->content,look->activebox,BS))) tag = arr(look->content,look->activebox,BS)->key; else tag = 0; detach1 (look) ; bsDestroy (look->objet) ; if (!(look->objet = bsUpdate (look->key))) { look->objet = bsCreateCopy (look->key) ; lookDraw (look) ; return ; } cacheMark (look->objet->x) ; look->updateMode = TRUE ; clearFlags (look->objet->root) ; bsFuseModel (look->objet) ; setOnFlag (look->objet->root) ; /* RD put back after FuseModel */ if (bbSetArray) /* mieg, inertia in open tags */ { bb = array(bbSetArray, class(look->key), BitSet) ; if (bb) setOnFlagSet (look->objet->root, bb) ; } look->activebox = 0 ; graphPop() ; lookDraw (look) ; if (tag && assFind(look->tag2box, assVoid(tag), &vp)) { float x1, y1; graphBoxDim(assInt(vp), &x1, &y1, NULL, NULL); graphGoto(x1, y1); lookPick(assInt(vp), 0, 0, 0); } graphRegister (PICK, updatePick) ; graphRegister (KEYBOARD, updateKbd) ; graphFreeMenu (lookMenu, updateMenu) ; graphHelp ("Update") ; } static void lookMenu (KEY k) { BS bs, p ; BitSet bb = 0 ; TreeDisp look = currentTreeDisp("lookMenu") ; char *text ; #ifdef DEBUG_TREEDISP printf("lookMenu"); #endif /* if at end then check gActive is correct first */ if (autoPreserve) displayPreserve () ; switch ((int) k) { case 3 : /* save */ if (look->bsWait) { messout ("Finish what you have started first, or cancel") ; return ; } updateSave(look, bb); break ; case 4 : /* switch to Tree Display */ graphDestroy(); treeDisplay(look->key, 0, FALSE, 0); break; case 5 : if (!look->activebox) { messout ("Please first select a node") ; return ; } bs = arr(look->content,look->activebox,BS) ; if (iskey(bs->key) == 2 ) { if(pickType(bs->key) == 'B') display(bs->key, look->key, "TREE") ; else messout("Sorry, I cannot display %s as a tree", name(bs->key)) ; } else messout("No data associated to %s", name(bs->key)) ; break ; case 7 : /* add comment */ if (look->bsWait) { messout ("Please finish first what you have started, or cancel") ; return ; } if (!look->activebox) { messout ("Sorry, you must first select a node to add to (single-click)") ; return ; } bs = arr(look->content,look->activebox,BS) ; if (bs->size & MODEL_FLAG) { messout ( "You must add comments to the original object (black and white stuff)") ; return ; } if (bsIsComment(bs)) { messout ("You can't add comments to a comment") ; return ; } bs->size |= ADD_COMMENT_FLAG ; look->bsWait = bs ; lookDraw (look) ; break ; case 8 : /* delete */ if (look->bsWait) { messout ("Finish what you have started first, or cancel") ; return ; } if (!look->activebox) { messout ("You must first select a node to prune (single-click)") ; return ; } bs = arr(look->content,look->activebox,BS) ; if (bs->size & MODEL_FLAG) { messout ("You can only delete parts of the original object") ; return ; } look->objet->curr = bs ; look->objet->modCurr = (bs->bt) ? bs->bt->bsm : 0 ; defuse (look->objet->root) ; bsRemove (look->objet) ; bsFuseModel (look->objet) ; setOnFlagModelOnly (look->objet->root) ; lookDraw (look) ; break ; case 9 : /* update */ displayPreserve() ; treeUpdate () ; break ; case 10 : /* cancel operation */ if (!look->bsWait) return ; if (look->bsWait->size & ADD_KEY_FLAG) displayUnBlock() ; /* clears display block */ look->bsWait->size &= ~WAITING_FLAGS ; /* clears edit and fake node flags */ look->bsWait = 0 ; lookDraw (look) ; break ; case 41: /* Find object */ { FindObject(look,FIND_FORWARD); break; } case 11: /* edit */ if (look->bsWait) { messout ("Finish what you have started first, or cancel") ; return ; } if (!look->activebox) { messout ("You must first select a node to edit (single-click)") ; return ; } bs = arr(look->content,look->activebox,BS) ; if (bsIsTag(bs)) { messout ("You can not edit tags - only object names or data") ; return ; } if (!(bs->size & MODEL_FLAG)) { if (class(bs->key)) { if (strcmp(className(bs->key), "Text") != 0) /* Object name other than ?Text */ { look->bsWait = bs; bs->size |= ADD_KEY_FLAG ; lookDraw (look) ; break; } } else if ((bs->key >= _LastC)) { look->bsWait = bs; bs->size |= ADD_DATA_FLAG ; lookDraw (look) ; break; } /* Don't edit ints and floats this way */ text = drawBStext(bs); } else /* (bs->size & MODEL_FLAG) */ { if (class(bs->key) && (strcmp(className(bs->key), "Text") != 0)) { messout ("You can only edit parts of the original object") ; break; } if (!bsIsTag(bs->up) && (bs->up->size & MODEL_FLAG)) break; if (bs->size & SUBTYPE_FLAG) /* a type */ break; text = NULL; } p = bs; while (p->up && p->up->down == p) p = p->up; if (p->up && p->up->up) p = p->up; look->bsWait = bs ; look->editor = gexTextEditorNew(messprintf("Edit text for tag %s", name(p->key)) ,text, 0, look, editorOK, editorCancel, TRUE); /* editable */ break ; case 13 : /* "Print" */ graphPrint() ; break ; case 20 : /* "Preserve" */ displayPreserve() ; break ; case 21 : /* "Set AutoPreserve" */ autoPreserve = TRUE ; break ; case 22 : /* "Unset AutoPreserve" */ autoPreserve = FALSE ; break ; case 14: /* "Export data" */ treeDump (look) ; break ; #ifdef JUNK case 23 : /* "Biblio" */ biblioKey (look->key) ; break ; case 24 : /* "Neighbors" */ { KEYSET nks = bsKeySet(look->key) , new = keySetCreate() ; KEY *kp ; int j1 = 0 , j2 ; if (nks) { j2 = keySetMax(nks) ; kp = arrp(nks,0,KEY) - 1 ; while(kp++, j2--) if (!pickXref(class(*kp)) && iskey(*kp) == 2) keySet(new, j1++) = *kp ; keySetDestroy(nks) ; } keySetNewDisplay(new, messprintf("Neighbours of %s", name(look->key))) ; } break ; #endif case 17: /* "Toggle Timestamps" */ showTimeStamps = showTimeStamps ? FALSE : TRUE ; look->activebox = 0; /* don't carry box-selection over to new display style */ lookDraw (look) ; break ; case 18: showTagCount = showTagCount ? FALSE : TRUE ; look->activebox = 0; /* don't carry box-selection over to new display style */ lookDraw (look) ; break ; case 25 : /* contract */ if (!look->activebox || !(bs = arr(look->content,look->activebox,BS))) { messout ("You must first select a node to edit (single-click)") ; return ; } if (!(bs->size & ON_FLAG)) { bs->size |= ON_FLAG ; lookDraw (look) ; } else messout ("That node was already contracted") ; break ; case 26 : /* expand */ if (!look->activebox || !(bs = arr(look->content,look->activebox,BS))) { messout ("You must first select a node to edit (single-click)") ; return ; } if (bs->size & ON_FLAG) { bs->size ^= ON_FLAG ; lookDraw (look) ; } else messout ("That node was already expanded") ; break ; case 27 : /* revert to default display */ revertDisplayToDefault(look); break; #ifdef JUNK_ACEDB_MAILER /* now that we can paste cut, just paste the adress to your favorite mailer */ case 31 : /* Mailer */ { OBJ obj = bsCreate(look->key) ; char *mailAddress ; ACEIN addr_in = NULL; graphHelp ("Mailer") ; if (!obj) break ; if (!bsGetData(obj, _E_mail, _Text,&mailAddress)) { if ((addr_in = messPrompt ("Please specify an email address", "","w", 0))) { OBJ obj1; mailAddress = aceInWord (addr_in); obj1 = bsUpdate(look->key) ; if (!obj1) goto fin ; if (!bsAddData(obj1, _E_mail, _Text, mailAddress)) { bsDestroy(obj1) ; messout ("Sorry, I can't save your address, " "no E_mail Tag in this class") ; goto fin ; } bsSave(obj1) ; } else goto fin ; } else acedbMailer(look->key, 0,0) ; fin: bsDestroy(obj); if (addr_in) aceInDestroy (addr_in); break ; } #endif /* ACEDB_MAILER */ case 98 : { int nn = 0 ; Stack h = stackCreate (50) ; KEYSET ks1 = keySetCreate () ; KEY k1 ; if (look->activebox) { bs = arr(look->content,look->activebox,BS) ; while (bs) /* look upward for a tag */ { keySet (ks1, nn++) = bs->key ; if (!class (bs->key) && bs->key >= 50) break ; bs = bs->up ; } } pushText (h, messprintf("%s_%s",graphHelp(0),className(look->key))) ; while (nn--) { catText (h, "_") ; k1 = keySet(ks1,nn) ; catText (h, class(k1) ? className(k1) : name(k1)) ; } helpOn (stackText (h, 0)) ; stackDestroy (h) ; keySetDestroy (ks1) ; } break ; case 99 : graphDestroy () ; break ; } } /****************************************************/ static void treeDump (TreeDisp look) { ACEOUT fo; static char directory[DIR_BUFFER_SIZE] = "" ; static char filename[FIL_BUFFER_SIZE] = "object" ; if (!lexIsKeyVisible (look->key)) { messout("Sorry, I don't know how to dump %s\n", name(look->key)); return; } fo = aceOutCreateToChooser ("Where do you wish to ace dump this object ?", directory, filename, "ace", "w", 0); if (!fo) return; aceOutPrint (fo, "// data dumped from tree display\n\n") ; dumpKey (fo, look->key); messout ("Object %s written to %s\n", name(look->key), aceOutGetURL(fo)); aceOutDestroy (fo); return; } /* treeDump */ /*************************************************************/ /*************** drawing package *****************************/ /*******************************************************/ static void attach (void) ; static void detach (void) ; static void attachRecursively (void) ; static void attachHelp (void) ; static MENUOPT attachMenu [] = { {attach, "Attach"}, {detach, "Detach"}, {attachRecursively, "Attach Recursively"}, {attachHelp, "Help on attach"}, {0, 0} } ; static TreeDisp drawLook ; static int xmax, ymax ; static BOOL isModel ; /******************* attach subpackage ***********************/ static void doDetach(BS bs) { #ifdef DEBUG_TREEDISP printf("doDetach\n"); #endif if (bs->right) doDetach (bs->right) ; if (bs->down) doDetach (bs->down) ; if (bs->right && (bs->right->size & ATTACH_FLAG)) { bsTreePrune (bs->down) ; bs->down = 0 ; } } static void detach1 (TreeDisp look) {int i ; Array a = look->attachedObjs ; #ifdef DEBUG_TREEDISP printf("detach1\n"); #endif if (arrayExists(a)) { doDetach (look->objet->root) ; i = arrayMax (a) ; while (i--) bsDestroy (array (a, i, OBJ)) ; } arrayDestroy (look->attachedObjs) ; } static void detach (void) { int box ; BS bs ; TreeDisp look = currentTreeDisp("detach") ; #ifdef DEBUG_TREEDISP printf("detach\n"); #endif box = look->activebox ; if (!box) { messout ("Please first select a node") ; return ; } else if ( box >= arrayMax (look->content)) { messerror("Sorry, detach received a wrong box number"); return ; } bs = arr(look->content,box,BS) ; if (bs && bs->right) doDetach (bs->right) ; /* do not detach down */ if (bs && bs->right && (bs->right->size & ATTACH_FLAG)) bs->right = 0 ; lookDraw (look) ; } static void doAttach (BS bs, BOOL whole, Array a, KEYSET ks, BOOL recursive, char *question) { OBJ obj ; int i, dummy ; KEY key ; KEYSET ks1 = 0 ; BS bs1 = bs , bs2 = bs ; #ifdef DEBUG_TREEDISP printf("doAttach\n"); #endif while (bs1->up && ( pickType(bs1->key) != 'B' || !class(bs1->key) || class(bs1->key) == _VComment || class(bs1->key) == _VUserSession)) bs1 = bs1->up ; if (bs->right) goto more ; ks1 = queryKey (bs1->key, question ? question : "IS *") ; for (i = 0 ; i < keySetMax (ks1) ; i++) { key = keySet (ks1, i) ; if ((recursive < 2 || !keySetFind (ks, key, &dummy)) && (obj = bsCreateCopy (key))) { if (whole || queryFind (obj, key, 0)) { bs1 = obj->curr ; if (whole && bs1->right) { bs1 = bs1->right ; if (bs1->down && !bs1->right) bs1 = bs1->down ; } else if (bs1->down) /* mieg, dec 98 else needed*/ { bsTreePrune (bs1->down) ; bs1->down = 0 ; } if (bs1->up) { if (bs1->up->down == bs1) bs1->up->down = 0 ; else if (bs1->up->right == bs1) bs1->up->right = 0 ; } if (bs2 == bs) bs2->right = bs1 ; else bs2->down = bs1 ; bs1->up = bs2 ; bsDestroy (obj) ; /* destroy the now stripped tmp obj */ } else { bsDestroy (obj) ; goto more ; } clearFlags (bs1) ; setOnFlag (bs1) ; bs1->size |= ATTACH_FLAG ; array (a, arrayMax (a), OBJ) = obj ; if (ks && recursive > 1) keySetInsert (ks, key) ; } } keySetDestroy (ks1) ; more: if (bs->right) if (recursive || !(bs->right->size & ATTACH_FLAG)) doAttach (bs->right, whole, a, ks, recursive ? recursive + 1 : 0, question) ; if (bs->down) doAttach (bs->down, whole, a, ks, recursive, question) ; } static void attach1 (int box, BOOL recursive) { BS bs, bs1, down ; int type, tcl ; KEY tag ; char *cp ; Stack sta = 0 ; KEYSET ks = 0 ; TreeDisp look = currentTreeDisp("attach") ; #ifdef DEBUG_TREEDISP printf("attach1\n"); #endif if (!box) { messout ("Please first select a node") ; return ; } else if ( box >= arrayMax (look->content)) { messerror("Sorry, attach received a wrong box number"); return ; } bs = arr(look->content,box,BS) ; bs1 = bs ; while (bs1->right && !class(bs1->key)) bs1 = bs1->right ; if (pickType(bs1->key) != 'B') { messout ("First object to the right is not a Tree. I can't attach, sorry") ; return ; } sta = stackCreate (50) ; if (treeChooseTagFromModel (&type, &tcl, class(bs1->key), &tag, sta, FALSE)) { if (!arrayExists(look->attachedObjs)) look->attachedObjs = arrayCreate (12, OBJ) ; if (recursive) ks = keySetCreate () ; /* against recursive attach runaway */ down = bs->down ; bs->down = 0 ; cp = stackText (sta, 0) ; if (!strcmp ("WHOLE", cp)) doAttach (bs, TRUE, look->attachedObjs, ks, recursive, 0) ; else { queryFind (0, 0, cp) ; /* Initialise queryFind */ doAttach (bs, FALSE, look->attachedObjs, ks, recursive, cp) ; } bs->size &= ~ON_FLAG ; stackDestroy (sta) ; keySetDestroy (ks) ; bs->down = down ; } lookDraw (look) ; } static void attach (void) { TreeDisp look = currentTreeDisp("attach") ; printf("attach\n"); attach1(look->activebox, FALSE) ; } static void attachRecursively (void) { TreeDisp look = currentTreeDisp("attachRecursivelly") ; printf("attachRecursively\n"); attach1(look->activebox, TRUE) ; } static void attachQuery (BS bs, KEY qr) { char *cp0, *cp = name (qr) ; TreeDisp look = currentTreeDisp("attachQuery") ; if (!arrayExists(look->attachedObjs)) look->attachedObjs = arrayCreate (12, OBJ) ; cp0 = cp ; cp += strlen (cp0) ; while (*(cp - 1 ) != ';' && cp > cp0) cp-- ; if (!strcmp ("WHOLE", cp)) doAttach (bs, TRUE, look->attachedObjs, 0, 0, 0) ; else { queryFind (0, 0, cp) ; /* Initialise queryFind */ doAttach (bs, FALSE, look->attachedObjs, 0, FALSE, cp0) ; } } static void attachHelp (void) { helpOn ("attach") ; } /*******************************************************************/ static FREEOPT directTreeActionMenu[] = { { 1, "directTreeAction menu"}, {'d', "Eliminate this node and everything on its right"} } ; static void directTreeAction (KEY key, int box) { BS bs, bs1 ; Stack s, s1, t ; OBJ obj ; TreeDisp look = currentTreeDisp("directTreeAction") ; #ifdef DEBUG_TREEDISP printf("directTreeAction\n"); #endif if (arrayExists(drawLook->content) && box > 0 && box < arrayMax(drawLook->content)) bs = array (drawLook->content, box, BS) ; else return ; if (!checkWriteAccess ()) return ; if (pickList[class(look->key) & 255 ].protected) { messout ("Sorry, this is a protected class, you cannot update it interactively") ; return ; } if (!class(look->key)) return ; obj = bsUpdate(look->key) ; if (!obj) return ; bsSave(obj) ; switch (key) { case 'd': /* eliminate */ s = stackCreate (64) ; s1 = stackCreate (1024) ; t = stackCreate (64) ; push (s, bs, BS) ; /* but not on t */ bs1 = bs ; bs = bs->up ; while (bs) { if (bs->right == bs1) { push (s, bs, BS) ; push (t, bs, BS) ; } bs1 = bs ; bs = bs->up ; } pushText (s1, messprintf("%s %s\n-D ", className(look->key), freeprotect(name(look->key)))) ; bs = pop (s, BS) ; /* jump root node */ while (!stackAtEnd(s)) { bs = pop (s, BS) ; if (class(bs->key) == _VUserSession) continue ; if (class(bs->key) == _VComment) { catText (s1, " -C ") ; catText (s1, freeprotect (name(bs->key))) ; } else if (class(bs->key)) catText (s1, freeprotect (name(bs->key))) ; else if (bs->key <= _LastC) catText (s1, freeprotect (bsText(bs))) ; else if (bs->key == _Int) catText (s1, messprintf ("%d", bs->n.i)) ; else if (bs->key == _Float) catText (s1, messprintf ("%g", bs->n.f)) ; else if (bs->key == _DateType) catText (s1, timeShow (bs->n.time)) ; else catText (s1, name (bs->key)) ; catText (s1, " ") ; } catText (s1, "\n") ; /* now reestablish upper node to counteract rd's bizare system */ bs = pop (t, BS) ; /* jump root node */ while (!stackAtEnd(t)) { bs = pop (t, BS) ; if (class(bs->key) == _VUserSession) continue ; if (class(bs->key) == _VComment) { catText (s1, " -C ") ; catText (s1, freeprotect (name(bs->key))) ; } else if (class(bs->key)) catText (s1, freeprotect (name(bs->key))) ; else if (bs->key <= _LastC) catText (s1, freeprotect (bsText(bs))) ; else if (bs->key == _Int) catText (s1, messprintf ("%d", bs->n.i)) ; else if (bs->key == _Float) catText (s1, messprintf ("%g", bs->n.f)) ; else if (bs->key == _DateType) catText (s1, timeShow (bs->n.time)) ; else catText (s1, name (bs->key)) ; catText (s1, " ") ; } parseBuffer (stackText (s1,0), 0, TRUE) ; stackDestroy (s) ; stackDestroy (s1); bsDestroy (look->objet) ; look->objet = bsCreateCopy(look->key) ; clearFlags (look->objet->root) ; setOnFlag (look->objet->root) ; lookDraw (look) ; break ; default: break ; } } /*******************************************************************/ static int treeClassComplete (char *cp, int len) { #ifdef DEBUG_TREEDISP printf("treeClassComplete\n"); #endif if (drawLook->classe) return ksetClassComplete (cp, len, drawLook->classe) ; else return 0 ; } static int drawTextEntry (int classe, int len, int x, int y, void (*fn)(char*)) { int box = graphCompScrollEntry (treeClassComplete, stackText(drawLook->textStack, 0), len, 32, x, y, fn) ; #ifdef DEBUG_TREEDISP printf("drawTextEntry\n"); #endif array (drawLook->content, box, BS) = 0 ; if (x + 32 > xmax) /* RD 9402 - 32 chars appear on screen */ xmax = x + 32 ; drawLook->classe = classe ; return y+1 ; } static BOOL dontExpand = FALSE ; char *expandText (KEY key) { KEY tag = pickExpandTag(key) ; OBJ obj ; char *text = 0 ; if (tag && (obj = bsCreate(key))) { if (bsGetKeyTags (obj,tag,0)) text = drawBStext (obj->curr) ; bsDestroy (obj) ; } return text ; } static char* drawBStext (BS bs) { char *text ; if (bs->size & SUBTYPE_FLAG) return name (bs->key - 1) ; else if (bs->size & MODEL_FLAG) return name (bs->key) ; /* use data tag names in model sections */ else if (!dontExpand && (text = expandText(bs->key))) return text ; else if (isModel) return name (bs->key) ; else if (bs->key <= _LastC) return bsText(bs) ; else if (bs->key == _Int) return messprintf ("%d", bs->n.i) ; else if (bs->key == _Float) return messprintf ("%g", bs->n.f) ; else if (bs->key == _DateType) return timeShow (bs->n.time) ; else return name (bs->key) ; } static int countSiblings (BS bs) { int n = 0 ; while (bs) { if (class(bs->key) != _VUserSession) n++ ; bs = bs->down ; } return n ; } static void drawTriangle(BS bs, int x, int y) { int n = countSiblings(bs->right) ; graphColor(BLUE) ; graphText(messprintf(" -----> %d ",n), x, y) ; graphColor(BLACK) ; } static void treeDispTagCount (int cl, BS bs) /* recursive routine */ { while (bs) { if (!class (bs->key) && bs->key >= _Date && !bs->tagCount) { bs->tagCount = bIndexTagCount(cl, bs->key) ; /* -1, dont know in bindex * 0, not evaluated * 1, only in the model itself */ } else return ; if (bs->right) treeDispTagCount (cl, bs->right) ; bs = bs->down ; } } static int drawBS (BS bs, BS bsm, int x, int y) /* recursive routine */ { int yMe = y ; int xPlus = 0 ; char *text, *cp, *vp, *textstr ; int box ; int oldTextFormat = PLAIN_FORMAT ; static int i = 0; #ifdef DEBUG_TREEDISP printf("drawBS\n"); #endif if (i > arrayMax(drawLook->names)) /* reset i after arrays have been reallocated */ i = 0; bsModelMatch (bs, &bsm) ; /* will fail on Time stamp, ignore returned value */ if (bs->size & ATTACH_FLAG && bs->up && bs->up->key == bs->key) goto suite ; /* because, i don t draw the attached node itself */ if (bs->size & ADD_DATA_FLAG) { y = drawTextEntry (0, bs->key < _LastC ? 2500 : 32, x, y, addData) ; } else if (bs->size & ADD_KEY_FLAG) { y = drawTextEntry (class(bs->key), 2500, x, y, addKeyText) ; displayBlock (addKey, "Or you can type in the name.\n" "If you press the TAB key you will autocomplete\n" "or get a list of possible entries.") ; } text = drawBStext (bs) ; yMe = y ; /* must do this here again: y can change */ if (bs->size & EDIT_NODE_FLAG) /* edit entry */ { stackTextForceFeed (drawLook->textStack, strlen(text) + 2560) ; stackClear (drawLook->textStack) ; pushText (drawLook->textStack, text) ; y = drawTextEntry (class(bs->key), strlen(text) + 2500, x, y, editEntry) ; } else if (!drawLook->updateMode && !drawLook->tagChooserMode && class(bs->key) == _VLongText && KEYKEY(bs->key) && iskey(bs->key) == 2) { text = messprintf ("", arrayMax(drawLook->longTextArray)+1) ; graphText (text, x, yMe++) ; if (strlen(text) > xPlus) xPlus = strlen(text) ; array(drawLook->longTextArray, arrayMax(drawLook->longTextArray), BS) = bs ; } else { box = graphBoxStart() ; array (drawLook->content, box, BS) = bs ; vp = (char *)0 + box ; if (bsIsTag(bs)) assInsert(drawLook->tag2box, assVoid(bs->key), vp); textstr = strnew(text, drawLook->handle); array(drawLook->names, i, char*) = textstr; /* store text in names array */ array(drawLook->boxes, i++, int) = box ; /* store corresponding box in boxes array */ assInsert (drawLook->bs2box, bs, vp) ; if ((iskey (bs->key) == 2 && class(bs->key) != _VText) || ((bs->size & ON_FLAG) && bs->right) ) oldTextFormat = graphTextFormat(BOLD) ; if (bs->key == _Greek) oldTextFormat = graphTextFormat(GREEK) ; uLinesText (text, LINE_LENGTH) ; /* Text fields can legitimately be null so to make sure null entries are drawn and * hence selectable for update we insert a token, this relies on drawBStext() * only returning NULL for text fields. */ if (!(cp = uNextLine(text))) cp = "" ; graphText (cp,x,yMe++) ; /* write out this node */ if (strlen(cp) > xPlus) xPlus = strlen(cp) ; while ((cp = uNextLine(text))) /* indent following lines */ { graphText (cp,x+2,yMe++) ; if (strlen(cp)+2 > xPlus) xPlus = strlen(cp)+2 ; } if ((bs->size & ON_FLAG) && bs->right ) drawTriangle(bs, x+xPlus, yMe-1) ; if ((iskey (bs->key) == 2 && class(bs->key) != _VText) || ( (bs->size & ON_FLAG) && bs->right ) || bs->key == _Greek ) graphTextFormat(oldTextFormat) ; graphBoxEnd() ; graphBoxInfo (box, bs->key, 0) ; if (box == drawLook->activebox) colourBox (box, bs, TRUE) ; else colourBox (box, bs, FALSE) ; if (doAttachActionMenu && !drawLook->updateMode && class(bs->key) != _VUserSession) { BS bs1 = drawLook->attachedObjs ? bs : 0 ; while (bs1) { if (bs1->size & ATTACH_FLAG) break ; bs1 = bs1->up ; } if (!bs1) graphBoxFreeMenu (box, directTreeAction, directTreeActionMenu) ; } } suite: xPlus += x ; if (xPlus > xmax) xmax = xPlus ; xPlus += 2 ; /* spacing */ if (bs->size & ADD_COMMENT_FLAG) /* add comment box */ y = drawTextEntry (_VComment, 2500, xPlus, y, addComment) ; else if (!dontExpand && showTimeStamps) { if (bs->timeStamp) { if ( (bs->size & ON_FLAG) && bs->right ) /* if collapsed */ xPlus += 9+2; /* length of drawTriangle()-string+2 */ box = graphBoxStart() ; graphText (name(bs->timeStamp), xPlus, y) ; graphBoxEnd () ; graphBoxDraw (box, BLACK, LIGHTGRAY) ; array (drawLook->content, box, BS) = 0 ; /* do not attach box to a bs */ } y = yMe ; xPlus = x+2 ; /* force vertical layout */ } else if (!dontExpand && showTagCount && bs->tagCount > 0) { if ( (bs->size & ON_FLAG) && bs->right ) /* if collapsed */ xPlus += 9+2; /* length of drawTriangle()-string+2 */ box = graphBoxStart() ; graphText (messprintf ("%d", bs->tagCount - 1), xPlus, y) ; graphBoxEnd () ; graphBoxDraw (box, BLACK, bs->tagCount > 1 ? GREEN : (bs->tagCount == 1 ? RED : ORANGE)) ; array (drawLook->content, box, BS) = 0 ; /* do not attach box to a bs */ y = yMe ; xPlus = x+2 ; /* force vertical layout */ } if (bs->right && !(bs->size & ON_FLAG)) { if ((!doHorizontalLayout) && bsIsTag(bs) && bs->key != _XREF && (bsIsTag(bs->right) || (class (bs->right->key) && countSiblings (bs->right) > 1))) { if (yMe > y) y = yMe ; y = drawBS (bs->right, bsm ? bsModelRight(bsm) : 0, x + 2, y) ; /* below, at x + 2 */ } else y = drawBS (bs->right, bsm ? bsModelRight(bsm) : 0, xPlus, y) ; /* to the right at same y */ } if (!bs->right && !(bs->size & ON_FLAG) && bsm && bsm->right && class (bsm->right->key) == _VQuery && KEYKEY (bsm->right->key) != 1) /* a hack, because treedisp overloads 1 as #type */ { attachQuery (bs, bsm->right->key) ; if (bs->right) y = drawBS (bs->right, bsm ? bsModelRight (bsm) : 0 , xPlus, y) ; /* to the right at same y */ } if (yMe > y) y = yMe ; if (bs->down) y = drawBS (bs->down, bsm, x, y) ; /* below at new y location */ return y ; } /************ table.menu.wrm ************** also see whelp/TreeTableMenu.html for details ********************************************/ static Array class2tableMenu = 0 ; /* this is an Array of Arrays of type FREEOPT, it is indexed by the classNumber. A correct FREEOPT Array is generated for each class for which a table menu entry exists in table.menu.wrm, such an entry looks like this : Person Email_addresses : wquery/e_mail.def Publications : wquery/papers.def Author Co_writers : wquery/writers.def The first word in each block is the class. All tree displays for that class will have a table menu with the items that follow. If the class Person is compiled as class Number 75, the class2tableMenu Array has an entry at index 75, which corresponds to this statis definition static FREEOPT xxx = { { 2, "Person"}, { 1, "Email_addresses : wquery/e_mail.def"}, { 2, "Publications : wquery/papers.def"}, { 0, 0 } }; The last word in each entry is translated to a file name for the saved Table_Maker query. */ static FREEOPT* getTableMenu(int theClassNum) { FILE *f ; int classNum = 0, level ; char *aWord, *theLine ; BOOL inside = FALSE ; int tablesInThisClass = 0; Array tableMenu = 0; char *menufile; if (!class2tableMenu) /* init menus */ { class2tableMenu = arrayCreate(64, Array) ; menufile = dbPathFilName("wspec", "table.menu", "wrm", "r", 0); if (!menufile) return 0; if (!(f = filopen (menufile, "", "r"))) { messfree(menufile); return 0 ; } messfree(menufile); level = freesetfile(f,"") ; while(freecard(level)) { theLine = freepos() ; aWord = freeword() ; if (aWord) { if (!inside) { /* first word is a class name */ if ((classNum = pickWord2Class(aWord))) { inside = TRUE ; tableMenu = array(class2tableMenu, classNum, Array); if (arrayExists(tableMenu)) /* a menu already exists, so append to it. */ tablesInThisClass = arr(tableMenu, 0, FREEOPT).key; else { tableMenu = arrayCreate(3, FREEOPT) ; tablesInThisClass = 0; } } } else { tablesInThisClass++ ; array(tableMenu,tablesInThisClass, FREEOPT).key = tablesInThisClass ; array(tableMenu,tablesInThisClass, FREEOPT).text = strnew(theLine, 0); array(tableMenu,tablesInThisClass+1, FREEOPT).key = 0; array(tableMenu,tablesInThisClass+1, FREEOPT).text = 0; } } else { if (inside) { inside = FALSE ; /* finish off menu for this class */ if (tablesInThisClass > 0) { array(tableMenu, 0, FREEOPT).key = tablesInThisClass; array(tableMenu, 0, FREEOPT).text = strnew(pickClass2Word(classNum), 0); array(class2tableMenu, classNum, Array) = tableMenu; } else arrayDestroy(tableMenu); } } } } /* end init */ if (class2tableMenu) tableMenu = array(class2tableMenu, theClassNum, Array); if (arrayExists(tableMenu)) return arrayp(tableMenu, 0, FREEOPT) ; return NULL ; } /* getTableMenu */ static void tableMenuOpenDisplay (char *menuitem, KEY objKey) { char *fname; SPREAD spread; TABLE *results = 0; Array flipped, widths; char *params; fname = menuitem ; /* last word is file name */ while(*fname) fname++ ; if (menuitem < fname) fname-- ; while(fname > menuitem && *(fname - 1) != ' ') fname-- ; /* the parameter to the table-definition is the object name in quotes, * the definition will refer to the object as %1 * (shown as %%1 in TableMaker) */ params = strnew(freeprotect(name(objKey)), 0); spread = spreadCreateFromFile (fname, "r", params); if (!spread) return; results = spreadCalculateOverWholeClass(spread); flipped = spreadGetFlipInfo(spread); widths = spreadGetWidths(spread); if (results) tableDisplayCreate (results, spreadGetTitle(spread), flipped, widths) ; spreadDestroy (spread); arrayDestroy (flipped); arrayDestroy (widths); messfree (params); return; } /* tableMenuOpenDisplay */ static void tableMenuSelector(void) { FREEOPT* ff = 0 ; KEY k ; TreeDisp look = currentTreeDisp("tableMenuSelector") ; ff = getTableMenu(class(look->key)) ; if (ff && graphSelect(&k, ff)) tableMenuOpenDisplay (freekey2text(k, ff), look->key); return; } /* tableMenuSelector */ static void tableMenuCall(KEY k, int box) { FREEOPT* ff = 0 ; TreeDisp look = currentTreeDisp("tableMenuCall") ; ff = getTableMenu(class(look->key)) ; if (ff) tableMenuOpenDisplay (freekey2text(k, ff), look->key); return; } /* tableMenuCall */ static float drawLongText (float ymax) { int box ; BS bs ; int i, x ; char *cp ; Stack text, path ; if (!arrayMax (drawLook->longTextArray)) return ymax ; path = stackCreate(64) ; for (i = 0; i < arrayMax (drawLook->longTextArray); i++) { bs = arr(drawLook->longTextArray, i, BS) ; if (!(text = stackGet (bs->key))) continue ; /* draw a spacer line */ ymax += 1.0 ; graphLine (0, ymax, 1000, ymax) ; ymax += 1.0 ; /* draw the path to this node */ stackClear (path) ; do { push (path, bs, BS) ; while (bs->up->down == bs) bs = bs->up ; bs = bs->up ; } while (bs->up) ; /* until reach root */ x = 0 ; cp = messprintf ("%d. ", i+1) ; graphText (cp, x, ymax) ; x += strlen(cp) ; while (!stackEmpty (path)) { cp = drawBStext (bs = pop (path, BS)) ; if (stackEmpty (path)) { box = graphBoxStart() ; array (drawLook->content, box, BS) = bs ; graphTextFormat (BOLD) ; } graphText (cp, x, ymax) ; x += strlen(cp) + 1 ; } graphBoxEnd () ; graphTextFormat (PLAIN_FORMAT) ; ymax += 2 ; if (xmax < 12) xmax = 12 ; stackCursor (text, 0) ; while ((cp = stackNextText(text))) { graphText (cp, 1, ymax++) ; if (1 + strlen (cp) > xmax) xmax = 1 + strlen(cp) ; } stackDestroy (text) ; } stackDestroy (path) ; return ymax ; } static void biblioButton (void) { TreeDisp look = currentTreeDisp("biblioButton") ; biblioKey (look->key) ; } static void lookDraw (TreeDisp look) { FREEOPT* ff = 0 ; int box ; float x1, y1, x2, y2 ; #ifdef DEBUG_TREEDISP printf("lookDraw\n"); #endif graphActivate (look->graph); if (look->activebox) graphBoxDim (look->activebox, &x1, &y1, &x2, &y2); else y1 = -1; graphClear () ; arrayMax (look->content) = 0 ; assClear (look->bs2box) ; assClear (look->tag2box); handleDestroy(look->handle); /* Clear boxpointers array & ** associated linked lists. */ look->handle = handleCreate(); /* Recreate everything */ look->names = arrayHandleCreate(32, char*, look->handle); look->boxes = arrayHandleCreate(32, int , look->handle); look->tagWarp = NULL; box = graphBoxStart() ; array (look->content, box, BS) = look->objet->root ; graphText (name(look->key),0,0) ; graphBoxEnd () ; isModel = class(look->key) == _VModel || !KEYKEY(look->key) ? TRUE : FALSE ; if (showTagCount) { int cl = isModel ? pickWord2Class (name(look->key)+1) : class (look->key) ; if (cl) treeDispTagCount (cl, look->objet->root->right) ; } if (look->objet->root && look->objet->root->right) { drawLook = look ; drawLook->textStack = stackReCreate (drawLook->textStack, 2600) ; drawLook->longTextArray = arrayReCreate (drawLook->longTextArray, 8, BS); xmax = 0 ; dontExpand = look->updateMode || look->tagChooserMode ; ymax = drawBS (look->objet->root->right, bsModelRoot(look->objet)->right, 2, 4) ; ymax = drawLongText (ymax) ; /* show any long text that occurred */ } else { xmax = 1 ; ymax = 2 ; /* for buttons */ } #if !defined(macintosh) graphButton("Quit", graphDestroy, 60-1 - 5, .4) ; if (xmax < 60) xmax = 60 ; #endif if (!look->updateMode && !look->tagChooserMode) { box = graphButton ("Attach...", attach, 60-1 - 11 - 5, .4) ; if (xmax < 52) xmax = 52 ; graphBoxMenu (box, attachMenu) ; if ((ff = getTableMenu(class(look->key)))) { box = graphButton("Tables...", tableMenuSelector, 60-1 - 10, 3.4) ; if (xmax < 40) xmax = 40 ; graphBoxFreeMenu(box, tableMenuCall, ff) ; } if (biblioKeyPossible(look->key)) graphButton("Biblio", biblioButton, 60-1 - 8 - 11 - 5, .4) ; } graphTextBounds (xmax,ymax) ; if (y1 > 0) graphGoto (x1, y1) ; graphRedraw() ; displaySetSizeFromWindow ("TREE") ; if (look->activebox >= arrayMax(look->content)) look->activebox = 0 ; isModel = FALSE ; } /************************************************************/ /************************************************************/ /* Updating: The plan is to build a new tree containing the union of the model and the current object. We will rebuild every time an action changes the structure. Parts of the model not yet represented in the object will be coloured blue. The XREF, UNIQUE etc bits of the model will not be shown. Class slots (?Class) will appear in cyan at the bottom of each column of objects, so you can add new members (or replace if unique). slots (#Class) will not be shown. If you double-click on a cyan tag it will be added to your object. If you double click on a (cyan) ?Class then you will be prompted for a name, but you can also pick something from that class elsewhere in the system. If you give a name that is a new lex entry it will ask for confirmation before adding. When displayBlocked you can not display any new objects, nor get the biblio etc. We will use bs->size to hold the information about whether an entry is in the model or not. Adding and subtracting the model is done by two routines bsFuseModel() in bssubs.c and defuse() below. Codes for ->size are: 1 part of model (painted cyan) 2 unique part of original tree 4 fake node to add data (TextEntry) 8 fake node to add comment (TextEntry) 16 fake node to add key (TextEntry and DisplayBlock) 32 edit current node - rather than enter a new one 64 special node to add Type (TextEntry and DisplayBlock) all from 4 on are in fact mutually exclusive - there should be at most one at any one time (set to bsWait). 128 ON_FLAG to contract/uncontract 256 ATTACH_FLAG 512 new node this session We disable the keyboard routine (used for data entry), and enable a new pick routine. The menu should change to contain Save (returns to standard form), Delete, and Add comment. We should replace Save in the standard menu by Update. Perhaps the standard menu could also contain Add comment? */ static void fixPath (TreeDisp look, BS bs) ; extern void bsSubFuseType(OBJ obj, BS bs) ; static void updatePick (int box, double x_unused, double y_unused, int modifier_unused) { BS bs ; TreeDisp look = currentTreeDisp("updatePick") ; if (autoPreserve) displayPreserve () ; if (!box) { colourActive (look, FALSE) ; look->activebox = 0 ; return ; } else if (box >= arrayMax (look->content)) { messerror("updatePick received a wrong box number %d",box); return ; } if (box == look->activebox) /* a second hit - follow it */ { bs = arr(look->content,look->activebox,BS) ; if (!bs) /* not a bs box, e.g. inside a textEntry */ return ; if (bs->size & ON_FLAG) /* if hidden, show branch */ { bs->size ^= ON_FLAG ; lookDraw(look) ; } else if (look->bsWait) messout ("Finish what you are doing first (or cancel)") ; else if (isDisplayBlocked()) /* from different update window */ messout ("First deal with the other object you are waiting on") ; else if (bsIsTag(bs)) { if (bs->size & MODEL_FLAG) fixPath (look, bs) ; else { bs->size |= ON_FLAG ; lookDraw(look) ; } } else if (!(bs->size & MODEL_FLAG)) /* in original object */ { look->bsWait = bs ; bs->size |= EDIT_NODE_FLAG ; /* edit flag */ lookDraw (look) ; } else if (bsIsTag(bs->up) || !(bs->up->size & MODEL_FLAG)) { if (bs->size & SUBTYPE_FLAG) /* a type */ { bsSubFuseType (look->objet, bs) ; lookDraw (look) ; } else { look->bsWait = bs ; if (class(bs->key)) bs->size |= ADD_KEY_FLAG ; /* displayBlock() + textEntry */ else bs->size |= ADD_DATA_FLAG ; /* textEntry only */ lookDraw (look) ; } } else messout ("Sorry - you must be next to a tag " "or an existing part of the object") ; } else /* change highlighted entry */ { colourActive (look, FALSE) ; look->activebox = box ; bs = arr(look->content,box,BS) ; if (!bs) /* not a bs box */ look->activebox = 0 ; colourActive (look, TRUE) ; if (bs) graphPostBuffer (drawBStext (bs)) ; } } static void updateKbd (int k, int modifier_unused) { char *vp ; BS bs ; int box ; TreeDisp look = currentTreeDisp("updateKbd") ; /* Find: enable Control/B to activate FindObject (backward)*/ if (k == B_KEY && (modifier_unused & CNTL_MODKEY)) FindObject(look, FIND_BACKWARD); /* Find: enable Control/F to activate FindObject (forward)*/ if (k == F_KEY && (modifier_unused & CNTL_MODKEY)) FindObject(look, FIND_FORWARD); /* Find Next: enable Control/RightChevron to activate FindBox (forward)*/ if (k == DOT_KEY && (modifier_unused & CNTL_MODKEY)) FindBox(look, FIND_FORWARD, UNDEFINED); /* Find Previous: enable Control/LeftChevron to activate FindBox (backward)*/ if (k == COMMA_KEY && (modifier_unused & CNTL_MODKEY)) FindBox(look, FIND_BACKWARD, UNDEFINED); /* TreeDisplay Mode: enable Control/T to switch to TreeDisplay Mode */ if (k == T_KEY && (modifier_unused & CNTL_MODKEY)) { graphDestroy(); treeDisplay(look->key, 0, FALSE, 0); look->activebox = 0; } /* Save: enable Control/U to save (ie Update the db) what's been done so far */ /* given that ^s has been used by XWindows and ^v is the universal Paste.... */ if (k == U_KEY && (modifier_unused & CNTL_MODKEY)) updateSave(look, 0); if (!look->activebox) return ; if (!(bs = arr(look->content,look->activebox,BS))) return ; switch (k) { case RETURN_KEY: updatePick (look->activebox, 0, 0, 0) ; break ; case LEFT_KEY : while (bs->up->down == bs) bs = bs->up ; if (bs->up->up) bs = bs->up ; break ; case RIGHT_KEY : if (bs->right) bs = bs->right ; break ; case DOWN_KEY : if (bs->down) bs = bs->down ; else while (bs->up->down == bs) bs = bs->up ; break ; case UP_KEY : if (bs->up->down == bs) bs = bs->up ; else while (bs->down) bs = bs->down ; break ; case '\t': case SPACE_KEY : if (bs->right) bs = bs->right ; else if (bs->down) bs = bs->down ; else while (bs->up->up) { while (bs->up->down == bs) bs = bs->up ; if (!bs->up->up) break ; bs = bs->up ; if (bs->down) { bs = bs->down ; break ; } } break ; default: return ; } if (assFind (look->bs2box, bs, &vp) && (box = vp - (char *) 0) && box != look->activebox) updatePick (box, 0, 0, 0) ; } /********************************/ static void defuse (BS bs) /* recursive */ { BS temp ; bs->size = (bs->size & (NEW_FLAG | ON_FLAG)) ; while (bs->right && (bs->right->size & MODEL_FLAG)) { temp = bs->right ; bs->right = bs->right->down ; if (bs->right) bs->right->up = bs ; temp->up = 0 ; temp->down = 0 ; bsTreePrune (temp) ; } if (bs->right) defuse (bs->right) ; while (bs->down && (bs->down->size & MODEL_FLAG)) { temp = bs->down ; bs->down = bs->down->down ; if (bs->down) bs->down->up = bs ; temp->up = 0 ; temp->down = 0 ; bsTreePrune (temp) ; } if (bs->down) defuse (bs->down) ; } /***********************************/ static void fixPath (TreeDisp look, BS bs) { BS start = bs ; while (bs->size & MODEL_FLAG) /* code for belonging to model */ { bs->size &= ~MODEL_FLAG ; bs->size |= NEW_FLAG ; bs->timeStamp = sessionUserKey() ; while (bs->up && bs->up->down == bs) /* go to top of column */ bs = bs->up ; if (bs->up) /* go to entry in previous column */ bs = bs->up ; } defuse (look->objet->root) ; /* leaves new stuff */ bs = start ; for (bs = start ; bs->up ; bs = bs->up) { if (bs->bt && bs->bt->bsm && bs->bt->bsm->n.key & UNIQUE_BIT) /* UNIQUE */ { while (bs->down) { look->objet->curr = bs->down ; look->objet->modCurr = bs->bt->bsm ; bsRemove (look->objet) ; } while (bs->up->down == bs && !bsIsComment(bs->up)) { look->objet->curr = bs->up ; look->objet->modCurr = bs->bt->bsm ; bsRemove (look->objet) ; } } while (bs->up->down == bs) /* go to top of column */ bs = bs->up ; } bsFuseModel (look->objet) ; setOnFlagModelOnly (look->objet->root) ; look->objet->flag |= OBJFLAG_TOUCHED ; lookDraw (look) ; } /*********************************/ static void addComment (char* text) { BS bs ; TreeDisp look = currentTreeDisp("addComment") ; if (!look->bsWait) messcrash ("screwup in addComment - no bs to add to") ; look->objet->curr = look->bsWait ; /* point to add to */ bsAddComment (look->objet, text, 'C') ; look->bsWait->size &= ~ADD_COMMENT_FLAG ; bs = look->bsWait ; look->bsWait = 0 ; fixPath (look, bs) ; } static void addKey (KEY key) { KEY old ; BS bs, bsm ; OBJ obj ; TreeDisp look = currentTreeDisp("addKey") ; #ifdef DEBUG_TREEDISP printf("addKey\n"); #endif if (!key) /* cancellation */ return ; if (!(bs = look->bsWait)) messcrash ("screwup in addKey - bs is missing") ; /* mieg, modified, sept 12.93 ***/ if (bsIsComment (bs)) /* no matching bsm */ { if (class(key) != class(bs->key)) { messout ("Sorry, that is not a comment you selected") ; return ; } else bs->key = key ; } else { old = bs->key ; bs->key = key ; /* try to enter the key */ if (!bs->bt || !(bsm = bs->bt->bsm)) messcrash ("screwup in addKey - bsm is missing") ; if (!bsModelMatch (bs, &bsm)) { messout ("Sorry - that key is not in the right class") ; bs->key = old ; return ; } } /* X-ref */ if (pickXref(class(key)) || KEYKEY(bsm->n.key)) { obj = look->objet ; if (!obj->xref) obj->xref = stackCreate (64) ; push (obj->xref, key, KEY) ; if (pickXref(class(key))) push (obj->xref, (_Quoted_in), KEY) ; else push (obj->xref, KEYKEY(bsm->n.key), KEY) ; } bs->size |= NEW_FLAG ; bs->timeStamp = sessionUserKey() ; bs->size &= ~(ADD_KEY_FLAG | EDIT_NODE_FLAG) ; /* must do this since display block is freed on return */ ++look->activebox ; look->bsWait = 0 ; fixPath (look, bs) ; /* clear "model" flags and redisplay */ } static void addKeyText (char *text) { BS bs ; KEY key ; int table ; TreeDisp look = currentTreeDisp("addKeyText") ; #ifdef DEBUG_TREEDISP printf("addKeyText\n"); #endif if (!(bs = look->bsWait)) messcrash ("Screwup in addKeyText - bs missing") ; table = class(bs->key) ; if (!*text || *text == '*' || !lexIsGoodName(text)) { while(*text) *text++ = 0 ; /* necessary to set totally to 0 for graphTextEntry */ graphRedraw() ; return ; } if (!lexword2key(text,&key,table)) { if (table != _VText && !messQuery (messprintf("Unknown name - do you want to create a new %s called:%s", className(KEYMAKE(table,0)), text))) { #ifdef WHY_IS_THIS_NEEDED___SHOULDNT_WE_LEAVE_TEXT_TO_CORRECT_TYPO while(*text) *text++ = 0 ; /* necessary to set totally to 0 for graphTextEntry */ graphRedraw() ; #endif return ; } else lexaddkey (text,&key,table) ; } display (key,0,0) ; /* calls addKey via displayBlock() */ } static void addData (char* text) { BS bs ; int i ; float f ; mytime_t tm ; TreeDisp look = currentTreeDisp("addData") ; #ifdef DEBUG_TREEDISP printf("addData\n"); #endif if (!(bs = look->bsWait)) messcrash ("screwup in addData - no bs to add to") ; if (bs->key < _LastC) { bs->bt->cp = (char*) messalloc (strlen (text) + 1) ; strcpy (bs->bt->cp, text) ; } else if (bs->key == _Int) { if (sscanf (text,"%d",&i)) bs->n.i = i ; else { messout ("Sorry - not a good integer") ; return ; } } else if (bs->key == _Float) { if (sscanf (text,"%f",&f)) bs->n.f = f ; else { messout ("Sorry - not a good floating point number") ; return ; } } else if (bs->key == _DateType) { if ((tm = timeParse (text))) bs->n.time = tm ; else { messout ("Sorry - not a good date") ; return ; } } else messcrash ("Bad data type %d in treedisp addData",bs->key) ; bs->size |= NEW_FLAG ; bs->timeStamp = sessionUserKey() ; bs->size &= ~(ADD_DATA_FLAG | EDIT_NODE_FLAG) ; /* only get here if we added OK */ look->bsWait = 0 ; ++look->activebox ; fixPath (look, bs) ; graphRegister(KEYBOARD,updateKbd); } /*******************/ static void editorOK(void *editor, void *data) { TreeDisp look = (TreeDisp)data; char *newText = gexEditorGetText(editor, 0); graphActivate(look->graph); /* for editEntry */ editEntry(newText); /* look->bsWait zeroed by addKey via editEntry */ messfree(newText); messfree(editor); look->editor = 0; } static void editorCancel(void *editor, void *data) { TreeDisp look = (TreeDisp)data; look->bsWait = 0; messfree(editor); look->editor = 0; return ; } static void editEntry (char *text) { BS bs, bsm=0 ; KEY key ; OBJ obj ; int table ; TreeDisp look = currentTreeDisp("editEntry") ; /* This test means that it is not possible to enter either a null string or blanks * (that's what lexIsGoodName tests basically). */ if (!*text || !lexIsGoodName(text)) { while(*text) *text++ = 0 ; /* necessary to set totally to 0 for graphTextEntry */ graphRedraw() ; return ; } if (!(bs = look->bsWait)) messcrash ("screwup in editEntry - no bs to edit") ; if (bs->key < _LastN) addData (text) ; else { table = class(bs->key) ; if (!lexword2key(text,&key,table)) { if (!strcmp(text,"*") || !strcmp(text,"?")) { messout("Please do not use ? or * as the name of an object, it would confuse subsequent searches") ; return ; } else if (!pickXref(table) && !messQuery ("Unknown name - do you want to create a new object? ")) return ; else lexaddkey (text,&key,table) ; } if (pickXref(table)) /* especially comments! */ { obj = look->objet ; if (!obj->xref) obj->xref = stackCreate (64) ; push (obj->xref, bs->key, KEY) ; push (obj->xref, (_Quoted_in | DELETE_BIT), KEY) ; } else { if (!bs->bt || !(bsm = bs->bt->bsm)) messcrash ("screwup in editEntry - bsm is missing") ; if (KEYKEY(bsm->n.key)) /* must delete XREF */ { obj = look->objet ; if (!obj->xref) obj->xref = stackCreate (64) ; push (obj->xref, bs->key, KEY) ; push (obj->xref, (KEYKEY(bsm->n.key) | DELETE_BIT), KEY) ; } } addKey (key) ; } graphRegister(KEYBOARD, updateKbd); return ; } /* editEntry */ /************************************************************/ /************************************************************/ static void chooseTagCancel(void) { TreeDisp look = currentTreeDisp("chooseTagCancel") ; if (look->bsWait) return ; graphLoopReturn(FALSE) ; } static void choosePick(int box, double x_unused, double y_unused, int modifier_unused) { BS bs ; TreeDisp look = currentTreeDisp("choosePick") ; if (look->bsWait) return ; if (!box) return ; else if (box >= arrayMax (look->content)) { messerror("updatePick received a wrong box number %d",box); return ; } if (box == look->activebox) /* a second hit - follow it */ { bs = arr(look->content,look->activebox,BS) ; if (!bs) /* not a bs box, e.g. inside a textEntry */ return ; if (bs == look->objet->root) return ; if (isDisplayBlocked()) /* from different update window */ { messout ("First deal with the other object you are waiting on") ; return ; } if (bs->size & SUBTYPE_FLAG) /* a type */ { bsSubFuseType (look->objet, bs) ; lookDraw (look) ; } else { while (bs->size & MODEL_FLAG) /* as in fixPath, but no UNIQUE problems */ { bs->size &= ~MODEL_FLAG ; while (bs->up && bs->up->down == bs) bs = bs->up ; if (bs->up) bs = bs->up ; } graphLoopReturn(TRUE) ; } } else /* change highlighted entry */ { colourActive (look, FALSE) ; look->activebox = box ; bs = arr(look->content,box,BS) ; if (!bs) /* not a bs box */ look->activebox = 0 ; colourActive (look, TRUE) ; } return; } /* choosePick */ /************/ static MENUOPT chooseMenu[] = { { chooseTagCancel, "Cancel" }, { help, "Help" }, { graphPrint, "Print" }, { 0, 0 } } ; /************/ static TreeDisp chooseCreate (KEY key, OBJ objet, Graph oldGraph) { TreeDisp look=(TreeDisp)messalloc(sizeof(struct TreeDispStruct)) ; look->graph = displayCreate ("TREE"); graphSetBlockMode(GRAPH_BLOCKING) ; /* User must answer tree choose dialog. */ graphRetitle (messprintf("Tag chooser: Class %s", className(key))) ; graphRegister (DESTROY, lookDestroy) ; graphRegister (PICK, choosePick) ; graphMenu (chooseMenu) ; graphHelp("Tag-chooser") ; look->magic = &TreeDisp_MAGIC; look->key = key; look->objet = objet ; look->tagChooserMode = TRUE ; graphAssociate (GRAPH2TreeDisp_ASSOC, look) ; look->content = arrayReCreate(look->content, 32,BS); look->bs2box = assReCreate (look->bs2box) ; look->tag2box = assReCreate (look->tag2box); if (look->textStack) stackDestroy (look->textStack) ; lookDraw (look) ; graphButtons (chooseMenu, 10, 1, 55) ; if (xmax < 65) graphTextBounds (65, ymax) ; return look ; } /* chooseCreate */ /************/ BOOL treeChooseTagFromModel(int *type, int *targetClass, int classe, KEY *tagp, Stack s, BOOL continuation) { Graph oldGraph = graphActive() ; Stack s1 ; Array toto ; BOOL lastTag = FALSE, contNeeded = FALSE ; TreeDisp look ; KEY key ; int i ; OBJ obj ; BS bs ; char *cp ; lexaddkey("Tag-chooser", &key, classe) ; obj = bsUpdate (key) ; while (bsGetKeyTags (obj, _bsRight, 0)) bsRemove (obj) ; cacheMark (obj->x) ; bsFuseModel (obj) ; look = chooseCreate(key, obj, 0) ; /* setOnFlag (look->objet->root) ; */ if (!look) { bsKill(obj) ; return FALSE ; } messStatus ("Please choose a Tag") ; if (!graphLoop(TRUE)) /* blocking loop was cancelled */ { if (graphActivate(look->graph)) { /* if we got here by graphLoopReturn(FALSE), which is called * by the cancel button, our graph is still there, * and we have to kill it ourselves.. */ look->objet = 0 ; /* no silly questions in graphDestroy */ look->tagChooserMode = FALSE ; graphDestroy(); } /* Otherwise we got here, because the window was graphDestroy'ed * which implies the graphLoopReturn(FALSE), but we've already * done everything we need to do, i.e. lookDestroy etc. */ if (obj->magic) bsKill(obj) ; graphActivate(oldGraph) ; graphPop() ; return FALSE ; } /* blocking loop returned TRUE */ look->objet = 0 ; /* no silly questions in graphDestroy */ look->tagChooserMode = FALSE ; if (graphActivate(look->graph)) graphDestroy(); defuse(obj->root) ; bs= obj->root ; lastTag = FALSE ; s1 = stackCreate(32) ; while (bs->right) bs = bs->right ; if (!bs->up) messcrash("No bs->up in treeChooseTagFromModel") ; if (bsIsTag(bs)) { *type = 'b' ; lastTag = TRUE ; *tagp = bs->key ; pushText (s1, name(bs->key)) ; } else if (bs->key == _Text) *type = 't' ; else if (bs->key == _Int) *type = 'i' ; else if (bs->key == _Float) *type = 'f' ; else if (bs->key == _DateType) *type = 'd' ; else if (class(bs->key)) { *type = 'k' ; *targetClass = class(bs->key) ; } while (bs = bs->up , bs->up) { if (bsIsTag(bs)) { if (!lastTag) pushText (s1, name(bs->key)) ; *tagp = bs->key ; lastTag = TRUE ; } else { if (lastTag) pushText (s1, " HERE # ") ; else pushText (s1, " HERE ") ; contNeeded = TRUE ; break ; } } stackCursor(s1, 0) ; toto = arrayCreate(32, char*) ; i = 0 ; while ((cp = stackNextText(s1))) array(toto, i++, char*) = cp ; if (i--) pushText(s, arr(toto,i,char*)) ; while(i--) catText(s, arr(toto,i,char*)) ; stackDestroy(s1) ; arrayDestroy(toto) ; bsKill(obj) ; if (contNeeded && ! continuation) { messout ("Except from a derived column of the table maker, " "You must choose a rooted tag") ; return FALSE ; } graphActivate(oldGraph) ; graphPop() ; return TRUE ; } /* treeChooseTagFromModel */ /*********************************************************/ static void treeDispMailer (KEY key) /* called by clicking on the special action tag 'E-mail' */ { OBJ obj = bsCreate(key), obj1 ; ACEIN addr_in = NULL; if (!obj) return ; if (!bsGetData (obj, _E_mail, _Text, 0)) { addr_in = messPrompt ("Please specify an email address", "", "w", 0); if (!addr_in) goto fin; if (!(obj1 = bsUpdate (key))) goto fin ; if (!bsAddData (obj1, _E_mail, _Text, aceInWord(addr_in))) { bsDestroy (obj1) ; messout ("Sorry, I can't save your address") ; goto fin ; } bsSave (obj1) ; } acedbMailer (key, 0, 0) ; fin: bsDestroy (obj); if (addr_in) aceInDestroy (addr_in); } /* At present, the Find function only scans the tree as it is currently ** displayed. It doesn't expand any nodes which are folded away. ** ** Broad functionality: Control/F or menu to open a dialogue box where ** you enter the string you're looking for. Or you can click an object ** and Next/Prev to others. Next/Prev looks for the next/prev occurrence ** of the current string. At either the end of the tree you get a message ** but if you Next/Prev again the search wraps. ** ** Leading and trailing wildcards are supported, though Lead* processing is ** simplistic and could be improved. */ static void FindObject(TreeDisp look, AceFindDirection direction) { ACEIN name_in; AceWild wildcard = NEITHER; char *pattern; /* used in stripping out wildcards */ /* messPrompt displays whatever is in param2 as the default */ if ((name_in = messPrompt("Find", look->pattern, "w", 0))) { if (look->tagWarp) messfree(look->tagWarp); look->keyClicked = FIND; look->tagWarp = strnew(aceInWord(name_in), 0); look->pattern = strnew(look->tagWarp,0); /* save for next time the prompt box shows */ /* strip out wildcards */ if (look->tagWarp[0] == '*') { wildcard = LEADING; pattern = strnew(look->tagWarp+1,0); strcpy(look->tagWarp, pattern); } if (look->tagWarp[strlen(look->tagWarp)-1] == '*') { look->tagWarp[strlen(look->tagWarp)-1] = '\0'; if (wildcard == LEADING) wildcard = BOTH; else wildcard = TRAILING; } if (strstr(look->tagWarp,"*")) { messout("* not permitted embedded in search pattern"); return; } /* find the box in the tree display */ if (!FindBox(look, direction, wildcard)) messout("Can't find object %s", look->pattern); aceInDestroy(name_in); } return; } BOOL FindBox(TreeDisp look, AceFindDirection direction, AceWild wildcard) { float x1, y1; /* screen coordinates of box */ static char *text; /* text we seek */ static BOOL AtEnd = FALSE; /* whether we're at the end of the list */ static int PrevDir = FIND_BACKWARD; /* search direction last time we were here */ static AceWild PrevWild = NEITHER; /* the nature of any wildcard */ int box = -1; static int PrevBox = 0; int ClickedBox = 0; /* if user clicks box & Next/Prev */ static int i = 0; /* remembers where you are in the arrays */ char *ActiveText; /* the text from the current box */ /* if there's no active box and no tagWarp (ie find target) ** then we don't know what we seek, so mak that plain. */ if ( !look->tagWarp && look->activebox == 0 ) messout("Please use Find before trying Next/Previous"); else { /* if the user has clicked a box and hit Next/Prev, ** then we'llhave an activebox, but no tagWarp. */ if (look->keyClicked == SELECT && look->activebox && look->activebox != PrevBox) { arrayFind(look->boxes, &look->activebox, &i, intOrder); /* must be there! */ text = strnew(array(look->names,i,char*),0); /* store the text of active box */ ClickedBox = look->activebox; /* skip the one you're actually on */ look->pattern = strnew(text,0); /* store search string for msg box */ AtEnd = FALSE; wildcard = NEITHER; } /* if user clicked a box and is now Next/Prev'ing, text will be set */ else if (!text || look->keyClicked == FIND) { text = strnew(look->tagWarp,0); AtEnd = TRUE; /* force it to start again */ } if (wildcard == UNDEFINED) wildcard = PrevWild; PrevWild = wildcard; /* if we're looking again for the same thing ** and we're not at the end of the array... */ if ((text && *text && look->tagWarp && strcasecmp(text,look->tagWarp)==0) && !AtEnd) { if (direction == FIND_FORWARD) { for (; i < arrayMax(look->names); i++) /* i should already be set */ if (examineElement(arr(look->names,i,char*), text, wildcard)) { box = arr(look->boxes, i, int); if (box != PrevBox) { PrevBox = box; break; } } if (i >= arrayMax(look->names)) { messout("That was the last %s", look->pattern); AtEnd = TRUE; } } else { for (; i >= 0; i--) /* i should already be set */ if (examineElement(arr(look->names,i,char*), text, wildcard)) { box = arr(look->boxes, i, int); if (box != PrevBox) { PrevBox = box; break; } } if (i <= 0) { messout("That was the first %s", look->pattern); AtEnd = TRUE; } } } else /* either we have a new target or we're at the end of the array */ { if (look->tagWarp) messfree(look->tagWarp); look->tagWarp = strnew(text,0); /* store target in look */ if (direction == FIND_FORWARD) { for (i = 0; i < arrayMax(look->names); i++) { if (look->activebox && look->activebox == arr(look->boxes, i, int)) ActiveText = arr(look->names, i, char*); if (examineElement(arr(look->names,i,char*), text, wildcard)) { box = arr(look->boxes, i, int); if (box != PrevBox && box != ClickedBox) { PrevBox = box; break; } else box = -1; } } if (i >= arrayMax(look->names)) { if (look->activebox && ActiveText && box == -1) { if (strcmp(ActiveText, text)==0) messout("That was the only %s", look->pattern); else messout("Can't find %s", look->pattern); AtEnd = TRUE; } } else AtEnd = FALSE; } else { for (i = arrayMax(look->names)-1; i >= 0; i--) { if (look->activebox && look->activebox == arr(look->boxes, i, int)) ActiveText = arr(look->names, i, char*); if (examineElement(arr(look->names,i,char*), text, wildcard)) { box = arr(look->boxes, i, int); if (box != PrevBox && box != ClickedBox) { PrevBox = box; break; } else box = -1; } } if (i <= 0) { if (look->activebox && ActiveText && box == -1) { if (strcmp(ActiveText, text)==0) messout("That was the only %s", look->pattern); else messout("Can't find %s", look->pattern); AtEnd = TRUE; } } else AtEnd = FALSE; } } PrevDir = direction; /* remember which direction we're going */ if (box >= 0) { graphBoxDim(box, &x1, &y1, NULL, NULL); graphGoto(x1, y1); colourActive(look, FALSE); /* unhighlight box */ look->activebox = 0; /* change selected box */ lookPick(box, 0, 0, 0); return TRUE; } } return FALSE; } BOOL examineElement(char *element, char *pattern, AceWild wildcard ) { if (wildcard == LEADING) { if (strcasestr(element, pattern)) return TRUE; } else if (wildcard == TRAILING) { if (g_strncasecmp(element, pattern, strlen(pattern)) ==0) return TRUE; } else if (strcasecmp(element,pattern) == 0) return TRUE; return FALSE; } static void revertDisplayToDefault(TreeDisp look) /* does what it says on the tin */ { look->activebox = 0; clearFlags(look->objet->root); setOnFlag(look->objet->root); lookDraw(look); return; } /* lifted straight from fmaposp.c */ static int intOrder(void *a, void *b) { return (int) ( (*(int *) a) - (*(int *) b) ) ; } /* intOrder */ /* treeUpdate Save function moved here so it can be called directly by keyboard shortcut */ static void updateSave(TreeDisp look, BitSet bb) { defuse (look->objet->root) ; if (!bbSetArray) /* mieg, inertia in open tags */ bbSetArray = arrayCreate (256, BitSet) ; bb = array(bbSetArray, class(look->key), BitSet) ; if (!bb) bb = array(bbSetArray, class(look->key), BitSet) = bitSetCreate(lexMax(0),0) ; getOnFlagSet (look->objet->root, bb) ; bsSave (look->objet) ; look->objet = bsCreateCopy (look->key) ; /* If the object exists only in the cache, and is empty, it will evaporate, if so put it back there (see displayKey in newkey.c) */ if (!look->objet) { OBJ obj = bsUpdate(look->key) ; /* to give it a cache entry */ bsDestroy (obj) ; look->objet = bsCreateCopy (look->key) ; } clearFlags (look->objet->root) ; setOnFlagSet (look->objet->root, bb) ; look->activebox = 0 ; look->updateMode = FALSE ; lookDraw (look) ; graphRegister (PICK, lookPick) ; graphRegister (KEYBOARD, lookKbd) ; graphFreeMenu (lookMenu, treeMenu) ; graphHelp ("Tree") ; return; } /************************ end of file ********************/