source: branches/species/SL/GUI_TK/config_manager.cxx

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