source: branches/species/SL/MASKS/input_mask.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: 93.5 KB
Line 
1//  ==================================================================== //
2//                                                                       //
3//    File      : input_mask.cxx                                         //
4//    Purpose   : General input masks                                    //
5//                                                                       //
6//                                                                       //
7//  Coded by Ralf Westram (coder@reallysoft.de) in August 2001           //
8//  Copyright Department of Microbiology (Technical University Munich)   //
9//                                                                       //
10//  Visit our web site at: http://www.arb-home.de/                       //
11//                                                                       //
12//                                                                       //
13//  ==================================================================== //
14
15#include "input_mask_internal.hxx"
16
17#include <www.hxx>
18
19#include <aw_edit.hxx>
20#include <aw_file.hxx>
21#include <aw_msg.hxx>
22#include <aw_question.hxx>
23
24#include <arbdbt.h>
25#include <gb_aci.h>
26#include <ad_cb.h>
27
28#include <arb_file.h>
29#include <app.hxx>
30
31#include <sys/stat.h>
32#include <sys/types.h>
33#include <dirent.h>
34#include <climits>
35#include <set>
36
37using namespace std;
38
39static const char *awt_itemtype_names[AWT_IT_TYPES+1] = {
40    "Unknown",
41    "Species", "Organism", "Gene", "Experiment",
42    "<overflow>"
43};
44
45#define SEC_XBORDER    3        // space left/right of NEW_SECTION line
46#define SEC_YBORDER    4        // space above/below
47#define SEC_LINE_WIDTH 1        // line width
48
49#define MIN_TEXTFIELD_SIZE 1
50#define MAX_TEXTFIELD_SIZE 1000
51
52#define AWT_SCOPE_LOCAL  0
53#define AWT_SCOPE_GLOBAL 1
54
55awt_input_mask_id_list awt_input_mask_global::global_ids; // stores global ids
56
57// ---------------------
58//      global awars
59
60#define AWAR_INPUT_MASK_BASE   "tmp/inputMask"
61#define AWAR_INPUT_MASK_NAME   AWAR_INPUT_MASK_BASE"/name"
62#define AWAR_INPUT_MASK_ITEM   AWAR_INPUT_MASK_BASE"/item"
63#define AWAR_INPUT_MASK_SCOPE  AWAR_INPUT_MASK_BASE"/scope"
64#define AWAR_INPUT_MASK_HIDDEN AWAR_INPUT_MASK_BASE"/hidden"
65
66#define AWAR_INPUT_MASKS_EDIT_ENABLED AWAR_INPUT_MASK_BASE"/edit_enabled"
67
68static bool global_awars_created = false;
69
70static void create_global_awars(AW_root *awr) {
71    msk_assert(!global_awars_created);
72    awr->awar_string(AWAR_INPUT_MASK_NAME, "new");
73    awr->awar_string(AWAR_INPUT_MASK_ITEM, awt_itemtype_names[AWT_IT_SPECIES]);
74    awr->awar_int(AWAR_INPUT_MASK_SCOPE, 0);
75    awr->awar_int(AWAR_INPUT_MASK_HIDDEN, 0);
76    awr->awar_int(AWAR_INPUT_MASKS_EDIT_ENABLED, 0);
77    global_awars_created = true;
78}
79
80// ------------------------------------------
81//      Callbacks from database and awars
82
83static bool in_item_changed_callback  = false;
84static bool in_field_changed_callback = false;
85static bool in_awar_changed_callback  = false;
86
87static void item_changed_cb(GBDATA*, awt_linked_to_item *item_link, GB_CB_TYPE type) {
88    if (!in_item_changed_callback) { // avoid deadlock
89        LocallyModify<bool> flag(in_item_changed_callback, true);
90        if (type&GB_CB_DELETE) { // handled child was deleted
91            item_link->relink();
92        }
93        else if ((type&GB_CB_CHANGED_OR_SON_CREATED) == GB_CB_CHANGED_OR_SON_CREATED) {
94            // child was created (not only changed)
95            item_link->relink();
96        }
97        else if (type&GB_CB_CHANGED) { // only changed
98            item_link->general_item_change();
99        }
100    }
101}
102
103static void field_changed_cb(GBDATA*, awt_input_handler *handler, GB_CB_TYPE type) {
104    if (!in_field_changed_callback) { // avoid deadlock
105        LocallyModify<bool> flag(in_field_changed_callback, true);
106        if (type&GB_CB_DELETE) { // field was deleted from db -> relink this item
107            handler->relink();
108        }
109        else if (type&GB_CB_CHANGED) {
110            handler->db_changed();  // database entry was changed
111        }
112    }
113}
114
115static void awar_changed_cb(AW_root*, awt_mask_awar_item *item) {
116    if (!in_awar_changed_callback) { // avoid deadlock
117        LocallyModify<bool> flag(in_awar_changed_callback, true);
118        msk_assert(item);
119        if (item) item->awar_changed();
120    }
121}
122
123string awt_input_mask_global::generate_id(const string& mask_name_) {
124    string result;
125    result.reserve(mask_name_.length());
126    for (string::const_iterator p = mask_name_.begin(); p != mask_name_.end(); ++p) {
127        if (isalnum(*p)) result.append(1, *p);
128        else result.append(1, '_');
129    }
130    return result;
131}
132
133bool awt_input_mask_global::edit_allowed() const {
134    return !test_edit_enabled ||
135        get_root()->awar(AWAR_INPUT_MASKS_EDIT_ENABLED)->read_int() == 1;
136}
137
138void awt_input_mask_global::no_item_selected() const {
139    aw_message(GBS_global_string("This had no effect, because no %s is selected",
140                                 awt_itemtype_names[get_itemtype()]));
141}
142
143GB_ERROR awt_input_mask_id_list::add(const string& name, awt_mask_item *item) {
144    awt_mask_item *existing = lookup(name);
145    if (existing) return GBS_global_string("ID '%s' already exists", name.c_str());
146
147    id[name] = item;
148    return NULp;
149}
150GB_ERROR awt_input_mask_id_list::remove(const string& name) {
151    if (!lookup(name)) return GBS_global_string("ID '%s' does not exist", name.c_str());
152    id.erase(name);
153    return NULp;
154}
155
156awt_mask_item::awt_mask_item(awt_input_mask_global& global_)
157    : global(global_)
158{}
159
160awt_mask_item::~awt_mask_item() {
161    msk_assert(!has_name()); // you forgot to call remove_name()
162}
163
164GB_ERROR awt_mask_item::set_name(const string& name_, bool is_global) {
165    GB_ERROR error = NULp;
166    if (has_name()) {
167        error = GBS_global_string("Element already has name (%s)", get_name().c_str());
168    }
169    else {
170        name = new string(name_);
171        if (is_global) {
172            if (!mask_global().has_global_id(*name)) { // do not add if variable already defined elsewhere
173                error = mask_global().add_global_id(*name, this);
174            }
175        }
176        else {
177            error = mask_global().add_local_id(*name, this);
178        }
179    }
180    return error;
181}
182
183GB_ERROR awt_mask_item::remove_name() {
184    GB_ERROR error = NULp;
185    if (has_name()) {
186        error = mask_global().remove_id(*name);
187        name.setNull();
188    }
189    return error;
190}
191
192awt_mask_awar_item::awt_mask_awar_item(awt_input_mask_global& global_, const string& awar_base, const string& default_value, bool saved_with_properties)
193    : awt_mask_item(global_)
194{
195    const char *root_name;
196
197    if (saved_with_properties) root_name = "/input_masks";
198    else root_name = "/tmp/input_masks"; // awars starting with /tmp are not saved
199
200    awarName = GBS_global_string("%s/%s", root_name, awar_base.c_str());
201#if defined(DEBUG)
202    printf("awarName='%s'\n", awarName.c_str());
203#endif // DEBUG
204    mask_global().get_root()->awar_string(awarName.c_str(), default_value.c_str()); // create the awar
205    add_awarItem_callbacks();
206}
207
208void awt_mask_awar_item::add_awarItem_callbacks() {
209    AW_awar *var = awar();
210    msk_assert(var);
211    if (var) var->add_callback(makeRootCallback(awar_changed_cb, this));
212}
213void awt_mask_awar_item::remove_awarItem_callbacks() {
214    AW_awar *var = awar();
215    msk_assert(var);
216    if (var) var->remove_callback(makeRootCallback(awar_changed_cb, this));
217}
218
219awt_variable::awt_variable(awt_input_mask_global& global_, const string& id, bool is_global_, const string& default_value, GB_ERROR& error) :
220    awt_mask_awar_item(global_, generate_baseName(global_, id, is_global_), default_value, true),
221    is_global(is_global_)
222{
223    error = set_name(id, is_global);
224}
225
226awt_variable::~awt_variable() {}
227
228string awt_script::get_value() const {
229    string                        result;
230    AW_root                      *root     = mask_global().get_root();
231    const awt_item_type_selector *selector = mask_global().get_selector();
232    GBDATA                       *gb_main  = mask_global().get_gb_main();
233    GBDATA                       *gbd      = selector->current(root, gb_main);
234
235    if (gbd) {
236        char           *species_name    = root->awar(selector->get_self_awar())->read_string();
237        GB_transaction  tscope(gb_main);
238
239        char *val = GB_command_interpreter_in_env(species_name, script.c_str(), GBL_maybe_itemless_call_env(gb_main, gbd));
240        if (!val) {
241            aw_message(GB_await_error());
242            result = "<error>";
243        }
244        else {
245            result = val;
246            free(val);
247        }
248        free(species_name);
249    }
250    else {
251        result = "<undefined>";
252    }
253
254
255    return result;
256}
257
258GB_ERROR awt_script::set_value(const string& /* new_value */) {
259    return GBS_global_string("You cannot assign a value to script '%s'", has_name() ? get_name().c_str() : "<unnamed>");
260}
261
262GB_ERROR awt_linked_to_item::add_db_callbacks() {
263    GB_ERROR error = NULp;
264    if (gb_item) error = GB_add_callback(gb_item, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(item_changed_cb, this));
265    return error;
266}
267
268void awt_linked_to_item::remove_db_callbacks() {
269    if (!GB_inside_callback(gb_item, GB_CB_DELETE)) {
270        GB_remove_callback(gb_item, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(item_changed_cb, this));
271    }
272}
273
274awt_script_viewport::awt_script_viewport(awt_input_mask_global& global_, const awt_script *script_, const string& label_, long field_width_) :
275    awt_viewport(global_, generate_baseName(global_), "", false, label_),
276    script(script_),
277    field_width(field_width_)
278{}
279
280awt_script_viewport::~awt_script_viewport() {
281    unlink();
282}
283
284GB_ERROR awt_script_viewport::link_to(GBDATA *gb_new_item) {
285    GB_ERROR       error = NULp;
286    GB_transaction ta(mask_global().get_gb_main());
287
288    remove_awarItem_callbacks();    // unbind awar callbacks temporarily
289
290    if (item()) {
291        remove_db_callbacks(); // ignore result (if handled db-entry was deleted, it returns an error)
292        set_item(NULp);
293    }
294
295    if (gb_new_item) {
296        set_item(gb_new_item);
297        db_changed();
298        error = add_db_callbacks();
299    }
300
301    add_awarItem_callbacks();       // rebind awar callbacks
302
303    return error;
304}
305
306void awt_script_viewport::build_widget(AW_window *aws) {
307    const string& lab = get_label();
308    if (lab.length()) aws->label(lab.c_str());
309
310    aws->create_input_field(awar_name().c_str(), field_width);
311}
312
313void awt_script_viewport::db_changed() {
314    msk_assert(script);
315    string   current_value = script->get_value();
316    GB_ERROR error         = awt_mask_awar_item::set_value(current_value);
317
318    if (error) aw_message(error);
319}
320
321void awt_script_viewport::awar_changed() {
322    aw_message("It makes no sense to change the result of a script");
323}
324
325GB_ERROR awt_input_handler::add_db_callbacks() {
326    GB_ERROR error = awt_linked_to_item::add_db_callbacks();
327    if (item() && gbd) error = GB_add_callback(gbd, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(field_changed_cb, this));
328    return error;
329}
330void awt_input_handler::remove_db_callbacks() {
331    awt_linked_to_item::remove_db_callbacks();
332    if (item() && gbd && !GB_inside_callback(gbd, GB_CB_DELETE)) {
333        GB_remove_callback(gbd, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(field_changed_cb, this));
334    }
335}
336
337awt_input_handler::awt_input_handler(awt_input_mask_global& global_, const string& child_path_, GB_TYPES type_, const string& label_) :
338    awt_viewport(global_, generate_baseName(global_, child_path_), "", false, label_),
339    gbd(NULp),
340    child_path(child_path_),
341    db_type(type_),
342    in_destructor(false)
343{}
344
345awt_input_handler::~awt_input_handler() {
346    in_destructor = true;
347    unlink();
348}
349
350GB_ERROR awt_input_handler::link_to(GBDATA *gb_new_item) {
351    GB_ERROR       error = NULp;
352    GB_transaction ta(mask_global().get_gb_main());
353
354    remove_awarItem_callbacks(); // unbind awar callbacks temporarily
355
356    if (item()) {
357        remove_db_callbacks();  // ignore result (if handled db-entry was deleted, it returns an error)
358        set_item(NULp);
359        gbd = NULp;
360    }
361    else {
362        msk_assert(!gbd);
363    }
364
365    if (!gb_new_item && !in_destructor) { // crashes if we are in ~awt_input_handler
366        db_changed();
367    }
368
369    if (!error && gb_new_item) {
370        set_item(gb_new_item);
371        gbd = GB_search(item(), child_path.c_str(), GB_FIND);
372
373        db_changed();           // synchronize AWAR with DB-entry
374
375        error = add_db_callbacks();
376    }
377
378    add_awarItem_callbacks(); // rebind awar callbacks
379
380    return error;
381}
382
383void awt_string_handler::awar_changed() {
384    GBDATA   *gbdata    = data();
385    GBDATA   *gb_main   = mask_global().get_gb_main();
386    bool      relink_me = false;
387    GB_ERROR  error     = NULp;
388
389    GB_push_transaction(gb_main);
390
391    if (!mask_global().edit_allowed()) error = "Editing is disabled. Check the 'Enable edit' switch!";
392
393    if (!error && !gbdata) {
394        const char *child   = get_child_path().c_str();
395        const char *keypath = mask_global().get_selector()->getKeyPath();
396
397        if (item()) {
398            gbdata = GB_search(item(), child, GB_FIND);
399
400            if (!gbdata) {
401                GB_TYPES found_type = GBT_get_type_of_changekey(gb_main, child, keypath);
402                if (found_type != GB_NONE) set_type(found_type); // fix type if different
403
404                gbdata = GB_search(item(), child, type()); // here new items are created
405                if (found_type == GB_NONE) {
406                    error = GBT_add_new_changekey_to_keypath(gb_main, child, type(), keypath);
407                }
408                relink_me = true; //  @@@ only if child was created!!
409            }
410        }
411        else {
412            mask_global().no_item_selected();
413            aw_message(GBS_global_string("This had no effect, because no %s is selected",
414                                         awt_itemtype_names[mask_global().get_itemtype()]));
415        }
416    }
417
418    if (!error && gbdata) {
419        char     *content    = awar()->read_string();
420        GB_TYPES  found_type = GB_read_type(gbdata);
421        if (found_type != type()) set_type(found_type); // fix type if different
422
423        error = GB_write_autoconv_string(gbdata, awar2db(content).c_str());
424
425        free(content);
426    }
427
428    if (error) {
429        aw_message(error);
430        GB_abort_transaction(gb_main);
431        db_changed(); // write back old value
432    }
433    else {
434        GB_pop_transaction(gb_main);
435    }
436
437    if (relink_me) relink();
438}
439
440void awt_string_handler::db_changed() {
441    GBDATA *gbdata = data();
442    if (gbdata) { // gbdata may be zero, if field does not exist
443        GB_transaction  ta(mask_global().get_gb_main());
444        char           *content = GB_read_as_string(gbdata);
445        awar()->write_string(db2awar(content).c_str());
446        free(content);
447    }
448    else {
449        awar()->write_string(default_value.c_str());
450    }
451}
452
453// ----------------
454//      Widgets
455
456void awt_input_field::build_widget(AW_window *aws) {
457    const string& lab = get_label();
458    if (lab.length()) aws->label(lab.c_str());
459
460    aws->create_input_field(awar_name().c_str(), field_width);
461}
462
463void awt_text_viewport::build_widget(AW_window *aws) {
464    const string& lab = get_label();
465    if (lab.length()) aws->label(lab.c_str());
466
467    aws->create_input_field(awar_name().c_str(), field_width);
468}
469
470void awt_check_box::build_widget(AW_window *aws) {
471    const string& lab = get_label();
472    if (lab.length()) aws->label(lab.c_str());
473
474    aws->create_toggle(awar_name().c_str());
475}
476void awt_radio_button::build_widget(AW_window *aws) {
477    const AW_orientation orientation = vertical ? AW_VERTICAL : AW_HORIZONTAL;
478
479    const string& lab = get_label();
480    if (lab.length()) {
481        aws->create_toggle_field(awar_name().c_str(), lab.c_str(), orientation);
482    }
483    else {
484        aws->create_toggle_field(awar_name().c_str(), orientation);
485    }
486
487    vector<string>::const_iterator b   = buttons.begin();
488    vector<string>::const_iterator v   = values.begin();
489    int                            pos = 0;
490
491    for (; b != buttons.end() && v != values.end(); ++b, ++v, ++pos) {
492        void (AW_window::*ins_togg)(const char*, const char*, const char*);
493
494        if (pos == default_position) ins_togg = &AW_window::insert_default_toggle;
495        else ins_togg                         = &AW_window::insert_toggle;
496
497        (aws->*ins_togg)(b->c_str(), mask_global().hotkey(*b), b->c_str());
498    }
499
500    msk_assert(b == buttons.end() && v == values.end());
501
502    aws->update_toggle_field();
503}
504
505// -----------------------------------------
506//      Special AWAR <-> DB translations
507
508string awt_check_box::awar2db(const string& awar_content) const {
509    GB_TYPES typ = type();
510
511    if (awar_content == "yes") {
512        if (typ == GB_STRING) return "yes";
513        return "1";
514    }
515    else {
516        if (typ == GB_STRING) return "no";
517        return "0";
518    }
519}
520string awt_check_box::db2awar(const string& db_content) const {
521    if (db_content == "yes" || db_content == "1") return "yes";
522    if (db_content == "no" || db_content == "0") return "no";
523    return atoi(db_content.c_str()) ? "yes" : "no";
524}
525
526string awt_radio_button::awar2db(const string& awar_content) const {
527    vector<string>::const_iterator b   = buttons.begin();
528    vector<string>::const_iterator v   = values.begin();
529    for (; b != buttons.end() && v != values.end(); ++b, ++v) {
530        if (*b == awar_content) {
531            return *v;
532        }
533    }
534
535    return string("Unknown awar_content '")+awar_content+"'";
536}
537
538string awt_radio_button::db2awar(const string& db_content) const {
539    vector<string>::const_iterator b   = buttons.begin();
540    vector<string>::const_iterator v   = values.begin();
541    for (; b != buttons.end() && v != values.end(); ++b, ++v) {
542        if (*v == db_content) {
543            return *b;
544        }
545    }
546    return buttons[default_position];
547}
548
549string awt_numeric_input_field::awar2db(const string& awar_content) const {
550    long val = strtol(awar_content.c_str(), NULp, 10);
551
552    // correct numeric value :
553    if (val<min) val = min;
554    if (val>max) val = max;
555
556    return GBS_global_string("%li", val);
557}
558
559// -----------------------------------------
560//      Routines to parse user-mask file
561
562static GB_ERROR readLine(FILE *in, string& line, size_t& lineNo) {
563    const int  BUFSIZE = 8000;
564    char       buffer[BUFSIZE];
565    char      *res     = fgets(&buffer[0], BUFSIZE-1, in);
566    GB_ERROR   error   = NULp;
567
568    if (int err = ferror(in)) {
569        error = strerror(err);
570    }
571    else if (!res) {
572        error = "Unexpected end of file (@MASK_BEGIN or @MASK_END missing?)";
573    }
574    else {
575        msk_assert(res == buffer);
576        res += strlen(buffer);
577        if (res>buffer) {
578            size_t last = res-buffer;
579            if (buffer[last-1] == '\n') {
580                buffer[last-1] = 0;
581            }
582        }
583        line = buffer;
584        lineNo++; // increment lineNo
585
586        size_t last = line.find_last_not_of(" \t");
587        if (last != string::npos && line[last] == '\\') {
588            string next;
589            error = readLine(in, next, lineNo);
590            line  = line.substr(0, last)+' '+next;
591        }
592    }
593
594    if (error) line = "";
595
596    return error;
597}
598
599inline size_t next_non_white(const string& line, size_t start) {
600    if (start == string::npos) return string::npos;
601    return line.find_first_not_of(" \t", start);
602}
603
604static bool was_last_parameter = false;
605
606inline size_t eat_para_separator(const string& line, size_t start, GB_ERROR& error) {
607    size_t para_sep = next_non_white(line, start);
608
609    if (para_sep == string::npos) {
610        error = "',' or ')' expected after parameter";
611    }
612    else {
613        switch (line[para_sep]) {
614            case ')':
615                was_last_parameter = true;
616                break;
617
618            case ',':
619                break;
620
621            default:
622                error = "',' or ')' expected after parameter";
623                break;
624        }
625        if (!error) para_sep++;
626    }
627
628    return para_sep;
629}
630
631static void check_last_parameter(GB_ERROR& error, const string& command) {
632    if (!error && !was_last_parameter) {
633        error = GBS_global_string("Superfluous arguments to '%s'", command.c_str());
634    }
635}
636
637static void check_no_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, const string& command) {
638    size_t start = next_non_white(line, scan_pos);
639    scan_pos     = start;
640
641    if (start == string::npos) {
642        error = "')' expected";
643    }
644    else if (line[start] != ')') {
645        was_last_parameter = false;
646        check_last_parameter(error, command);
647    }
648    else { // ok
649        scan_pos++;
650    }
651}
652
653inline GB_ERROR decodeEscapes(string& s) {
654    string::iterator f = s.begin();
655    string::iterator t = s.begin();
656
657    for (; f != s.end(); ++f, ++t) {
658        if (*f == '\\') {
659            ++f;
660            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
661        }
662        *t = *f;
663    }
664
665    s.erase(t, f);
666
667    return NULp;
668}
669
670static string check_data_path(const string& path, GB_ERROR& error) {
671    if (!error) error = GB_check_hkey(path.c_str());
672    return path;
673}
674
675static string scan_string_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, bool allow_escaped = false) {
676    string result;
677    size_t open_quote = next_non_white(line, scan_pos);
678    scan_pos          = open_quote;
679
680    if (open_quote == string::npos || line[open_quote] != '\"') {
681        error = "string parameter expected";
682    }
683    else {
684        size_t close_quote = string::npos;
685
686        if (allow_escaped) {
687            size_t start = open_quote+1;
688
689            while (1) {
690                close_quote = line.find_first_of("\\\"", start);
691
692                if (close_quote == string::npos) break; // error
693                if (line[close_quote] == '\"') break; // found closing quote
694
695                if (line[close_quote] == '\\') { // escape next char
696                    close_quote++;
697                }
698                start = close_quote+1;
699            }
700        }
701        else {
702            close_quote = line.find('\"', open_quote+1);
703        }
704
705        if (close_quote == string::npos) {
706            error = "string parameter missing closing '\"'";
707        }
708        else {
709            result = line.substr(open_quote+1, close_quote-open_quote-1);
710            if (allow_escaped) {
711                msk_assert(!error);
712                error = decodeEscapes(result);
713            }
714            if (!error) scan_pos = eat_para_separator(line, close_quote+1, error);
715        }
716    }
717
718    return result;
719}
720
721#if (GCC_VERSION_CODE >= 901) && (GCC_VERSION_CODE <= 905) // (please do not activate for all future versions, test each of them)
722# define GCC_ARRAY_BOUNDS_FALSE_POSITIVE
723#endif
724#if (GCC_VERSION_CODE >= 1001) && (GCC_VERSION_CODE <= 1005) // (please do not activate for all future versions, test each of them)
725# define GCC_ARRAY_BOUNDS_FALSE_POSITIVE
726#endif
727
728#ifdef GCC_ARRAY_BOUNDS_FALSE_POSITIVE
729# pragma GCC diagnostic push
730# pragma GCC diagnostic ignored "-Warray-bounds"
731#endif
732
733static string list_keywords(const char **allowed_keywords) {
734    // it is required that 'allowed_keywords' has a NULp sentinel.
735    string result;
736    for (int i = 0; allowed_keywords[i]; ++i) {
737        if (i) {
738            if (allowed_keywords[i+1]) result += ", "; // (false positive "-Warray-bounds"; i+1 always inside array bounds if has sentinel)
739            else result                       += " or ";
740        }
741        result += allowed_keywords[i];
742    }
743    return result;
744}
745
746#ifdef GCC_ARRAY_BOUNDS_FALSE_POSITIVE
747# pragma GCC diagnostic pop
748#endif
749
750inline int isKeyword(const char *current, const char *keyword) {
751    int pos = 0;
752    for (; keyword[pos]; ++pos) {
753        if (!current[pos] || std::tolower(current[pos]) != std::tolower(keyword[pos])) {
754            return 0;
755        }
756    }
757    return pos;
758}
759
760static int scan_keyword_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, const char **allowed_keywords) {
761    // return index of keyword (or -1)
762    // allowed_keywords has to be 0-terminated
763    int    result = -1;
764    size_t start  = next_non_white(line, scan_pos);
765    scan_pos      = start;
766
767    msk_assert(!error);
768
769    if (start == string::npos) {
770EXPECTED :
771        string keywords = list_keywords(allowed_keywords);
772        error           = GBS_global_string("%s expected", keywords.c_str());
773    }
774    else {
775        const char *current = line.c_str()+start;
776
777        int i = 0;
778        for (; allowed_keywords[i]; ++i) {
779            int found_keyword_size = isKeyword(current, allowed_keywords[i]);
780            if (found_keyword_size) {
781                result    = i;
782                scan_pos += found_keyword_size;
783                break;
784            }
785        }
786        if (!allowed_keywords[i]) goto EXPECTED;
787        msk_assert(!error);
788        scan_pos = eat_para_separator(line, scan_pos, error);
789    }
790    return result;
791}
792
793static void scan_string_or_keyword_parameter(const string& line, size_t& scan_pos, GB_ERROR& error,
794                                             string& string_para, int& keyword_index, // result parameters
795                                             const char **allowed_keywords) {
796    // if keyword_index != -1 -> keyword found
797    //                  == -1 -> string_para contains string-parameter
798
799    msk_assert(!error);
800
801    string_para = scan_string_parameter(line, scan_pos, error);
802    if (!error) {
803        keyword_index = -1;
804    }
805    else {                         // no string
806        error         = NULp;      // ignore error - test for keywords
807        keyword_index = scan_keyword_parameter(line, scan_pos, error, allowed_keywords);
808        if (keyword_index == -1) { // no keyword
809            error = GBS_global_string("string parameter or %s", error);
810        }
811    }
812}
813
814static long scan_long_parameter(const string& line, size_t& scan_pos, GB_ERROR& error) {
815    int    result = 0;
816    size_t start  = next_non_white(line, scan_pos);
817    bool   neg    = false;
818
819    msk_assert(!error);
820
821    while (start != string::npos) {
822        char c = line[start];
823        if (c != '+' && c != '-') break;
824
825        start             = next_non_white(line, start+1);
826        if (c == '-') neg = !neg;
827        continue;
828    }
829
830    if (start == string::npos || !isdigit(line[start])) {
831        scan_pos = start;
832        error    = "digits (or+-) expected";
833    }
834    else {
835        size_t end = line.find_first_not_of("0123456789", start);
836        scan_pos   = eat_para_separator(line, end, error);
837        if (!error) {
838            msk_assert(end != string::npos);
839            result = atoi(line.substr(start, end-start+1).c_str());
840        }
841    }
842
843    return neg ? -result : result;
844}
845static long scan_long_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, long min, long max) {
846    // with range-check
847    msk_assert(!error);
848    size_t old_scan_pos = scan_pos;
849    long   result       = scan_long_parameter(line, scan_pos, error);
850    if (!error) {
851        if (result<min || result>max) {
852            scan_pos = old_scan_pos;
853            error    = GBS_global_string("value %li is outside allowed range (%li-%li)", result, min, max);
854        }
855    }
856    return result;
857}
858
859static long scan_optional_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, long if_empty) {
860    msk_assert(!error);
861    size_t old_scan_pos = scan_pos;
862    long   result       = scan_long_parameter(line, scan_pos, error);
863    if (error) {
864        error    = NULp; // ignore and test for empty parameter
865        scan_pos = old_scan_pos;
866        scan_pos = eat_para_separator(line, scan_pos, error);
867
868        if (error) {
869            error    = "expected number or empty parameter";
870            scan_pos = old_scan_pos;
871        }
872        else {
873            result = if_empty;
874        }
875    }
876    return result;
877}
878
879static int scan_flag_parameter(const string& line, size_t& scan_pos, GB_ERROR& error, const string& allowed_flags) {
880    // return 0..n-1 ( = position in 'allowed_flags')
881    // or error is set
882    msk_assert(!error);
883    int    result = 0;
884    size_t start  = next_non_white(line, scan_pos);
885    scan_pos      = start;
886
887    if (start == string::npos) {
888        error    = GBS_global_string("One of '%s' expected", allowed_flags.c_str());
889    }
890    else {
891        char   found       = line[start];
892        char   upper_found = std::toupper(found);
893        size_t pos         = allowed_flags.find(upper_found);
894
895        if (pos != string::npos) {
896            result   = pos;
897            scan_pos = eat_para_separator(line, start+1, error);
898        }
899        else {
900            error = GBS_global_string("One of '%s' expected (found '%c')", allowed_flags.c_str(), found);
901        }
902    }
903    return result;
904}
905static bool scan_bool_parameter(const string& line, size_t& scan_pos, GB_ERROR& error) {
906    msk_assert(!error);
907    size_t old_scan_pos = scan_pos;
908    long   result       = scan_long_parameter(line, scan_pos, error);
909
910    if (!error && (result != 0) && (result != 1)) {
911        scan_pos = old_scan_pos;
912        error    = "'0' or '1' expected";
913    }
914    return result != 0;
915}
916
917static string scan_identifier(const string& line, size_t& scan_pos, GB_ERROR& error) {
918    string id;
919    size_t start = next_non_white(line, scan_pos);
920    if (start == string::npos) {
921        error = "identifier expected";
922    }
923    else {
924        size_t end = start;
925        while (end<line.length()) {
926            char c                 = line[end];
927            if (!(isalnum(c) || c == '_')) break;
928            ++end;
929        }
930        id         = line.substr(start, end-start);
931        scan_pos   = eat_para_separator(line, end, error);
932    }
933    return id;
934}
935
936inline const char *inputMaskDir(bool local) {
937    if (local) {
938        static char *local_mask_dir;
939        if (!local_mask_dir) local_mask_dir = ARB_strdup(GB_path_in_arbprop("inputMasks"));
940        return local_mask_dir;
941    }
942
943    static char *global_mask_dir;
944    if (!global_mask_dir) global_mask_dir = ARB_strdup(GB_path_in_ARBLIB("inputMasks"));
945    return global_mask_dir;
946}
947
948inline string inputMaskFullname(const string& mask_name, bool local) {
949    const char *dir = inputMaskDir(local);
950    return string(dir)+'/'+mask_name;
951}
952
953#define ARB_INPUT_MASK_ID "ARB-Input-Mask"
954
955static awt_input_mask_descriptor *quick_scan_input_mask(const string& mask_name, const string& filename, bool local) {
956    awt_input_mask_descriptor *res = NULp;
957    FILE     *in    = fopen(filename.c_str(), "rt");
958    GB_ERROR  error = NULp;
959
960    if (in) {
961        string   line;
962        size_t   lineNo = 0;
963        error = readLine(in, line, lineNo);
964
965        if (!error) {
966            if (line == ARB_INPUT_MASK_ID) {
967                bool   done   = false;
968                int    hidden = 0; // 0 = 'not hidden'
969                string title;
970                string itemtype;
971
972                while (!feof(in)) {
973                    error = readLine(in, line, lineNo);
974                    if (error) break;
975
976                    if (line[0] == '#') continue; // ignore comments
977                    if (line == "@MASK_BEGIN") { done = true; break; }
978
979                    size_t at = line.find('@');
980                    size_t eq = line.find('=', at);
981
982                    if (at == string::npos || eq == string::npos) {
983                        continue; // ignore all other lines
984                    }
985                    else {
986                        string parameter = line.substr(at+1, eq-at-1);
987                        string value     = line.substr(eq+1);
988
989                        if (parameter == "ITEMTYPE") itemtype = value;
990                        else if (parameter == "TITLE") title  = value;
991                        else if (parameter == "HIDE") hidden  = atoi(value.c_str());
992                    }
993                }
994
995                if (!error && done) {
996                    if (itemtype == "") {
997                        error = "No itemtype defined";
998                    }
999                    else {
1000                        if (title == "") title = mask_name;
1001                        res = new awt_input_mask_descriptor(title.c_str(), mask_name.c_str(), itemtype.c_str(), local, hidden);
1002                    }
1003                }
1004            }
1005            else {
1006                fprintf(stderr, "Skipping '%s' (not a input mask)\n", filename.c_str());
1007            }
1008        }
1009        fclose(in);
1010    }
1011
1012    if (error) {
1013        aw_message(GBS_global_string("%s (while scanning user-mask '%s')", error, filename.c_str()));
1014    }
1015
1016    return res;
1017}
1018
1019static void AWT_edit_input_mask(AW_window *, const string *mask_name, bool local) {
1020    string fullmask = inputMaskFullname(*mask_name, local);
1021    AW_edit(fullmask.c_str()); // @@@ add callback and automatically reload input mask
1022}
1023
1024//  ---------------------------------
1025//      input mask container :
1026
1027typedef SmartPtr<awt_input_mask>        awt_input_mask_ptr;
1028typedef map<string, awt_input_mask_ptr> InputMaskList; // contains all active masks
1029static InputMaskList                    input_mask_list;
1030
1031static void awt_input_mask_awar_changed_cb(AW_root*, awt_input_mask *mask) {
1032    mask->relink();
1033}
1034static void link_mask_to_database(awt_input_mask_ptr mask) {
1035    awt_input_mask_global&        global = mask->mask_global();
1036    const awt_item_type_selector *sel    = global.get_selector();
1037    AW_root                      *root   = global.get_root();
1038
1039    sel->add_awar_callbacks(root, makeRootCallback(awt_input_mask_awar_changed_cb, &*mask));
1040    awt_input_mask_awar_changed_cb(root, &*mask);
1041}
1042static void unlink_mask_from_database(awt_input_mask_ptr mask) {
1043    awt_input_mask_global&        global = mask->mask_global();
1044    const awt_item_type_selector *sel    = global.get_selector();
1045    AW_root                      *root   = global.get_root();
1046
1047    sel->remove_awar_callbacks(root, makeRootCallback(awt_input_mask_awar_changed_cb, &*mask));
1048}
1049
1050inline bool isInternalMaskName(const string& s) {
1051    return s[0] == '0' || s[0] == '1';
1052}
1053
1054static void awt_open_input_mask(AW_window *aww, const string *internal_mask_name, const string *mask_to_open, bool reload, bool hide_current) {
1055    InputMaskList::iterator mask_iter = input_mask_list.find(*internal_mask_name);
1056
1057    msk_assert(internal_mask_name && isInternalMaskName(*internal_mask_name));
1058    msk_assert(mask_to_open && isInternalMaskName(*mask_to_open));
1059
1060    if (mask_iter != input_mask_list.end()) {
1061        awt_input_mask_ptr     mask   = mask_iter->second;
1062        awt_input_mask_global& global = mask->mask_global();
1063
1064        printf("aww=%p root=%p ; global=%p root=%p\n", aww, aww->get_root(), &global, global.get_root());
1065        msk_assert(aww->get_root() == global.get_root());
1066
1067        if (reload) mask->set_reload_on_reinit(true);
1068        if (hide_current) mask->hide();
1069        // @@@ hier sollte nicht der Selector der alten Maske verwendet werden, sondern anhand des Typs ein
1070        // Selector ausgewaehlt werden. Dazu muessen jedoch alle Selectoren registriert werden.
1071        GB_ERROR error = AWT_initialize_input_mask(global.get_root(), global.get_gb_main(), global.get_selector(), mask_to_open->c_str(), global.is_local_mask());
1072        // CAUTION: AWT_initialize_input_mask invalidates mask and mask_iter
1073        if (error && hide_current) {
1074            mask_iter = input_mask_list.find(*internal_mask_name);
1075            msk_assert(mask_iter != input_mask_list.end());
1076            mask_iter->second->show();
1077        }
1078    }
1079#if defined(DEBUG)
1080    else {
1081        string mask_name = internal_mask_name->substr(1);
1082        printf("'%s' (no such mask in input_mask_list)\n", mask_name.c_str());
1083        msk_assert(0);
1084    }
1085#endif // DEBUG
1086}
1087
1088static void AWT_reload_input_mask(AW_window *aww, const string *internal_mask_name) {
1089    awt_open_input_mask(aww, internal_mask_name, internal_mask_name, true, true);
1090}
1091static void AWT_open_input_mask(AW_window *aww, const string *internal_mask_name, const string *mask_to_open) {
1092    awt_open_input_mask(aww, internal_mask_name, mask_to_open, false, false);
1093}
1094static void AWT_change_input_mask(AW_window *aww, const string *internal_mask_name, const string *mask_to_open) {
1095    awt_open_input_mask(aww, internal_mask_name, mask_to_open, false, true);
1096}
1097
1098//  ------------------------------
1099//      class awt_mask_action
1100
1101class awt_mask_action {
1102    // something that is performed i.e. when user pressed a mask button
1103    // used as callback parameter
1104private:
1105    virtual GB_ERROR action() = 0;
1106protected:
1107    awt_input_mask_ptr mask;
1108public:
1109    awt_mask_action(awt_input_mask_ptr mask_) : mask(mask_) {}
1110    virtual ~awt_mask_action() {}
1111
1112    void perform_action() {
1113        GB_ERROR error = action();
1114        if (error) aw_message(error);
1115    }
1116};
1117
1118
1119//  ------------------------------------------------------
1120//      class awt_assignment : public awt_mask_action
1121
1122class awt_assignment : public awt_mask_action {
1123private:
1124    string id_dest;
1125    string id_source;
1126
1127    GB_ERROR action() OVERRIDE {
1128        GB_ERROR             error       = NULp;
1129        const awt_mask_item *item_source = mask->mask_global().get_identified_item(id_source, error);
1130        awt_mask_item       *item_dest   = mask->mask_global().get_identified_item(id_dest, error);
1131
1132        if (!error) error = item_dest->set_value(item_source->get_value());
1133
1134        return error;
1135    }
1136public:
1137    awt_assignment(awt_input_mask_ptr mask_, const string& id_dest_, const string& id_source_) :
1138        awt_mask_action(mask_),
1139        id_dest(id_dest_),
1140        id_source(id_source_)
1141    {}
1142    ~awt_assignment() OVERRIDE {}
1143};
1144
1145static void AWT_input_mask_perform_action(AW_window*, awt_mask_action *action) {
1146    action->perform_action();
1147}
1148
1149static void AWT_input_mask_browse_url(AW_window *aww, const string *url_aci, const awt_input_mask *mask) {
1150    const awt_input_mask_global&  global   = mask->mask_global();
1151    const awt_item_type_selector *selector = global.get_selector();
1152
1153    AW_root *root    = aww->get_root();
1154    GBDATA  *gb_main = global.get_gb_main();
1155    GBDATA  *gbd     = selector->current(root, gb_main);
1156
1157    if (!gbd) {
1158        aw_message(GBS_global_string("You have to select a %s first", awt_itemtype_names[selector->get_item_type()]));
1159    }
1160    else {
1161        GB_ERROR error = awt_open_ACI_URL_with_item(root, gb_main, gbd, url_aci->c_str());
1162        if (error) aw_message(error);
1163    }
1164}
1165
1166
1167// ---------------------------
1168//      User Mask Commands
1169
1170enum MaskCommand {
1171    CMD_TEXTFIELD,
1172    CMD_NUMFIELD,
1173    CMD_CHECKBOX,
1174    CMD_RADIO,
1175    CMD_OPENMASK,
1176    CMD_CHANGEMASK,
1177    CMD_TEXT,
1178    CMD_SELF,
1179    CMD_WWW,
1180    CMD_NEW_LINE,
1181    CMD_NEW_SECTION,
1182    CMD_ID,
1183    CMD_GLOBAL,
1184    CMD_LOCAL,
1185    CMD_SHOW,
1186    CMD_ASSIGN,
1187    CMD_SCRIPT,
1188
1189    MASK_COMMANDS,
1190    CMD_UNKNOWN = MASK_COMMANDS
1191};
1192
1193struct MaskCommandDefinition {
1194    const char  *cmd_name;
1195    MaskCommand  cmd;
1196};
1197
1198static struct MaskCommandDefinition mask_command[MASK_COMMANDS+1] = {
1199    { "TEXTFIELD", CMD_TEXTFIELD },
1200    { "NUMFIELD", CMD_NUMFIELD },
1201    { "CHECKBOX", CMD_CHECKBOX },
1202    { "RADIO", CMD_RADIO },
1203    { "OPENMASK", CMD_OPENMASK },
1204    { "CHANGEMASK", CMD_CHANGEMASK },
1205    { "TEXT", CMD_TEXT },
1206    { "SELF", CMD_SELF },
1207    { "NEW_LINE", CMD_NEW_LINE },
1208    { "NEW_SECTION", CMD_NEW_SECTION },
1209    { "WWW", CMD_WWW },
1210    { "ID", CMD_ID },
1211    { "GLOBAL", CMD_GLOBAL },
1212    { "LOCAL", CMD_LOCAL },
1213    { "SHOW", CMD_SHOW },
1214    { "ASSIGN", CMD_ASSIGN },
1215    { "SCRIPT", CMD_SCRIPT },
1216
1217    { NULp, CMD_UNKNOWN }
1218};
1219
1220inline MaskCommand findCommand(const string& cmd_name) {
1221    int mc = 0;
1222
1223    for (; mask_command[mc].cmd != CMD_UNKNOWN; ++mc) {
1224        if (mask_command[mc].cmd_name == cmd_name) {
1225            return mask_command[mc].cmd;
1226        }
1227    }
1228    return CMD_UNKNOWN;
1229}
1230
1231static void parse_CMD_RADIO(string& line, size_t& scan_pos, GB_ERROR& error, const string& command,
1232                            awt_mask_item_ptr& handler1, awt_mask_item_ptr& handler2, awt_input_mask_global& global)
1233{
1234    string         label, data_path;
1235    int            default_position = -1, orientation = -1;
1236    vector<string> buttons;
1237    vector<string> values;
1238    bool           allow_edit       = false;
1239    int            width            = -1;
1240    int            edit_position    = -1;
1241
1242    label                        = scan_string_parameter(line, scan_pos, error);
1243    if (!error) data_path        = check_data_path(scan_string_parameter(line, scan_pos, error), error);
1244    if (!error) default_position = scan_long_parameter(line, scan_pos, error);
1245    if (!error) {
1246        orientation = scan_flag_parameter(line, scan_pos, error, "HVXY");
1247        orientation = orientation&1;
1248    }
1249    while (!error && !was_last_parameter) {
1250        string but = scan_string_parameter(line, scan_pos, error);
1251        string val = "";
1252        if (!error) {
1253            int keyword_index;
1254            const char *allowed_keywords[] = { "ALLOW_EDIT", NULp };
1255            scan_string_or_keyword_parameter(line, scan_pos, error, val, keyword_index, allowed_keywords);
1256
1257            if (!error) {
1258                if (keyword_index != -1) { // found keyword
1259                    if (allow_edit) error = "ALLOW_EDIT is allowed only once for each RADIO";
1260
1261                    if (!error) width = scan_long_parameter(line, scan_pos, error, MIN_TEXTFIELD_SIZE, MAX_TEXTFIELD_SIZE);
1262                    if (!error) val   = scan_string_parameter(line, scan_pos, error);
1263
1264                    if (!error) {
1265                        allow_edit    = true;
1266                        edit_position = buttons.size()+1; // positions are 1..n
1267                        buttons.push_back(but);
1268                        values.push_back(val);
1269                    }
1270                }
1271                else { // found string
1272                    buttons.push_back(but);
1273                    values.push_back(val);
1274                }
1275            }
1276        }
1277    }
1278    check_last_parameter(error, command);
1279
1280    if (!error && (buttons.size()<2)) error = "You have to define at least 2 buttons.";
1281
1282    if (!error && allow_edit && edit_position != default_position) {
1283        error = GBS_global_string("Invalid default %i (must be index of ALLOW_EDIT: %i )", default_position, edit_position);
1284    }
1285    if (!error && (default_position<1 || size_t(default_position)>buttons.size())) {
1286        error = GBS_global_string("Invalid default %i (valid: 1..%zu)", default_position, buttons.size());
1287    }
1288
1289    if (!error) {
1290        if (allow_edit) {
1291            // create radio-button + textfield
1292            handler1 = new awt_radio_button(global, data_path, label, default_position-1, orientation, buttons, values);
1293            handler2 = new awt_input_field(global, data_path, "", width, "", GB_STRING);
1294        }
1295        else {
1296            handler1 = new awt_radio_button(global, data_path, label, default_position-1, orientation, buttons, values);
1297        }
1298    }
1299}
1300
1301static string find_internal_name(const string& mask_name, bool search_in_local) {
1302    const char *internal_local  = NULp;
1303    const char *internal_global = NULp;
1304
1305    for (int id = 0; ; ++id) {
1306        const awt_input_mask_descriptor *descriptor = AWT_look_input_mask(id);
1307        if (!descriptor) break;
1308
1309        const char *internal_name = descriptor->get_internal_maskname();
1310
1311        if (strcmp(internal_name+1, mask_name.c_str()) == 0) {
1312            if (descriptor->is_local_mask()) {
1313                msk_assert(!internal_local);
1314                internal_local = internal_name;
1315            }
1316            else {
1317                msk_assert(!internal_global);
1318                internal_global = internal_name;
1319            }
1320        }
1321    }
1322
1323    return (search_in_local && internal_local) ? internal_local : null2empty(internal_global);
1324}
1325
1326// ----------------------------------
1327//      class awt_marked_checkbox
1328
1329class awt_marked_checkbox FINAL_TYPE : public awt_viewport, public awt_linked_to_item {
1330private:
1331
1332    string generate_baseName(awt_input_mask_global& global_) {
1333        return GBS_global_string("%s/marked", global_.get_maskid().c_str());
1334    }
1335
1336public:
1337    awt_marked_checkbox(awt_input_mask_global& global_, const std::string& label_) :
1338        awt_viewport(global_, generate_baseName(global_), "0", false, label_),
1339        awt_linked_to_item()
1340    {}
1341    ~awt_marked_checkbox() OVERRIDE {}
1342
1343    GB_ERROR link_to(GBDATA *gb_new_item) OVERRIDE; // link to a new item
1344    GB_ERROR relink() OVERRIDE { return link_to(mask_global().get_selected_item()); }
1345    void awar_changed() OVERRIDE;
1346    void db_changed() OVERRIDE;
1347    void general_item_change() OVERRIDE { db_changed(); } // called if item was changed (somehow)
1348    void build_widget(AW_window *aws) OVERRIDE; // builds the widget at the current position
1349};
1350
1351GB_ERROR awt_marked_checkbox::link_to(GBDATA *gb_new_item) { // link to a new item
1352    GB_ERROR       error = NULp;
1353    GB_transaction ta(mask_global().get_gb_main());
1354
1355    remove_awarItem_callbacks();    // unbind awar callbacks temporarily
1356
1357    if (item()) {
1358        remove_db_callbacks(); // ignore result (if handled db-entry was deleted, it returns an error)
1359        set_item(NULp);
1360    }
1361
1362    if (gb_new_item) {
1363        set_item(gb_new_item);
1364        db_changed();
1365        error = add_db_callbacks();
1366    }
1367
1368    add_awarItem_callbacks();       // rebind awar callbacks
1369    return error;
1370}
1371
1372void awt_marked_checkbox::awar_changed() { // called when awar changes
1373    if (item()) {
1374        string         value  = get_value();
1375        bool           marked = value == "yes";
1376        GB_transaction ta(mask_global().get_gb_main());
1377        GB_write_flag(item(), marked);
1378    }
1379    else {
1380        mask_global().no_item_selected();
1381    }
1382}
1383
1384void awt_marked_checkbox::db_changed() {
1385    if (item()) {
1386        GB_transaction ta(mask_global().get_gb_main());
1387        set_value(GB_read_flag(item()) ? "yes" : "no");
1388    }
1389}
1390
1391void awt_marked_checkbox::build_widget(AW_window *aws) { // builds the widget at the current position
1392    const string& lab = get_label();
1393    if (lab.length()) aws->label(lab.c_str());
1394
1395    aws->create_toggle(awar_name().c_str());
1396}
1397
1398static GB_ERROR writeDefaultMaskfile(const string& fullname, const string& maskname, const string& itemtypename, bool hidden) {
1399    FILE *out = fopen(fullname.c_str(), "wt");
1400    if (!out) return GBS_global_string("Can't open '%s'", fullname.c_str());
1401
1402    fprintf(out,
1403            "%s\n"
1404            "# New mask '%s'\n\n"
1405            "# What kind of item to edit:\n"
1406            "@ITEMTYPE=%s\n\n"
1407            "# Window title:\n"
1408            "@TITLE=%s\n\n", ARB_INPUT_MASK_ID, maskname.c_str(), itemtypename.c_str(), maskname.c_str());
1409
1410    fprintf(out,
1411            "# Should mask appear in 'User mask' menu\n"
1412            "@HIDE=%i\n\n", int(hidden));
1413
1414    fputs("# Spacing in window:\n"
1415          "@X_SPACING=5\n"
1416          "@Y_SPACING=3\n\n"
1417          "# Show edit/reload button?\n"
1418          "@EDIT=1\n"
1419          "# Show 'edit enable' toggle?\n"
1420          "@EDIT_ENABLE=1\n"
1421          "# Show 'marked' toggle?\n"
1422          "@SHOW_MARKED=1\n"
1423          "\n# ---------------------------\n"
1424          "# The definition of the mask:\n\n"
1425          "@MASK_BEGIN\n\n"
1426          "    TEXT(\"You are editing\") SELF()\n"
1427          "    NEW_SECTION()\n"
1428          "    TEXTFIELD(\"Full name\", \"full_name\", 30)\n\n"
1429          "@MASK_END\n\n", out);
1430
1431    fclose(out);
1432    return NULp;
1433}
1434
1435class ID_checker {
1436    bool        reloading;
1437    set<string> seen;
1438    set<string> dup;
1439    string      curr_id;
1440
1441    bool is_known(const string& id) { return seen.find(id) != seen.end(); }
1442
1443    string makeUnique(string id) {
1444        if (is_known(id)) {
1445            dup.insert(id);
1446            for (int i = 0; ; ++i) {
1447                string undup = GBS_global_string("%s%i", id.c_str(), i);
1448                if (!is_known(undup)) {
1449                    id = undup;
1450                    break;
1451                }
1452            }
1453        }
1454        seen.insert(id);
1455        return id;
1456    }
1457
1458public:
1459    ID_checker(bool reloading_)
1460        : reloading(reloading_)
1461    {}
1462
1463    const char *fromKey(const char *id) {
1464        curr_id = makeUnique(id);
1465        return reloading ? NULp : curr_id.c_str();
1466    }
1467    const char *fromText(const string& anystr) {
1468        SmartCharPtr key = GBS_string_2_key(anystr.c_str());
1469        return fromKey(&*key);
1470    }
1471
1472    bool seenDups() const { return !dup.empty(); }
1473    const char *get_dup_error(const string& maskName) const {
1474        string dupList;
1475        for (set<string>::iterator d = dup.begin(); d != dup.end(); ++d) {
1476            dupList = dupList+" '"+*d+"'";
1477        }
1478        return GBS_global_string("Warning: duplicated IDs seen in '%s':\n"
1479                                 "%s\n"
1480                                 "(they need to be unique; change button texts etc. to change them)",
1481                                 maskName.c_str(), dupList.c_str());
1482    }
1483};
1484
1485static awt_input_mask_ptr awt_create_input_mask(AW_root *root, GBDATA *gb_main, const awt_item_type_selector *sel,
1486                                                const string& mask_name, bool local, GB_ERROR& error, bool reloading) {
1487    awt_input_mask_ptr mask;
1488
1489    error = NULp;
1490
1491    FILE *in = NULp;
1492    {
1493        string  fullfile = inputMaskFullname(mask_name, local);
1494        in               = fopen(fullfile.c_str(), "rt");
1495        if (!in) error   = GBS_global_string("Can't open '%s'", fullfile.c_str());
1496    }
1497
1498    if (!error) {
1499        bool   mask_began = false;
1500        bool   mask_ended = false;
1501
1502        // data to be scanned :
1503        string        title;
1504        string        itemtypename;
1505        int           x_spacing   = 5;
1506        int           y_spacing   = 3;
1507        bool          edit_reload = false;
1508        bool          edit_enable = true;
1509        bool          show_marked = true;
1510
1511        string line;
1512        size_t lineNo  = 0;
1513        size_t err_pos = 0;     // 0 = unknown; string::npos = at end of line; else position+1;
1514
1515        error = readLine(in, line, lineNo);
1516
1517        if (!error && line != ARB_INPUT_MASK_ID) {
1518            error = "'" ARB_INPUT_MASK_ID "' expected";
1519        }
1520
1521        while (!error && !mask_began && !feof(in)) {
1522            error = readLine(in, line, lineNo);
1523            if (error) break;
1524
1525            if (line[0] == '#') continue; // ignore comments
1526
1527            if (line == "@MASK_BEGIN") mask_began = true;
1528            else {
1529                size_t at = line.find('@');
1530                size_t eq = line.find('=', at);
1531
1532                if (at == string::npos || eq == string::npos) {
1533                    continue;
1534                }
1535                else {
1536                    string parameter = line.substr(at+1, eq-at-1);
1537                    string value     = line.substr(eq+1);
1538
1539                    if (parameter == "ITEMTYPE")            itemtypename = value;
1540                    else if (parameter == "TITLE")          title        = value;
1541                    else if (parameter == "X_SPACING")      x_spacing    = atoi(value.c_str());
1542                    else if (parameter == "Y_SPACING")      y_spacing    = atoi(value.c_str());
1543                    else if (parameter == "EDIT")           edit_reload  = atoi(value.c_str()) != 0;
1544                    else if (parameter == "EDIT_ENABLE")    edit_enable  = atoi(value.c_str()) != 0;
1545                    else if (parameter == "SHOW_MARKED")    show_marked  = atoi(value.c_str()) != 0;
1546                    else if (parameter == "HIDE") ; // ignore parameter here
1547                    else {
1548                        error = GBS_global_string("Unknown parameter '%s'", parameter.c_str());
1549                    }
1550                }
1551            }
1552        }
1553
1554        if (!error && !mask_began) error = "@MASK_BEGIN expected";
1555
1556        // check data :
1557        if (!error) {
1558            if (title == "") title = string("Untitled (")+mask_name+")";
1559            awt_item_type itemtype = AWT_getItemType(itemtypename);
1560
1561            if (itemtype == AWT_IT_UNKNOWN)         error = GBS_global_string("Unknown @ITEMTYPE '%s'", itemtypename.c_str());
1562            if (itemtype != sel->get_item_type())   error = GBS_global_string("Mask is designed for @ITEMTYPE '%s' (current @ITEMTYPE '%s')",
1563                                                                              itemtypename.c_str(), awt_itemtype_names[sel->get_item_type()]);
1564
1565            // create mask
1566            if (!error) mask = new awt_input_mask(root, gb_main, mask_name, itemtype, local, sel, edit_enable);
1567        }
1568
1569        // create window
1570        if (!error) {
1571            msk_assert(!mask.isNull());
1572            AW_window_simple*& aws = mask->get_window();
1573            aws                    = new AW_window_simple;
1574
1575            ID_checker ID(reloading);
1576
1577            {
1578                char *window_id = GBS_global_string_copy("INPUT_MASK_%s", mask->mask_global().get_maskid().c_str()); // create a unique id for each mask
1579                aws->init(root, window_id, title.c_str());
1580                free(window_id);
1581            }
1582
1583            aws->load_xfig(NULp, true);
1584            aws->recalc_size_atShow(AW_RESIZE_DEFAULT); // ignore user size!
1585
1586            aws->auto_space(x_spacing, y_spacing);
1587            aws->at_newline();
1588
1589            aws->callback(AW_POPDOWN);                          aws->create_button(ID.fromKey("CLOSE"), "CLOSE", "C");
1590            aws->callback(makeHelpCallback("input_mask.hlp"));  aws->create_button(ID.fromKey("HELP"),  "HELP",  "H");
1591
1592            if (edit_reload) {
1593                aws->callback(makeWindowCallback(AWT_edit_input_mask, &mask->mask_global().get_maskname(), mask->mask_global().is_local_mask()));
1594                aws->create_button(NULp, "!EDIT", "E");
1595
1596                aws->callback(makeWindowCallback(AWT_reload_input_mask, &mask->mask_global().get_internal_maskname()));
1597                aws->create_button(NULp, "RELOAD", "R");
1598            }
1599
1600            if (edit_reload && edit_enable && show_marked) aws->at_newline();
1601
1602            if (edit_enable) {
1603                aws->label("Enable edit?");
1604                aws->create_toggle(AWAR_INPUT_MASKS_EDIT_ENABLED);
1605            }
1606
1607            if (show_marked) {
1608                awt_mask_item_ptr handler = new awt_marked_checkbox(mask->mask_global(), "Marked");
1609                mask->add_handler(handler);
1610                if (handler->is_viewport()) handler->to_viewport()->build_widget(aws);
1611
1612            }
1613
1614            aws->at_newline();
1615
1616            vector<int>         horizontal_lines; // y-positions of horizontal lines
1617            map<string, size_t> referenced_ids; // all ids that where referenced by the script (size_t contains lineNo of last reference)
1618            map<string, size_t> declared_ids; // all ids that where declared by the script (size_t contains lineNo of declaration)
1619
1620            awt_mask_item_ptr lasthandler;
1621
1622            // read mask itself :
1623            while (!error && !mask_ended && !feof(in)) {
1624                error = readLine(in, line, lineNo);
1625                if (error) break;
1626
1627                if (line.empty()) continue;
1628                if (line[0] == '#') continue;
1629
1630                if (line == "@MASK_END") {
1631                    mask_ended = true;
1632                }
1633                else {
1634                PARSE_REST_OF_LINE :
1635                    was_last_parameter = false;
1636                    size_t start       = next_non_white(line, 0);
1637                    if (start != string::npos) { // line contains sth
1638                        size_t after_command = line.find_first_of(string(" \t("), start);
1639                        if (after_command == string::npos) {
1640                            string command = line.substr(start);
1641                            error          = GBS_global_string("arguments missing after '%s'", command.c_str());
1642                        }
1643                        else {
1644                            string command    = line.substr(start, after_command-start);
1645                            size_t paren_open = line.find('(', after_command);
1646                            if (paren_open == string::npos) {
1647                                error = GBS_global_string("'(' expected after '%s'", command.c_str());
1648                            }
1649                            else {
1650                                size_t            scan_pos = paren_open+1;
1651                                awt_mask_item_ptr handler;
1652                                awt_mask_item_ptr radio_edit_handler;
1653                                MaskCommand       cmd      = findCommand(command);
1654
1655                                //  --------------------------------------
1656                                //      code for different commands :
1657
1658                                if (cmd == CMD_TEXTFIELD) {
1659                                    string label, data_path;
1660                                    long   width          = -1;
1661                                    label                 = scan_string_parameter(line, scan_pos, error);
1662                                    if (!error) data_path = check_data_path(scan_string_parameter(line, scan_pos, error), error);
1663                                    if (!error) width     = scan_long_parameter(line, scan_pos, error, MIN_TEXTFIELD_SIZE, MAX_TEXTFIELD_SIZE);
1664                                    check_last_parameter(error, command);
1665
1666                                    if (!error) handler = new awt_input_field(mask->mask_global(), data_path, label, width, "", GB_STRING);
1667                                }
1668                                else if (cmd == CMD_NUMFIELD) {
1669                                    string label, data_path;
1670                                    long   width = -1;
1671
1672                                    long min = LONG_MIN;
1673                                    long max = LONG_MAX;
1674
1675                                    label                 = scan_string_parameter(line, scan_pos, error);
1676                                    if (!error) data_path = check_data_path(scan_string_parameter(line, scan_pos, error), error);
1677                                    if (!error) width     = scan_long_parameter(line, scan_pos, error, MIN_TEXTFIELD_SIZE, MAX_TEXTFIELD_SIZE);
1678                                    if (!was_last_parameter) {
1679                                        if (!error) min = scan_optional_parameter(line, scan_pos, error, LONG_MIN);
1680                                        if (!error) max = scan_optional_parameter(line, scan_pos, error, LONG_MAX);
1681                                    }
1682                                    check_last_parameter(error, command);
1683
1684                                    if (!error) handler = new awt_numeric_input_field(mask->mask_global(), data_path, label, width, 0, min, max);
1685                                }
1686                                else if (cmd == CMD_CHECKBOX) {
1687                                    string label, data_path;
1688                                    bool   checked        = false;
1689                                    label                 = scan_string_parameter(line, scan_pos, error);
1690                                    if (!error) data_path = check_data_path(scan_string_parameter(line, scan_pos, error), error);
1691                                    if (!error) checked   = scan_bool_parameter(line, scan_pos, error);
1692                                    check_last_parameter(error, command);
1693
1694                                    if (!error) handler = new awt_check_box(mask->mask_global(), data_path, label, checked);
1695                                }
1696                                else if (cmd == CMD_RADIO) {
1697                                    parse_CMD_RADIO(line, scan_pos, error, command, handler, radio_edit_handler, mask->mask_global());
1698                                }
1699                                else if (cmd == CMD_SCRIPT) {
1700                                    string id, script;
1701                                    id                 = scan_identifier(line, scan_pos, error);
1702                                    if (!error) script = scan_string_parameter(line, scan_pos, error, true);
1703                                    check_last_parameter(error, command);
1704
1705                                    if (!error) {
1706                                        handler          = new awt_script(mask->mask_global(), script);
1707                                        error            = handler->set_name(id, false);
1708                                        declared_ids[id] = lineNo;
1709                                    }
1710                                }
1711                                else if (cmd == CMD_GLOBAL || cmd == CMD_LOCAL) {
1712                                    string id, def_value;
1713
1714                                    id                 = scan_identifier(line, scan_pos, error);
1715                                    bool global_exists = mask->mask_global().has_global_id(id);
1716                                    bool local_exists  = mask->mask_global().has_local_id(id);
1717
1718                                    if ((cmd == CMD_GLOBAL && local_exists) || (cmd == CMD_LOCAL && global_exists)) {
1719                                        error = GBS_global_string("ID '%s' already declared as %s ID (rename your local id)",
1720                                                                  id.c_str(), cmd == CMD_LOCAL ? "global" : "local");
1721                                    }
1722                                    else if (cmd == CMD_LOCAL && local_exists) {
1723                                        error = GBS_global_string("ID '%s' declared twice", id.c_str());
1724                                    }
1725
1726                                    if (!error) def_value = scan_string_parameter(line, scan_pos, error);
1727                                    if (!error) {
1728                                        if (cmd == CMD_GLOBAL) {
1729                                            if (!mask->mask_global().has_global_id(id)) { // do not create globals more than once
1730                                                // and never free them -> so we don't need pointer
1731                                                new awt_variable(mask->mask_global(), id, true, def_value, error);
1732                                            }
1733                                            msk_assert(handler.isNull());
1734                                        }
1735                                        else {
1736                                            handler = new awt_variable(mask->mask_global(), id, false, def_value, error);
1737                                        }
1738                                        declared_ids[id] = lineNo;
1739                                    }
1740                                }
1741                                else if (cmd == CMD_ID) {
1742                                    string id = scan_identifier(line, scan_pos, error);
1743                                    check_last_parameter(error, command);
1744
1745                                    if (!error) {
1746                                        if (lasthandler.isNull()) {
1747                                            error = "ID() may only be used BEHIND another element";
1748                                        }
1749                                        else {
1750                                            error            = lasthandler->set_name(id, false);
1751                                            declared_ids[id] = lineNo;
1752                                        }
1753                                    }
1754                                }
1755                                else if (cmd == CMD_SHOW) {
1756                                    string         label, id;
1757                                    long           width = -1;
1758                                    awt_mask_item *item  = NULp;
1759
1760                                    label             = scan_string_parameter(line, scan_pos, error);
1761                                    if (!error) id    = scan_identifier(line, scan_pos, error);
1762                                    if (!error) item  = (awt_mask_item*)mask->mask_global().get_identified_item(id, error);
1763                                    if (!error) width = scan_long_parameter(line, scan_pos, error, MIN_TEXTFIELD_SIZE, MAX_TEXTFIELD_SIZE);
1764                                    check_last_parameter(error, command);
1765
1766                                    if (!error) {
1767                                        awt_mask_awar_item *awar_item = dynamic_cast<awt_mask_awar_item*>(item);
1768
1769                                        if (awar_item) { // item has an awar -> create a viewport using that awar
1770                                            handler = new awt_text_viewport(awar_item, label, width);
1771                                        }
1772                                        else { // item has no awar -> test if it's a script
1773                                            awt_script *script  = dynamic_cast<awt_script*>(item);
1774                                            if (script) handler = new awt_script_viewport(mask->mask_global(), script, label, width);
1775                                            else        error   = "SHOW cannot be used on this ID-type";
1776                                        }
1777
1778                                        referenced_ids[id] = lineNo;
1779                                    }
1780                                }
1781                                else if (cmd == CMD_OPENMASK || cmd == CMD_CHANGEMASK) {
1782                                    string label, mask_to_start;
1783                                    label                     = scan_string_parameter(line, scan_pos, error);
1784                                    if (!error) mask_to_start = scan_string_parameter(line, scan_pos, error);
1785                                    check_last_parameter(error, command);
1786
1787                                    if (!error) {
1788                                        string mask_to_start_internal = find_internal_name(mask_to_start, local);
1789
1790                                        if (mask_to_start_internal.length() == 0) {
1791                                            error = "Can't detect which mask to load";
1792                                        }
1793                                        else {
1794                                            const char *key = ID.fromText(label);
1795
1796                                            string *const internal_mask_name = new string(mask->mask_global().get_internal_maskname());
1797                                            string *const mask_to_open       = new string(mask_to_start_internal);
1798
1799                                            msk_assert(internal_mask_name);
1800                                            msk_assert(mask_to_open);
1801
1802                                            aws->callback(makeWindowCallback(cmd == CMD_OPENMASK ? AWT_open_input_mask : AWT_change_input_mask, internal_mask_name, mask_to_open));
1803
1804                                            aws->button_length(label.length()+2);
1805                                            aws->create_button(key, label.c_str());
1806                                        }
1807                                    }
1808                                }
1809                                else if (cmd == CMD_WWW) {
1810                                    string button_text, url_aci;
1811                                    button_text         = scan_string_parameter(line, scan_pos, error);
1812                                    if (!error) url_aci = scan_string_parameter(line, scan_pos, error, true);
1813                                    check_last_parameter(error, command);
1814
1815                                    if (!error) {
1816                                        const char *key = ID.fromText(button_text);
1817                                        aws->callback(makeWindowCallback(AWT_input_mask_browse_url, new string(url_aci), &*mask));
1818                                        aws->button_length(button_text.length()+2);
1819                                        aws->create_button(key, button_text.c_str());
1820                                    }
1821                                }
1822                                else if (cmd == CMD_ASSIGN) {
1823                                    string id_dest, id_source, button_text;
1824
1825                                    id_dest                 = scan_identifier(line, scan_pos, error);
1826                                    if (!error) id_source   = scan_identifier(line, scan_pos, error);
1827                                    if (!error) button_text = scan_string_parameter(line, scan_pos, error);
1828
1829                                    if (!error) {
1830                                        referenced_ids[id_source] = lineNo;
1831                                        referenced_ids[id_dest]   = lineNo;
1832
1833                                        const char *key = ID.fromText(button_text);
1834                                        aws->callback(makeWindowCallback(AWT_input_mask_perform_action, static_cast<awt_mask_action*>(new awt_assignment(mask, id_dest, id_source))));
1835                                        aws->button_length(button_text.length()+2);
1836                                        aws->create_button(key, button_text.c_str());
1837                                    }
1838                                }
1839                                else if (cmd == CMD_TEXT) {
1840                                    string text;
1841                                    text = scan_string_parameter(line, scan_pos, error);
1842                                    check_last_parameter(error, command);
1843
1844                                    if (!error) {
1845                                        aws->button_length(text.length()+2);
1846                                        aws->create_button(NULp, text.c_str());
1847                                    }
1848                                }
1849                                else if (cmd == CMD_SELF) {
1850                                    check_no_parameter(line, scan_pos, error, command);
1851                                    if (!error) {
1852                                        const awt_item_type_selector *selector = mask->mask_global().get_selector();
1853                                        aws->button_length(selector->get_self_awar_content_length());
1854                                        aws->create_button(NULp, selector->get_self_awar());
1855                                    }
1856                                }
1857                                else if (cmd == CMD_NEW_LINE) {
1858                                    check_no_parameter(line, scan_pos, error, command);
1859                                    if (!error) {
1860                                        int width, height;
1861                                        aws->get_window_size(width, height);
1862                                        aws->at_shift(0, 2*SEC_YBORDER+SEC_LINE_WIDTH);
1863                                    }
1864                                }
1865                                else if (cmd == CMD_NEW_SECTION) {
1866                                    check_no_parameter(line, scan_pos, error, command);
1867                                    if (!error) {
1868                                        int width, height;
1869                                        aws->get_window_size(width, height);
1870                                        horizontal_lines.push_back(height);
1871                                        aws->at_shift(0, 2*SEC_YBORDER+SEC_LINE_WIDTH);
1872                                    }
1873                                }
1874                                else if (cmd == CMD_UNKNOWN) {
1875                                    error = GBS_global_string("Unknown command '%s'", command.c_str());
1876                                }
1877                                else {
1878                                    error = GBS_global_string("No handler for '%s'", command.c_str());
1879                                    msk_assert(0);
1880                                }
1881
1882                                //  --------------------------
1883                                //      insert handler(s)
1884
1885                                if (!handler.isNull() && !error) {
1886                                    if (!radio_edit_handler.isNull()) { // special radio handler
1887                                        const awt_radio_button *radio = dynamic_cast<const awt_radio_button*>(&*handler);
1888                                        msk_assert(radio);
1889
1890                                        int x_start, y_start;
1891
1892                                        aws->get_at_position(&x_start, &y_start);
1893
1894                                        mask->add_handler(handler);
1895                                        handler->to_viewport()->build_widget(aws);
1896
1897                                        int x_end, y_end, dummy;
1898                                        aws->get_at_position(&x_end, &dummy);
1899                                        aws->at_newline();
1900                                        aws->get_at_position(&dummy, &y_end);
1901
1902                                        int height    = y_end-y_start;
1903                                        int each_line = height/radio->no_of_toggles();
1904                                        int y_offset  = each_line*(radio->default_toggle());
1905
1906                                        aws->at(x_end+x_spacing, y_start+y_offset);
1907
1908                                        mask->add_handler(radio_edit_handler);
1909                                        radio_edit_handler->to_viewport()->build_widget(aws);
1910
1911                                        radio_edit_handler.setNull();
1912                                    }
1913                                    else {
1914                                        mask->add_handler(handler);
1915                                        if (handler->is_viewport()) handler->to_viewport()->build_widget(aws);
1916                                    }
1917                                    lasthandler = handler; // store handler (used by CMD_ID)
1918                                }
1919
1920                                // parse rest of line or abort
1921                                if (!error) {
1922                                    line = line.substr(scan_pos);
1923                                    goto PARSE_REST_OF_LINE;
1924                                }
1925                                err_pos = scan_pos;
1926                                if (err_pos != string::npos) err_pos++; // because 0 means unknown
1927                            }
1928                        }
1929                    }
1930                    else { // reached the end of the current line
1931                        aws->at_newline();
1932                    }
1933                }
1934            }
1935
1936            // check declarations and references
1937            if (!error) {
1938                for (map<string, size_t>::const_iterator r = referenced_ids.begin(); r != referenced_ids.end(); ++r) {
1939                    if (declared_ids.find(r->first) == declared_ids.end()) {
1940                        error = GBS_global_string("ID '%s' used in line #%zu was not declared", r->first.c_str(), r->second);
1941                        aw_message(error);
1942                    }
1943                }
1944
1945                for (map<string, size_t>::const_iterator d = declared_ids.begin(); d != declared_ids.end(); ++d) {
1946                    if (referenced_ids.find(d->first) == referenced_ids.end()) {
1947                        const char *warning = GBS_global_string("ID '%s' declared in line #%zu is never used in %s",
1948                                                                d->first.c_str(), d->second, mask_name.c_str());
1949                        aw_message(warning);
1950                    }
1951                }
1952
1953                if (error) error = "Inconsistent IDs";
1954            }
1955
1956            if (!error) {
1957                if (!horizontal_lines.empty()) { // draw all horizontal lines
1958                    int width, height;
1959                    aws->get_window_size(width, height);
1960                    for (vector<int>::const_iterator yi = horizontal_lines.begin(); yi != horizontal_lines.end(); ++yi) {
1961                        int y = (*yi)+SEC_YBORDER;
1962                        aws->draw_line(SEC_XBORDER, y, width-SEC_XBORDER, y, SEC_LINE_WIDTH, true);
1963                    }
1964                }
1965
1966                // fit the window
1967                aws->window_fit();
1968            }
1969
1970            if (!error) link_mask_to_database(mask);
1971            if (ID.seenDups()) aw_message(ID.get_dup_error(mask_name));
1972        }
1973
1974        if (error) {
1975            if (lineNo == 0) {
1976                ; // do not change error
1977            }
1978            else if (err_pos == 0) { // don't knows exact error position
1979                error = GBS_global_string("%s in line #%zu", error, lineNo);
1980            }
1981            else if (err_pos == string::npos) {
1982                error = GBS_global_string("%s at end of line #%zu", error, lineNo);
1983            }
1984            else {
1985                int    wanted         = 35;
1986                size_t end            = line.length();
1987                string context;
1988                context.reserve(wanted);
1989                bool   last_was_space = false;
1990
1991                for (size_t ex = err_pos-1; ex<end && wanted>0; ++ex) {
1992                    char ch            = line[ex];
1993                    bool this_is_space = ch == ' ';
1994
1995                    if (!this_is_space || !last_was_space) {
1996                        context.append(1, ch);
1997                        --wanted;
1998                    }
1999                    last_was_space = this_is_space;
2000                }
2001
2002                error = GBS_global_string("%s in line #%zu at '%s...'", error, lineNo, context.c_str());
2003            }
2004        }
2005
2006        fclose(in);
2007    }
2008
2009    if (error) mask.setNull();
2010
2011    return mask;
2012}
2013
2014GB_ERROR AWT_initialize_input_mask(AW_root *root, GBDATA *gb_main, const awt_item_type_selector *sel, const char* internal_mask_name, bool local) {
2015    const char              *mask_name  = internal_mask_name+1;
2016    InputMaskList::iterator  mask_iter  = input_mask_list.find(internal_mask_name);
2017    GB_ERROR                 error      = NULp;
2018    awt_input_mask_ptr       old_mask;
2019    bool                     unlink_old = false;
2020
2021    if (mask_iter != input_mask_list.end() && mask_iter->second->reload_on_reinit()) { // reload wanted ?
2022        // erase mask (so it loads again from scratch)
2023        old_mask = mask_iter->second;
2024        input_mask_list.erase(mask_iter);
2025        mask_iter = input_mask_list.end();
2026
2027        old_mask->hide();
2028        unlink_old = true;
2029    }
2030
2031    if (mask_iter == input_mask_list.end()) { // mask not loaded yet
2032        awt_input_mask_ptr newMask = awt_create_input_mask(root, gb_main, sel, mask_name, local, error, unlink_old);
2033        if (error) {
2034            error = GBS_global_string("Error reading %s (%s)", mask_name, error);
2035            if (!old_mask.isNull()) { // are we doing a reload or changemask ?
2036                input_mask_list[internal_mask_name] = old_mask; // error loading modified mask -> put old one back to mask-list
2037                unlink_old                          = false;
2038            }
2039        }
2040        else {                                      // new mask has been generated
2041            input_mask_list[internal_mask_name] = newMask;
2042        }
2043        mask_iter = input_mask_list.find(internal_mask_name);
2044    }
2045
2046    if (!error) {
2047        msk_assert(mask_iter != input_mask_list.end());
2048        mask_iter->second->get_window()->activate();
2049    }
2050
2051    if (unlink_old) {
2052        old_mask->unlink(); // unlink old mask from database ()
2053        unlink_mask_from_database(old_mask);
2054    }
2055
2056    if (error) aw_message(error);
2057    return error;
2058}
2059
2060// start of implementation of class awt_input_mask:
2061
2062awt_input_mask::~awt_input_mask() {
2063    unlink();
2064    for (awt_mask_item_list::iterator h = handlers.begin(); h != handlers.end(); ++h) {
2065        (*h)->remove_name();
2066    }
2067}
2068
2069void awt_input_mask::link_to(GBDATA *gb_item) {
2070    // this functions links/unlinks all registered item handlers to/from the database
2071    for (awt_mask_item_list::iterator h = handlers.begin(); h != handlers.end(); ++h) {
2072        if ((*h)->is_linked_item()) (*h)->to_linked_item()->link_to(gb_item);
2073    }
2074}
2075
2076// -end- of implementation of class awt_input_mask.
2077
2078
2079awt_input_mask_descriptor::awt_input_mask_descriptor(const char *title_, const char *maskname_, const char *itemtypename_, bool local, bool hidden_) {
2080    title = ARB_strdup(title_);
2081    internal_maskname    = ARB_alloc<char>(strlen(maskname_)+2);
2082    internal_maskname[0] = local ? '0' : '1';
2083    strcpy(internal_maskname+1, maskname_);
2084    itemtypename         = ARB_strdup(itemtypename_);
2085    local_mask           = local;
2086    hidden               = hidden_;
2087}
2088awt_input_mask_descriptor::~awt_input_mask_descriptor() {
2089    free(itemtypename);
2090    free(internal_maskname);
2091    free(title);
2092}
2093
2094awt_input_mask_descriptor::awt_input_mask_descriptor(const awt_input_mask_descriptor& other) {
2095    title             = ARB_strdup(other.title);
2096    internal_maskname = ARB_strdup(other.internal_maskname);
2097    itemtypename      = ARB_strdup(other.itemtypename);
2098    local_mask        = other.local_mask;
2099    hidden            = other.hidden;
2100}
2101awt_input_mask_descriptor& awt_input_mask_descriptor::operator = (const awt_input_mask_descriptor& other) {
2102    if (this != &other) {
2103        free(itemtypename);
2104        free(internal_maskname);
2105        free(title);
2106
2107        title             = ARB_strdup(other.title);
2108        internal_maskname = ARB_strdup(other.internal_maskname);
2109        itemtypename      = ARB_strdup(other.itemtypename);
2110        local_mask        = other.local_mask;
2111        hidden            = other.hidden;
2112    }
2113
2114    return *this;
2115}
2116
2117static bool scanned_existing_input_masks = false;
2118static vector<awt_input_mask_descriptor> existing_masks;
2119
2120static void add_new_input_mask(const string& maskname, const string& fullname, bool local) {
2121    awt_input_mask_descriptor *descriptor = quick_scan_input_mask(maskname, fullname, local);
2122    if (descriptor) {
2123        existing_masks.push_back(*descriptor);
2124        delete descriptor;
2125    }
2126}
2127static void scan_existing_input_masks() {
2128    msk_assert(!scanned_existing_input_masks);
2129
2130    for (int scope = 0; scope <= 1; ++scope) {
2131        const char *dirname = inputMaskDir(scope == AWT_SCOPE_LOCAL);
2132
2133        if (!GB_is_directory(dirname)) {
2134            if (scope == AWT_SCOPE_LOCAL) {         // in local scope
2135                GB_ERROR warning = GB_create_directory(dirname); // try to create directory
2136                if (warning) GB_warning(warning);
2137            }
2138        }
2139
2140        DIR *dirp = opendir(dirname);
2141        if (!dirp) {
2142            fprintf(stderr, "Warning: No such directory '%s'\n", dirname);
2143        }
2144        else {
2145            struct dirent *dp;
2146            for (dp = readdir(dirp); dp; dp = readdir(dirp)) {
2147                struct stat st;
2148                string      maskname = dp->d_name;
2149                string      fullname = inputMaskFullname(maskname, scope == AWT_SCOPE_LOCAL);
2150
2151                if (stat(fullname.c_str(), &st)) continue;
2152                if (!S_ISREG(st.st_mode)) continue;
2153                // now we have a regular file
2154
2155                size_t ext_pos = maskname.find(".mask");
2156
2157                if (ext_pos != string::npos && maskname.substr(ext_pos) == ".mask") {
2158                    awt_input_mask_descriptor *descriptor = quick_scan_input_mask(maskname, fullname, scope == AWT_SCOPE_LOCAL);
2159                    if (descriptor) { // we found a input mask file
2160                        existing_masks.push_back(*descriptor);
2161                        delete descriptor;
2162                    }
2163                }
2164            }
2165            closedir(dirp);
2166        }
2167    }
2168    scanned_existing_input_masks = true;
2169}
2170
2171const awt_input_mask_descriptor *AWT_look_input_mask(int id) {
2172    if (!scanned_existing_input_masks) scan_existing_input_masks();
2173
2174    if (id<0 || size_t(id) >= existing_masks.size()) return NULp;
2175
2176    const awt_input_mask_descriptor *descriptor = &existing_masks[id];
2177    return descriptor;
2178}
2179
2180awt_item_type AWT_getItemType(const string& itemtype_name) {
2181    awt_item_type type = AWT_IT_UNKNOWN;
2182
2183    for (int i = AWT_IT_UNKNOWN+1; i<AWT_IT_TYPES; ++i) {
2184        if (itemtype_name == awt_itemtype_names[i]) {
2185            type = awt_item_type(i);
2186            break;
2187        }
2188    }
2189
2190    return type;
2191}
2192
2193// -----------------------------
2194//      Registered Itemtypes
2195
2196class AWT_registered_itemtype {
2197    // stores information about so-far-used item types
2198    RefPtr<AW_window_menu_modes> awm;               // the main window responsible for opening windows
2199    AWT_OpenMaskWindowCallback   open_window_cb;    // callback to open the window
2200
2201public:
2202    AWT_registered_itemtype() :
2203        awm(NULp),
2204        open_window_cb(NULp)
2205    {}
2206    AWT_registered_itemtype(AW_window_menu_modes *awm_, AWT_OpenMaskWindowCallback open_window_cb_) :
2207        awm(awm_),
2208        open_window_cb(open_window_cb_)
2209    {}
2210
2211    AW_window_menu_modes *getWindow() const { return awm; }
2212    AWT_OpenMaskWindowCallback getOpenCb() const { return open_window_cb; }
2213};
2214
2215typedef map<awt_item_type, AWT_registered_itemtype> TypeRegistry;
2216typedef TypeRegistry::const_iterator                TypeRegistryIter;
2217
2218static TypeRegistry registeredTypes;
2219
2220static GB_ERROR openMaskWindowByType(int mask_id, awt_item_type type) {
2221    TypeRegistryIter registered = registeredTypes.find(type);
2222    GB_ERROR         error      = NULp;
2223
2224    if (registered == registeredTypes.end()) error = GBS_global_string("Type '%s' not registered (yet)", awt_itemtype_names[type]);
2225    else registered->second.getOpenCb()(registered->second.getWindow(), mask_id, NULp);
2226
2227    return error;
2228}
2229
2230static void registerType(awt_item_type type, AW_window_menu_modes *awm, AWT_OpenMaskWindowCallback open_window_cb) {
2231    TypeRegistryIter alreadyRegistered = registeredTypes.find(type);
2232    if (alreadyRegistered == registeredTypes.end()) {
2233        registeredTypes[type] = AWT_registered_itemtype(awm, open_window_cb);
2234    }
2235#if defined(DEBUG)
2236    else {
2237        msk_assert(alreadyRegistered->second.getOpenCb() == open_window_cb);
2238    }
2239#endif // DEBUG
2240}
2241
2242// ----------------------------------------------
2243//      Create a new input mask (interactive)
2244
2245static void create_new_mask_cb(AW_window *aww) {
2246    AW_root *awr = aww->get_root();
2247
2248    string maskname = awr->awar(AWAR_INPUT_MASK_NAME)->read_char_pntr();
2249    {
2250        size_t ext = maskname.find(".mask");
2251
2252        if (ext == string::npos) maskname = maskname+".mask";
2253        else maskname                     = maskname.substr(0, ext)+".mask";
2254
2255        awr->awar(AWAR_INPUT_MASK_NAME)->write_string(maskname.c_str());
2256    }
2257
2258
2259    string itemname     = awr->awar(AWAR_INPUT_MASK_ITEM)->read_char_pntr();
2260    int    scope        = awr->awar(AWAR_INPUT_MASK_SCOPE)->read_int();
2261    int    hidden       = awr->awar(AWAR_INPUT_MASK_HIDDEN)->read_int();
2262    bool   local        = scope == AWT_SCOPE_LOCAL;
2263    string maskfullname = inputMaskFullname(maskname, local);
2264    bool   openMask     = false;
2265
2266    const char  *error = NULp;
2267    struct stat  st;
2268
2269    if (stat(maskfullname.c_str(), &st) == 0) { // file exists
2270        int answer = aw_question("overwrite_mask", "File does already exist", "Overwrite mask,Cancel");
2271        switch (answer) {
2272            case 0:
2273                openMask   = true;
2274                break;
2275            case 1: break;
2276            default: msk_assert(0); break;
2277        }
2278    }
2279    else {                      // file does not exist
2280        error = GB_create_directory(inputMaskDir(local));
2281        if (!error) {
2282            error = writeDefaultMaskfile(maskfullname, maskname, itemname, hidden);
2283        }
2284        if (!error) {
2285            add_new_input_mask(maskname, maskfullname, local);
2286            openMask = true;
2287        }
2288    }
2289
2290    if (!error && openMask) {
2291        int           mask_id;
2292        awt_item_type item_type = AWT_IT_UNKNOWN;
2293
2294        for (mask_id = 0; ; ++mask_id) {
2295            const awt_input_mask_descriptor *descriptor = AWT_look_input_mask(mask_id);
2296            if (!descriptor) {
2297                error = GBS_global_string("Can't find descriptor for mask '%s'", maskname.c_str());
2298                break;
2299            }
2300
2301            if (strcmp(descriptor->get_maskname(), maskname.c_str()) == 0 &&
2302                descriptor->is_local_mask() == local)
2303            {
2304                item_type = AWT_getItemType(descriptor->get_itemtypename());
2305                break;          // found wanted mask id
2306            }
2307        }
2308
2309        if (!error) {
2310            error = openMaskWindowByType(mask_id, item_type);
2311        }
2312    }
2313
2314    if (error) aw_message(error);
2315}
2316
2317static void create_new_input_mask(AW_window *aww, awt_item_type item_type) { // create new user mask (interactively)
2318    static AW_window_simple *aws = NULp;
2319
2320    if (!aws) {
2321        aws = new AW_window_simple;
2322
2323        aws->init(aww->get_root(), "CREATE_USER_MASK", "Create new input mask:");
2324
2325        aws->auto_space(10, 10);
2326
2327        aws->button_length(10);
2328        aws->callback(AW_POPDOWN);
2329        aws->create_button("CLOSE", "CLOSE");
2330        aws->callback(makeHelpCallback("input_mask_new.hlp"));
2331        aws->create_button("HELP", "HELP", "H");
2332
2333        aws->at_newline();
2334
2335        aws->label("Name of new input mask");
2336        aws->create_input_field(AWAR_INPUT_MASK_NAME, 20);
2337
2338        aws->at_newline();
2339
2340        aws->label("Item type");
2341        aws->create_option_menu(AWAR_INPUT_MASK_ITEM);
2342        for (int i = AWT_IT_UNKNOWN+1; i<AWT_IT_TYPES; ++i) {
2343            aws->insert_option(awt_itemtype_names[i], "", awt_itemtype_names[i]);
2344        }
2345        aws->update_option_menu();
2346
2347        aws->at_newline();
2348
2349        aws->create_toggle_field(AWAR_INPUT_MASK_SCOPE, "Scope", AW_HORIZONTAL);
2350        aws->insert_toggle("Local", "L", AWT_SCOPE_LOCAL);
2351        aws->insert_toggle("Global", "G", AWT_SCOPE_GLOBAL);
2352        aws->update_toggle_field();
2353
2354        aws->at_newline();
2355
2356        aws->create_toggle_field(AWAR_INPUT_MASK_HIDDEN, "Visibility", AW_HORIZONTAL);
2357        aws->insert_toggle("Normal", "N", 0);
2358        aws->insert_toggle("Hidden", "H", 1);
2359        aws->update_toggle_field();
2360
2361        aws->at_newline();
2362
2363        aws->callback(create_new_mask_cb);
2364        aws->create_button("CREATE", "CREATE", "C");
2365
2366        aws->window_fit();
2367    }
2368
2369    aws->activate();
2370    aww->get_root()->awar(AWAR_INPUT_MASK_ITEM)->write_string(awt_itemtype_names[item_type]);
2371}
2372
2373// -----------------------------------------------------
2374//      Create User-Mask-Submenu for any application
2375
2376static bool hadMnemonic(char *availableMnemonics, char c) {
2377    // return true if 'c' occurs in 'availableMnemonics' (case ignored)
2378    // (in that case 'c' is removed from 'availableMnemonics').
2379    // returns false otherwise.
2380    char  lc   = tolower(c);
2381    char *cand = strchr(availableMnemonics, lc);
2382    if (cand) {
2383        char *last = strchr(cand+1, 0)-1;
2384        if (last>cand) {
2385            cand[0] = last[0];
2386            last[0] = 0;
2387        }
2388        else {
2389            msk_assert(last == cand);
2390            cand[0] = 0;
2391        }
2392        return true;
2393    }
2394    return false;
2395}
2396
2397static char *selectMnemonic(const char *orgTitle, char *availableMnemonics, char& mnemonic) {
2398    // select (and remove) one from 'availableMnemonics' occurring in orgTitle
2399    // return selected in 'mnemonic'
2400    // return orgTitle (eventually modified if no matching mnemonic available)
2401
2402    bool prevWasChar = false;
2403    for (int startOfWord = 1; startOfWord>=0; --startOfWord) {
2404        for (int i = 0; orgTitle[i]; ++i) {
2405            char c = orgTitle[i];
2406            if (isalnum(c)) {
2407                if (!prevWasChar || !startOfWord) {
2408                    if (hadMnemonic(availableMnemonics, c)) {
2409                        mnemonic = c;
2410                        return ARB_strdup(orgTitle);
2411                    }
2412                }
2413                prevWasChar = true;
2414            }
2415            else prevWasChar = false;
2416        }
2417    }
2418
2419    for (int i = 0; i<2; ++i) {
2420        const char *takeAny = i ? availableMnemonics : "1234567890";
2421        for (int t = 0; takeAny[t]; ++t) {
2422            char c = takeAny[t];
2423            if (hadMnemonic(availableMnemonics, c)) {
2424                mnemonic = c;
2425                return GBS_global_string_copy("%s [%c]", orgTitle, c);
2426            }
2427        }
2428    }
2429
2430    mnemonic = 0; // failed
2431    return ARB_strdup(orgTitle);
2432}
2433
2434static void destroy_input_masks() {
2435    // unlink from DB manually - there are too many smartptrs to
2436    // get rid of all of them before DB gets destroyed on exit
2437    for (InputMaskList::iterator i = input_mask_list.begin();
2438         i != input_mask_list.end();
2439         ++i)
2440    {
2441        i->second->unlink();
2442    }
2443    input_mask_list.clear();
2444}
2445
2446void AWT_create_mask_submenu(AW_window_menu_modes *awm, awt_item_type wanted_item_type, AWT_OpenMaskWindowCallback open_mask_window_cb, GBDATA *gb_main) {
2447    // add a user mask submenu at current position
2448    AW_root *awr = awm->get_root();
2449
2450    if (!global_awars_created) {
2451        // this branch is executed only once
2452        create_global_awars(awr);
2453        ARB_atdisconnect_callback(makeArbDisconnectCallback(destroy_input_masks));
2454    }
2455
2456    awm->insert_sub_menu("User Masks", "k");
2457
2458    char *availableMnemonics = ARB_strdup("abcdefghijklmopqrstuvwxyz0123456789"); // 'n' excluded!
2459
2460    for (int scope = 0; scope <= 1; ++scope) {
2461        bool entries_made = false;
2462
2463        for (int id = 0; ; ++id) {
2464            const awt_input_mask_descriptor *descriptor = AWT_look_input_mask(id);
2465
2466            if (!descriptor) break;
2467            if (descriptor->is_local_mask() != (scope == AWT_SCOPE_LOCAL)) continue; // wrong scope type
2468
2469            awt_item_type item_type = AWT_getItemType(descriptor->get_itemtypename());
2470
2471            if (item_type == wanted_item_type) {
2472                if (!descriptor->is_hidden()) { // do not show masks with hidden-flag
2473                    entries_made        = true;
2474                    char *macroname2key = GBS_string_2_key(descriptor->get_internal_maskname());
2475#if defined(DEBUG) && 0
2476                    printf("added user-mask '%s' with id=%i\n", descriptor->get_maskname(), id);
2477#endif // DEBUG
2478                    char  mnemonic[2] = "x";
2479                    char *mod_title   = selectMnemonic(descriptor->get_title(), availableMnemonics, mnemonic[0]);
2480
2481                    awm->insert_menu_topic(macroname2key, mod_title, mnemonic, "input_mask.hlp", AWM_ALL, makeWindowCallback(open_mask_window_cb, id, gb_main));
2482                    free(mod_title);
2483                    free(macroname2key);
2484                }
2485                registerType(item_type, awm, open_mask_window_cb);
2486            }
2487            else if (item_type == AWT_IT_UNKNOWN) {
2488                aw_message(GBS_global_string("Unknown @ITEMTYPE '%s' in '%s'", descriptor->get_itemtypename(), descriptor->get_internal_maskname()));
2489            }
2490        }
2491        if (entries_made) awm->sep______________();
2492    }
2493
2494    {
2495        const char *itemname            = awt_itemtype_names[wanted_item_type];
2496        char       *new_item_mask_id    = GBS_global_string_copy("new_%s_mask", itemname);
2497        char       *new_item_mask_label = GBS_global_string_copy("New %s mask..", itemname);
2498
2499        awm->insert_menu_topic(new_item_mask_id, new_item_mask_label, "N", "input_mask_new.hlp", AWM_ALL, makeWindowCallback(create_new_input_mask, wanted_item_type));
2500
2501        free(new_item_mask_label);
2502        free(new_item_mask_id);
2503    }
2504    free(availableMnemonics);
2505    awm->close_sub_menu();
2506}
2507
2508void awt_item_type_selector::add_awar_callbacks(AW_root *root, const RootCallback& cb) const {
2509    root->awar(get_self_awar())->add_callback(cb);
2510}
2511
2512void awt_item_type_selector::remove_awar_callbacks(AW_root *root, const RootCallback& cb) const {
2513    root->awar(get_self_awar())->remove_callback(cb);
2514}
Note: See TracBrowser for help on using the repository browser.