source: branches/species/SL/APP/db_browser.cxx

Last change on this file was 19654, checked in by westram, 5 weeks ago
  • reintegrates 'macros' into 'trunk'
    • improves program termination (#867)
      • introduces MacroExitor classes
        • handles confirmation (to quit)
        • waits for macros to finish, then exits w/o confirmation
        • provides specialized termination for different programs via derived classes
          • has been implemented for "normal" arb and merge-tool.
      • introduces ARB_disconnect_from_db
        • generalizes code to terminate all interconnections between GUI, database and macro-ability
        • allow to install atdisconnect-callbacks
          • usable by modules operating on a database; allow to inform module that database will vanish.
        • now used by all arb applications to disconnect from all their database(s), except the properties.
    • fixes some broken behavior
      • merge-tool
        • crashed when quitting via macro
        • wrong restarts, if originally started with arguments,
      • importer
        • failed to record/playback macros
        • crashed in modules operating on the temporary import database
      • database browser
        • crashed on disappearing database
  • adds: log:branches/macros@19620:19653
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 50.0 KB
Line 
1//  ==================================================================== //
2//                                                                       //
3//    File      : db_browser.cxx                                         //
4//    Purpose   : Simple database viewer                                 //
5//                                                                       //
6//                                                                       //
7//  Coded by Ralf Westram (coder@reallysoft.de) in May 2004              //
8//  Copyright Department of Microbiology (Technical University Munich)   //
9//                                                                       //
10//  Visit our web site at: http://www.arb-home.de/                       //
11//                                                                       //
12//  ==================================================================== //
13
14#include "hexdump.hxx"
15#include "app.hxx"
16
17#include <aw_window.hxx>
18#include <aw_msg.hxx>
19#include <aw_awar.hxx>
20#include <aw_select.hxx>
21#include <aw_system.hxx>
22#include <aw_advice.hxx>
23
24#include <arbdbt.h>
25
26#include <arb_str.h>
27#include <arb_strarray.h>
28
29#include <arb_misc.h>
30#include <arb_diff.h>
31#include <arb_file.h>
32#include <arb_sleep.h>
33#include <ad_cb.h>
34
35#include <string>
36#include <vector>
37#include <map>
38#include <algorithm>
39
40// do includes above (otherwise depends depend on DEBUG)
41#if defined(DEBUG)
42
43using namespace std;
44
45// used AWARs :
46
47#define AWAR_DBB_BASE     "/dbbrowser"
48#define AWAR_DBB_TMP_BASE "/tmp" AWAR_DBB_BASE
49
50#define AWAR_DBB_DB      AWAR_DBB_BASE "/db"
51#define AWAR_DBB_ORDER   AWAR_DBB_BASE "/order"
52#define AWAR_DBB_PATH    AWAR_DBB_BASE "/path"
53#define AWAR_DBB_HISTORY AWAR_DBB_BASE "/history"
54
55#define AWAR_DBB_BROWSE AWAR_DBB_TMP_BASE "/browse"
56#define AWAR_DBB_INFO   AWAR_DBB_TMP_BASE "/info"
57
58#define AWAR_DBB_RECSIZE AWAR_DBB_BASE "/recsize"
59
60#define AWAR_DUMP_HEX   AWAR_DBB_BASE "/hex"
61#define AWAR_DUMP_ASCII AWAR_DBB_BASE "/ascii"
62#define AWAR_DUMP_SPACE AWAR_DBB_BASE "/space"
63#define AWAR_DUMP_WIDTH AWAR_DBB_BASE "/width"
64#define AWAR_DUMP_BLOCK AWAR_DBB_BASE "/block"
65
66#define HISTORY_PSEUDO_PATH "*history*"
67#define ENTRY_MAX_LENGTH    1000
68#define HISTORY_MAX_LENGTH  20000
69
70inline bool is_dbbrowser_pseudo_path(const char *path) {
71    return
72        path           &&
73        path[0] == '*' &&
74        strcmp(path, HISTORY_PSEUDO_PATH) == 0;
75}
76
77enum SortOrder {
78    SORT_NONE,
79    SORT_NAME,
80    SORT_CONTAINER,
81    SORT_OCCUR,
82    SORT_TYPE,
83    SORT_CONTENT,
84
85    SORT_COUNT
86};
87
88static const char *sort_order_name[SORT_COUNT] = {
89    "Unsorted",
90    "Name",
91    "Name (Container)",
92    "Name (Occur)",
93    "Type",
94    "Content",
95};
96
97// used to sort entries in list
98struct list_entry {
99    const char *key_name;
100    GB_TYPES    type;
101    int         childCount;     // -1 if only one child with key_name exists
102    GBDATA     *gbd;
103    string      content;
104
105    static SortOrder sort_order;
106
107    inline bool less_than_by_name(const list_entry& other) const {
108        int cmp = ARB_stricmp(key_name, other.key_name);
109        if (cmp != 0) return cmp<0; // name differs!
110        return childCount<other.childCount; // equal names -> compare child count
111    }
112
113    inline int cmp_by_container(const list_entry& other) const { return int(type != GB_DB) - int(other.type != GB_DB); }
114    inline int cmp_by_childcount(const list_entry& other) const { return childCount - other.childCount; }
115
116    inline bool less_than_by_container_name(const list_entry& other) const {
117        int cmp = cmp_by_container(other);
118        if (cmp == 0) return less_than_by_name(other);
119        return cmp<0;
120    }
121    inline bool less_than_by_childcount_name(const list_entry& other) const {
122        int cmp = cmp_by_childcount(other);
123        if (cmp == 0) return less_than_by_name(other);
124        return cmp<0;
125    }
126
127    bool operator<(const list_entry& other) const {
128        bool is_less = false;
129        switch (sort_order) {
130            case SORT_COUNT: break;
131            case SORT_NONE:
132                arb_assert(0);  // not possible
133                break;
134
135            case SORT_NAME:
136                is_less = less_than_by_name(other);
137                break;
138
139            case SORT_CONTAINER:
140                is_less = less_than_by_container_name(other);
141                break;
142
143            case SORT_OCCUR:
144                is_less = less_than_by_childcount_name(other);
145                break;
146
147            case SORT_CONTENT: {
148                int cmp = ARB_stricmp(content.c_str(), other.content.c_str());
149
150                if (cmp != 0) is_less = cmp<0;
151                else is_less          = less_than_by_container_name(other);
152
153                break;
154            }
155            case SORT_TYPE: {
156                int cmp = type-other.type;
157
158                if (cmp == 0) is_less = less_than_by_name(other);
159                else is_less          = cmp<0;
160
161                break;
162            }
163        }
164        return is_less;
165    }
166};
167
168SortOrder list_entry::sort_order = SORT_NONE;
169
170// ---------------------
171//      create AWARs
172
173static MemDump make_userdefined_MemDump(AW_root *awr) {
174    bool   hex      = awr->awar(AWAR_DUMP_HEX)->read_int();
175    bool   ascii    = awr->awar(AWAR_DUMP_ASCII)->read_int();
176    bool   space    = awr->awar(AWAR_DUMP_SPACE)->read_int();
177    size_t width    = awr->awar(AWAR_DUMP_WIDTH)->read_int();
178    size_t separate = awr->awar(AWAR_DUMP_BLOCK)->read_int();
179
180    bool offset = (hex||ascii) && width;
181
182    return MemDump(offset, hex, ascii, width, separate, space);
183}
184
185static void nodedisplay_changed_cb(AW_root *aw_root) {
186    aw_root->awar(AWAR_DBB_BROWSE)->touch();
187}
188
189void AWT_create_db_browser_awars(AW_root *aw_root, AW_default aw_def) {
190    aw_root->awar_int   (AWAR_DBB_DB,      0,                     aw_def); // index to internal order of announced databases
191    aw_root->awar_int   (AWAR_DBB_ORDER,   SORT_CONTAINER,        aw_def); // sort order for "browse"-box
192    aw_root->awar_string(AWAR_DBB_PATH,    "/",                   aw_def); // path in database
193    aw_root->awar_string(AWAR_DBB_BROWSE,  "",                    aw_def); // selection in browser (= child name)
194    aw_root->awar_string(AWAR_DBB_INFO,    "<select an element>", aw_def); // information about selected item
195    aw_root->awar_string(AWAR_DBB_HISTORY, "",                    aw_def); // '\n'-separated string containing visited nodes
196
197    aw_root->awar_int(AWAR_DBB_RECSIZE, 0,  aw_def)->add_callback(nodedisplay_changed_cb); // collect size recursive?
198
199    // hex-dump-options
200    aw_root->awar_int(AWAR_DUMP_HEX,   1,  aw_def)->add_callback(nodedisplay_changed_cb); // show hex ?
201    aw_root->awar_int(AWAR_DUMP_ASCII, 1,  aw_def)->add_callback(nodedisplay_changed_cb); // show ascii ?
202    aw_root->awar_int(AWAR_DUMP_SPACE, 1,  aw_def)->add_callback(nodedisplay_changed_cb); // space bytes
203    aw_root->awar_int(AWAR_DUMP_WIDTH, 16, aw_def)->add_callback(nodedisplay_changed_cb); // bytes/line
204    aw_root->awar_int(AWAR_DUMP_BLOCK, 8,  aw_def)->add_callback(nodedisplay_changed_cb); // separate each bytes
205}
206
207static GBDATA *GB_search_numbered(GBDATA *gbd, const char *str, GB_TYPES create) { // @@@  this may be moved to ARBDB-sources
208    arb_assert(!GB_have_error());
209    if (str) {
210        if (str[0] == '/' && str[1] == 0) { // root
211            return GB_get_root(gbd);
212        }
213
214        const char *first_bracket = strchr(str, '[');
215        if (first_bracket) {
216            const char *second_bracket = strchr(first_bracket+1, ']');
217            if (second_bracket && (second_bracket[1] == 0 || second_bracket[1] == '/')) {
218                int count = atoi(first_bracket+1);
219                if (count >= 0 && isdigit(first_bracket[1])) {
220                    // we are sure we have sth with number in brackets (e.g. "/species_data/species[42]/name")
221                    const char *previous_slash = first_bracket-1;
222                    while (previous_slash >= str && previous_slash[0] != '/') previous_slash--; //
223                    if (previous_slash<str) previous_slash = NULp;
224
225                    GBDATA *gb_parent = NULp;
226                    {
227                        if (previous_slash) { // found a slash
228                            char *parent_path = ARB_strpartdup(str, previous_slash-1);
229
230                            // we are sure parent path does not contain brackets -> search normal
231                            if (parent_path[0] == 0) { // that means : root-item is numbered (e.g. '/species_data[7]/...')
232                                gb_parent = GB_get_root(gbd);
233                            }
234                            else {
235                                gb_parent = GB_search(gbd, parent_path, GB_FIND);
236                            }
237
238                            if (!gb_parent) fprintf(stderr, "Warning: parent '%s' not found\n", parent_path);
239                            free(parent_path);
240                        }
241                        else {
242                            gb_parent = gbd;
243                        }
244                    }
245
246                    if (gb_parent) {
247                        GBDATA *gb_son = NULp;
248                        {
249                            const char *name_start = previous_slash ? previous_slash+1 : str;
250                            char       *key_name   = ARB_strpartdup(name_start, first_bracket-1);
251                            int         c          = 0;
252
253                            gb_son = GB_entry(gb_parent, key_name);
254                            while (c<count && gb_son) {
255                                gb_son = GB_nextEntry(gb_son);
256                                if (gb_son) ++c;
257                            }
258
259                            if (!gb_son) fprintf(stderr, "Warning: did not find %i. son '%s'\n", count, key_name);
260                            free(key_name);
261                        }
262
263                        if (gb_son) {
264                            const char *rest = NULp;
265                            if (second_bracket[1] == '/') { // continue search ?
266                                if (second_bracket[2]) {
267                                    rest = second_bracket+2;
268                                }
269                            }
270
271                            return rest
272                                ? GB_search_numbered(gb_son, rest, create) // recursive search
273                                : gb_son; // numbering occurred at end of search path
274                        }
275                    }
276                    else {
277                        fprintf(stderr, "Warning: don't know where to start numbered search in '%s'\n", str);
278                    }
279
280                    return NULp; // not found
281                }
282                else {
283                    fprintf(stderr, "Warning: Illegal content in search path - expected digits at '%s'\n", first_bracket+1);
284                }
285            }
286            else {
287                fprintf(stderr, "Warning: Unbalanced or illegal [] in search path (%s)\n", str);
288            }
289        }
290        // no brackets -> normal search
291    }
292    arb_assert(!GB_have_error());
293    return GB_search(gbd, str, create); // do normal search
294}
295
296// -----------------------
297//      class KnownDB
298
299class KnownDB {
300    RefPtr<GBDATA> gb_main;
301
302    string description;
303    string current_path;
304
305public:
306    KnownDB(GBDATA *gb_main_, const char *description_) :
307        gb_main(gb_main_),
308        description(description_),
309        current_path("/")
310    {}
311
312    const GBDATA *get_db() const { return gb_main; }
313    const string& get_description() const { return description; }
314
315    const string& get_path() const { return current_path; }
316    void set_path(const string& path) { current_path = path; }
317    void set_path(const char* path) { current_path = path; }
318};
319
320class hasDB {
321    GBDATA *db;
322public:
323    explicit hasDB(GBDATA *gbm) : db(gbm) {}
324    bool operator()(const KnownDB& kdb) { return kdb.get_db() == db; }
325};
326
327// --------------------------
328//      class DB_browser
329
330class DB_browser;
331static DB_browser *get_the_browser(bool autocreate);
332
333class DB_browser : virtual Noncopyable {
334    typedef vector<KnownDB>::iterator KnownDBiterator;
335
336    vector<KnownDB> known_databases;
337    size_t          current_db; // index of current db (in known_databases)
338
339    AW_window             *aww;                     // browser window
340    AW_option_menu_struct *oms;                     // the DB selector
341    AW_selection_list     *browse_list;             // the browse subwindow
342
343    static SmartPtr<DB_browser> the_browser;
344    friend DB_browser *get_the_browser(bool autocreate);
345
346    void update_DB_selector();
347
348public:
349    DB_browser() : current_db(0), aww(NULp), oms(NULp) {}
350
351    void add_db(GBDATA *gb_main, const char *description) {
352        known_databases.push_back(KnownDB(gb_main, description));
353        if (aww) update_DB_selector();
354    }
355
356    void del_db(GBDATA *gb_main) {
357        KnownDBiterator known = find_if(known_databases.begin(), known_databases.end(), hasDB(gb_main));
358        if (known != known_databases.end()) known_databases.erase(known);
359#if defined(DEBUG)
360        else arb_assert(0); // no need to delete unknown databases
361#endif // DEBUG
362
363        if (aww) update_DB_selector();
364    }
365
366    AW_window *get_window(AW_root *aw_root);
367    AW_selection_list *get_browser_list() { return browse_list; }
368
369    bool legal_selected_db() const { return current_db < known_databases.size(); }
370
371    size_t get_selected_db() const { arb_assert(legal_selected_db()); return current_db; }
372    void set_selected_db(size_t idx) { arb_assert(idx < known_databases.size()); current_db = idx; }
373
374    const char *get_path() const { return known_databases[get_selected_db()].get_path().c_str(); }
375    void set_path(const char *new_path) { known_databases[get_selected_db()].set_path(new_path); }
376
377    GBDATA *get_db() const {
378        return legal_selected_db() ? const_cast<GBDATA*>(known_databases[get_selected_db()].get_db()) : NULp;
379    }
380};
381
382
383// -----------------------------
384//      DB_browser singleton
385
386SmartPtr<DB_browser> DB_browser::the_browser;
387
388static DB_browser *get_the_browser(bool autocreate = true) {
389    if (DB_browser::the_browser.isNull() && autocreate) {
390        DB_browser::the_browser = new DB_browser;
391    }
392    return &*DB_browser::the_browser;
393}
394
395// --------------------------
396//      announce databases
397
398static void browser_auto_forget_db(AW_root*, GBDATA *gb_main) {
399    DB_browser *browser = get_the_browser(false);
400    arb_assert(browser);
401    arb_assert(gb_main);
402    if (browser) browser->del_db(gb_main);
403}
404
405void AWT_announce_db_to_browser(GBDATA *gb_main, const char *description) {
406    get_the_browser()->add_db(gb_main, description);
407    ARB_atdisconnect_callback(makeArbDisconnectCallback(browser_auto_forget_db));
408}
409
410static void AWT_announce_properties_to_browser(GBDATA *gb_defaults, const char *defaults_name) {
411    AWT_announce_db_to_browser(gb_defaults, GBS_global_string("Properties (%s)", defaults_name));
412}
413
414// ---------------------------------------
415//      browser window callbacks
416
417static void toggle_tmp_cb(AW_window *aww) {
418    AW_awar *awar_path = aww->get_root()->awar(AWAR_DBB_PATH);
419    char    *path      = awar_path->read_string();
420    bool     done      = false;
421
422    if (ARB_strBeginsWith(path, "/tmp")) {
423        if (path[4] == '/') {
424            awar_path->write_string(path+4);
425            done = true;
426        }
427        else if (path[4] == 0) {
428            awar_path->write_string("/");
429            done = true;
430        }
431    }
432
433    if (!done && !is_dbbrowser_pseudo_path(path)) {
434        char *path_in_tmp = GBS_global_string_copy("/tmp%s", path);
435
436        char *lslash = strrchr(path_in_tmp, '/');
437        if (lslash && !lslash[1]) { // ends with '/'
438            lslash[0] = 0; // cut-off trailing '/'
439        }
440        awar_path->write_string(path_in_tmp);
441        free(path_in_tmp);
442    }
443    free(path);
444}
445
446static void show_history_cb(AW_window *aww) {
447    aww->get_root()->awar(AWAR_DBB_PATH)->write_string(HISTORY_PSEUDO_PATH);
448}
449
450static void goto_root_cb(AW_window *aww) {
451    AW_awar *awar_path = aww->get_root()->awar(AWAR_DBB_PATH);
452    awar_path->write_string("/");
453}
454static void go_up_cb(AW_window *aww) {
455    AW_awar *awar_path = aww->get_root()->awar(AWAR_DBB_PATH);
456    char    *path      = awar_path->read_string();
457    char    *lslash    = strrchr(path, '/');
458
459    if (lslash) {
460        lslash[0] = 0;
461        if (!path[0]) strcpy(path, "/");
462        awar_path->write_string(path);
463    }
464}
465
466// --------------------------
467//      browser commands:
468
469#define BROWSE_CMD_PREFIX          "browse_cmd___"
470#define BROWSE_CMD_GOTO_VALID_NODE BROWSE_CMD_PREFIX "goto_valid_node"
471#define BROWSE_CMD_GO_UP           BROWSE_CMD_PREFIX "go_up"
472
473struct BrowserCommand {
474    const char *name;
475    GB_ERROR (*function)(AW_window *);
476};
477
478
479static GB_ERROR browse_cmd_go_up(AW_window *aww) {
480    go_up_cb(aww);
481    return NULp;
482}
483static GB_ERROR browse_cmd_goto_valid_node(AW_window *aww) {
484    AW_root *aw_root   = aww->get_root();
485    AW_awar *awar_path = aw_root->awar(AWAR_DBB_PATH);
486    char    *path      = awar_path->read_string();
487    int      len       = strlen(path);
488    GBDATA  *gb_main   = get_the_browser()->get_db();
489
490    if (!gb_main) {
491        aw_message("previously selected database vanished => selected first database");
492        aw_root->awar(AWAR_DBB_DB)->rewrite_int(0);
493    }
494    else {
495        GB_transaction t(gb_main);
496        while (len>0 && !GB_search_numbered(gb_main, path, GB_FIND)) {
497            GB_clear_error();
498            path[--len] = 0;
499        }
500    }
501
502    awar_path->write_string(len ? path : "/");
503    aw_root->awar(AWAR_DBB_BROWSE)->write_string("");
504    return NULp;
505}
506
507static BrowserCommand browser_command_table[] = {
508    { BROWSE_CMD_GOTO_VALID_NODE, browse_cmd_goto_valid_node },
509    { BROWSE_CMD_GO_UP, browse_cmd_go_up },
510    { NULp, NULp }
511};
512
513static void execute_DB_browser_command(AW_window *aww, const char *command) {
514    int idx;
515    for (idx = 0; browser_command_table[idx].name; ++idx) {
516        if (strcmp(command, browser_command_table[idx].name) == 0) {
517            GB_ERROR error = browser_command_table[idx].function(aww);
518            if (error) aw_message(error);
519            break;
520        }
521    }
522
523    if (!browser_command_table[idx].name) {
524        aw_message(GBS_global_string("Unknown DB-browser command '%s'", command));
525    }
526}
527
528// ----------------------------
529//      the browser window
530
531static AW_window *create_db_browser(AW_root *aw_root) {
532    return get_the_browser()->get_window(aw_root);
533}
534
535struct counterPair {
536    int occur, count;
537    counterPair() : occur(0), count(0) {}
538};
539
540inline void insert_history_selection(AW_selection_list *sel, const char *entry, int wanted_db) {
541    const char *colon = strchr(entry, ':');
542    if (colon && (atoi(entry) == wanted_db)) {
543        sel->insert(colon+1, colon+1);
544    }
545}
546
547
548static char *get_container_description(GBDATA *gbd) {
549    char *content = NULp;
550    const char *known_children[] = { "@name", "name", "key_name", "alignment_name", "group_name", "key_text", NULp };
551    for (int i = 0; known_children[i]; ++i) {
552        GBDATA *gb_known = GB_entry(gbd, known_children[i]);
553        if (gb_known && GB_read_type(gb_known) != GB_DB && !GB_nextEntry(gb_known)) { // exactly one child exits
554            char *asStr = GB_read_as_string(gb_known);
555            content     = GBS_global_string_copy("[%s=%s]", known_children[i], asStr);
556            free(asStr);
557            break;
558        }
559    }
560
561    return content;
562}
563
564static char *get_dbentry_content(GBDATA *gbd, GB_TYPES type, bool shorten_repeats, const MemDump& dump) {
565    arb_assert(type != GB_DB);
566
567    char *content = NULp;
568    if (!dump.wrapped()) content = GB_read_as_string(gbd);
569    if (!content) { // use dumper
570        long size;
571
572        switch (type) {
573            case GB_BIT:
574            case GB_BYTE:    size = 1;    break;
575            case GB_INT:     size = sizeof(int32_t);    break;
576            case GB_FLOAT:   size = sizeof(float);      break;
577            case GB_POINTER: size = sizeof(GBDATA*);    break;
578
579            case GB_BITS:
580            case GB_BYTES:
581            case GB_INTS:
582            case GB_FLOATS:
583            case GB_STRING:  size = GB_read_count(gbd); break;
584
585            default: aw_assert(0); break;
586        }
587
588        const int     plen = 30;
589        GBS_strstruct buf(dump.mem_needed_for_dump(size)+plen);
590
591        if (!dump.wrapped()) buf.nprintf(plen, "<%li bytes>: ", size);
592        dump.dump_to(buf, GB_read_pntr(gbd), size);
593
594        content         = buf.release();
595        shorten_repeats = false;
596    }
597    arb_assert(content);
598
599    size_t len = shorten_repeats ? GBS_shorten_repeated_data(content) : strlen(content);
600    if (!dump.wrapped() && len>(ENTRY_MAX_LENGTH+15)) {
601        content[ENTRY_MAX_LENGTH] = 0;
602        freeset(content, GBS_global_string_copy("%s [rest skipped]", content));
603    }
604    return content;
605}
606
607static void update_browser_selection_list(AW_root *aw_root, AW_selection_list *id) {
608    arb_assert(!GB_have_error());
609    DB_browser *browser = get_the_browser();
610    char       *path    = aw_root->awar(AWAR_DBB_PATH)->read_string();
611    bool        is_root;
612    GBDATA     *node    = NULp;
613
614    {
615        GBDATA *gb_main = browser->get_db();
616        if (gb_main) {
617            GB_transaction ta(gb_main);
618            is_root = (strcmp(path, "/") == 0);
619            node    = GB_search_numbered(gb_main, path, GB_FIND);
620        }
621    }
622
623    id->clear();
624
625    if (!node) {
626        if (strcmp(path, HISTORY_PSEUDO_PATH) == 0) {
627            GB_clear_error(); // ignore error about invalid key
628            char *history = aw_root->awar(AWAR_DBB_HISTORY)->read_string();
629            id->insert("Previously visited nodes:", "");
630            char *start   = history;
631            int   curr_db = aw_root->awar(AWAR_DBB_DB)->read_int();
632
633            for (char *lf = strchr(start, '\n'); lf; start = lf+1, lf = strchr(start, '\n')) {
634                lf[0] = 0;
635                insert_history_selection(id, start, curr_db);
636            }
637            insert_history_selection(id, start, curr_db);
638            free(history);
639        }
640        else {
641            if (GB_have_error()) id->insert(GBS_global_string("Error: %s", GB_await_error()), "");
642            id->insert("No such node!", "");
643            id->insert("-> goto valid node", BROWSE_CMD_GOTO_VALID_NODE);
644        }
645    }
646    else {
647        map<string, counterPair> child_count;
648        GB_transaction           ta(browser->get_db());
649
650        for (GBDATA *child = GB_child(node); child; child = GB_nextChild(child)) {
651            const char *key_name   = GB_read_key_pntr(child);
652            child_count[key_name].occur++;
653        }
654
655        int maxkeylen = 0;
656        int maxtypelen = 0;
657        for (map<string, counterPair>::iterator i = child_count.begin(); i != child_count.end(); ++i) {
658            {
659                int keylen   = i->first.length();
660                int maxcount = i->second.occur;
661
662                if (maxcount != 1) {
663                    keylen += 2;    // brackets
664                    while (maxcount) { maxcount /= 10; keylen++; } // increase keylen for each digit
665                }
666
667                if (keylen>maxkeylen) maxkeylen = keylen;
668            }
669
670            {
671                GBDATA     *child     = GB_entry(node, i->first.c_str()); // find first child
672                const char *type_name = GB_get_type_name(child);
673                int         typelen   = strlen(type_name);
674
675                if (typelen>maxtypelen) maxtypelen = typelen;
676            }
677        }
678
679        if (!is_root) {
680            id->insert(GBS_global_string("%-*s   parent container", maxkeylen, ".."), BROWSE_CMD_GO_UP);
681            id->insert(GBS_global_string("%-*s   container", maxkeylen, "."), "");
682        }
683        else {
684            id->insert(GBS_global_string("%-*s   root container", maxkeylen, "/"), "");
685        }
686
687        // collect children and sort them
688
689        vector<list_entry> sorted_children;
690
691        MemDump simpleDump(false, true, false);
692        for (GBDATA *child = GB_child(node); child; child = GB_nextChild(child)) {
693            list_entry entry;
694            entry.key_name   = GB_read_key_pntr(child);
695            entry.childCount = -1;
696            entry.type       = GB_read_type(child);
697            entry.gbd        = child;
698
699            int occurrences = child_count[entry.key_name].occur;
700            if (occurrences != 1) {
701                entry.childCount = child_count[entry.key_name].count;
702                child_count[entry.key_name].count++;
703            }
704            char *display = NULp;
705            if (entry.type == GB_DB) {
706                display = get_container_description(entry.gbd);
707            }
708            else {
709                display = get_dbentry_content(entry.gbd, entry.type, true, simpleDump);
710            }
711            if (display) entry.content = display;
712            sorted_children.push_back(entry);
713        }
714
715        list_entry::sort_order = (SortOrder)aw_root->awar(AWAR_DBB_ORDER)->read_int();
716        if (list_entry::sort_order != SORT_NONE) {
717            sort(sorted_children.begin(), sorted_children.end());
718        }
719
720        for (vector<list_entry>::iterator ch = sorted_children.begin(); ch != sorted_children.end(); ++ch) {
721            const list_entry&  entry            = *ch;
722            const char        *key_name         = entry.key_name;
723            char              *numbered_keyname = NULp;
724
725            if (entry.childCount >= 0) {
726                numbered_keyname = GBS_global_string_copy("%s[%i]", key_name, entry.childCount);
727            }
728
729            key_name = numbered_keyname ? numbered_keyname : key_name;
730            const char *displayed = GBS_global_string("%-*s   %-*s   %s", maxkeylen, key_name, maxtypelen, GB_get_type_name(entry.gbd), entry.content.c_str());
731            id->insert(displayed, key_name);
732
733            free(numbered_keyname);
734        }
735    }
736    id->insert_default("", "");
737    id->update();
738
739    free(path);
740    arb_assert(!GB_have_error());
741}
742
743static void order_changed_cb(AW_root *aw_root) {
744    DB_browser *browser = get_the_browser();
745    update_browser_selection_list(aw_root, browser->get_browser_list());
746}
747
748inline char *strmove(char *dest, char *source) {
749    return (char*)memmove(dest, source, strlen(source)+1);
750}
751
752static void add_to_history(AW_root *aw_root, const char *path) {
753    // adds 'path' to history
754
755    if (strcmp(path, HISTORY_PSEUDO_PATH) != 0) {
756        int      db           = aw_root->awar(AWAR_DBB_DB)->read_int();
757        AW_awar *awar_history = aw_root->awar(AWAR_DBB_HISTORY);
758        char    *old_history  = awar_history->read_string();
759        char    *entry        = GBS_global_string_copy("%i:%s", db, path);
760        int      entry_len    = strlen(entry);
761
762        char *found = strstr(old_history, entry);
763        while (found) {
764            if (found == old_history || found[-1] == '\n') { // found start of an entry
765                if (found[entry_len] == '\n') {
766                    strmove(found, found+entry_len+1);
767                    found = strstr(old_history, entry);
768                }
769                else if (found[entry_len] == 0) { // found as last entry
770                    if (found == old_history) { // which is also first entry
771                        old_history[0] = 0; // empty history
772                    }
773                    else {
774                        strmove(found, found+entry_len+1);
775                    }
776                    found = strstr(old_history, entry);
777                }
778                else {
779                    found = strstr(found+1, entry);
780                }
781            }
782            else {
783                found = strstr(found+1, entry);
784            }
785        }
786
787        if (old_history[0]) {
788            char *new_history = GBS_global_string_copy("%s\n%s", entry, old_history);
789
790            while (strlen(old_history)>HISTORY_MAX_LENGTH) { // shorten history
791                char *llf = strrchr(old_history, '\n');
792                if (!llf) break;
793                llf[0]    = 0;
794            }
795
796            awar_history->write_string(new_history);
797            free(new_history);
798        }
799        else {
800            awar_history->write_string(entry);
801        }
802
803        free(entry);
804        free(old_history);
805    }
806}
807
808static bool    inside_path_change = false;
809static GBDATA *gb_tracked_node    = NULp;
810
811static void selected_node_modified_cb(GBDATA *gb_node, GB_CB_TYPE cb_type) {
812    arb_assert(gb_node == gb_tracked_node);
813
814    if (cb_type & GB_CB_DELETE) {
815        gb_tracked_node = NULp; // no need to remove callback ?
816    }
817
818    if (!inside_path_change) { // ignore refresh callbacks triggered by dbbrowser-awars
819        static bool avoid_recursion = false;
820        if (!avoid_recursion) {
821            LocallyModify<bool> flag(avoid_recursion, true);
822            GlobalStringBuffers *old_buffers = GBS_store_global_buffers();
823
824            AW_root *aw_root   = AW_root::SINGLETON;
825            AW_awar *awar_path = aw_root->awar_no_error(AWAR_DBB_PATH);
826            if (awar_path) { // avoid crash during exit
827                AW_awar    *awar_child = aw_root->awar(AWAR_DBB_BROWSE);
828                const char *child      = awar_child->read_char_pntr();
829
830                if (child[0]) {
831                    const char *path     = awar_path->read_char_pntr();
832                    const char *new_path;
833
834                    if (!path[0] || !path[1]) {
835                        new_path = GBS_global_string("/%s", child);
836                    }
837                    else {
838                        new_path = GBS_global_string("%s/%s", path, child);
839                    }
840
841                    char *fixed_path = GBS_string_eval(new_path, "//=/");
842                    awar_path->write_string(fixed_path);
843                    free(fixed_path);
844                }
845                else {
846                    awar_path->touch();
847                }
848            }
849            GBS_restore_global_buffers(old_buffers);
850        }
851    }
852}
853static void untrack_node() {
854    if (gb_tracked_node) {
855        GB_remove_callback(gb_tracked_node, GB_CB_ALL, makeDatabaseCallback(selected_node_modified_cb));
856        gb_tracked_node = NULp;
857    }
858}
859static void track_node(GBDATA *gb_node) {
860    untrack_node();
861    GB_ERROR error = GB_add_callback(gb_node, GB_CB_ALL, makeDatabaseCallback(selected_node_modified_cb));
862    if (error) {
863        aw_message(error);
864    }
865    else {
866        gb_tracked_node = gb_node;
867    }
868}
869
870static string get_node_info_string(AW_root *aw_root, GBDATA *gb_selected_node, const char *fullpath) {
871    string info;
872    info += GBS_global_string("Fullpath  | '%s'\n", fullpath);
873
874    if (!gb_selected_node) {
875        info += "Address   | NULp\n";
876        info += GBS_global_string("Error     | %s\n", GB_have_error() ? GB_await_error() : "<none>");
877    }
878    else {
879        add_to_history(aw_root, fullpath);
880
881        info += GBS_global_string("Address   | %p\n", gb_selected_node);
882        info += GBS_global_string("Key index | %i\n", GB_get_quark(gb_selected_node));
883
884
885        GB_SizeInfo sizeInfo;
886        bool        collect_recursive = aw_root->awar(AWAR_DBB_RECSIZE)->read_int();
887
888        GB_TYPES type = GB_read_type(gb_selected_node);
889        if (collect_recursive || type != GB_DB) {
890            sizeInfo.collect(gb_selected_node);
891        }
892
893        string structure_add;
894        string childs;
895
896        if (type == GB_DB) {
897            long struct_size = GB_calc_structure_size(gb_selected_node);
898            if (collect_recursive) {
899                structure_add = GBS_global_string(" (this: %s)", GBS_readable_size(struct_size, "b"));
900            }
901            else {
902                sizeInfo.structure = struct_size;
903            }
904
905            info += "Node type | container\n";
906
907            childs = GBS_global_string("Childs    | %li", GB_number_of_subentries(gb_selected_node));
908            if (collect_recursive) {
909                childs = childs+" (rec: " + GBS_readable_size(sizeInfo.containers, "containers");
910                childs = childs+" + " + GBS_readable_size(sizeInfo.terminals, "terminals");
911                childs = childs+" = " + GBS_readable_size(sizeInfo.terminals+sizeInfo.containers, "nodes")+')';
912            }
913            childs += '\n';
914
915            {
916                LocallyModify<bool> flag2(inside_path_change, true);
917                aw_root->awar(AWAR_DBB_BROWSE)->write_string("");
918                aw_root->awar(AWAR_DBB_PATH)->write_string(fullpath);
919            }
920        }
921        else {
922            info += GBS_global_string("Node type | data [type=%s]\n", GB_get_type_name(gb_selected_node));
923        }
924
925        long overall = sizeInfo.mem+sizeInfo.structure;
926
927        info += GBS_global_string("Data size | %7s\n", GBS_readable_size(sizeInfo.data, "b"));
928        info += GBS_global_string("Memory    | %7s  %5.2f%%\n", GBS_readable_size(sizeInfo.mem, "b"), double(sizeInfo.mem)/overall*100+.0049);
929        info += GBS_global_string("Structure | %7s  %5.2f%%", GBS_readable_size(sizeInfo.structure, "b"), double(sizeInfo.structure)/overall*100+.0049) + structure_add + '\n';
930        info += GBS_global_string("Overall   | %7s\n", GBS_readable_size(overall, "b"));
931        if (sizeInfo.data) {
932            info += GBS_global_string("CompRatio | %5.2f%% (mem-only: %5.2f%%)\n", double(overall)/sizeInfo.data*100+.0049, double(sizeInfo.mem)/sizeInfo.data*100+.0049);
933        }
934        info += childs;
935
936        {
937            bool is_tmp             = GB_is_temporary(gb_selected_node);
938            bool not_tmp_but_in_tmp = !is_tmp && GB_in_temporary_branch(gb_selected_node);
939
940            if (is_tmp || not_tmp_but_in_tmp) {
941                info += GBS_global_string("Temporary | yes%s\n", not_tmp_but_in_tmp ? " (in temporary branch)" : "");
942            }
943        }
944
945        GBDATA *gb_recurse_here = NULp;
946        char   *recursed_path   = NULp;
947        {
948            AW_awar *awar_this = aw_root->awar_no_error(fullpath+1); // skip leading slash
949            if (awar_this) {
950                bool is_mapped  = awar_this->is_mapped();
951                info           += "AWAR      | yes\n";
952                if (is_mapped) {
953                    gb_recurse_here = awar_this->gb_var;
954                    recursed_path   = ARB_strdup(GB_get_db_path(gb_recurse_here));
955                    info            = info + "          | mapped to '" + recursed_path + "' (see below)\n";
956                }
957            }
958        }
959
960        info += GBS_global_string("Security  | read=%i write=%i delete=%i\n",
961                                  GB_read_security_read(gb_selected_node),
962                                  GB_read_security_write(gb_selected_node),
963                                  GB_read_security_delete(gb_selected_node));
964
965        char *callback_info = GB_get_callback_info(gb_selected_node);
966        if (callback_info) {
967            ConstStrArray callbacks;
968            GBT_splitNdestroy_string(callbacks, callback_info, "\n", SPLIT_DROPEMPTY);
969
970            for (size_t i = 0; i<callbacks.size(); ++i) {
971                const char *prefix = i ? "         " : "Callbacks";
972                info               = info + prefix + " | " + callbacks[i] + '\n';
973            }
974
975            if (gb_selected_node == gb_tracked_node) {
976                info += "          | (Note: one callback was installed by this browser)\n";
977            }
978        }
979
980        if (type != GB_DB) {
981            MemDump  dump    = make_userdefined_MemDump(aw_root);
982            char    *content = get_dbentry_content(gb_selected_node, GB_read_type(gb_selected_node), false, dump);
983            if (content[0]) {
984                info = info+"\nContent:\n"+content+'\n';
985            }
986            else {
987                info += "\nNo content.\n";
988            }
989            free(content);
990        }
991
992        if (gb_recurse_here) {
993            GBDATA     *gb_sel_main = GB_get_root(gb_selected_node);
994            GBDATA     *gb_sub_main = GB_get_root(gb_recurse_here);
995            const bool  same_DB     = gb_sel_main == gb_sub_main;
996
997            GB_transaction ta(gb_sub_main);  // target database may differ from current
998
999            string subinfo = get_node_info_string(aw_root, gb_recurse_here, recursed_path);
1000
1001            info +=
1002                "\n"
1003                + string(same_DB
1004                   ?
1005                   "===========================\n"
1006                   "Recursive database element:\n"
1007                   :
1008                   "===================================================\n"
1009                   "Recursive database element (in different database):\n"
1010                    )
1011                + "\n"
1012                + subinfo;
1013
1014            freenull(recursed_path);
1015        }
1016    }
1017    return info;
1018}
1019
1020static void child_changed_cb(AW_root *aw_root) {
1021    char *child = aw_root->awar(AWAR_DBB_BROWSE)->read_string();
1022    if (strncmp(child, BROWSE_CMD_PREFIX, sizeof(BROWSE_CMD_PREFIX)-1) == 0) { // a symbolic browser command
1023        execute_DB_browser_command(get_the_browser()->get_window(aw_root), child);
1024    }
1025    else {
1026        char *path = aw_root->awar(AWAR_DBB_PATH)->read_string();
1027
1028        if (strcmp(path, HISTORY_PSEUDO_PATH) == 0) {
1029            if (child[0]) {
1030                LocallyModify<bool> flag(inside_path_change, true);
1031                aw_root->awar(AWAR_DBB_PATH)->write_string(child);
1032            }
1033        }
1034        else {
1035            static bool avoid_recursion = false;
1036            if (!avoid_recursion) {
1037                LocallyModify<bool> flag(avoid_recursion, true);
1038
1039                char *fullpath;
1040                if (strcmp(path, "/") == 0) {
1041                    fullpath = GBS_global_string_copy("/%s", child);
1042                }
1043                else if (child[0] == 0) {
1044                    fullpath = ARB_strdup(path);
1045                }
1046                else {
1047                    fullpath = GBS_global_string_copy("%s/%s", path, child);
1048                }
1049
1050                DB_browser *browser = get_the_browser();
1051                GBDATA     *gb_main = browser->get_db();
1052
1053                if (gb_main) {
1054                    GB_transaction  ta(gb_main);
1055                    GBDATA         *gb_selected_node = GB_search_numbered(gb_main, fullpath, GB_FIND);
1056
1057                    string info = get_node_info_string(aw_root, gb_selected_node, fullpath);
1058
1059
1060                    aw_root->awar(AWAR_DBB_INFO)->write_string(info.c_str());
1061                }
1062                free(fullpath);
1063            }
1064        }
1065
1066        free(path);
1067    }
1068    free(child);
1069}
1070
1071static void path_changed_cb(AW_root *aw_root) {
1072    static bool avoid_recursion = false;
1073    if (!avoid_recursion) {
1074        arb_assert(!GB_have_error());
1075        LocallyModify<bool> flag(avoid_recursion, true);
1076
1077        DB_browser *browser    = get_the_browser();
1078        char       *goto_child = NULp;
1079
1080        GBDATA *gb_main = browser->get_db();
1081        if (gb_main) {
1082            GB_transaction  t(gb_main);
1083            AW_awar        *awar_path = aw_root->awar(AWAR_DBB_PATH);
1084            char           *path      = awar_path->read_string();
1085            GBDATA         *found     = GB_search_numbered(gb_main, path, GB_FIND);
1086
1087            if (found && GB_read_type(found) != GB_DB) { // exists, but is not a container
1088                char *lslash = strrchr(path, '/');
1089                if (lslash) {
1090                    goto_child = ARB_strdup(lslash+1);
1091                    lslash[lslash == path] = 0; // truncate at last slash (but keep sole slash)
1092                    awar_path->write_string(path);
1093                }
1094            }
1095
1096            if (found) {
1097                LocallyModify<bool> flag2(inside_path_change, true);
1098                add_to_history(aw_root, goto_child ? GBS_global_string("%s/%s", path, goto_child) : path);
1099                GBDATA *father = GB_get_father(found);
1100                track_node(father ? father : found);
1101            }
1102            else if (is_dbbrowser_pseudo_path(path)) {
1103                GB_clear_error(); // ignore error about invalid key
1104            }
1105            else if (GB_have_error()) {
1106                aw_message(GB_await_error());
1107            }
1108            browser->set_path(path);
1109            free(path);
1110        }
1111
1112        update_browser_selection_list(aw_root, browser->get_browser_list());
1113
1114        LocallyModify<bool> flag2(inside_path_change, true);
1115        aw_root->awar(AWAR_DBB_BROWSE)->rewrite_string(null2empty(goto_child));
1116        arb_assert(!GB_have_error());
1117    }
1118}
1119static void db_changed_cb(AW_root *aw_root) {
1120    int         selected = aw_root->awar(AWAR_DBB_DB)->read_int();
1121    DB_browser *browser  = get_the_browser();
1122
1123    LocallyModify<bool> flag(inside_path_change, true);
1124    browser->set_selected_db(selected);
1125    aw_root->awar(AWAR_DBB_PATH)->rewrite_string(browser->get_path());
1126}
1127
1128void DB_browser::update_DB_selector() {
1129    if (!oms) oms = aww->create_option_menu(AWAR_DBB_DB);
1130    else aww->clear_option_menu(oms);
1131
1132    int idx = 0;
1133    for (KnownDBiterator i = known_databases.begin(); i != known_databases.end(); ++i, ++idx) {
1134        const KnownDB& db = *i;
1135        aww->insert_option(db.get_description().c_str(), "", idx);
1136    }
1137    aww->update_option_menu();
1138}
1139
1140AW_window *DB_browser::get_window(AW_root *aw_root) {
1141    arb_assert(!known_databases.empty()); // have no db to browse
1142
1143    if (!aww) {
1144        // select current db+path
1145        {
1146            int wanted_db = aw_root->awar(AWAR_DBB_DB)->read_int();
1147            int known     = known_databases.size();
1148            if (wanted_db >= known) {
1149                wanted_db = 0;
1150                aw_root->awar(AWAR_DBB_DB)->write_int(wanted_db);
1151                aw_root->awar(AWAR_DBB_PATH)->write_string("/"); // reset
1152            }
1153            set_selected_db(wanted_db);
1154
1155            char *wanted_path = aw_root->awar(AWAR_DBB_PATH)->read_string();
1156            known_databases[wanted_db].set_path(wanted_path);
1157            free(wanted_path);
1158        }
1159
1160        AW_window_simple *aws = new AW_window_simple;
1161        aww                   = aws;
1162        aws->init(aw_root, "DB_BROWSER", "ARB database browser");
1163        aws->load_xfig("dbbrowser.fig");
1164
1165        aws->at("close"); aws->callback(AW_POPDOWN);
1166        aws->create_button("CLOSE", "CLOSE", "C");
1167
1168        aws->at("db");
1169        update_DB_selector();
1170
1171        aws->at("order");
1172        aws->create_option_menu(AWAR_DBB_ORDER);
1173        for (int idx = 0; idx<SORT_COUNT; ++idx) {
1174            aws->insert_option(sort_order_name[idx], "", idx);
1175        }
1176        aws->update_option_menu();
1177
1178        aws->at("path");
1179        aws->create_input_field(AWAR_DBB_PATH, 10);
1180
1181        aws->auto_space(10, 10);
1182        aws->button_length(8);
1183
1184        aws->at("navigation");
1185        aws->callback(go_up_cb); aws->create_button("go_up", "Up");
1186        aws->callback(goto_root_cb); aws->create_button("goto_root", "Top");
1187        aws->callback(show_history_cb); aws->create_button("history", "History");
1188        aws->callback(toggle_tmp_cb); aws->create_button("toggle_tmp", "/tmp");
1189
1190        aws->label("Rec.size"); aws->create_toggle(AWAR_DBB_RECSIZE);
1191
1192        aws->at("browse");
1193        browse_list = aws->create_selection_list(AWAR_DBB_BROWSE);
1194        update_browser_selection_list(aw_root, browse_list);
1195
1196        aws->at("infoopt");
1197        aws->label("ASCII"); aws->create_toggle     (AWAR_DUMP_ASCII);
1198        aws->label("Hex");   aws->create_toggle     (AWAR_DUMP_HEX);
1199        aws->label("Sep?");  aws->create_toggle     (AWAR_DUMP_SPACE);
1200        aws->label("Width"); aws->create_input_field(AWAR_DUMP_WIDTH, 3);
1201        aws->label("Block"); aws->create_input_field(AWAR_DUMP_BLOCK, 3);
1202
1203        aws->at("info");
1204        aws->create_text_field(AWAR_DBB_INFO, 40, 40);
1205
1206        // add callbacks
1207        aw_root->awar(AWAR_DBB_BROWSE)->add_callback(child_changed_cb);
1208        aw_root->awar(AWAR_DBB_PATH)->add_callback(path_changed_cb);
1209        aw_root->awar(AWAR_DBB_DB)->add_callback(db_changed_cb);
1210        aw_root->awar(AWAR_DBB_ORDER)->add_callback(order_changed_cb);
1211
1212        db_changed_cb(aw_root); // force update
1213    }
1214    return aww;
1215}
1216
1217static void callallcallbacks(AW_window *aww, int mode) {
1218    static bool running = false; // avoid deadlock
1219    if (!running) {
1220        LocallyModify<bool> flag(running, true);
1221        aww->get_root()->callallcallbacks(mode);
1222    }
1223}
1224
1225
1226
1227void AWT_create_debug_menu(AW_window *awmm) {
1228    awmm->create_menu("4debugz", "z", AWM_ALL);
1229
1230    awmm->insert_menu_topic(awmm->local_id("-db_browser"), "Browse loaded database(s)", "B", NULp, AWM_ALL, create_db_browser);
1231
1232    awmm->sep______________();
1233    {
1234        awmm->insert_sub_menu("Callbacks (dangerous! use at your own risk)", "C", AWM_ALL);
1235        awmm->insert_menu_topic("!run_all_cbs_alph",  "Call all callbacks (alpha-order)",     "a", "", AWM_ALL, makeWindowCallback(callallcallbacks, 0));
1236        awmm->insert_menu_topic("!run_all_cbs_nalph", "Call all callbacks (alpha-reverse)",   "l", "", AWM_ALL, makeWindowCallback(callallcallbacks, 1));
1237        awmm->insert_menu_topic("!run_all_cbs_loc",   "Call all callbacks (code-order)",      "c", "", AWM_ALL, makeWindowCallback(callallcallbacks, 2));
1238        awmm->insert_menu_topic("!run_all_cbs_nloc",  "Call all callbacks (code-reverse)",    "o", "", AWM_ALL, makeWindowCallback(callallcallbacks, 3));
1239        awmm->insert_menu_topic("!run_all_cbs_rnd",   "Call all callbacks (random)",          "r", "", AWM_ALL, makeWindowCallback(callallcallbacks, 4));
1240        awmm->sep______________();
1241        awmm->insert_menu_topic("!forget_called_cbs", "Forget called",     "F", "", AWM_ALL, makeWindowCallback(callallcallbacks, -1));
1242        awmm->insert_menu_topic("!mark_all_called",   "Mark all called",   "M", "", AWM_ALL, makeWindowCallback(callallcallbacks, -2));
1243        awmm->sep______________();
1244        {
1245            awmm->insert_sub_menu("Call repeated", "p", AWM_ALL);
1246            awmm->insert_menu_topic("!run_all_cbs_alph_inf",  "Call all callbacks (alpha-order repeated)",     "a", "", AWM_ALL, makeWindowCallback(callallcallbacks, 8|0));
1247            awmm->insert_menu_topic("!run_all_cbs_nalph_inf", "Call all callbacks (alpha-reverse repeated)",   "l", "", AWM_ALL, makeWindowCallback(callallcallbacks, 8|1));
1248            awmm->insert_menu_topic("!run_all_cbs_loc_inf",   "Call all callbacks (code-order repeated)",      "c", "", AWM_ALL, makeWindowCallback(callallcallbacks, 8|2));
1249            awmm->insert_menu_topic("!run_all_cbs_nloc_inf",  "Call all callbacks (code-reverse repeated)",    "o", "", AWM_ALL, makeWindowCallback(callallcallbacks, 8|3));
1250            awmm->insert_menu_topic("!run_all_cbs_rnd_inf",   "Call all callbacks (random repeated)",          "r", "", AWM_ALL, makeWindowCallback(callallcallbacks, 8|4));
1251            awmm->close_sub_menu();
1252        }
1253        awmm->close_sub_menu();
1254    }
1255    awmm->sep______________();
1256
1257    awmm->insert_menu_topic(awmm->local_id("-client_ntree"), "Start ARB_NTREE as client", "N", "", AWM_ALL, makeWindowCallback(AW_system_async_cb, "arb_ntree :"));
1258}
1259
1260#if 1
1261void AWT_check_action_ids(AW_root *, const char *) {
1262}
1263#else
1264void AWT_check_action_ids(AW_root *aw_root, const char *suffix) {
1265    // check actions ids (see #428)
1266    // suffix is appended to filenames (needed if one application may have different states)
1267    GB_ERROR error = NULp;
1268    {
1269        SmartPtr<ConstStrArray> action_ids = aw_root->get_action_ids();
1270
1271        const char *checksdir = GB_path_in_ARBLIB("macros/.checks");
1272        char       *save      = GBS_global_string_copy("%s/%s%s_action.ids", checksdir, aw_root->program_name, suffix);
1273
1274        FILE *out       = fopen(save, "wt");
1275        if (!out) error = GB_IO_error("writing", save);
1276        else {
1277            for (size_t i = 0; i<action_ids->size(); ++i) {
1278                fprintf(out, "%s\n", (*action_ids)[i]);
1279            }
1280            fclose(out);
1281        }
1282
1283        if (!error) {
1284            char *expected              = GBS_global_string_copy("%s.expected", save);
1285            bool  IDs_have_changed      = ARB_textfiles_have_difflines(expected, save, 0, TDM_NOT_DIFF_LINECOUNT);
1286            if (IDs_have_changed) error = GBS_global_string("action ids differ from expected (see console)");
1287            free(expected);
1288        }
1289
1290        if (!error) GB_unlink(save);
1291        free(save);
1292    }
1293    if (error) fprintf(stderr, "AWT_check_action_ids: Error: %s\n", error);
1294}
1295#endif
1296
1297#endif // DEBUG
1298
1299AW_root *AWT_create_root(const char *properties, const char *program, UserActionTracker *user_tracker) {
1300    // Implementation note:
1301    //     there probably should be a corresponding cleanup method (e.g. AWT_delete_root)
1302    //     which could be used to forget properties database and cleanup advisor.
1303    //     see also: destroy_AW_root()
1304
1305    AW_root *aw_root = new AW_root(properties, program, false, user_tracker);
1306#if defined(DEBUG)
1307    AWT_announce_properties_to_browser(AW_ROOT_DEFAULT, properties);
1308    // database is never forgotten (i.e. ARB_disconnect_from_db is never called for it)
1309#endif // DEBUG
1310    init_Advisor(aw_root);
1311    return aw_root;
1312}
1313
1314void AWT_trigger_remote_action(UNFIXED, GBDATA *gb_main, const char *remote_action_spec) {
1315    /*! trigger one or several action(s) (e.g. menu entries) in remote applications
1316     * @param gb_main             database root
1317     * @param remote_action_spec  "app:action[;app:action]*"
1318     */
1319
1320    ConstStrArray appAction;
1321    GBT_split_string(appAction, remote_action_spec, ";", SPLIT_DROPEMPTY);
1322
1323    GB_ERROR error = NULp;
1324    if (appAction.empty()) {
1325        error = "No action found";
1326    }
1327    else {
1328        for (unsigned a = 0; a<appAction.size() && !error; ++a) {
1329            ConstStrArray cmd;
1330            GBT_split_string(cmd, appAction[a], ":", SPLIT_DROPEMPTY);
1331
1332            if (cmd.size() != 2) {
1333                error = GBS_global_string("Invalid action '%s'", appAction[a]);
1334            }
1335            else {
1336                const char *app    = cmd[0];
1337                const char *action = cmd[1];
1338
1339                ARB_timeout after(2000, MS);
1340                error = GBT_remote_action_with_timeout(gb_main, app, action, &after, false);
1341            }
1342        }
1343    }
1344
1345    aw_message_if(error);
1346}
1347
1348// ------------------------
1349//      callback guards
1350
1351static void before_callback_guard() {
1352    if (GB_have_error()) {
1353        GB_ERROR error = GB_await_error();
1354        aw_message(GBS_global_string("Error not clear before calling callback\n"
1355                                     "Unhandled error was:\n"
1356                                     "%s", error));
1357#if defined(DEVEL_RALF)
1358        arb_assert(0);
1359#endif // DEVEL_RALF
1360    }
1361}
1362static void after_callback_guard() {
1363    if (GB_have_error()) {
1364        GB_ERROR error = GB_await_error();
1365        aw_message(GBS_global_string("Error not handled by callback!\n"
1366                                     "Unhandled error was:\n"
1367                                     "'%s'", error));
1368#if defined(DEVEL_RALF)
1369        arb_assert(0);
1370#endif // DEVEL_RALF
1371    }
1372}
1373
1374void AWT_install_cb_guards() {
1375    arb_assert(!GB_have_error());
1376    AW_cb::set_AW_cb_guards(before_callback_guard, after_callback_guard);
1377}
1378void AWT_install_postcb_cb(AW_postcb_cb postcb_cb) {
1379    AW_cb::set_AW_postcb_cb(postcb_cb);
1380}
1381
Note: See TracBrowser for help on using the repository browser.