source: branches/profile/AWT/AWT_config_manager.cxx

Last change on this file was 12848, checked in by westram, 10 years ago
  • reintegrates 'fix' into 'trunk'
    • removes calls to aw_string_selection and aw_string_selection2awar (implementing another step for #179)
  • adds:
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.1 KB
Line 
1//  ==================================================================== //
2//                                                                       //
3//    File      : AWT_config_manager.cxx                                 //
4//    Purpose   :                                                        //
5//                                                                       //
6//                                                                       //
7//  Coded by Ralf Westram (coder@reallysoft.de) in January 2002          //
8//  Copyright Department of Microbiology (Technical University Munich)   //
9//                                                                       //
10//  Visit our web site at: http://www.arb-home.de/                       //
11//                                                                       //
12//                                                                       //
13//  ==================================================================== //
14
15#include "awt_config_manager.hxx"
16#include "awt.hxx"
17#include <arbdb.h>
18#include <aw_window.hxx>
19#include <aw_root.hxx>
20#include <aw_question.hxx>
21#include <aw_awar.hxx>
22#include <aw_msg.hxx>
23#include <aw_select.hxx>
24#include <arb_strarray.h>
25
26#include <map>
27#include <string>
28#include "awt_sel_boxes.hxx"
29#include <arb_defs.h>
30
31using namespace std;
32
33// --------------------------
34//      AWT_configuration
35
36class AWT_configuration : virtual Noncopyable {
37private:
38    string id;
39
40    AWT_store_config_to_string  store;
41    AWT_load_config_from_string load;
42
43    AW_CL client1; // client data for callbacks above
44    AW_CL client2;
45
46    AW_default default_file;
47
48public:
49    AWT_configuration(AW_default default_file_, const char *id_, AWT_store_config_to_string store_, AWT_load_config_from_string load_, AW_CL cl1, AW_CL cl2)
50        : id(id_),
51          store(store_),
52          load(load_),
53          client1(cl1),
54          client2(cl2),
55          default_file(default_file_)
56    {}
57
58    bool operator<(const AWT_configuration& other) const { return id<other.id; }
59
60    string get_awar_name(const string& subname) const { return string("general_configs/")+id+'/'+subname; }
61    AW_awar *get_awar(const string& subname, const char *default_value = "") const {
62        string   awar_name = get_awar_name(subname);
63        return AW_root::SINGLETON->awar_string(awar_name.c_str(), default_value, default_file);
64    }
65
66    string get_awar_value(const string& subname, const char *default_value = "") const {
67        char   *value  = get_awar(subname, default_value)->read_string();
68        string  result = value;
69        free(value);
70        return result;
71    }
72    void set_awar_value(const string& subname, const string& new_value) const {
73        get_awar(subname)->write_string(new_value.c_str());
74    }
75
76    const char *get_id() const { return id.c_str(); }
77
78    char *Store() const { return store(client1, client2); }
79    GB_ERROR Restore(const string& s) const {
80        GB_ERROR error = 0;
81
82        if (s.empty()) error = "empty/nonexistant config";
83        else load(s.c_str(), client1, client2);
84
85        return error;
86    }
87
88    GB_ERROR Save(const char* filename, const string& awar_name); // AWAR content -> FILE
89    GB_ERROR Load(const char* filename, const string& awar_name); // FILE -> AWAR content
90};
91
92#define HEADER    "ARB_CONFIGURATION"
93#define HEADERLEN 17
94
95GB_ERROR AWT_configuration::Save(const char* filename, const string& awar_name) {
96    awt_assert(strlen(HEADER) == HEADERLEN);
97
98    printf("Saving config to '%s'..\n", filename);
99
100    FILE     *out   = fopen(filename, "wt");
101    GB_ERROR  error = 0;
102    if (!out) {
103        error = GB_export_IO_error("saving", filename);
104    }
105    else {
106        fprintf(out, HEADER ":%s\n", id.c_str());
107        string content = get_awar_value(awar_name);
108        fputs(content.c_str(), out);
109        fclose(out);
110    }
111    return error;
112}
113
114GB_ERROR AWT_configuration::Load(const char* filename, const string& awar_name) {
115    GB_ERROR error = 0;
116
117    printf("Loading config from '%s'..\n", filename);
118
119    char *content = GB_read_file(filename);
120    if (!content) {
121        error = GB_await_error();
122    }
123    else {
124        if (strncmp(content, HEADER ":", HEADERLEN+1) != 0) {
125            error = "Unexpected content (" HEADER " missing)";
126        }
127        else {
128            char *id_pos = content+HEADERLEN+1;
129            char *nl     = strchr(id_pos, '\n');
130
131            if (!nl) {
132                error = "Unexpected content (no ID)";
133            }
134            else {
135                *nl++ = 0;
136                if (strcmp(id_pos, id.c_str()) != 0) {
137                    error = GBS_global_string("Wrong config (id=%s, expected=%s)", id_pos, id.c_str());
138                }
139                else {
140                    set_awar_value(awar_name, nl);
141                }
142            }
143        }
144
145        if (error) {
146            error = GBS_global_string("Error: %s (while reading %s)", error, filename);
147        }
148
149        free(content);
150    }
151
152    return error;
153}
154
155void remove_from_configs(const string& config, string& existing_configs) {
156    ConstStrArray cfgs;
157    GBT_split_string(cfgs, existing_configs.c_str(), ';');
158
159    ConstStrArray remaining;
160    for (int i = 0; cfgs[i]; ++i) {
161        if (strcmp(cfgs[i], config.c_str()) != 0) {
162            remaining.put(cfgs[i]);
163        }
164    }
165
166    char *rest       = GBT_join_names(remaining, ';');
167    existing_configs = rest;
168    free(rest);
169}
170
171static string config_prefix = "cfg_";
172
173static void correct_key_name_cb(AW_root*, AW_awar *awar) {
174    string  with_prefix = config_prefix+awar->read_char_pntr();
175    char   *corrected   = GBS_string_2_key(with_prefix.c_str());
176    awar->write_string(corrected+config_prefix.length());
177    free(corrected);
178}
179
180static void restore_cb(AW_window *, AWT_configuration *config) {
181    string   cfgName   = config->get_awar_value("current");
182    string   awar_name = config_prefix+cfgName;
183    GB_ERROR error     = config->Restore(config->get_awar_value(awar_name));
184    aw_message_if(error);
185}
186static void store_cb(AW_window *, AWT_configuration *config) {
187    string existing = config->get_awar_value("existing");
188    string cfgName  = config->get_awar_value("current");
189
190    remove_from_configs(cfgName, existing); // remove selected from existing configs
191
192    existing = existing.empty() ? cfgName : (string(cfgName)+';'+existing);
193    {
194        char   *config_string = config->Store();
195        string  awar_name     = config_prefix+cfgName;
196        config->set_awar_value(awar_name, config_string);
197        free(config_string);
198    }
199    config->set_awar_value("existing", existing);
200}
201static void delete_cb(AW_window *, AWT_configuration *config) {
202    string existing = config->get_awar_value("existing");
203    string cfgName  = config->get_awar_value("current");
204    remove_from_configs(cfgName, existing); // remove selected from existing configs
205    config->set_awar_value("current", "");
206    config->set_awar_value("existing", existing);
207}
208static void load_cb(AW_window *, AWT_configuration *config) {
209    string   cfgName = config->get_awar_value("current");
210    GB_ERROR error   = NULL;
211
212    if (cfgName.empty()) error = "Please enter or select target config";
213    else {
214        char *loadMask = GBS_global_string_copy("%s_*", config->get_id());
215        char *filename = aw_file_selection("Load config from file", "$(ARBCONFIG)", loadMask, ".arbcfg");
216        if (filename) {
217            string awar_name = config_prefix+cfgName;
218
219            error = config->Load(filename, awar_name);
220            if (!error) {
221                // after successful load restore and store config
222                restore_cb(NULL, config);
223                store_cb(NULL, config);
224            }
225            free(filename);
226        }
227        free(loadMask);
228    }
229    aw_message_if(error);
230}
231static void save_cb(AW_window *, AWT_configuration *config) {
232    string   cfgName = config->get_awar_value("current");
233    GB_ERROR error   = NULL;
234
235    if (cfgName.empty()) error = "Please enter or select config to save";
236    else {
237        char *saveAs   = GBS_global_string_copy("%s_%s", config->get_id(), cfgName.c_str());
238        char *filename = aw_file_selection("Save config to file", "$(ARBCONFIG)", saveAs, ".arbcfg");
239        if (filename) {
240            store_cb(NULL, config);
241            string awar_name = config_prefix+cfgName;
242            error            = config->Save(filename, awar_name);
243            free(filename);
244        }
245        free(saveAs);
246    }
247    aw_message_if(error);
248}
249
250static void refresh_config_sellist_cb(AW_root*, AWT_configuration *config, AW_selection_list *sel) {
251    string        cfgs_str = config->get_awar_value("existing");
252    ConstStrArray cfgs;
253    GBT_split_string(cfgs, cfgs_str.c_str(), ';');
254    sel->init_from_array(cfgs, "<new>", "");
255}
256
257static AW_window *create_config_manager_window(AW_root *, AWT_configuration *config, AW_window *aww) {
258    AW_window_simple *aws = new AW_window_simple;
259
260    char *title = GBS_global_string_copy("Configurations for '%s'", aww->get_window_title());
261    char *id    = GBS_global_string_copy("%s_config",               aww->get_window_id());
262
263    aws->init(aww->get_root(), id, title);
264    aws->load_xfig("awt/manage_config.fig");
265
266    aws->at("close");
267    aws->callback(AW_POPDOWN);
268    aws->create_button("CLOSE", "CLOSE");
269
270    aws->at("help");
271    aws->callback(makeHelpCallback("configurations.hlp"));
272    aws->create_button("HELP", "HELP");
273
274    // create awars
275    AW_awar *awar_existing = config->get_awar("existing");
276    AW_awar *awar_current  = config->get_awar("current");
277    awar_current->add_callback(makeRootCallback(correct_key_name_cb, awar_current));
278
279    AW_selection_list *sel = awt_create_selection_list_with_input_field(aws, awar_current->awar_name, "box", "name");
280
281    awar_existing->add_callback(makeRootCallback(refresh_config_sellist_cb, config, sel));
282    awar_existing->touch(); // fills selection list
283
284    aws->auto_space(0, 3);
285    aws->button_length(10);
286    aws->at("button");
287
288    int xpos = aws->get_at_xposition();
289    int ypos = aws->get_at_yposition();
290
291    int yoffset = 0; // irrelevant for 1st loop
292
293    struct but {
294        void (*cb)(AW_window*, AWT_configuration*);
295        const char *id;
296        const char *label;
297        const char *mnemonic;
298    } butDef[] = {
299        { restore_cb, "RESTORE", "Restore", "R" },
300        { store_cb,   "STORE",   "Store",   "S" },
301        { delete_cb,  "DELETE",  "Delete",  "D" },
302        { load_cb,    "LOAD",    "Load",    "L" },
303        { save_cb,    "SAVE",    "Save",    "v" },
304    };
305    const int buttons = ARRAY_ELEMS(butDef);
306    for (int b = 0; b<buttons; ++b) {
307        const but& B = butDef[b];
308
309        if (b>0) aws->at(xpos, ypos + b*yoffset);
310
311        aws->callback(makeWindowCallback(B.cb, config));
312        aws->create_button(B.id, B.label, B.mnemonic);
313
314        if (!b) {
315            aws->at_newline();
316            int ypos2 = aws->get_at_yposition();
317            yoffset   = ypos2-ypos;
318            aws->at_x(xpos);
319        }
320    }
321
322    return aws;
323}
324
325static void destroy_AWT_configuration(AWT_configuration *c, AW_window*) { delete c; }
326
327void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, AWT_store_config_to_string store_cb,
328                               AWT_load_config_from_string load_cb, AW_CL cl1, AW_CL cl2, const char *macro_id)
329{
330    /*! inserts a config-button into aww
331     * @param default_file_ db where configs will be stored
332     * @param id unique id (has to be a key)
333     * @param store_cb creates a string from current state
334     * @param load_cb restores state from string
335     * @param macro_id custom macro id (normally NULL will do)
336     */
337    AWT_configuration * const config = new AWT_configuration(default_file_, id, store_cb, load_cb, cl1, cl2);
338
339    aww->button_length(0); // -> autodetect size by size of graphic
340    aww->callback(makeCreateWindowCallback(create_config_manager_window, destroy_AWT_configuration, config, aww));
341    aww->create_button(macro_id ? macro_id : "SAVELOAD_CONFIG", "#conf_save.xpm");
342}
343
344static GB_ERROR decode_escapes(string& s) {
345    string::iterator f = s.begin();
346    string::iterator t = s.begin();
347
348    for (; f != s.end(); ++f, ++t) {
349        if (*f == '\\') {
350            ++f;
351            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
352            switch (*f) {
353                case 'n':
354                    *t = '\n';
355                    break;
356                case 'r':
357                    *t = '\r';
358                    break;
359                case 't':
360                    *t = '\t';
361                    break;
362                default:
363                    *t = *f;
364                    break;
365            }
366        }
367        else {
368            *t = *f;
369        }
370    }
371
372    s.erase(t, f);
373
374    return 0;
375}
376
377static void encode_escapes(string& s, const char *to_escape) {
378    string neu;
379    neu.reserve(s.length()*2+1);
380
381    for (string::iterator p = s.begin(); p != s.end(); ++p) {
382        if (*p == '\\' || strchr(to_escape, *p) != 0) {
383            neu = neu+'\\'+*p;
384        }
385        else if (*p == '\n') { neu = neu+"\\n"; }
386        else if (*p == '\r') { neu = neu+"\\r"; }
387        else if (*p == '\t') { neu = neu+"\\t"; }
388        else { neu = neu+*p; }
389    }
390    s = neu;
391}
392
393typedef map<string, string> config_map;
394struct AWT_config_mapping {
395    config_map cmap;
396
397    config_map::iterator entry(const string &e) { return cmap.find(e); }
398
399    config_map::iterator begin() { return cmap.begin(); }
400    config_map::const_iterator end() const { return cmap.end(); }
401    config_map::iterator end() { return cmap.end(); }
402};
403
404// -------------------
405//      AWT_config
406
407AWT_config::AWT_config(const char *config_char_ptr)
408    : mapping(new AWT_config_mapping)
409    , parse_error(0)
410{
411    // parse string in format "key1='value1';key2='value2'"..
412    // and put values into a map.
413    // assumes that keys are unique
414
415    string      configString(config_char_ptr);
416    config_map& cmap  = mapping->cmap;
417    size_t      pos   = 0;
418
419    while (!parse_error) {
420        size_t equal = configString.find('=', pos);
421        if (equal == string::npos) break;
422
423        if (configString[equal+1] != '\'') {
424            parse_error = "expected quote \"'\"";
425            break;
426        }
427        size_t start = equal+2;
428        size_t end   = configString.find('\'', start);
429        while (end != string::npos) {
430            if (configString[end-1] != '\\') break;
431            end = configString.find('\'', end+1);
432        }
433        if (end == string::npos) {
434            parse_error = "could not find matching quote \"'\"";
435            break;
436        }
437
438        string config_name = configString.substr(pos, equal-pos);
439        string value       = configString.substr(start, end-start);
440
441        parse_error = decode_escapes(value);
442        if (!parse_error) {
443            cmap[config_name] = value;
444        }
445
446        pos = end+2;            // skip ';'
447    }
448}
449AWT_config::AWT_config(const AWT_config_mapping *cfgname_2_awar)
450    : mapping(new AWT_config_mapping),
451      parse_error(0)
452{
453    const config_map&  awarmap  = cfgname_2_awar->cmap;
454    config_map&        valuemap = mapping->cmap;
455    AW_root           *aw_root  = AW_root::SINGLETON;
456
457    for (config_map::const_iterator c = awarmap.begin(); c != awarmap.end(); ++c) {
458        const string& key(c->first);
459        const string& awar_name(c->second);
460
461        char *awar_value = aw_root->awar(awar_name.c_str())->read_as_string();
462        valuemap[key]    = awar_value;
463        free(awar_value);
464    }
465
466    awt_assert(valuemap.size() == awarmap.size());
467}
468
469AWT_config::~AWT_config() {
470    delete mapping;
471}
472
473bool AWT_config::has_entry(const char *entry) const {
474    awt_assert(!parse_error);
475    return mapping->entry(entry) != mapping->end();
476}
477const char *AWT_config::get_entry(const char *entry) const {
478    awt_assert(!parse_error);
479    config_map::iterator found = mapping->entry(entry);
480    return (found == mapping->end()) ? 0 : found->second.c_str();
481}
482void AWT_config::set_entry(const char *entry, const char *value) {
483    awt_assert(!parse_error);
484    mapping->cmap[entry] = value;
485}
486void AWT_config::delete_entry(const char *entry) {
487    awt_assert(!parse_error);
488    mapping->cmap.erase(entry);
489}
490
491char *AWT_config::config_string() const {
492    awt_assert(!parse_error);
493    string result;
494    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
495        const string& config_name(e->first);
496        string        value(e->second);
497
498        encode_escapes(value, "\'");
499        string entry = config_name+"='"+value+'\'';
500        if (result.empty()) {
501            result = entry;
502        }
503        else {
504            result = result+';'+entry;
505        }
506    }
507    return strdup(result.c_str());
508}
509GB_ERROR AWT_config::write_to_awars(const AWT_config_mapping *cfgname_2_awar) const {
510    GB_ERROR       error = 0;
511    GB_transaction ta(AW_ROOT_DEFAULT);
512    // Notes:
513    // * Opening a TA on AW_ROOT_DEFAULT has no effect, as awar-DB is TA-free and each
514    //   awar-change opens a TA anyway.
515    // * Motif version did open TA on awar->gb_var (in 1st loop), which would make a
516    //   difference IF the 1st awar is bound to main-DB. At best old behavior was obscure.
517
518    awt_assert(!parse_error);
519    AW_root *aw_root = AW_root::SINGLETON;
520    for (config_map::iterator e = mapping->begin(); !error && e != mapping->end(); ++e) {
521        const string& config_name(e->first);
522        const string& value(e->second);
523
524        config_map::const_iterator found = cfgname_2_awar->cmap.find(config_name);
525        if (found == cfgname_2_awar->end()) {
526            error = GBS_global_string("config contains unmapped entry '%s'", config_name.c_str());
527        }
528        else {
529            const string&  awar_name(found->second);
530            AW_awar       *awar = aw_root->awar(awar_name.c_str());
531            awar->write_as_string(value.c_str());
532        }
533    }
534    return error;
535}
536
537// ------------------------------
538//      AWT_config_definition
539
540AWT_config_definition::AWT_config_definition()
541    : config_mapping(new AWT_config_mapping)
542{}
543
544AWT_config_definition::AWT_config_definition(AWT_config_mapping_def *mdef)
545    : config_mapping(new AWT_config_mapping)
546{
547    add(mdef);
548}
549
550AWT_config_definition::~AWT_config_definition() {
551    delete config_mapping;
552}
553
554void AWT_config_definition::add(const char *awar_name, const char *config_name) {
555    config_mapping->cmap[config_name] = awar_name;
556}
557void AWT_config_definition::add(const char *awar_name, const char *config_name, int counter) {
558    add(awar_name, GBS_global_string("%s%i", config_name, counter));
559}
560void AWT_config_definition::add(AWT_config_mapping_def *mdef) {
561    while (mdef->awar_name && mdef->config_name) {
562        add(mdef->awar_name, mdef->config_name);
563        mdef++;
564    }
565}
566
567char *AWT_config_definition::read() const {
568    // creates a string from awar values
569
570    AWT_config current_state(config_mapping);
571    return current_state.config_string();
572}
573void AWT_config_definition::write(const char *config_char_ptr) const {
574    // write values from string to awars
575    // if the string contains unknown settings, they are silently ignored
576
577    AWT_config wanted_state(config_char_ptr);
578    GB_ERROR   error  = wanted_state.parseError();
579    if (!error) error = wanted_state.write_to_awars(config_mapping);
580    if (error) aw_message(GBS_global_string("Error restoring configuration (%s)", error));
581}
582
Note: See TracBrowser for help on using the repository browser.