source: tags/ms_r16q3/AWT/AWT_input_mask.cxx

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