source: trunk/SL/GUI_TK/config_manager.cxx

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