source: branches/lib/NTREE/NT_main.cxx

Last change on this file was 19580, checked in by westram, 3 hours ago
  • move prototypes (from AWT) to new header in SL/APP:
    • fix includes
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.2 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : NT_main.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "NT_local.h"
12
13#include <mg_merge.hxx>
14#include <awti_import.hxx>
15#include <insdel.h>
16#include <macros.hxx>
17#include <mod_rlimit.h>
18#include <TreeDisplay.hxx>
19
20#include <app.hxx>
21#include <awt_sel_boxes.hxx>
22#include <awt_TreeAwars.hxx>
23
24#include <aw_advice.hxx>
25#include <aw_question.hxx>
26#include <aw_awars.hxx>
27#include <aw_edit.hxx>
28#include <aw_file.hxx>
29#include <aw_msg.hxx>
30#include <aw_root.hxx>
31#include <aw_global_awars.hxx>
32#include <aw_system.hxx>
33
34#include <adGene.h>
35
36#include <arb_version.h>
37#include <arb_progress.h>
38#include <arb_file.h>
39
40#include <signal.h>
41
42using namespace std;
43
44AW_HEADER_MAIN
45
46NT_global GLOBAL;
47
48// NT_format_all_alignments may be called after any operation which causes
49// unformatted alignments (e.g importing sequences)
50//
51// It tests all alignments whether they need to be formatted
52// and asks the user if they should be formatted.
53
54GB_ERROR NT_format_all_alignments(GBDATA *gb_main) {
55    GB_ERROR            err = NULp;
56    GB_transaction      ta(gb_main);
57    GB_topSecurityLevel unsecured(gb_main);
58
59    long ali_count = GBT_count_alignments(gb_main);
60    if (ali_count) {
61        arb_progress progress("Formatting alignments", ali_count);
62        err = GBT_check_data(gb_main, NULp);
63
64        AW_repeated_question question;
65        question.add_help("prompt/format_alignments.hlp");
66
67        GBDATA *gb_presets = GBT_get_presets(gb_main);
68        for (GBDATA *gb_ali = GB_entry(gb_presets, "alignment");
69             gb_ali && !err;
70             gb_ali        = GB_nextEntry(gb_ali))
71        {
72            GBDATA *gb_aligned = GB_search(gb_ali, "aligned", GB_INT);
73
74            if (GB_read_int(gb_aligned) == 0) { // sequences in alignment are not formatted
75                enum FormatAction {
76                    FA_ASK_USER   = 0, // ask user
77                    FA_FORMAT_ALL = 1, // format automatically w/o asking
78                    FA_SKIP_ALL   = 2, // skip automatically w/o asking
79                };
80                FormatAction  format_action = FA_ASK_USER;
81                GBDATA       *gb_ali_name   = GB_entry(gb_ali, "alignment_name");
82                const char   *ali_name      = GB_read_char_pntr(gb_ali_name);
83
84                {
85                    bool    is_ali_genom   = strcmp(ali_name, GENOM_ALIGNMENT) == 0;
86                    GBDATA *gb_auto_format = GB_entry(gb_ali, "auto_format");
87
88                    if (gb_auto_format) {
89                        format_action = FormatAction(GB_read_int(gb_auto_format));
90                        if (is_ali_genom) {
91                            if (format_action != FA_SKIP_ALL) {
92                                format_action = FA_SKIP_ALL; // always skip ali_genom
93                                err           = GB_write_int(gb_auto_format, FA_SKIP_ALL);
94                            }
95                        }
96                    }
97                    else if (is_ali_genom) {
98                        format_action = FA_SKIP_ALL;
99                        err           = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);  // always skip
100                    }
101                }
102
103                bool perform_format = false;
104                if (!err) {
105                    switch (format_action) {
106                        case FA_FORMAT_ALL: perform_format = true; break;
107                        case FA_SKIP_ALL:   perform_format = false; break;
108                        default: {
109                            char *qtext  = GBS_global_string_copy("Alignment '%s' is not formatted. Format?", ali_name);
110                            int   answer = question.get_answer("format_alignments", qtext, "Format,Skip,Always format,Always skip", "all", false);
111
112                            switch (answer) {
113                                case 2:
114                                    err = GBT_write_int(gb_ali, "auto_format", FA_FORMAT_ALL);
115                                    // fall-through
116                                case 0:
117                                    perform_format = true;
118                                    break;
119
120                                case 3:
121                                    err = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);
122                                    break;
123                            }
124
125                            free(qtext);
126                            break;
127                        }
128                    }
129                }
130                if (!err && perform_format) {
131                    err = ARB_format_alignment(gb_main, ali_name);
132                }
133            }
134            progress.inc_and_check_user_abort(err);
135        }
136
137        if (err) progress.done();
138    }
139
140    err = ta.close(err);
141    return err;
142}
143
144
145// --------------------------------------------------------------------------------
146
147
148static GB_ERROR nt_check_database_consistency() {
149    // called once on ARB_NTREE startup
150    arb_progress cons_progress("Checking consistency");
151
152    GB_ERROR err  = NT_format_all_alignments(GLOBAL.gb_main);
153    if (!err) err = NT_repair_DB(GLOBAL.gb_main);
154
155    return err;
156}
157
158__ATTR__USERESULT static GB_ERROR startup_mainwindow_and_dbserver(AW_root *aw_root, const char *autorun_macro, TREE_canvas*& result_ntw) {
159    AWT_initTreeAwarRegistry(GLOBAL.gb_main);
160
161    GB_ERROR error = configure_macro_recording(aw_root, "ARB_NT", GLOBAL.gb_main); // @@@ problematic if called from startup-importer
162    if (!error) {
163        result_ntw = NT_create_main_window(aw_root);
164        if (GB_is_server(GLOBAL.gb_main)) {
165            error = nt_check_database_consistency();
166            if (!error) NT_repair_userland_problems();
167        }
168    }
169
170    if (!error) {
171        error = GBK_system("arb_macro_compatibility.sh &"); // run async to avoid (DB) lock problems
172    }
173
174    if (!error && autorun_macro) execute_macro(aw_root, autorun_macro); // @@@ triggering execution here is ok, but its a bad place to pass 'autorun_macro'. Should be handled more generally
175
176    return error;
177}
178
179static ARB_ERROR load_and_startup_main_window(AW_root *aw_root, const char *autorun_macro) {
180    char *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
181    GLOBAL.gb_main  = GBT_open(db_server, "rw");
182
183    ARB_ERROR error;
184    if (!GLOBAL.gb_main) {
185        error = GB_await_error();
186    }
187    else {
188        aw_root->awar(AWAR_DB_PATH)->write_string(db_server);
189        aw_root->awar(AWAR_SAVED_DB_PATH)->write_string(db_server);
190
191#define MAXNAMELEN 35
192        int len = strlen(db_server);
193        if (len>MAXNAMELEN) {
194            char *nameOnly = strrchr(db_server, '/');
195            if (nameOnly) {
196                nameOnly++;
197                len -= (nameOnly-db_server);
198                memmove(db_server, nameOnly, len+1);
199                if (len>MAXNAMELEN) {
200                    strcpy(db_server+MAXNAMELEN-3, "...");
201                }
202            }
203        }
204#if defined(DEBUG)
205        AWT_announce_db_to_browser(GLOBAL.gb_main, GBS_global_string("ARB database (%s)", db_server));
206#endif // DEBUG
207
208        TREE_canvas *dummy   = NULp;
209        GB_ERROR     problem = startup_mainwindow_and_dbserver(aw_root, autorun_macro, dummy);
210        aw_message_if(problem); // no need to terminate ARB
211    }
212
213    free(db_server);
214    return error;
215}
216
217#define AWAR_DB_FILTER    AWAR_DBBASE "/filter"
218#define AWAR_DB_DIRECTORY AWAR_DBBASE "/directory"
219
220static void nt_delete_database(AW_window *aww) {
221    AW_root *aw_root   = aww->get_root();
222    char    *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
223
224    if (strlen(db_server)) {
225        if (aw_ask_sure(NULp, GBS_global_string("Are you sure to delete database %s\nNote: there is no way to undelete it afterwards", db_server))) {
226            GB_ERROR error = GB_delete_database(db_server);
227            if (error) {
228                aw_message(error);
229            }
230            else {
231                aw_root->awar(AWAR_DB_FILTER)->touch();
232            }
233        }
234    }
235    else {
236        aw_message("No database selected");
237    }
238    free(db_server);
239}
240
241static void start_main_window_after_import(AW_root *aw_root) {
242    GLOBAL.aw_root = aw_root;
243
244    GBDATA *gb_imported = AWTI_acquire_imported_DB_and_cleanup_importer();
245    nt_assert(gb_imported == GLOBAL.gb_main); // import-DB should already be used as main-DB
246    GLOBAL.gb_main      = gb_imported;
247
248    TREE_canvas *ntw = NULp;
249    aw_message_if(startup_mainwindow_and_dbserver(aw_root, NULp, ntw));
250
251    if (aw_root->awar(AWAR_IMPORT_AUTOCONF)->read_int()) {
252        NT_create_config_after_import(ntw, true);
253    }
254
255    aw_root->awar(AWAR_TREE_REFRESH)->touch();
256}
257
258static void nt_intro_start_existing(AW_window *aw_intro) {
259    aw_intro->hide();
260    ARB_ERROR error = load_and_startup_main_window(aw_intro->get_root(), NULp);
261    nt_assert(contradicted(error, got_macro_ability(aw_intro->get_root())));
262    if (error) {
263        aw_intro->show();
264        aw_popup_ok(error.deliver());
265    }
266    else {
267        error.expect_no_error();
268    }
269}
270
271static void nt_intro_start_merge(AW_window *aw_intro) {
272    AW_root    *aw_root    = aw_intro->get_root();
273    const char *dir        = aw_root->awar(AWAR_DB_DIRECTORY)->read_char_pntr();
274    char       *merge_args = GBS_global_string_copy("'%s' '%s'", dir, dir);
275
276    NT_restart(aw_root, merge_args); //  call arb_ntree as merge-tool on exit
277}
278
279static void nt_intro_start_import(AW_window *aw_intro) {
280    aw_intro->hide();
281
282    AW_root *aw_root = aw_intro->get_root();
283    aw_root->awar_string(AWAR_DB_PATH)->write_string("noname.arb");
284
285    AWTI_open_import_window(aw_root, NULp, true, NULp, makeRootCallback(start_main_window_after_import));
286    AWTI_set_importDB_pointer(GLOBAL.gb_main);
287
288    nt_assert(got_macro_ability(aw_root));
289}
290
291static AW_window *nt_create_intro_window(AW_root *awr) {
292    AW_window_simple *aws = new AW_window_simple;
293    aws->init(awr, "ARB_INTRO", "ARB INTRO");
294    aws->load_xfig("arb_intro.fig");
295
296    aws->callback(makeWindowCallback(NT_exit, EXIT_SUCCESS));
297    aws->at("close");
298    aws->create_button("EXIT", "Exit", "x");
299
300    aws->at("help");
301    aws->callback(makeHelpCallback("arb_intro.hlp"));
302    aws->create_button("HELP", "HELP", "H");
303
304    AW_create_standard_fileselection(aws, AWAR_DBBASE);
305
306    aws->button_length(0);
307
308    aws->at("logo");
309    aws->create_button(NULp, "#logo.xpm");
310
311    aws->at("version");
312    aws->create_button(NULp, GBS_global_string("Version " ARB_VERSION "\n(" ARB_BUILD_DATE ")")); // version + date
313
314    aws->at("copyright");
315    aws->create_button(NULp, GBS_global_string("(C) 1993-" ARB_BUILD_YEAR));
316
317    aws->at("old");
318    aws->callback(nt_intro_start_existing);
319    aws->create_autosize_button("OPEN_SELECTED", "OPEN SELECTED", "O");
320
321    aws->at("del");
322    aws->callback(nt_delete_database);
323    aws->create_autosize_button("DELETE_SELECTED", "DELETE SELECTED");
324
325    aws->at("new_complex");
326    aws->callback(nt_intro_start_import);
327    aws->create_autosize_button("CREATE_AND_IMPORT", "CREATE AND IMPORT", "I");
328
329    aws->at("merge");
330    aws->callback(nt_intro_start_merge);
331    aws->create_autosize_button("MERGE_TWO_DATABASES", "MERGE TWO ARB DATABASES", "O");
332
333    aws->at("expert");
334    aws->create_toggle(AWAR_EXPERT);
335
336    return aws;
337}
338
339static void AWAR_DB_PATH_changed_cb(AW_root *awr) {
340    static bool avoid_recursion = false;
341
342    if (!avoid_recursion) {
343        LocallyModify<bool> flag(avoid_recursion, true);
344
345        char *value  = awr->awar(AWAR_SAVED_DB_PATH)->read_string();
346        char *lslash = strrchr(value, '/');
347
348        char *name = lslash ? lslash+1 : value;
349#if defined(DEBUG)
350        printf("writing '%s' to AWAR_DB_NAME\n", name);
351#endif // DEBUG
352        awr->awar(AWAR_DB_NAME)->write_string(name);
353
354        if (lslash) {               // update value of directory
355            lslash[0] = 0;
356            awr->awar(AWAR_DB_DIRECTORY)->write_string(value);
357            lslash[0] = '/';
358        }
359
360        free(value);
361    }
362}
363
364class NtreeCommandLine : virtual Noncopyable {
365    int               arg_count;
366    char const*const* args;
367
368    bool help_requested;
369    bool do_import;
370
371    const char *macro_name;
372
373public:
374    NtreeCommandLine(int argc_, char const*const* argv_)
375        : arg_count(argc_-1),
376          args(argv_+1),
377          help_requested(false),
378          do_import(false),
379          macro_name(NULp)
380    {}
381
382    void shift() { ++args; --arg_count; }
383
384    int free_args() const { return arg_count; }
385    const char *get_arg(int num) const { return num<arg_count ? args[num] : NULp; }
386
387    bool wants_help() const { return help_requested; }
388    bool wants_import() const { return do_import; }
389    bool wants_merge() const { return arg_count == 2; }
390
391    const char *autorun_macro() const { return macro_name; }
392
393    void print_help(FILE *out) const {
394        fprintf(out,
395                "\n"
396                "arb_ntree version " ARB_VERSION_DETAILED "\n"
397                "(C) 1993-" ARB_BUILD_YEAR " The ARB-project\n"
398                "http://www.arb-home.de/\n"
399                "(version build by: " ARB_BUILD_USER "@" ARB_BUILD_HOST ")\n"
400                "\n"
401                "Possible usage:\n"
402                "  arb_ntree               => start ARB (intro)\n"
403                "  arb_ntree DB            => start ARB with DB\n"
404                "  arb_ntree DB1 DB2       => merge from DB1 into DB2\n"
405                "  arb_ntree --import FILE => import FILE into new database (FILE may be a filemask)\n"
406                "\n"
407                "Additional arguments possible with command lines above:\n"
408                "  --execute macroname     => execute macro 'macroname' after startup\n"
409                "\n"
410                "Each DB argument may be one of the following:\n"
411                "  database.arb            -> use existing or new database\n"
412                \":\"                     -> connect to database of a running instance of ARB\n"
413                "  directory               -> prompt for databases inside directory\n"
414                "  filemask                -> also prompt for DB, but more specific (e.g. \"prot*.arb\")\n"
415                "\n"
416            );
417    }
418
419    ARB_ERROR parse() {
420        ARB_ERROR error;
421
422        while (!error && arg_count>0 && args[0][0] == '-') {
423            const char *opt = args[0]+1;
424            if (opt[0] == '-') ++opt; // accept '-' or '--'
425            if (strcmp(opt, "help") == 0 || strcmp(opt, "h") == 0) { help_requested = true; }
426            else if (strcmp(opt, "execute") == 0) { shift(); macro_name = *args; }
427            else if (strcmp(opt, "import") == 0) { do_import = true; }
428
429            // bunch of test switches to provoke various ways to terminate (see also http://bugs.arb-home.de/ticket/538)
430            else if (strcmp(opt, "crash") == 0) { GBK_terminate("crash requested"); }
431            else if (strcmp(opt, "trap") == 0) { arb_assert(0); }
432            else if (strcmp(opt, "sighup") == 0) { raise(SIGHUP); }
433            else if (strcmp(opt, "sigsegv") == 0) { raise(SIGSEGV); }
434            else if (strcmp(opt, "sigterm") == 0) { raise(SIGTERM); }
435            else if (strcmp(opt, "fail") == 0) { exit(EXIT_FAILURE); }
436            else if (strcmp(opt, "exit") == 0) { exit(EXIT_SUCCESS); }
437            // end of test switches ----------------------------------------
438
439            else error = GBS_global_string("Unknown switch '%s'", args[0]);
440            shift();
441        }
442        // non-switch arguments remain in arg_count/args
443        if (!error) {
444            if (do_import && arg_count != 1) error = "expected exactly one file-name or file-mask behind --import";
445            else if (arg_count>2)            error = "too many stray arguments given (max. 2 accepted)";
446        }
447        return error;
448    }
449};
450
451enum ArgType { // args that might be specified on command line (for DB or FILE; see above)
452    RUNNING_DB,
453    DIRECTORY,
454    EXISTING_DB,
455    NEW_DB,
456    FILEMASK,
457    EXISTING_FILE,
458    UNKNOWN_ARG,
459};
460
461inline bool has_arb_suffix(const char *arg) {
462    const char *suffix = strstr(arg, ".arb");
463    if (suffix) {
464        return strcmp(suffix, ".arb") == 0;
465    }
466    return false;
467}
468
469static ArgType detectArgType(const char *arg) {
470    if (strcmp(arg, ":") == 0) return RUNNING_DB;
471    if (GB_is_directory(arg)) return DIRECTORY;
472    if (strpbrk(arg, "*?")) return FILEMASK;
473    if (has_arb_suffix(arg)) {
474        return GB_is_regularfile(arg) ? EXISTING_DB : NEW_DB;
475    }
476
477    GB_ERROR load_file_err = GBT_check_arb_file(arg);
478    if (!load_file_err) return EXISTING_DB;
479
480    return GB_is_regularfile(arg) ? EXISTING_FILE : UNKNOWN_ARG;
481}
482
483enum RunMode { NORMAL, IMPORT, MERGE, BROWSE };
484
485#define ABORTED_BY_USER "aborted by user"
486
487static ARB_ERROR check_argument_for_mode(const char *database, char *&browser_startdir, RunMode& mode) {
488    // Check whether 'database' is a
489    // - ARB database
490    // - directory name
491    // - file to import
492    //
493    // Modify 'mode' occordingly.
494    // Set 'browser_startdir'
495
496    ARB_ERROR error;
497    if (mode != IMPORT) {
498        if (!database) mode = BROWSE;
499        else {
500            GB_ERROR load_file_err = GBT_check_arb_file(database);
501            if (load_file_err) {
502                char *full_path = AW_unfold_path("PWD", database);
503
504                enum Action { LOAD_DB, CONVERT_DB, BROWSE_DB, EXIT, NOIDEA };
505                Action action = NOIDEA;
506                if (GB_is_directory(full_path)) {
507                    action = BROWSE_DB;
508                }
509                else {
510                    if (!GB_is_regularfile(full_path)) {
511                        const char *msg = GBS_global_string("'%s' is neither a known option nor a legal file- or directory-name.\n(Error: %s)",
512                                                            full_path, load_file_err);
513
514                        int ans = aw_question(NULp, msg, "Browser,Exit");
515                        action  = ans ? EXIT : BROWSE_DB;
516                    }
517                    else {
518                        const char *msg = GBS_global_string("Your file is not an original arb file\n(%s)", load_file_err);
519                        action          = (Action)aw_question(NULp, msg, "Continue (dangerous),Start Converter,Browser,Exit");
520                    }
521                }
522
523                switch (action) {
524                    case CONVERT_DB:    mode = IMPORT; break;
525                    case LOAD_DB:       break;
526                    case NOIDEA:        nt_assert(0); FALLTHROUGH; // in NDEBUG
527                    case EXIT:          error = ABORTED_BY_USER; break;
528                    case BROWSE_DB: {
529                        char *dir = nulldup(full_path);
530                        while (dir && !GB_is_directory(dir)) freeset(dir, AW_extract_directory(dir));
531
532                        if (dir) {
533                            nt_assert(GB_is_directory(dir));
534                            reassign(browser_startdir, dir);
535                            mode = BROWSE;
536                        }
537                        free(dir);
538                        break;
539                    }
540
541                }
542                free(full_path);
543            }
544        }
545    }
546
547    return error;
548}
549
550class SelectedDatabase : virtual Noncopyable {
551    GBDATA*&  gb_main;
552    char     *name;
553    ArgType   type;
554    char     *role;
555
556    void fix_name() {
557        if (type != RUNNING_DB && name[0]) {
558            const char *changed;
559
560            if (type == NEW_DB) {
561                changed = GB_unfold_in_directory(GB_getcwd(), name);
562            }
563            else {
564                changed = GB_canonical_path(name);
565            }
566
567            if (strcmp(name, changed) != 0) {
568                reselect_file(changed);
569            }
570        }
571    }
572
573public:
574    SelectedDatabase(GBDATA*& gb_main_, const char *name_, const char *role_)
575        : gb_main(gb_main_),
576          name(ARB_strdup(name_)),
577          type(detectArgType(name)),
578          role(ARB_strdup(role_))
579    {
580        fix_name();
581    }
582    ~SelectedDatabase() {
583        free(role);
584        free(name);
585    }
586
587    ArgType arg_type() const { return type; }
588
589    bool needs_to_prompt() const {
590        return type == DIRECTORY || type == FILEMASK || type == UNKNOWN_ARG;
591    }
592
593    const char *get_fullname() const { return name; }
594    const char *get_nameonly() const {
595        const char *slash = strrchr(name, '/');
596        return slash ? slash+1 : name;
597    }
598    const char *get_role() const { return role; }
599    const char *get_description() const { return GBS_global_string("%s (%s)", get_role(), get_nameonly()); }
600
601    ArgType get_type() const { return type; }
602
603    const char *get_dir() const {
604        const char *dir = NULp;
605        switch (type) {
606            case DIRECTORY:
607                dir = get_fullname();
608                break;
609
610            case NEW_DB:
611            case FILEMASK:
612            case EXISTING_FILE:
613            case EXISTING_DB: {
614                char *dir_copy;
615                GB_split_full_path(name, &dir_copy, NULp, NULp, NULp);
616
617                static SmartCharPtr dir_store;
618                dir_store = dir_copy;
619
620                dir = dir_copy;
621                break;
622            }
623            case UNKNOWN_ARG:
624            case RUNNING_DB:
625                dir = ".";
626                break;
627        }
628        return dir;
629    }
630
631    const char *get_mask() const {
632        const char *mask;
633        switch (type) {
634            case FILEMASK: {
635                char *mask_copy;
636                GB_split_full_path(name, NULp, &mask_copy, NULp, NULp);
637
638                static SmartCharPtr mask_store;
639                mask_store = mask_copy;
640
641                mask = mask_copy;
642                break;
643            }
644            default:
645                mask = ".arb";
646                break;
647        }
648        return mask;
649    }
650
651    void reselect_file(const char *file) {
652        freedup(name, file);
653        type = detectArgType(name);
654        fix_name();
655    }
656    void reselect_from_awar(AW_root *aw_root, const char *awar_name) {
657        if (awar_name) {
658            const char *file = aw_root->awar(awar_name)->read_char_pntr();
659            if (file) reselect_file(file);
660        }
661    }
662
663    GB_ERROR open_db_for_merge(bool is_source_db);
664    void close_db() { if (gb_main) GB_close(gb_main); }
665};
666
667GB_ERROR SelectedDatabase::open_db_for_merge(bool is_source_db) {
668    GB_ERROR    error    = NULp;
669    const char *openMode = "rw";
670
671    switch (get_type()) {
672        case DIRECTORY:
673        case FILEMASK:
674            GBK_terminate("Program logic error (should have been prompted for explicit DB name)");
675            break;
676
677        case UNKNOWN_ARG:
678        case EXISTING_FILE:
679            error = GBS_global_string("'%s' is no arb-database", get_fullname());
680            break;
681
682        case NEW_DB:
683            if (is_source_db) {
684                error = GBS_global_string("'%s' has to exist", get_fullname());
685                break;
686            }
687            openMode = "crw"; // allow to create DB
688            break;
689
690        case EXISTING_DB:
691        case RUNNING_DB:
692            break;
693    }
694
695    if (!error) {
696        gb_main             = GBT_open(get_fullname(), openMode);
697        if (!gb_main) error = GB_await_error();
698        else {
699            IF_DEBUG(AWT_announce_db_to_browser(gb_main, get_description()));
700        }
701    }
702
703    return error;
704}
705
706struct merge_scheme : virtual Noncopyable {
707    SelectedDatabase *src;
708    SelectedDatabase *dst;
709
710    char *awar_src;
711    char *awar_dst;
712
713    GB_ERROR error;
714
715    merge_scheme(SelectedDatabase *src_, SelectedDatabase *dst_)
716        : src(src_),
717          dst(dst_),
718          awar_src(NULp),
719          awar_dst(NULp),
720          error(NULp)
721    {}
722    ~merge_scheme() {
723        if (error) {
724            src->close_db();
725            dst->close_db();
726        }
727        delete src;
728        delete dst;
729        free(awar_src);
730        free(awar_dst);
731    }
732
733
734    void open_dbs() {
735        if (!error) error = src->open_db_for_merge(true);
736        if (!error) error = dst->open_db_for_merge(false);
737    }
738
739    void fix_dst(AW_root *aw_root) { dst->reselect_from_awar(aw_root, awar_dst); }
740    void fix_src(AW_root *aw_root) { src->reselect_from_awar(aw_root, awar_src); }
741
742    bool knows_dbs() const { return !src->needs_to_prompt() && !dst->needs_to_prompt(); }
743};
744
745static bool merge_tool_running_as_client = true; // default to safe state (true avoids call of 'arb_clean' at exit)
746static void exit_from_merge(const char *restart_args) {
747    if (merge_tool_running_as_client) { // there is a main ARB running
748        if (restart_args) NT_start(restart_args, true);
749        exit(EXIT_SUCCESS); // exit w/o killing clients (in contrast to NT_exit())
750    }
751    else {
752        NT_restart(AW_root::SINGLETON, null2empty(restart_args));
753        nt_assert(0);
754    }
755}
756
757static void merge_startup_abort_cb(AW_window*) {
758    fputs("Error: merge aborted by user\n", stderr);
759    exit_from_merge(NULp);
760}
761
762static AW_window *merge_startup_error_window(AW_root *aw_root, GB_ERROR error) {
763    AW_window_simple *aw_msg = new AW_window_simple;
764
765    aw_msg->init(aw_root, "arb_merge_error", "ARB merge error");
766    aw_msg->recalc_size_atShow(AW_RESIZE_DEFAULT); // force size recalc (ignores user size)
767
768    aw_msg->at(10, 10);
769    aw_msg->auto_space(10, 10);
770
771    aw_msg->create_autosize_button(NULp, error, "", 2);
772    aw_msg->at_newline();
773
774    aw_msg->callback(merge_startup_abort_cb);
775    aw_msg->create_autosize_button("OK", "Ok", "O", 2);
776
777    aw_msg->window_fit();
778
779    return aw_msg;
780}
781static AW_window *startup_merge_main_window(AW_root *aw_root, merge_scheme *ms) {
782    ms->fix_src(aw_root);
783    ms->fix_dst(aw_root);
784
785    if (ms->knows_dbs()) {
786        ms->open_dbs();
787    }
788    else {
789        ms->error = "Need two databases to merge";
790    }
791
792    AW_window *aw_result;
793    if (!ms->error) {
794        MERGE_create_db_file_awars(aw_root, AW_ROOT_DEFAULT, ms->src->get_fullname(), ms->dst->get_fullname());
795        bool dst_is_new = ms->dst->arg_type() == NEW_DB;
796        aw_result       = MERGE_create_main_window(aw_root, dst_is_new, exit_from_merge);
797
798        nt_assert(got_macro_ability(aw_root));
799    }
800    else {
801        aw_result = merge_startup_error_window(aw_root, ms->error);
802    }
803    delete ms; // was allocated in startup_gui()
804    return aw_result;
805}
806
807static AW_window *startup_merge_prompting_for_nonexplicit_dst_db(AW_root *aw_root, merge_scheme *ms) {
808    AW_window *aw_result;
809    if (ms->dst->needs_to_prompt()) {
810        aw_result = awt_create_load_box(aw_root, "Select", ms->dst->get_role(),
811                                        ms->dst->get_dir(), ms->dst->get_mask(),
812                                        &(ms->awar_dst),
813                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_main_window, ms)),
814                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
815    }
816    else {
817        aw_result = startup_merge_main_window(aw_root, ms);
818    }
819    return aw_result;
820}
821
822static AW_window *startup_merge_prompting_for_nonexplicit_dbs(AW_root *aw_root, merge_scheme *ms) {
823    // if src_spec or dst_spec needs to be prompted for -> startup prompters with callbacks bound to continue
824    // otherwise just startup merge
825
826    AW_window *aw_result;
827    if (ms->src->needs_to_prompt()) {
828        aw_result = awt_create_load_box(aw_root, "Select", ms->src->get_role(),
829                                        ms->src->get_dir(), ms->src->get_mask(),
830                                        &(ms->awar_src),
831                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_prompting_for_nonexplicit_dst_db, ms)),
832                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
833    }
834    else {
835        aw_result = startup_merge_prompting_for_nonexplicit_dst_db(aw_root, ms);
836    }
837    return aw_result;
838}
839
840static void startup_gui(NtreeCommandLine& cl, ARB_ERROR& error) {
841    {
842        char *message = ARB_strdup(GB_path_in_ARBLIB("message"));
843        char *stamp   = ARB_strdup(GB_path_in_arbprop("msgtime"));
844        if (GB_time_of_file(message)>GB_time_of_file(stamp)) {
845            AW_edit(message);
846            AW_system(GBS_global_string("touch %s", stamp));
847        }
848        free(stamp);
849        free(message);
850    }
851
852    // create some early awars
853    // Note: normally you don't like to add your awar-init-function here, but into nt_create_all_awars()
854
855    AW_root* aw_root = GLOBAL.aw_root;
856
857    AW_create_fileselection_awars(aw_root, AWAR_DBBASE, "", ".arb", "noname.arb"); // creates AWAR_DB_PATH
858    aw_root->awar_string(AWAR_DB_NAME, "noname.arb");
859
860    aw_root->awar_int(AWAR_EXPERT, 0);
861
862    AWT_install_cb_guards();
863
864    ARB_declare_global_awars(aw_root, AW_ROOT_DEFAULT);
865    aw_root->awar(AWAR_SAVED_DB_PATH)->add_callback(AWAR_DB_PATH_changed_cb);
866
867    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside prompters that may popup
868    if (!error) {
869        nt_assert(!cl.wants_help());
870
871        RunMode mode = NORMAL;
872
873        if      (cl.wants_merge())  mode = MERGE;
874        else if (cl.wants_import()) mode = IMPORT;
875
876        if (mode == MERGE) {
877            MERGE_create_all_awars(aw_root, AW_ROOT_DEFAULT);
878
879            merge_scheme *ms = new merge_scheme( // freed in startup_merge_main_window()
880                new SelectedDatabase(GLOBAL_gb_src, cl.get_arg(0), "Source DB for merge"),
881                new SelectedDatabase(GLOBAL_gb_dst, cl.get_arg(1), "Destination DB for merge"));
882
883            merge_tool_running_as_client = ms->src->arg_type() == RUNNING_DB || ms->dst->arg_type() == RUNNING_DB;
884
885            if (ms->src->arg_type() == RUNNING_DB && ms->dst->arg_type() == RUNNING_DB) {
886                error = "You cannot merge from running to running DB";
887            }
888            else {
889                aw_root->setUserActionTracker(new NullTracker); // no macro recording during startup of merge tool (file prompts)
890                AW_window *aww = startup_merge_prompting_for_nonexplicit_dbs(aw_root, ms);
891
892                nt_assert(contradicted(aww, error));
893
894                if (!error) {
895                    aww->show();
896                    aw_root->main_loop();
897                    nt_assert(0);
898                }
899            }
900        }
901        else {
902            const char *database         = NULp;
903            char       *browser_startdir = ARB_strdup(".");
904
905            if (cl.free_args() > 0) database = cl.get_arg(0);
906
907            error = check_argument_for_mode(database, browser_startdir, mode);
908            if (!error) {
909                if (mode == IMPORT) {
910                    AWTI_open_import_window(aw_root, database, true, NULp, makeRootCallback(start_main_window_after_import));
911                    AWTI_set_importDB_pointer(GLOBAL.gb_main);
912
913                    nt_assert(got_macro_ability(aw_root));
914                    aw_root->main_loop();
915                }
916                else if (mode == NORMAL) {
917                    aw_root->awar(AWAR_DB_PATH)->write_string(database);
918                    error = load_and_startup_main_window(aw_root, cl.autorun_macro());
919                    if (!error) {
920                        nt_assert(got_macro_ability(aw_root));
921                        aw_root->main_loop();
922                    }
923                }
924                else if (mode == BROWSE) {
925                    aw_root->awar(AWAR_DB_DIRECTORY)->write_string(browser_startdir);
926                    char *latest = GB_find_latest_file(browser_startdir, "/\\.(arb|a[0-9]{2})$/");
927                    if (latest) {
928                        int l = strlen(latest);
929                        strcpy(latest+l-3, "arb");
930                        aw_root->awar(AWAR_DB_PATH)->write_string(latest);
931                        free(latest);
932                    }
933                    nt_create_intro_window(aw_root)->show();
934                    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside intro window
935                    aw_root->main_loop();
936                }
937            }
938            free(browser_startdir);
939        }
940    }
941
942    if (error) {
943        const char *msg = error.preserve();
944        if (strcmp(msg, ABORTED_BY_USER) != 0) aw_popup_ok(msg);
945    }
946}
947
948int ARB_main(int argc, char *argv[]) {
949    {
950        // i really dont want 'arb_ntree --help' to startup the GUI
951        // hack: parse CL twice
952        NtreeCommandLine cl(argc, argv);
953        bool             cl_ok = !cl.parse().deliver();
954        if (cl_ok) {
955            if (cl.wants_help()) {
956                cl.print_help(stderr);
957                return EXIT_SUCCESS;
958            }
959        }
960    }
961
962    aw_initstatus();
963    GB_set_verbose();
964
965    GB_shell shell;
966    AW_root *aw_root = AWT_create_root("ntree.arb", "ARB_NT", need_macro_ability());
967
968    GLOBAL.aw_root = aw_root;
969
970    NtreeCommandLine cl(argc, argv);
971    ARB_ERROR        error = cl.parse();
972
973    if (!error) {
974        if (cl.wants_help()) {
975            cl.print_help(stderr);
976        }
977        else {
978            ModRLimit increase_stacksize(RLIMIT_STACK, TREEDISP_STACKSIZE);
979            startup_gui(cl, error);
980        }
981    }
982
983    int exitcode = EXIT_SUCCESS;
984    if (error) {
985        exitcode = EXIT_FAILURE;
986        fprintf(stderr, "arb_ntree: Error: %s\n", error.deliver());
987    }
988    else {
989        error.expect_no_error();
990    }
991
992    delete aw_root;
993
994    return exitcode;
995}
996
Note: See TracBrowser for help on using the repository browser.