source: tags/ms_r18q1/AWT/AWT_input_mask.cxx

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