/* 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: Aug 22 10:30 2002 (edgrif) * * 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.136 2002/08/22 10:28:55 edgrif 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 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 ; /* mapping from tags to lines on the display */ 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; } *TreeDisp ; static Array bbSetArray = 0 ; static BOOL autoPreserve = TRUE ; static magic_t TreeDisp_MAGIC = "TreeDisp_MAGIC"; static magic_t GRAPH2TreeDisp_ASSOC = "TreeDisp_ASSOC"; static FREEOPT treeMenu[] = { { 12,"Tree display" }, { 99,"Quit" }, { 98,"Help"} , { 13,"Print"} , { 0,""}, { 9,"Update"} , { 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 */ { 17,"Toggle Timestamps"} , { 41,"Find Tag"}, { 5,"Show as Text"} } ; static FREEOPT updateMenu[] = { { 11,"Tree Edit"} , { 99,"Quit"} , { 98,"Help"} , { 0,""}, { 3,"Save"} , { 7,"Add Comment"} , { 10,"Cancel"} , { 8,"Delete"} , { 11,"Edit"} , { 25,"Contract"} , { 26,"Expand"}, { 41,"Find Tag"}, } ; 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) ; static void lookKbd (int k) ; static void lookDraw (TreeDisp look); static void updatePick (int k) ; static void updateKbd (int k) ; 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 BOOL showTimeStamps = FALSE ; static int doAttachActionMenu; static int doHorizontalLayout; #define LINE_LENGTH 40 /**************************************************************/ static TreeDisp currentTreeDisp (char *caller) { TreeDisp tree; Graph g = graphActive(); 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)); 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->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") ; 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) ; assDestroy (look->tag2box) ; 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) { BS bs ; TreeDisp look = currentTreeDisp("lookPick") ; /* 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 ; colourActive (look, TRUE) ; } } /*****************/ static void lookKbd (int k) { char *vp ; BS bs ; int box ; int nn = 3 ; TreeDisp look = currentTreeDisp("lookKbd") ; if (isDisplayBlocked()) return ; if (autoPreserve) displayPreserve () ; 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; 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)); } 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 ; /* 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 ; } 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") ; 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 */ 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 tag */ { ACEIN tag_in; KEY tag; void *vp; if (!(tag_in = messPrompt("Warp to tag", look->tagWarp, "w", 0))) break; if (look->tagWarp) messfree(look->tagWarp); look->tagWarp = strnew(aceInWord(tag_in), 0); if (lexword2key(look->tagWarp, &tag, 0)) { if (assFind(look->tag2box, assVoid(tag), &vp)) { float x1, y1; graphBoxDim(assInt(vp), &x1, &y1, NULL, NULL); graphGoto(x1, y1); look->activebox = 0; lookPick(assInt(vp)); } else { setAllOnFlags(look->objet->root); look->activebox = 0; lookDraw(look); if (assFind(look->tag2box, assVoid(tag), &vp)) { float x1, y1; graphBoxDim(assInt(vp), &x1, &y1, NULL, NULL); graphGoto(x1, y1); lookPick(assInt(vp)); } } } aceInDestroy(tag_in); 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); 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 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 ; #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) { 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 ; 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") ; 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 ; 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") ; 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") ; attach1(look->activebox, FALSE) ; } static void attachRecursively (void) { TreeDisp look = currentTreeDisp("attachRecursivelly") ; 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") ; 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) { 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) ; 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 int drawBS (BS bs, BS bsm, int x, int y) /* recursive routine */ { int yMe = y ; int xPlus = 0 ; char *text, *cp, *vp ; int box ; int oldTextFormat = PLAIN_FORMAT ; 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 ? 250 : 32, x, y, addData) ; } else if (bs->size & ADD_KEY_FLAG) { y = drawTextEntry (class(bs->key), 250, 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) + 256) ; stackClear (drawLook->textStack) ; pushText (drawLook->textStack, text) ; y = drawTextEntry (class(bs->key), strlen(text) + 250, 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); 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, 250, 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 */ } 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 ; 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); 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 (look->objet->root && look->objet->root->right) { drawLook = look ; drawLook->textStack = stackReCreate (drawLook->textStack, 256) ; 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) { 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) { char *vp ; BS bs ; int box ; TreeDisp look = currentTreeDisp("updateKbd") ; if (!look->activebox) return ; if (!(bs = arr(look->content,look->activebox,BS))) return ; switch (k) { case RETURN_KEY: updatePick (look->activebox) ; 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) ; } /********************************/ 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") ; 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") ; 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") ; 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) ; } /*******************/ 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) ; } return ; } /* editEntry */ /************************************************************/ /************************************************************/ static void chooseTagCancel(void) { TreeDisp look = currentTreeDisp("chooseTagCancel") ; if (look->bsWait) return ; graphLoopReturn(FALSE) ; } static void choosePick(int box) { 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); } /************************ end of file ********************/