source: tags/ms_r18q1/AWT/AWT_config_manager.cxx

Last change on this file was 16763, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.5 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(NULp),
111          field_selection(NULp)
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 = NULp;
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(NULp);
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 NULp;
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 NULp;
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)) {
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 = NULp;
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 = NULp;
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    = NULp;
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   = NULp;
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(NULp, config);
533                store_cb(NULp, 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   = NULp;
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(NULp, 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
569    char *escaped = GBS_string_eval(str.c_str(), "\\\\=\\\\\\\\:\"=\\\\\":\\n=\\\\n:\\t=\\\\t");
570    // unescaped once by compiler and once by SRT interpreter
571    // results in SRT: '\=\\:"=\":<lf>=\n:<tab>=\t'
572
573    string result(escaped);
574    free(escaped);
575    return result;
576}
577
578static void dump_cb(AW_window *, AWT_configuration *config) {
579    // dump code ready to insert into AWT_predefined_config
580    string   cfgName = config->get_awar_value(CURRENT_CFG);
581    GB_ERROR error   = NULp;
582
583    if (cfgName.empty()) error = "Please select config to dump";
584    else {
585        string comment = esc(config->get_awar_value(VISIBLE_COMMENT));
586        string confStr = esc(config->get_config(cfgName));
587
588        cfgName         = esc(cfgName);
589        const char *cfg = cfgName.c_str();
590
591        fprintf(stderr, "    { \"*%s\", \"%s\", \"%s\" },\n",
592                cfg[0] == '*' ? cfg+1 : cfg,
593                comment.c_str(),
594                confStr.c_str());
595    }
596    aw_message_if(error);
597}
598#endif
599
600
601void AWT_configuration::update_field_selection_list() {
602    if (field_selection) {
603        string  cfgName      = get_awar_value(CURRENT_CFG);
604        char   *selected     = get_awar(SELECTED_FIELD)->read_string();
605        bool    seenSelected = false;
606
607        field_selection->clear();
608        if (!cfgName.empty() && has_existing(cfgName)) {
609            string     configString = get_config(cfgName);
610            AWT_config stored(configString.c_str());
611            ConstStrArray entries;
612            stored.get_entries(entries);
613
614            StrArray entries_with_content;
615            size_t   maxlen = 0;
616            for (size_t e = 0; e<entries.size(); ++e) {
617                maxlen = std::max(maxlen, strlen(entries[e]));
618                if (strcmp(selected, entries[e]) == 0) seenSelected = true;
619            }
620            for (size_t e = 0; e<entries.size(); ++e) {
621                field_selection->insert(GBS_global_string("%-*s  |  %s",
622                                                          int(maxlen), entries[e],
623                                                          stored.get_entry(entries[e])),
624                                        entries[e]);
625            }
626        }
627        field_selection->insert_default("", "");
628        field_selection->update();
629
630        if (!seenSelected) {
631            get_awar(SELECTED_FIELD)->write_string("");
632        }
633        else {
634            get_awar(SELECTED_FIELD)->touch();
635        }
636        free(selected);
637    }
638}
639
640void AWT_configuration::update_field_content() {
641    string cfgName = get_awar_value(CURRENT_CFG);
642    string content = "<select a field below>";
643    if (!cfgName.empty() && has_existing(cfgName)) {
644        string selected = get_awar_value(SELECTED_FIELD);
645        if (!selected.empty()) {
646            string     configString = get_config(cfgName);
647            AWT_config stored(configString.c_str());
648
649            if (stored.has_entry(selected.c_str())) {
650                content = stored.get_entry(selected.c_str());
651            }
652            else {
653                content = GBS_global_string("<field '%s' not stored in config>", selected.c_str());
654            }
655        }
656    }
657    set_awar_value(FIELD_CONTENT, content.c_str());
658}
659
660GB_ERROR AWT_configuration::update_config(const string& cfgname, const AWT_config& config) {
661    char     *changedConfigString = config.config_string();
662    GB_ERROR  error               = set_config(cfgname, changedConfigString);
663    free(changedConfigString);
664    return error;
665}
666
667void AWT_configuration::store_changed_field_content() {
668    string cfgName = get_awar_value(CURRENT_CFG);
669    if (!cfgName.empty() && has_existing(cfgName)) {
670        string selected = get_awar_value(SELECTED_FIELD);
671        if (!selected.empty()) {
672            string     configString = get_config(cfgName);
673            AWT_config stored(configString.c_str());
674            if (stored.has_entry(selected.c_str())) {
675                string stored_content  = stored.get_entry(selected.c_str());
676                string changed_content = get_awar_value(FIELD_CONTENT);
677
678                if (stored_content != changed_content) {
679                    stored.set_entry(selected.c_str(), changed_content.c_str());
680                    aw_message_if(update_config(cfgName, stored));
681                }
682            }
683        }
684    }
685}
686
687void AWT_configuration::delete_selected_field() {
688    string cfgName = get_awar_value(CURRENT_CFG);
689    if (!cfgName.empty() && has_existing(cfgName)) {
690        string selected = get_awar_value(SELECTED_FIELD);
691        if (!selected.empty()) {
692            string     configString = get_config(cfgName);
693            AWT_config stored(configString.c_str());
694            if (stored.has_entry(selected.c_str())) {
695                stored.delete_entry(selected.c_str());
696                aw_message_if(update_config(cfgName, stored));
697                field_selection->move_selection(1);
698                update_field_selection_list();
699            }
700        }
701    }
702}
703
704void AWT_configuration::keep_changed_fields() {
705    string cfgName = get_awar_value(CURRENT_CFG);
706    if (!cfgName.empty() && has_existing(cfgName)) {
707        string     configString = get_config(cfgName);
708        AWT_config stored(configString.c_str());
709
710        char       *current_state = Store();
711        AWT_config  current(current_state);
712
713        ConstStrArray entries;
714        stored.get_entries(entries);
715        int           deleted = 0;
716
717        for (size_t e = 0; e<entries.size(); ++e) {
718            const char *entry          = entries[e];
719            const char *stored_content = stored.get_entry(entry);
720
721            if (current.has_entry(entry)) {
722                const char *current_content = current.get_entry(entry);
723                if (strcmp(stored_content, current_content) == 0) {
724                    stored.delete_entry(entry);
725                    deleted++;
726                }
727            }
728            else {
729                aw_message(GBS_global_string("Entry '%s' is not (or no longer) supported", entry));
730            }
731        }
732
733        if (deleted) {
734            aw_message_if(update_config(cfgName, stored));
735            update_field_selection_list();
736        }
737        else {
738            aw_message("All entries differ from current state");
739        }
740
741        free(current_state);
742    }
743}
744
745static void keep_changed_fields_cb(AW_window*, AWT_configuration *config) { config->keep_changed_fields(); }
746static void delete_field_cb(AW_window*, AWT_configuration *config) { config->delete_selected_field(); }
747static void selected_field_changed_cb(AW_root*, AWT_configuration *config) { config->update_field_content(); }
748static void field_content_changed_cb(AW_root*, AWT_configuration *config) { config->store_changed_field_content(); }
749
750void AWT_configuration::popup_edit_window(AW_window *aw_config) {
751    if (!aw_edit) {
752        AW_root          *root = aw_config->get_root();
753        AW_window_simple *aws  = new AW_window_simple;
754        {
755            char *wid = GBS_global_string_copy("%s_edit", aw_config->get_window_id());
756            aws->init(root, wid, "Edit configuration entries");
757            free(wid);
758        }
759        aws->load_xfig("awt/edit_config.fig");
760
761        aws->at("close");
762        aws->callback(AW_POPDOWN);
763        aws->create_button("CLOSE", "CLOSE");
764
765        aws->at("help");
766        aws->callback(makeHelpCallback("prop_configs_edit.hlp"));
767        aws->create_button("HELP", "HELP");
768
769        aws->at("content");
770        aws->create_input_field(get_awar(FIELD_CONTENT)->awar_name);
771
772        aws->at("name");
773        aws->create_button(NULp, get_awar(CURRENT_CFG)->awar_name, NULp, "+");
774
775        aws->at("entries");
776        field_selection = aws->create_selection_list(get_awar(SELECTED_FIELD)->awar_name, true);
777
778        aws->auto_space(0, 3);
779        aws->button_length(10);
780        aws->at("button");
781
782        int xpos = aws->get_at_xposition();
783        int ypos = aws->get_at_yposition();
784
785        aws->callback(makeWindowCallback(delete_field_cb, this));
786        aws->create_button("DELETE", "Delete\nselected\nentry", "D");
787
788        aws->at_newline();
789        ypos = aws->get_at_yposition();
790        aws->at("button");
791        aws->at(xpos, ypos);
792
793        aws->callback(makeWindowCallback(keep_changed_fields_cb, this));
794        aws->create_button("KEEP_CHANGED", "Keep only\nentries\ndiffering\nfrom\ncurrent\nstate", "K");
795
796        aw_edit = aws;
797
798        // bind callbacks to awars
799        get_awar(SELECTED_FIELD)->add_callback(makeRootCallback(selected_field_changed_cb, this));
800        get_awar(FIELD_CONTENT)->add_callback(makeRootCallback(field_content_changed_cb, this));
801
802        // fill selection list
803        update_field_selection_list();
804    }
805
806    aw_edit->activate();
807}
808
809static void edit_cb(AW_window *aww, AWT_configuration *config) { config->popup_edit_window(aww); }
810static void reset_cb(AW_window *, AWT_configuration *config) { config->Reset(); }
811
812static void get_existing_configs(ConfigDefinition& configDef, ConstStrArray& cfgs) {
813    string cfgs_str = configDef.get_awar_value(EXISTING_CFGS);
814    GBT_split_string(cfgs, cfgs_str.c_str(), ';');
815}
816
817static void refresh_config_sellist_cb(AW_root*, AWT_configuration *config, AW_selection_list *sel) {
818    ConstStrArray cfgs;
819    get_existing_configs(*config, cfgs);
820
821    config->add_predefined_to(cfgs);
822    sel->init_from_array(cfgs, "<new>", "");
823}
824
825static AW_window *create_config_manager_window(AW_root *, AWT_configuration *config, AW_window *aww) {
826    AW_window_simple *aws = new AW_window_simple;
827
828    char *title = GBS_global_string_copy("Configurations for '%s'", aww->get_window_title());
829    char *id    = GBS_global_string_copy("%s_config",               aww->get_window_id());
830
831    aws->init(aww->get_root(), id, title);
832    aws->load_xfig("awt/manage_config.fig");
833
834    aws->at("close");
835    aws->callback(AW_POPDOWN);
836    aws->create_button("CLOSE", "CLOSE");
837
838    aws->at("help");
839    aws->callback(makeHelpCallback("prop_configs.hlp"));
840    aws->create_button("HELP", "HELP");
841
842    // create awars
843    AW_awar *awar_existing = config->get_awar(EXISTING_CFGS);
844    AW_awar *awar_current  = config->get_awar(CURRENT_CFG);
845    AW_awar *awar_comment  = config->get_awar(VISIBLE_COMMENT);
846
847    aws->at("comment");
848    aws->create_text_field(awar_comment->awar_name);
849
850    aws->at("clr");
851    aws->callback(makeWindowCallback(erase_comment_cb, awar_comment));
852    aws->create_autosize_button("erase", "Erase", "E");
853
854    awar_current->add_callback(makeRootCallback(current_changed_cb, config));
855    awar_comment->add_callback(makeRootCallback(comment_changed_cb, config));
856
857    AW_selection_list *sel = awt_create_selection_list_with_input_field(aws, awar_current->awar_name, "cfgs", "name");
858
859    awar_existing->add_callback(makeRootCallback(refresh_config_sellist_cb, config, sel));
860    awar_existing->touch(); // fills selection list
861    awar_current->touch(); // initialized comment textbox
862
863    aws->auto_space(0, 3);
864    aws->button_length(10);
865    aws->at("button");
866
867    int xpos = aws->get_at_xposition();
868    int ypos = aws->get_at_yposition();
869
870    struct but {
871        void (*cb)(AW_window*, AWT_configuration*);
872        const char *id;
873        const char *label;
874        const char *mnemonic;
875    } butDef[] = {
876        { restore_cb, "RESTORE", "Restore",           "R" },
877        { store_cb,   "STORE",   "Store",             "S" },
878        { delete_cb,  "DELETE",  "Delete",            "D" },
879        { load_cb,    "LOAD",    "Load",              "L" },
880        { save_cb,    "SAVE",    "Save",              "v" },
881        { reset_cb,   "RESET",   "Factory\ndefaults", "F" },
882        { edit_cb,    "EDIT",    "Edit",              "E" },
883#if defined(DEBUG)
884        { dump_cb,    "DUMP",    "dump\npredef",  "U" },
885#endif
886    };
887    const int buttons = ARRAY_ELEMS(butDef);
888    for (int b = 0; b<buttons; ++b) {
889        const but& B = butDef[b];
890
891        if (b>0) {
892            aws->at("button");
893            aws->at(xpos, ypos);
894        }
895
896        aws->callback(makeWindowCallback(B.cb, config));
897        aws->create_button(B.id, B.label, B.mnemonic);
898
899        aws->at_newline();
900        ypos = aws->get_at_yposition();
901    }
902
903    free(id);
904    free(title);
905
906    return aws;
907}
908
909static void destroy_AWT_configuration(AWT_configuration *c, AW_window*) { delete c; }
910
911void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, const StoreConfigCallback& store_cb,
912                               const RestoreConfigCallback& load_or_reset_cb, const char *macro_id, const AWT_predefined_config *predef)
913{
914    /*! inserts a config-button into aww
915     * @param default_file_ db where configs will be stored (use AW_ROOT_DEFAULT to store in properties)
916     * @param id unique id (has to be a key)
917     * @param store_cb creates a string from current state
918     * @param load_or_reset_cb restores state from string or resets factory defaults if string is NULp
919     * @param macro_id custom macro id (normally default (=NULp) will do)
920     * @param predef predefined configs (default: none)
921     */
922    AWT_configuration * const config = new AWT_configuration(default_file_, id, store_cb, load_or_reset_cb, predef);
923
924    aww->button_length(0); // -> autodetect size by size of graphic
925    aww->callback(makeCreateWindowCallback(create_config_manager_window, destroy_AWT_configuration, config, aww));
926    aww->create_button(macro_id ? macro_id : "SAVELOAD_CONFIG", "#conf_save.xpm");
927}
928
929static char *store_generated_config_cb(const ConfigSetupCallback *setup_cb) {
930    AWT_config_definition cdef;
931    (*setup_cb)(cdef);
932
933    return cdef.read();
934}
935static void load_or_reset_generated_config_cb(const char *stored_string, const ConfigSetupCallback *setup_cb) {
936    AWT_config_definition cdef;
937    (*setup_cb)(cdef);
938
939    if (stored_string) cdef.write(stored_string);
940    else cdef.reset();
941}
942void 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) {
943    /*! inserts a config-button into aww
944     * @param default_file_ db where configs will be stored (use AW_ROOT_DEFAULT to store in properties)
945     * @param id unique id (has to be a key)
946     * @param setup_cb populates an AWT_config_definition (cl is passed to setup_cb)
947     * @param macro_id custom macro id (normally default (=NULp) will do)
948     * @param predef predefined configs (default: none)
949     */
950
951    ConfigSetupCallback * const setup_cb_copy = new ConfigSetupCallback(setup_cb); // not freed (bound to cb)
952    AWT_insert_config_manager(aww, default_file_, id,
953                              makeStoreConfigCallback(store_generated_config_cb, setup_cb_copy),
954                              makeRestoreConfigCallback(load_or_reset_generated_config_cb, setup_cb_copy),
955                              macro_id, predef);
956}
957
958static void generate_config_from_mapping_cb(AWT_config_definition& cdef, const AWT_config_mapping_def *mapping) {
959    cdef.add(mapping);
960}
961
962void 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) {
963    /*! inserts a config-button into aww
964     * @param default_file_ db where configs will be stored (use AW_ROOT_DEFAULT to store in properties)
965     * @param id unique id (has to be a key)
966     * @param mapping hardcoded mapping between AWARS and config strings
967     * @param macro_id custom macro id (normally default (=NULp) will do)
968     * @param predef predefined configs (default: none)
969     */
970    AWT_insert_config_manager(aww, default_file_, id, makeConfigSetupCallback(generate_config_from_mapping_cb, mapping), macro_id, predef);
971}
972
973typedef map<string, string> config_map;
974struct AWT_config_mapping {
975    config_map cmap;
976
977    config_map::iterator entry(const string &e) { return cmap.find(e); }
978
979    config_map::iterator begin() { return cmap.begin(); }
980    config_map::const_iterator end() const { return cmap.end(); }
981    config_map::iterator end() { return cmap.end(); }
982    config_map::size_type size() const { return cmap.size(); }
983};
984
985// -------------------
986//      AWT_config
987
988AWT_config::AWT_config(const char *config_char_ptr) :
989    mapping(new AWT_config_mapping),
990    parse_error(NULp)
991{
992    // parse string in format "key1='value1';key2='value2'"..
993    // and put values into a map.
994    // assumes that keys are unique
995
996    string      configString(config_char_ptr);
997    config_map& cmap  = mapping->cmap;
998    size_t      pos   = 0;
999
1000    while (!parse_error) {
1001        size_t equal = configString.find('=', pos);
1002        if (equal == string::npos) break;
1003
1004        if (configString[equal+1] != '\'') {
1005            parse_error = "expected quote \"'\"";
1006            break;
1007        }
1008        size_t start = equal+2;
1009        size_t end   = configString.find('\'', start);
1010        while (end != string::npos) {
1011            if (configString[end-1] != '\\') break;
1012            end = configString.find('\'', end+1);
1013        }
1014        if (end == string::npos) {
1015            parse_error = "could not find matching quote \"'\"";
1016            break;
1017        }
1018
1019        string config_name = configString.substr(pos, equal-pos);
1020        string value       = configString.substr(start, end-start);
1021
1022        parse_error = decode_escapes(value);
1023        if (!parse_error) {
1024            cmap[config_name] = value;
1025        }
1026
1027        pos = end+2;            // skip ';'
1028    }
1029}
1030
1031inline void warn_unknown_awar(const string& awar_name) {
1032    aw_message(GBS_global_string("Warning: unknown awar referenced\n(%s)", awar_name.c_str()));
1033}
1034
1035void AWT_config::init_from_awars(const AWT_config_mapping *cfgname_2_awar) {
1036    const config_map&  awarmap  = cfgname_2_awar->cmap;
1037    config_map&        valuemap = mapping->cmap;
1038    AW_root           *aw_root  = AW_root::SINGLETON;
1039
1040    int skipped = 0;
1041    for (config_map::const_iterator c = awarmap.begin(); c != awarmap.end(); ++c) {
1042        const string& key(c->first);
1043        const string& awar_name(c->second);
1044
1045        AW_awar *awar = aw_root->awar_no_error(awar_name.c_str());
1046        if (awar) {
1047            char *awar_value = awar->read_as_string();
1048            valuemap[key] = awar_value;
1049            free(awar_value);
1050        }
1051        else {
1052            valuemap.erase(key);
1053            warn_unknown_awar(awar_name);
1054            ++skipped;
1055        }
1056    }
1057
1058    awt_assert((valuemap.size()+skipped) == awarmap.size());
1059    awt_assert(!parse_error);
1060}
1061
1062AWT_config::AWT_config(const AWT_config_mapping *cfgname_2_awar) :
1063    mapping(new AWT_config_mapping),
1064    parse_error(NULp)
1065{
1066    init_from_awars(cfgname_2_awar);
1067}
1068
1069AWT_config::AWT_config(const AWT_config_definition *cfg_def) :
1070    mapping(new AWT_config_mapping),
1071    parse_error(NULp)
1072{
1073    init_from_awars(cfg_def->get_mapping());
1074}
1075
1076AWT_config::~AWT_config() {
1077    delete mapping;
1078}
1079
1080bool AWT_config::has_entry(const char *entry) const {
1081    awt_assert(!parse_error);
1082    return mapping->entry(entry) != mapping->end();
1083}
1084const char *AWT_config::get_entry(const char *entry) const {
1085    awt_assert(!parse_error);
1086    config_map::iterator found = mapping->entry(entry);
1087    return (found == mapping->end()) ? NULp : found->second.c_str();
1088}
1089void AWT_config::set_entry(const char *entry, const char *value) {
1090    awt_assert(!parse_error);
1091    mapping->cmap[entry] = value;
1092}
1093void AWT_config::delete_entry(const char *entry) {
1094    awt_assert(!parse_error);
1095    mapping->cmap.erase(entry);
1096}
1097
1098char *AWT_config::config_string() const {
1099    awt_assert(!parse_error);
1100    string result;
1101    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1102        const string& config_name(e->first);
1103        string        value(e->second);
1104
1105        encode_escapes(value, "\'");
1106        string entry = config_name+"='"+value+'\'';
1107        if (result.empty()) {
1108            result = entry;
1109        }
1110        else {
1111            result = result+';'+entry;
1112        }
1113    }
1114    return ARB_strdup(result.c_str());
1115}
1116void AWT_config::write_to_awars(const AWT_config_mapping *cfgname_2_awar, bool warn) const {
1117    awt_assert(!parse_error);
1118    AW_root *aw_root  = AW_root::SINGLETON;
1119    int      unmapped = 0;
1120    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1121        const string& config_name(e->first);
1122        const string& value(e->second);
1123
1124        config_map::const_iterator found = cfgname_2_awar->cmap.find(config_name);
1125        if (found == cfgname_2_awar->end()) {
1126            if (warn) aw_message(GBS_global_string("config contains unknown entry '%s'", config_name.c_str()));
1127            unmapped++;
1128        }
1129        else {
1130            const string&  awar_name(found->second);
1131            AW_awar       *awar = aw_root->awar(awar_name.c_str());
1132            awar->write_as_string(value.c_str());
1133        }
1134    }
1135
1136    if (unmapped && warn) {
1137        int mapped = mapping->size()-unmapped;
1138        aw_message(GBS_global_string("Not all config entries were valid:\n"
1139                                     "(known/restored: %i, unknown/ignored: %i)\n"
1140                                     "Note: ok for configs shared between multiple windows",
1141                                     mapped, unmapped));
1142    }
1143}
1144
1145void AWT_config::get_entries(ConstStrArray& to_array) {
1146    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
1147        const string& key(e->first);
1148        to_array.put(key.c_str());
1149    }
1150}
1151
1152// ------------------------------
1153//      AWT_config_definition
1154
1155AWT_config_definition::AWT_config_definition()
1156    : config_mapping(new AWT_config_mapping)
1157{}
1158
1159AWT_config_definition::AWT_config_definition(AWT_config_mapping_def *mdef)
1160    : config_mapping(new AWT_config_mapping)
1161{
1162    add(mdef);
1163}
1164
1165AWT_config_definition::~AWT_config_definition() {
1166    delete config_mapping;
1167}
1168
1169void AWT_config_definition::add(const char *awar_name, const char *config_name) {
1170    config_mapping->cmap[config_name] = awar_name;
1171}
1172void AWT_config_definition::add(const char *awar_name, const char *config_name, int counter) {
1173    add(awar_name, GBS_global_string("%s%i", config_name, counter));
1174}
1175void AWT_config_definition::add(const AWT_config_mapping_def *mdef) {
1176    while (mdef->awar_name && mdef->config_name) {
1177        add(mdef->awar_name, mdef->config_name);
1178        mdef++;
1179    }
1180}
1181
1182char *AWT_config_definition::read() const {
1183    // creates a string from awar values
1184
1185    AWT_config current_state(config_mapping);
1186    return current_state.config_string();
1187}
1188void AWT_config_definition::write(const char *config_char_ptr) const {
1189    // write values from string to awars
1190    // if the string contains unknown settings, they are silently ignored
1191
1192    awt_assert(config_char_ptr);
1193
1194    AWT_config wanted_state(config_char_ptr);
1195    GB_ERROR   error = wanted_state.parseError();
1196    if (!error) {
1197        char *old_state = read();
1198        wanted_state.write_to_awars(config_mapping, true);
1199        if (strcmp(old_state, config_char_ptr) != 0) { // expect that anything gets changed?
1200            char *new_state = read();
1201            if (strcmp(new_state, config_char_ptr) != 0) {
1202                bool retry      = true;
1203                int  maxRetries = 10;
1204                while (retry && maxRetries--) {
1205                    printf("Note: repeating config restore (did not set all awars correct)\n");
1206                    wanted_state.write_to_awars(config_mapping, false);
1207                    char *new_state2 = read();
1208                    if (strcmp(new_state, new_state2) != 0) { // retrying had some effect -> repeat
1209                        reassign(new_state, new_state2);
1210                    }
1211                    else {
1212                        retry = false;
1213                        free(new_state2);
1214                    }
1215                }
1216                if (retry) {
1217                    error = "Unable to restore everything (might be caused by outdated, now invalid settings)";
1218                }
1219            }
1220            free(new_state);
1221        }
1222        free(old_state);
1223    }
1224    if (error) aw_message(GBS_global_string("Error restoring configuration (%s)", error));
1225}
1226
1227void AWT_config_definition::reset() const {
1228    // reset all awars (stored in config) to factory defaults
1229    AW_root *aw_root = AW_root::SINGLETON;
1230    for (config_map::iterator e = config_mapping->begin(); e != config_mapping->end(); ++e) {
1231        const string&  awar_name(e->second);
1232        AW_awar       *awar = aw_root->awar_no_error(awar_name.c_str());
1233        if (awar) {
1234            awar->reset_to_default();
1235        }
1236        else {
1237            warn_unknown_awar(awar_name);
1238        }
1239    }
1240}
1241
1242// --------------------------------------------------------------------------------
1243
1244void AWT_modify_managed_configs(AW_default default_file, const char *id, ConfigModifyCallback mod_cb, AW_CL cl_user) {
1245    /*! allows to modify (parts of) all stored configs
1246     * @param default_file   has to be same as used in AWT_insert_config_manager()
1247     * @param id             ditto
1248     * @param mod_cb         called with each key/value pair of each stored config. result == NULp -> delete pair; result != NULp -> change or leave unchanged (result has to be a heapcopy!)
1249     * @param cl_user        forwarded to mod_cb
1250     */
1251
1252    ConfigDefinition configDef(default_file, id);
1253
1254    ConstStrArray cfgs;
1255    get_existing_configs(configDef, cfgs);
1256
1257    for (size_t c = 0; c<cfgs.size(); ++c) {
1258        GB_transaction  ta(configDef.get_db());
1259        GBDATA         *gb_cfg = GB_search(configDef.get_db(), configDef.get_config_dbpath(cfgs[c]).c_str(), GB_FIND);
1260        GB_ERROR        error  = NULp;
1261
1262        if (gb_cfg) {
1263            const char *content = GB_read_char_pntr(gb_cfg);
1264
1265            AWT_config cmap(content);
1266            error = cmap.parseError();
1267            if (!error) {
1268                ConstStrArray entries;
1269                cmap.get_entries(entries);
1270
1271                bool update = false;
1272                for (size_t e = 0; e<entries.size(); ++e) {
1273                    const char *old_content = cmap.get_entry(entries[e]);
1274                    char       *new_content = mod_cb(entries[e], old_content, cl_user);
1275
1276                    if (!new_content) {
1277                        cmap.delete_entry(entries[e]);
1278                        update = true;
1279                    }
1280                    else if (strcmp(old_content, new_content) != 0) {
1281                        cmap.set_entry(entries[e], new_content);
1282                        update = true;
1283                    }
1284                    free(new_content);
1285                }
1286
1287                if (update) {
1288                    char *cs = cmap.config_string();
1289                    error    = GB_write_string(gb_cfg, cs);
1290                    free(cs);
1291                }
1292            }
1293        }
1294
1295        if (error) {
1296            error = GBS_global_string("%s (config='%s')", error, cfgs[c]);
1297            aw_message(error);
1298        }
1299    }
1300}
Note: See TracBrowser for help on using the repository browser.