source: trunk/AWT/AWT_input_mask.cxx

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