root/trunk/AWT/AWT_input_mask.cxx

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