source: branches/items/AWT/AWT_config_manager.cxx

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