source: tags/arb-6.0/AWT/AWT_input_mask.cxx

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