source: tags/ms_r17q3/AWT/AWT_input_mask.cxx

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