source: tags/ms_r16q2/AWT/AWT_config_manager.cxx

Last change on this file was 15040, checked in by westram, 8 years ago
  • allow to build AWT_config from mapping in AWT_config_definition
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.1 KB
Line 
1//  ==================================================================== //
2//                                                                       //
3//    File      : AWT_config_manager.cxx                                 //
4//    Purpose   :                                                        //
5//                                                                       //
6//                                                                       //
7//  Coded by Ralf Westram (coder@reallysoft.de) in January 2002          //
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_config_manager.hxx"
16#include "awt.hxx"
17#include <arbdb.h>
18#include <aw_window.hxx>
19#include <aw_root.hxx>
20#include <aw_question.hxx>
21#include <aw_awar.hxx>
22#include <aw_msg.hxx>
23#include <aw_select.hxx>
24#include <arb_strarray.h>
25
26#include <map>
27#include <string>
28#include "awt_sel_boxes.hxx"
29#include <arb_defs.h>
30#include <arb_str.h>
31
32using namespace std;
33
34// --------------------------
35//      AWT_configuration
36
37enum ConfigAwar {
38    VISIBLE_COMMENT,
39    STORED_COMMENTS,
40    EXISTING_CFGS,
41    CURRENT_CFG,
42
43    // 'Edit' subwindow
44    SELECTED_FIELD,
45    FIELD_CONTENT,
46
47    CONFIG_AWARS, // must be last
48};
49
50bool is_prefined(const string& cfgname) { return cfgname[0] == '*'; }
51
52class ConfigDefinition : virtual Noncopyable {
53    AW_default default_file;
54    string     id;
55
56    AW_awar *std_awar[CONFIG_AWARS];
57
58public:
59    ConfigDefinition(AW_default default_file_, const char *id_)
60        : default_file(default_file_),
61          id(id_)
62    {
63        std_awar[VISIBLE_COMMENT] = get_awar("comment", true);
64        std_awar[STORED_COMMENTS] = get_awar("comments");
65        std_awar[EXISTING_CFGS]   = get_awar("existing");
66        std_awar[CURRENT_CFG]     = get_awar("current");
67        std_awar[SELECTED_FIELD]  = get_awar("field",   true);
68        std_awar[FIELD_CONTENT]   = get_awar("content", true);
69    }
70
71    bool operator<(const ConfigDefinition& other) const { return id<other.id; }
72
73    AW_default get_db() const { return default_file; }
74    const char *get_id() const { return id.c_str(); }
75
76    string get_awar_name(const string& subname, bool temp = false) const {
77        return string("tmp/general_configs/"+(temp ? 0 : 4))+id+'/'+subname;
78    }
79    string get_config_dbpath(const string& cfgname) const {
80        return get_awar_name(string("cfg_")+cfgname);
81    }
82
83    AW_awar *get_awar(const string& subname, bool temp = false) const {
84        string   awar_name = get_awar_name(subname, temp);
85        return AW_root::SINGLETON->awar_string(awar_name.c_str(), "", default_file);
86    }
87    AW_awar *get_awar(ConfigAwar a) const { return std_awar[a]; }
88
89    string get_awar_value(ConfigAwar a) const { return get_awar(a)->read_char_pntr(); }
90    void set_awar_value(ConfigAwar a, const string& new_value) const { get_awar(a)->write_string(new_value.c_str()); }
91};
92
93class AWT_configuration : public ConfigDefinition { // derived from Noncopyable
94    StoreConfigCallback   store;
95    RestoreConfigCallback load_or_reset;
96
97    const AWT_predefined_config *predefined;
98
99    AW_window         *aw_edit;
100    AW_selection_list *field_selection;
101
102    GB_ERROR update_config(const string& cfgname, const AWT_config& config);
103
104public:
105    AWT_configuration(AW_default default_file_, const char *id_, const StoreConfigCallback& store_, const RestoreConfigCallback& load_or_reset_, const AWT_predefined_config *predef)
106        : ConfigDefinition(default_file_, id_),
107          store(store_),
108          load_or_reset(load_or_reset_),
109          predefined(predef),
110          aw_edit(NULL),
111          field_selection(NULL)
112    {
113    }
114
115    string get_config(const string& cfgname) {
116        if (is_prefined(cfgname)) {
117            const AWT_predefined_config *predef = find_predefined(cfgname);
118            return predef ? predef->config : "";
119        }
120        else {
121            GB_transaction  ta(get_db());
122            GBDATA         *gb_cfg = GB_search(get_db(), get_config_dbpath(cfgname).c_str(), GB_FIND);
123            return gb_cfg ? GB_read_char_pntr(gb_cfg) : "";
124        }
125    }
126    GB_ERROR set_config(const string& cfgname, const string& new_value) {
127        GB_ERROR error;
128        if (is_prefined(cfgname)) {
129            error = "cannot modify predefined config";
130        }
131        else {
132            GB_transaction  ta(get_db());
133            GBDATA         *gb_cfg = GB_search(get_db(), get_config_dbpath(cfgname).c_str(), GB_STRING);
134            if (!gb_cfg) {
135                error = GB_await_error();
136            }
137            else {
138                error = GB_write_string(gb_cfg, new_value.c_str());
139                get_awar(CURRENT_CFG)->touch(); // force refresh of config editor
140            }
141        }
142        return error;
143    }
144
145    char *Store() const { return store(); }
146    GB_ERROR Restore(const string& s) const {
147        GB_ERROR error = 0;
148
149        if (s.empty()) error = "empty/nonexistant config";
150        else load_or_reset(s.c_str());
151
152        return error;
153    }
154    void Reset() const {
155        load_or_reset(NULL);
156    }
157
158    GB_ERROR Save(const char* filename, const string& cfg_name, const string& comment); // AWAR content -> FILE
159    GB_ERROR Load(const char* filename, const string& cfg_name, string& found_comment); // FILE -> AWAR content
160
161    bool has_existing(string lookFor) {
162        string S(";");
163        string existing = S+ get_awar_value(EXISTING_CFGS) +S;
164        string wanted   = S+ lookFor +S;
165
166        return existing.find(wanted) != string::npos;
167    }
168
169    void erase_deleted_configs();
170
171    void add_predefined_to(ConstStrArray& cfgs) {
172        if (predefined) {
173            for (int i = 0; predefined[i].name; ++i) {
174                awt_assert(predefined[i].name[0] == '*'); // names have to start with '*'
175                cfgs.put(predefined[i].name);
176            }
177        }
178    }
179    const AWT_predefined_config *find_predefined(const string& cfgname) {
180        awt_assert(is_prefined(cfgname));
181        for (int i = 0; predefined[i].name; ++i) {
182            if (cfgname == predefined[i].name) {
183                return &predefined[i];
184            }
185        }
186        return NULL;
187    }
188
189    void popup_edit_window(AW_window *aw_config);
190    void update_field_selection_list();
191    void update_field_content();
192    void store_changed_field_content();
193    void delete_selected_field();
194    void keep_changed_fields();
195};
196
197static GB_ERROR decode_escapes(string& s) {
198    string::iterator f = s.begin();
199    string::iterator t = s.begin();
200
201    for (; f != s.end(); ++f, ++t) {
202        if (*f == '\\') {
203            ++f;
204            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
205            switch (*f) {
206                case 'n':
207                    *t = '\n';
208                    break;
209                case 'r':
210                    *t = '\r';
211                    break;
212                case 't':
213                    *t = '\t';
214                    break;
215                default:
216                    *t = *f;
217                    break;
218            }
219        }
220        else {
221            *t = *f;
222        }
223    }
224
225    s.erase(t, f);
226
227    return 0;
228}
229
230static void encode_escapes(string& s, const char *to_escape) {
231    string neu;
232    neu.reserve(s.length()*2+1);
233
234    for (string::iterator p = s.begin(); p != s.end(); ++p) {
235        if (*p == '\\' || strchr(to_escape, *p) != 0) {
236            neu = neu+'\\'+*p;
237        }
238        else if (*p == '\n') { neu = neu+"\\n"; }
239        else if (*p == '\r') { neu = neu+"\\r"; }
240        else if (*p == '\t') { neu = neu+"\\t"; }
241        else { neu = neu+*p; }
242    }
243    s = neu;
244}
245
246#define HEADER    "ARB_CONFIGURATION"
247#define HEADERLEN 17
248
249GB_ERROR AWT_configuration::Save(const char* filename, const string& cfg_name, const string& comment) {
250    awt_assert(strlen(HEADER) == HEADERLEN);
251
252    printf("Saving config to '%s'..\n", filename);
253
254    FILE     *out   = fopen(filename, "wt");
255    GB_ERROR  error = 0;
256    if (!out) {
257        error = GB_IO_error("saving", filename);
258    }
259    else {
260        if (comment.empty()) {
261            fprintf(out, HEADER ":%s\n", get_id()); // =same as old format
262        }
263        else {
264            string encoded_comment(comment);
265            encode_escapes(encoded_comment, "");
266            fprintf(out, HEADER ":%s;%s\n", get_id(), encoded_comment.c_str());
267        }
268
269        string content = get_config(cfg_name);
270        fputs(content.c_str(), out);
271        fclose(out);
272    }
273    return error;
274}
275
276GB_ERROR AWT_configuration::Load(const char* filename, const string& cfg_name, string& found_comment) {
277    GB_ERROR error = 0;
278
279    found_comment = "";
280    printf("Loading config from '%s'..\n", filename);
281
282    char *content = GB_read_file(filename);
283    if (!content) {
284        error = GB_await_error();
285    }
286    else {
287        if (strncmp(content, HEADER ":", HEADERLEN+1) != 0) {
288            error = "Unexpected content (" HEADER " missing)";
289        }
290        else {
291            char *id_pos = content+HEADERLEN+1;
292            char *nl     = strchr(id_pos, '\n');
293
294            if (!nl) {
295                error = "Unexpected content (no ID)";
296            }
297            else {
298                *nl++ = 0;
299
300                char *comment = strchr(id_pos, ';');
301                if (comment) *comment++ = 0;
302
303                if (strcmp(id_pos, get_id()) != 0) {
304                    error = GBS_global_string("Wrong config type (expected=%s, found=%s)", get_id(), id_pos);
305                }
306                else {
307                    error = set_config(cfg_name, nl);
308                    if (comment && !error) {
309                        found_comment = comment;
310                        error         = decode_escapes(found_comment);
311                    }
312                }
313            }
314        }
315
316        if (error) {
317            error = GBS_global_string("Error: %s (while reading %s)", error, filename);
318        }
319
320        free(content);
321    }
322
323    return error;
324}
325
326void AWT_configuration::erase_deleted_configs() {
327    string   cfg_base = get_awar_name("", false);
328    GB_ERROR error    = NULL;
329    {
330        GB_transaction  ta(get_db());
331        GBDATA         *gb_base = GB_search(get_db(), cfg_base.c_str(), GB_FIND);
332        if (gb_base) {
333            for (GBDATA *gb_cfg = GB_child(gb_base); gb_cfg && !error; ) {
334                GBDATA     *gb_next = GB_nextChild(gb_cfg);
335                const char *key     = GB_read_key_pntr(gb_cfg);
336                if (key && ARB_strBeginsWith(key, "cfg_")) {
337                    const char *name = key+4;
338                    if (!has_existing(name)) {
339                        error = GB_delete(gb_cfg);
340                    }
341                }
342                gb_cfg = gb_next;
343            }
344        }
345    }
346    aw_message_if(error);
347}
348
349void remove_from_configs(const string& config, string& existing_configs) {
350    ConstStrArray cfgs;
351    GBT_split_string(cfgs, existing_configs.c_str(), ';');
352
353    ConstStrArray remaining;
354    for (int i = 0; cfgs[i]; ++i) {
355        if (strcmp(cfgs[i], config.c_str()) != 0) {
356            remaining.put(cfgs[i]);
357        }
358    }
359
360    char *rest       = GBT_join_strings(remaining, ';');
361    existing_configs = rest;
362    free(rest);
363}
364
365#define NO_CONFIG_SELECTED "<no config selected>"
366
367static void current_changed_cb(AW_root*, AWT_configuration *config) {
368    AW_awar *awar_current = config->get_awar(CURRENT_CFG);
369    AW_awar *awar_comment = config->get_awar(VISIBLE_COMMENT);
370
371    // convert name to key (but allow empty string and strings starting with '*')
372    string name;
373    {
374        const char *entered_name = awar_current->read_char_pntr();
375        if (entered_name[0]) {
376            bool  isPredefined = is_prefined(entered_name);
377            char *asKey        = GBS_string_2_key(entered_name);
378            name = isPredefined ? string(1, '*')+asKey : asKey;
379            free(asKey);
380        }
381        else {
382            name = "";
383        }
384    }
385
386    awar_current->write_string(name.c_str());
387
388    // refresh comment field
389    if (name[0]) { // cfg not empty
390        if (config->has_existing(name)) { // load comment of existing config
391            string     storedComments = config->get_awar_value(STORED_COMMENTS);
392            AWT_config comments(storedComments.c_str());
393
394            const char *display;
395            if (comments.parseError()) {
396                display = GBS_global_string("Error reading config comments:\n%s", comments.parseError());
397            }
398            else {
399                const char *saved_comment = comments.get_entry(name.c_str());
400                display                   = saved_comment ? saved_comment : "";
401            }
402            awar_comment->write_string(display);
403        }
404        else if (is_prefined(name)) {
405            const AWT_predefined_config *found = config->find_predefined(name);
406            awar_comment->write_string(found ? found->description : NO_CONFIG_SELECTED);
407        }
408        else { // new config (not stored yet)
409            // click <new> and enter name -> clear comment
410            // click existing and change name -> reuse existing comment
411            if (strcmp(awar_comment->read_char_pntr(), NO_CONFIG_SELECTED) == 0) {
412                awar_comment->write_string("");
413            }
414        }
415    }
416    else { // no config selected
417        awar_comment->write_string(NO_CONFIG_SELECTED);
418    }
419
420    // refresh field selection list + content field
421    config->update_field_selection_list();
422}
423
424inline void save_comments(const AWT_config& comments, AWT_configuration *config) {
425    char *comments_string = comments.config_string();
426    config->set_awar_value(STORED_COMMENTS, comments_string);
427    free(comments_string);
428}
429
430static void comment_changed_cb(AW_root*, AWT_configuration *config) {
431    string curr_cfg = config->get_awar_value(CURRENT_CFG);
432    if (!curr_cfg.empty()) {
433        string changed_comment = config->get_awar_value(VISIBLE_COMMENT);
434        if (is_prefined(curr_cfg)) {
435            const AWT_predefined_config *found = config->find_predefined(curr_cfg);
436            if (found && changed_comment != found->description) {
437                aw_message("The description of predefined configs is immutable");
438                config->get_awar(CURRENT_CFG)->touch(); // reload comment
439            }
440        }
441        else if (config->has_existing(curr_cfg)) {
442            AWT_config comments(config->get_awar_value(STORED_COMMENTS).c_str());
443            if (comments.parseError()) {
444                aw_message(GBS_global_string("Failed to parse config-comments (%s)", comments.parseError()));
445            }
446            else {
447                if (changed_comment.empty()) {
448                    comments.delete_entry(curr_cfg.c_str());
449                }
450                else {
451                    comments.set_entry(curr_cfg.c_str(), changed_comment.c_str());
452                }
453                save_comments(comments, config);
454            }
455        }
456    }
457}
458static void erase_comment_cb(AW_window*, AW_awar *awar_comment) {
459    awar_comment->write_string("");
460}
461
462static void restore_cb(AW_window *, AWT_configuration *config) {
463    string cfgName = config->get_awar_value(CURRENT_CFG);
464    GB_ERROR error;
465    if (cfgName.empty()) {
466        error = "Please select config to restore";
467    }
468    else {
469        error = config->Restore(config->get_config(cfgName));
470    }
471    aw_message_if(error);
472}
473static void store_cb(AW_window *, AWT_configuration *config) {
474    string cfgName = config->get_awar_value(CURRENT_CFG);
475    if (cfgName.empty()) aw_message("Please select or enter name of config to store");
476    else if (is_prefined(cfgName)) aw_message("You can't modify predefined configs");
477    else {
478        string existing = config->get_awar_value(EXISTING_CFGS);
479
480        AW_awar *awar_comment = config->get_awar(VISIBLE_COMMENT);
481        string visibleComment(awar_comment->read_char_pntr());
482
483        remove_from_configs(cfgName, existing); // remove selected from existing configs
484
485        existing = existing.empty() ? cfgName : (string(cfgName)+';'+existing);
486        {
487            char     *config_string = config->Store();
488            GB_ERROR  error         = config->set_config(cfgName, config_string);
489            aw_message_if(error);
490            free(config_string);
491        }
492        config->set_awar_value(EXISTING_CFGS, existing);
493        awar_comment->rewrite_string(visibleComment.c_str()); // force new config to use last visible comment
494
495        config->get_awar(CURRENT_CFG)->touch(); // force refresh of config editor
496    }
497}
498static void delete_cb(AW_window *, AWT_configuration *config) {
499    string cfgName = config->get_awar_value(CURRENT_CFG);
500    if (is_prefined(cfgName)) {
501        aw_message("You may not delete predefined configs");
502    }
503    else {
504        string existing = config->get_awar_value(EXISTING_CFGS);
505        remove_from_configs(cfgName, existing); // remove selected from existing configs
506        config->set_awar_value(CURRENT_CFG, "");
507        config->set_awar_value(EXISTING_CFGS, existing);
508
509        // erase existing comment:
510        AWT_config comments(config->get_awar_value(STORED_COMMENTS).c_str());
511        comments.delete_entry(cfgName.c_str());
512        save_comments(comments, config);
513
514        config->erase_deleted_configs();
515    }
516}
517static void load_cb(AW_window *, AWT_configuration *config) {
518    string   cfgName = config->get_awar_value(CURRENT_CFG);
519    GB_ERROR error   = NULL;
520
521    if (cfgName.empty()) error = "Please enter or select target config";
522    else if (is_prefined(cfgName)) error = "You may not load over a predefined config";
523    else {
524        char *loadMask = GBS_global_string_copy("%s_*", config->get_id());
525        char *filename = aw_file_selection("Load config from file", "$(ARBCONFIG)", loadMask, ".arbcfg");
526        if (filename) {
527            string comment;
528
529            error = config->Load(filename, cfgName, comment);
530            if (!error) {
531                // after successful load restore and store config
532                restore_cb(NULL, config);
533                store_cb(NULL, config);
534                config->set_awar_value(VISIBLE_COMMENT, comment);
535            }
536            free(filename);
537        }
538        free(loadMask);
539    }
540    aw_message_if(error);
541}
542static void save_cb(AW_window *, AWT_configuration *config) {
543    string   cfgName = config->get_awar_value(CURRENT_CFG);
544    GB_ERROR error   = NULL;
545
546    if (cfgName.empty()) error = "Please select config to save";
547    else {
548        char *saveAs = GBS_global_string_copy("%s_%s",
549                                              config->get_id(),
550                                              cfgName.c_str() + (cfgName[0] == '*')); // skip leading '*'
551
552        char *filename = aw_file_selection("Save config to file", "$(ARBCONFIG)", saveAs, ".arbcfg");
553        if (filename && filename[0]) {
554            restore_cb(NULL, config);
555            string comment = config->get_awar_value(VISIBLE_COMMENT);
556            error          = config->Save(filename, cfgName, comment);
557            free(filename);
558        }
559        free(saveAs);
560    }
561    aw_message_if(error);
562}
563
564#if defined(DEBUG)
565
566static string esc(const string& str) {
567    // escape C string
568    char   *escaped = GBS_string_eval(str.c_str(), "\\n=\\\\n:\\t=\\\\t:\"=\\\\\"", NULL);
569    string  result(escaped);
570    free(escaped);
571    return result;
572}
573
574static void dump_cb(AW_window *, AWT_configuration *config) {
575    // dump code ready to insert into AWT_predefined_config
576    string   cfgName = config->get_awar_value(CURRENT_CFG);
577    GB_ERROR error   = NULL;
578
579    if (cfgName.empty()) error = "Please select config to dump";
580    else {
581        string comment = esc(config->get_awar_value(VISIBLE_COMMENT));
582        string confStr = esc(config->get_config(cfgName));
583
584        cfgName         = esc(cfgName);
585        const char *cfg = cfgName.c_str();
586
587        fprintf(stderr, "    { \"*%s\", \"%s\", \"%s\" },\n",
588                cfg[0] == '*' ? cfg+1 : cfg,
589                comment.c_str(),
590                confStr.c_str());
591    }
592    aw_message_if(error);
593}
594#endif
595
596
597void AWT_configuration::update_field_selection_list() {
598    if (field_selection) {
599        string  cfgName      = get_awar_value(CURRENT_CFG);
600        char   *selected     = get_awar(SELECTED_FIELD)->read_string();
601        bool    seenSelected = false;
602
603        field_selection->clear();
604        if (!cfgName.empty() && has_existing(cfgName)) {
605            string     configString = get_config(cfgName);
606            AWT_config stored(configString.c_str());
607            ConstStrArray entries;
608            stored.get_entries(entries);
609
610            StrArray entries_with_content;
611            size_t   maxlen = 0;
612            for (size_t e = 0; e<entries.size(); ++e) {
613                maxlen = std::max(maxlen, strlen(entries[e]));
614                if (strcmp(selected, entries[e]) == 0) seenSelected = true;
615            }
616            for (size_t e = 0; e<entries.size(); ++e) {
617                field_selection->insert(GBS_global_string("%-*s  |  %s",
618                                                          int(maxlen), entries[e],
619                                                          stored.get_entry(entries[e])),
620                                        entries[e]);
621            }
622        }
623        field_selection->insert_default("", "");
624        field_selection->update();
625
626        if (!seenSelected) {
627            get_awar(SELECTED_FIELD)->write_string("");
628        }
629        else {
630            get_awar(SELECTED_FIELD)->touch();
631        }
632        free(selected);
633    }
634}
635
636void AWT_configuration::update_field_content() {
637    string cfgName = get_awar_value(CURRENT_CFG);
638    string content = "<select a field below>";
639    if (!cfgName.empty() && has_existing(cfgName)) {
640        string selected = get_awar_value(SELECTED_FIELD);
641        if (!selected.empty()) {
642            string     configString = get_config(cfgName);
643            AWT_config stored(configString.c_str());
644
645            if (stored.has_entry(selected.c_str())) {
646                content = stored.get_entry(selected.c_str());
647            }
648            else {
649                content = GBS_global_string("<field '%s' not stored in config>", selected.c_str());
650            }
651        }
652    }
653    set_awar_value(FIELD_CONTENT, content.c_str());
654}
655
656GB_ERROR AWT_configuration::update_config(const string& cfgname, const AWT_config& config) {
657    char     *changedConfigString = config.config_string();
658    GB_ERROR  error               = set_config(cfgname, changedConfigString);
659    free(changedConfigString);
660    return error;
661}
662
663void AWT_configuration::store_changed_field_content() {
664    string cfgName = get_awar_value(CURRENT_CFG);
665    if (!cfgName.empty() && has_existing(cfgName)) {
666        string selected = get_awar_value(SELECTED_FIELD);
667        if (!selected.empty()) {
668            string     configString = get_config(cfgName);
669            AWT_config stored(configString.c_str());
670            if (stored.has_entry(selected.c_str())) {
671                string stored_content  = stored.get_entry(selected.c_str());
672                string changed_content = get_awar_value(FIELD_CONTENT);
673
674                if (stored_content != changed_content) {
675                    stored.set_entry(selected.c_str(), changed_content.c_str());
676                    aw_message_if(update_config(cfgName, stored));
677                }
678            }
679        }
680    }
681}
682
683void AWT_configuration::delete_selected_field() {
684    string cfgName = get_awar_value(CURRENT_CFG);
685    if (!cfgName.empty() && has_existing(cfgName)) {
686        string selected = get_awar_value(SELECTED_FIELD);
687        if (!selected.empty()) {
688            string     configString = get_config(cfgName);
689            AWT_config stored(configString.c_str());
690            if (stored.has_entry(selected.c_str())) {
691                stored.delete_entry(selected.c_str());
692                aw_message_if(update_config(cfgName, stored));
693                field_selection->move_selection(1);
694                update_field_selection_list();
695            }
696        }
697    }
698}
699
700void AWT_configuration::keep_changed_fields() {
701    string cfgName = get_awar_value(CURRENT_CFG);
702    if (!cfgName.empty() && has_existing(cfgName)) {
703        string     configString = get_config(cfgName);
704        AWT_config stored(configString.c_str());
705
706        char       *current_state = Store();
707        AWT_config  current(current_state);
708
709        ConstStrArray entries;
710        stored.get_entries(entries);
711        int           deleted = 0;
712
713        for (size_t e = 0; e<entries.size(); ++e) {
714            const char *entry          = entries[e];
715            const char *stored_content = stored.get_entry(entry);
716
717            if (current.has_entry(entry)) {
718                const char *current_content = current.get_entry(entry);
719                if (strcmp(stored_content, current_content) == 0) {
720                    stored.delete_entry(entry);
721                    deleted++;
722                }
723            }
724            else {
725                aw_message(GBS_global_string("Entry '%s' is not (or no longer) supported", entry));
726            }
727        }
728
729        if (deleted) {
730            aw_message_if(update_config(cfgName, stored));
731            update_field_selection_list();
732        }
733        else {
734            aw_message("All entries differ from current state");
735        }
736
737        free(current_state);
738    }
739}
740
741static void keep_changed_fields_cb(AW_window*, AWT_configuration *config) { config->keep_changed_fields(); }
742static void delete_field_cb(AW_window*, AWT_configuration *config) { config->delete_selected_field(); }
743static void selected_field_changed_cb(AW_root*, AWT_configuration *config) { config->update_field_content(); }
744static void field_content_changed_cb(AW_root*, AWT_configuration *config) { config->store_changed_field_content(); }
745
746void AWT_configuration::popup_edit_window(AW_window *aw_config) {
747    if (!aw_edit) {
748        AW_root          *root = aw_config->get_root();
749        AW_window_simple *aws  = new AW_window_simple;
750        {
751            char *wid = GBS_global_string_copy("%s_edit", aw_config->get_window_id());
752            aws->init(root, wid, "Edit configuration entries");
753            free(wid);
754        }
755        aws->load_xfig("awt/edit_config.fig");
756
757        aws->at("close");
758        aws->callback(AW_POPDOWN);
759        aws->create_button("CLOSE", "CLOSE");
760
761        aws->at("help");
762        aws->callback(makeHelpCallback("prop_configs_edit.hlp"));
763        aws->create_button("HELP", "HELP");
764
765        aws->at("content");
766        aws->create_input_field(get_awar(FIELD_CONTENT)->awar_name);
767
768        aws->at("name");
769        aws->create_button(NULL, get_awar(CURRENT_CFG)->awar_name, 0, "+");
770
771        aws->at("entries");
772        field_selection = aws->create_selection_list(get_awar(SELECTED_FIELD)->awar_name, true);
773
774        aws->auto_space(0, 3);
775        aws->button_length(10);
776        aws->at("button");
777
778        int xpos = aws->get_at_xposition();
779        int ypos = aws->get_at_yposition();
780
781        aws->callback(makeWindowCallback(delete_field_cb, this));
782        aws->create_button("DELETE", "Delete\nselected\nentry", "D");
783
784        aws->at_newline();
785        ypos = aws->get_at_yposition();
786        aws->at("button");
787        aws->at(xpos, ypos);
788
789        aws->callback(makeWindowCallback(keep_changed_fields_cb, this));
790        aws->create_button("KEEP_CHANGED", "Keep only\nentries\ndiffering\nfrom\ncurrent\nstate", "K");
791
792        aw_edit = aws;
793
794        // bind callbacks to awars
795        get_awar(SELECTED_FIELD)->add_callback(makeRootCallback(selected_field_changed_cb, this));
796        get_awar(FIELD_CONTENT)->add_callback(makeRootCallback(field_content_changed_cb, this));
797
798        // fill selection list
799        update_field_selection_list();
800    }
801
802    aw_edit->activate();
803}
804
805static void edit_cb(AW_window *aww, AWT_configuration *config) { config->popup_edit_window(aww); }
806static void reset_cb(AW_window *, AWT_configuration *config) { config->Reset(); }
807
808static void get_existing_configs(ConfigDefinition& configDef, ConstStrArray& cfgs) {
809    string cfgs_str = configDef.get_awar_value(EXISTING_CFGS);
810    GBT_split_string(cfgs, cfgs_str.c_str(), ';');
811}
812
813static void refresh_config_sellist_cb(AW_root*, AWT_configuration *config, AW_selection_list *sel) {
814    ConstStrArray cfgs;
815    get_existing_configs(*config, cfgs);
816
817    config->add_predefined_to(cfgs);
818    sel->init_from_array(cfgs, "<new>", "");
819}
820
821static AW_window *create_config_manager_window(AW_root *, AWT_configuration *config, AW_window *aww) {
822    AW_window_simple *aws = new AW_window_simple;
823
824    char *title = GBS_global_string_copy("Configurations for '%s'", aww->get_window_title());
825    char *id    = GBS_global_string_copy("%s_config",               aww->get_window_id());
826
827    aws->init(aww->get_root(), id, title);
828    aws->load_xfig("awt/manage_config.fig");
829
830    aws->at("close");
831    aws->callback(AW_POPDOWN);
832    aws->create_button("CLOSE", "CLOSE");
833
834    aws->at("help");
835    aws->callback(makeHelpCallback("prop_configs.hlp"));
836    aws->create_button("HELP", "HELP");
837
838    // create awars
839    AW_awar *awar_existing = config->get_awar(EXISTING_CFGS);
840    AW_awar *awar_current  = config->get_awar(CURRENT_CFG);
841    AW_awar *awar_comment  = config->get_awar(VISIBLE_COMMENT);
842
843    aws->at("comment");
844    aws->create_text_field(awar_comment->awar_name);
845
846    aws->at("clr");
847    aws->callback(makeWindowCallback(erase_comment_cb, awar_comment));
848    aws->create_autosize_button("erase", "Erase", "E");
849
850    awar_current->add_callback(makeRootCallback(current_changed_cb, config));
851    awar_comment->add_callback(makeRootCallback(comment_changed_cb, config));
852
853    AW_selection_list *sel = awt_create_selection_list_with_input_field(aws, awar_current->awar_name, "cfgs", "name");
854
855    awar_existing->add_callback(makeRootCallback(refresh_config_sellist_cb, config, sel));
856    awar_existing->touch(); // fills selection list
857    awar_current->touch(); // initialized comment textbox
858
859    aws->auto_space(0, 3);
860    aws->button_length(10);
861    aws->at("button");
862
863    int xpos = aws->get_at_xposition();
864    int ypos = aws->get_at_yposition();
865
866    struct but {
867        void (*cb)(AW_window*, AWT_configuration*);
868        const char *id;
869        const char *label;
870        const char *mnemonic;
871    } butDef[] = {
872        { restore_cb, "RESTORE", "Restore",           "R" },
873        { store_cb,   "STORE",   "Store",             "S" },
874        { delete_cb,  "DELETE",  "Delete",            "D" },
875        { load_cb,    "LOAD",    "Load",              "L" },
876        { save_cb,    "SAVE",    "Save",              "v" },
877        { reset_cb,   "RESET",   "Factory\ndefaults", "F" },
878        { edit_cb,    "EDIT",    "Edit",              "E" },
879#if defined(DEBUG)
880        { dump_cb,    "DUMP",    "dump\npredef",  "U" },
881#endif
882    };
883    const int buttons = ARRAY_ELEMS(butDef);
884    for (int b = 0; b<buttons; ++b) {
885        const but& B = butDef[b];
886
887        if (b>0) {
888            aws->at("button");
889            aws->at(xpos, ypos);
890        }
891
892        aws->callback(makeWindowCallback(B.cb, config));
893        aws->create_button(B.id, B.label, B.mnemonic);
894
895        aws->at_newline();
896        ypos = aws->get_at_yposition();
897    }
898
899    free(id);
900    free(title);
901
902    return aws;
903}
904
905static void destroy_AWT_configuration(AWT_configuration *c, AW_window*) { delete c; }
906
907void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, const StoreConfigCallback& store_cb,
908                               const RestoreConfigCallback& load_or_reset_cb, const char *macro_id, const AWT_predefined_config *predef)
909{
910    /*! inserts a config-button into aww
911     * @param default_file_ db where configs will be stored
912     * @param id unique id (has to be a key)
913     * @param store_cb creates a string from current state
914     * @param load_or_reset_cb restores state from string or resets factory defaults if string is NULL
915     * @param macro_id custom macro id (normally NULL will do)
916     * @param predef predefined configs
917     */
918    AWT_configuration * const config = new AWT_configuration(default_file_, id, store_cb, load_or_reset_cb, predef);
919
920    aww->button_length(0); // -> autodetect size by size of graphic
921    aww->callback(makeCreateWindowCallback(create_config_manager_window, destroy_AWT_configuration, config, aww));
922    aww->create_button(macro_id ? macro_id : "SAVELOAD_CONFIG", "#conf_save.xpm");
923}
924
925static char *store_generated_config_cb(const ConfigSetupCallback *setup_cb) {
926    AWT_config_definition cdef;
927    (*setup_cb)(cdef);
928
929    return cdef.read();
930}
931static void load_or_reset_generated_config_cb(const char *stored_string, const ConfigSetupCallback *setup_cb) {
932    AWT_config_definition cdef;
933    (*setup_cb)(cdef);
934
935    if (stored_string) cdef.write(stored_string);
936    else cdef.reset();
937}
938void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, ConfigSetupCallback setup_cb, const char *macro_id, const AWT_predefined_config *predef) {
939    /*! inserts a config-button into aww
940     * @param default_file_ db where configs will be stored
941     * @param id unique id (has to be a key)
942     * @param setup_cb populates an AWT_config_definition (cl is passed to setup_cb)
943     * @param macro_id custom macro id (normally NULL will do)
944     * @param predef predefined configs
945     */
946
947    ConfigSetupCallback * const setup_cb_copy = new ConfigSetupCallback(setup_cb); // not freed (bound to cb)
948    AWT_insert_config_manager(aww, default_file_, id,
949                              makeStoreConfigCallback(store_generated_config_cb, setup_cb_copy),
950                              makeRestoreConfigCallback(load_or_reset_generated_config_cb, setup_cb_copy),
951                              macro_id, predef);
952}
953
954static void generate_config_from_mapping_cb(AWT_config_definition& cdef, const AWT_config_mapping_def *mapping) {
955    cdef.add(mapping);
956}
957
958void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, const AWT_config_mapping_def *mapping, const char *macro_id, const AWT_predefined_config *predef) {
959    /*! inserts a config-button into aww
960     * @param default_file_ db where configs will be stored
961     * @param id unique id (has to be a key)
962     * @param mapping hardcoded mapping between AWARS and config strings
963     * @param macro_id custom macro id (normally NULL will do)
964     * @param predef predefined configs
965     */
966    AWT_insert_config_manager(aww, default_file_, id, makeConfigSetupCallback(generate_config_from_mapping_cb, mapping), macro_id, predef);
967}
968
969typedef map<string, string> config_map;
970struct AWT_config_mapping {
971    config_map cmap;
972
973    config_map::iterator entry(const string &e) { return cmap.find(e); }
974
975    config_map::iterator begin() { return cmap.begin(); }
976    config_map::const_iterator end() const { return cmap.end(); }
977    config_map::iterator end() { return cmap.end(); }
978    config_map::size_type size() const { return cmap.size(); }
979};
980
981// -------------------
982//      AWT_config
983
984AWT_config::AWT_config(const char *config_char_ptr) :
985    mapping(new AWT_config_mapping),
986    parse_error(0)
987{
988    // parse string in format "key1='value1';key2='value2'"..
989    // and put values into a map.
990    // assumes that keys are unique
991
992    string      configString(config_char_ptr);
993    config_map& cmap  = mapping->cmap;
994    size_t      pos   = 0;
995
996    while (!parse_error) {
997        size_t equal = configString.find('=', pos);
998        if (equal == string::npos) break;
999
1000        if (configString[equal+1] != '\'') {
1001            parse_error = "expected quote \"'\"";
1002            break;
1003        }
1004        size_t start = equal+2;
1005        size_t end   = configString.find('\'', start);
1006        while (end != string::npos) {
1007            if (configString[end-1] != '\\') break;
1008            end = configString.find('\'', end+1);
1009        }
1010        if (end == string::npos) {
1011            parse_error = "could not find matching quote \"'\"";
1012            break;
1013        }
1014
1015        string config_name = configString.substr(pos, equal-pos);
1016        string value       = configString.substr(start, end-start);
1017
1018        parse_error = decode_escapes(value);
1019        if (!parse_error) {
1020            cmap[config_name] = value;
1021        }
1022
1023        pos = end+2;            // skip ';'
1024    }
1025}
1026
1027inline void warn_unknown_awar(const string& awar_name) {
1028    aw_message(GBS_global_string("Warning: unknown awar referenced\n(%s)", awar_name.c_str()));
1029}
1030
1031void AWT_config::init_from_awars(const AWT_config_mapping *cfgname_2_awar) {
1032    const config_map&  awarmap  = cfgname_2_awar->cmap;
1033    config_map&        valuemap = mapping->cmap;
1034    AW_root           *aw_root  = AW_root::SINGLETON;
1035
1036    int skipped = 0;
1037    for (config_map::const_iterator c = awarmap.begin(); c != awarmap.end(); ++c) {
1038        const string& key(c->first);
1039        const string& awar_name(c->second);
1040
1041        AW_awar *awar = aw_root->awar_no_error(awar_name.c_str());
1042        if (awar) {
1043            char *awar_value = awar->read_as_string();
1044            valuemap[key] = awar_value;
1045            free(awar_value);
1046        }
1047        else {
1048            valuemap.erase(key);
1049            warn_unknown_awar(awar_name);
1050            ++skipped;
1051        }
1052    }
1053
1054    awt_assert((valuemap.size()+skipped) == awarmap.size());
1055    awt_assert(!parse_error);
1056}
1057
1058AWT_config::AWT_config(const AWT_config_mapping *cfgname_2_awar) :
1059    mapping(new AWT_config_mapping),
1060    parse_error(0)
1061{
1062    init_from_awars(cfgname_2_awar);
1063}
1064
1065AWT_config::AWT_config(const AWT_config_definition *cfg_def) :
1066    mapping(new AWT_config_mapping),
1067    parse_error(0)
1068{
1069    init_from_awars(cfg_def->get_mapping());
1070}
1071
1072AWT_config::~AWT_config() {
1073    delete mapping;
1074}
1075
1076bool AWT_config::has_entry(const char *entry) const {
1077    awt_assert(!parse_error);
1078    return mapping->entry(entry) != mapping->end();
1079}
1080const char *AWT_config::get_entry(const char *entry) const {
1081    awt_assert(!parse_error);
1082    config_map::iterator found = mapping->entry(entry);
1083    return (found == mapping->end()) ? 0 : found->second.c_str();
1084}
1085void AWT_config::set_entry(const char *entry, const char *value) {
1086    awt_assert(!parse_error);
1087    mapping->cmap[entry] = value;
1088}
1089void AWT_config::delete_entry(const char *entry) {
1090    awt_assert(!parse_error);
1091    mapping->cmap.erase(entry);
1092}
1093
1094char *AWT_config::config_string() const {
1095    awt_assert(!parse_error);
1096    string result;
1097    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1098        const string& config_name(e->first);
1099        string        value(e->second);
1100
1101        encode_escapes(value, "\'");
1102        string entry = config_name+"='"+value+'\'';
1103        if (result.empty()) {
1104            result = entry;
1105        }
1106        else {
1107            result = result+';'+entry;
1108        }
1109    }
1110    return strdup(result.c_str());
1111}
1112void AWT_config::write_to_awars(const AWT_config_mapping *cfgname_2_awar, bool warn) const {
1113    awt_assert(!parse_error);
1114    AW_root *aw_root  = AW_root::SINGLETON;
1115    int      unmapped = 0;
1116    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1117        const string& config_name(e->first);
1118        const string& value(e->second);
1119
1120        config_map::const_iterator found = cfgname_2_awar->cmap.find(config_name);
1121        if (found == cfgname_2_awar->end()) {
1122            if (warn) aw_message(GBS_global_string("config contains unknown entry '%s'", config_name.c_str()));
1123            unmapped++;
1124        }
1125        else {
1126            const string&  awar_name(found->second);
1127            AW_awar       *awar = aw_root->awar(awar_name.c_str());
1128            awar->write_as_string(value.c_str());
1129        }
1130    }
1131
1132    if (unmapped && warn) {
1133        int mapped = mapping->size()-unmapped;
1134        aw_message(GBS_global_string("Not all config entries were valid:\n"
1135                                     "(known/restored: %i, unknown/ignored: %i)\n"
1136                                     "Note: ok for configs shared between multiple windows",
1137                                     mapped, unmapped));
1138    }
1139}
1140
1141void AWT_config::get_entries(ConstStrArray& to_array) {
1142    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1143        const string& key(e->first);
1144        to_array.put(key.c_str());
1145    }
1146}
1147
1148// ------------------------------
1149//      AWT_config_definition
1150
1151AWT_config_definition::AWT_config_definition()
1152    : config_mapping(new AWT_config_mapping)
1153{}
1154
1155AWT_config_definition::AWT_config_definition(AWT_config_mapping_def *mdef)
1156    : config_mapping(new AWT_config_mapping)
1157{
1158    add(mdef);
1159}
1160
1161AWT_config_definition::~AWT_config_definition() {
1162    delete config_mapping;
1163}
1164
1165void AWT_config_definition::add(const char *awar_name, const char *config_name) {
1166    config_mapping->cmap[config_name] = awar_name;
1167}
1168void AWT_config_definition::add(const char *awar_name, const char *config_name, int counter) {
1169    add(awar_name, GBS_global_string("%s%i", config_name, counter));
1170}
1171void AWT_config_definition::add(const AWT_config_mapping_def *mdef) {
1172    while (mdef->awar_name && mdef->config_name) {
1173        add(mdef->awar_name, mdef->config_name);
1174        mdef++;
1175    }
1176}
1177
1178char *AWT_config_definition::read() const {
1179    // creates a string from awar values
1180
1181    AWT_config current_state(config_mapping);
1182    return current_state.config_string();
1183}
1184void AWT_config_definition::write(const char *config_char_ptr) const {
1185    // write values from string to awars
1186    // if the string contains unknown settings, they are silently ignored
1187
1188    awt_assert(config_char_ptr);
1189
1190    AWT_config wanted_state(config_char_ptr);
1191    GB_ERROR   error = wanted_state.parseError();
1192    if (!error) {
1193        char *old_state = read();
1194        wanted_state.write_to_awars(config_mapping, true);
1195        if (strcmp(old_state, config_char_ptr) != 0) { // expect that anything gets changed?
1196            char *new_state = read();
1197            if (strcmp(new_state, config_char_ptr) != 0) {
1198                bool retry      = true;
1199                int  maxRetries = 10;
1200                while (retry && maxRetries--) {
1201                    printf("Note: repeating config restore (did not set all awars correct)\n");
1202                    wanted_state.write_to_awars(config_mapping, false);
1203                    char *new_state2 = read();
1204                    if (strcmp(new_state, new_state2) != 0) { // retrying had some effect -> repeat
1205                        reassign(new_state, new_state2);
1206                    }
1207                    else {
1208                        retry = false;
1209                        free(new_state2);
1210                    }
1211                }
1212                if (retry) {
1213                    error = "Unable to restore everything (might be caused by outdated, now invalid settings)";
1214                }
1215            }
1216            free(new_state);
1217        }
1218        free(old_state);
1219    }
1220    if (error) aw_message(GBS_global_string("Error restoring configuration (%s)", error));
1221}
1222
1223void AWT_config_definition::reset() const {
1224    // reset all awars (stored in config) to factory defaults
1225    AW_root *aw_root = AW_root::SINGLETON;
1226    for (config_map::iterator e = config_mapping->begin(); e != config_mapping->end(); ++e) {
1227        const string&  awar_name(e->second);
1228        AW_awar       *awar = aw_root->awar_no_error(awar_name.c_str());
1229        if (awar) {
1230            awar->reset_to_default();
1231        }
1232        else {
1233            warn_unknown_awar(awar_name);
1234        }
1235    }
1236}
1237
1238// --------------------------------------------------------------------------------
1239
1240void AWT_modify_managed_configs(AW_default default_file, const char *id, ConfigModifyCallback mod_cb, AW_CL cl_user) {
1241    /*! allows to modify (parts of) all stored configs
1242     * @param default_file   has to be same as used in AWT_insert_config_manager()
1243     * @param id             ditto
1244     * @param mod_cb         called with each key/value pair of each stored config. result == NULL -> delete pair; result != NULL -> change or leave unchanged (result has to be a heapcopy!)
1245     * @param cl_user        forwarded to mod_cb
1246     */
1247
1248    ConfigDefinition configDef(default_file, id);
1249
1250    ConstStrArray cfgs;
1251    get_existing_configs(configDef, cfgs);
1252
1253    for (size_t c = 0; c<cfgs.size(); ++c) {
1254        GB_transaction  ta(configDef.get_db());
1255        GBDATA         *gb_cfg = GB_search(configDef.get_db(), configDef.get_config_dbpath(cfgs[c]).c_str(), GB_FIND);
1256        GB_ERROR        error  = NULL;
1257
1258        if (gb_cfg) {
1259            const char *content = GB_read_char_pntr(gb_cfg);
1260
1261            AWT_config cmap(content);
1262            error = cmap.parseError();
1263            if (!error) {
1264                ConstStrArray entries;
1265                cmap.get_entries(entries);
1266
1267                bool update = false;
1268                for (size_t e = 0; e<entries.size(); ++e) {
1269                    const char *old_content = cmap.get_entry(entries[e]);
1270                    char       *new_content = mod_cb(entries[e], old_content, cl_user);
1271
1272                    if (!new_content) {
1273                        cmap.delete_entry(entries[e]);
1274                        update = true;
1275                    }
1276                    else if (strcmp(old_content, new_content) != 0) {
1277                        cmap.set_entry(entries[e], new_content);
1278                        update = true;
1279                    }
1280                    free(new_content);
1281                }
1282
1283                if (update) {
1284                    char *cs = cmap.config_string();
1285                    error    = GB_write_string(gb_cfg, cs);
1286                    free(cs);
1287                }
1288            }
1289        }
1290
1291        if (error) {
1292            error = GBS_global_string("%s (config='%s')", error, cfgs[c]);
1293            aw_message(error);
1294        }
1295    }
1296}
Note: See TracBrowser for help on using the repository browser.