source: branches/help/AWT/AWT_input_mask.cxx

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