root/trunk/AWT/AWT_config_manager.cxx

Revision 8355, 18.0 KB (checked in by westram, 4 months ago)
  • moved aw_question() and relatives into aw_question.hxx
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
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 <aw_window.hxx>
18#include <aw_root.hxx>
19#include <aw_question.hxx>
20#include <aw_awar.hxx>
21#include <aw_msg.hxx>
22#include <arbdb.h>
23
24#include <map>
25#include <string>
26
27using namespace std;
28
29// --------------------------
30//      AWT_configuration
31
32class AWT_configuration : virtual Noncopyable {
33private:
34    string id;
35
36    AWT_store_config_to_string  store;
37    AWT_load_config_from_string load;
38    AW_CL                       client1; // client data
39    AW_CL                       client2;
40
41    AW_window  *last_client_aww;
42    AW_default  default_file;
43
44public:
45    AWT_configuration(AW_window *aww, AW_default default_file_, const char *id_, AWT_store_config_to_string store_,
46                      AWT_load_config_from_string load_, AW_CL cl1, AW_CL cl2)
47    {
48        id              = id_;
49        store           = store_;
50        load            = load_;
51        client1         = cl1;
52        client2         = cl2;
53        last_client_aww = aww;
54        default_file    = default_file_;
55    }
56    virtual ~AWT_configuration() {}
57
58    bool operator<(const AWT_configuration& other) const { return id<other.id; }
59    string get_awar_name(const string& subname) const { return string("general_configs/")+id+'/'+subname; }
60    string get_awar_value(const string& subname, const char *default_value = "") const {
61        AW_root *aw_root   = last_client_aww->get_root();
62        string   awar_name = get_awar_name(subname);
63        char    *value     = aw_root->awar_string(awar_name.c_str(), default_value, default_file)->read_string();
64        string   result    = value;
65        free(value);
66        return result;
67    }
68    void set_awar_value(const string& subname, const string& new_value) const {
69        AW_root *aw_root   = last_client_aww->get_root();
70        aw_root->awar_string(get_awar_name(subname).c_str(), "")->write_string(new_value.c_str());
71    }
72
73    const char *get_id() const { return id.c_str(); }
74
75    char *Store() const { return store(last_client_aww, client1, client2); }
76    GB_ERROR Restore(const string& s) const {
77        GB_ERROR error = 0;
78
79        if (s.empty()) error = "empty/nonexistant config";
80        else load(last_client_aww, s.c_str(), client1, client2);
81
82        return error;
83    }
84
85    GB_ERROR Save(const char* filename, const string& awar_name); // AWAR content -> FILE
86    GB_ERROR Load(const char* filename, const string& awar_name); // FILE -> AWAR content
87};
88
89#define HEADER    "ARB_CONFIGURATION"
90#define HEADERLEN 17
91
92GB_ERROR AWT_configuration::Save(const char* filename, const string& awar_name) {
93    awt_assert(strlen(HEADER) == HEADERLEN);
94
95    printf("Saving config to '%s'..\n", filename);
96
97    FILE     *out   = fopen(filename, "wt");
98    GB_ERROR  error = 0;
99    if (!out) {
100        error = GB_export_IO_error("saving", filename);
101    }
102    else {
103        fprintf(out, HEADER ":%s\n", id.c_str());
104        string content = get_awar_value(awar_name);
105        fputs(content.c_str(), out);
106        fclose(out);
107    }
108    return error;
109}
110
111GB_ERROR AWT_configuration::Load(const char* filename, const string& awar_name) {
112    GB_ERROR error = 0;
113
114    printf("Loading config from '%s'..\n", filename);
115
116    char *content = GB_read_file(filename);
117    if (!content) {
118        error = GB_await_error();
119    }
120    else {
121        if (strncmp(content, HEADER ":", HEADERLEN+1) != 0) {
122            error = "Unexpected content (" HEADER " missing)";
123        }
124        else {
125            char *id_pos = content+HEADERLEN+1;
126            char *nl     = strchr(id_pos, '\n');
127
128            if (!nl) {
129                error = "Unexpected content (no ID)";
130            }
131            else {
132                *nl++ = 0;
133                if (strcmp(id_pos, id.c_str()) != 0) {
134                    error = GBS_global_string("Wrong config (id=%s, expected=%s)", id_pos, id.c_str());
135                }
136                else {
137                    set_awar_value(awar_name, nl);
138                }
139            }
140        }
141
142        if (error) {
143            error = GBS_global_string("Error: %s (while reading %s)", error, filename);
144        }
145
146        free(content);
147    }
148
149    return error;
150}
151
152void remove_from_configs(const string& config, string& existing_configs) {
153    size_t start = -1U;
154    printf("erasing '%s' from '%s'\n", config.c_str(), existing_configs.c_str());
155
156    while (1) {
157        start = existing_configs.find(config, start+1);
158        if (start == string::npos) break; // not found
159        if (start == 0 || existing_configs[start-1] == ';') { // config starts with string
160            size_t stop = start+config.length();
161            if (stop != existing_configs.length()) {
162                if (stop>existing_configs.length()) break; // not found
163                if (existing_configs[stop] != ';') continue; // name continues
164            }
165            existing_configs.erase(start, stop-start+1);
166            if (existing_configs[existing_configs.length()-1] == ';') {
167                existing_configs.erase(existing_configs.length()-1);
168            }
169            remove_from_configs(config, existing_configs);
170            break;
171        }
172    }
173#if defined(DEBUG)
174    printf("result: '%s'\n", existing_configs.c_str());
175#endif // DEBUG
176}
177static char *correct_key_name(const char *name) {
178    char *corrected = GBS_string_2_key(name);
179
180    if (strcmp(corrected, "__") == 0) freedup(corrected, "");
181    return corrected;
182}
183
184// ---------------------------------
185//      AWT_start_config_manager
186
187static void AWT_start_config_manager(AW_window *aww, AW_CL cl_config)
188{
189    AWT_configuration *config           = (AWT_configuration*)cl_config;
190    string             existing_configs = config->get_awar_value("existing");
191    config->get_awar_value("current"); // create!
192    bool               reopen           = false;
193    char              *title            = GBS_global_string_copy("Configurations for '%s'", aww->window_name);
194
195    const char *buttons = "\nRESTORE,STORE,DELETE,LOAD,SAVE,\nCLOSE,HELP";
196    enum Answer { CM_RESTORE, CM_STORE, CM_DELETE, CM_LOAD, CM_SAVE, CM_CLOSE, CM_HELP };
197
198    char   *cfgName = aw_string_selection2awar(title, "Enter a new or select an existing config",
199                                              config->get_awar_name("current").c_str(), existing_configs.c_str(),
200                                              buttons, correct_key_name);
201    Answer  button = (Answer)aw_string_selection_button();
202
203    GB_ERROR error = NULL;
204    if (button >= CM_RESTORE && button <= CM_SAVE) {
205        if (!cfgName || !cfgName[0]) {                // did user specify a config-name ?
206            error = "Please enter or select a config";
207        }
208    }
209
210    if (!error) {
211        string  awar_name = string("cfg_")+cfgName;
212        char   *filename  = 0;
213        bool    loading   = false;
214
215        reopen = true;
216
217        switch (button) {
218            case CM_LOAD: {
219                char *loadMask = GBS_global_string_copy("%s_*", config->get_id());
220                filename       = aw_file_selection("Load config from file", "$(ARBCONFIG)", loadMask, ".arbcfg");
221                free(loadMask);
222                if (!filename) break;
223
224                error = config->Load(filename, awar_name);
225                if (error) break;
226
227                loading = true;
228                // fall-through
229            }
230
231            case CM_RESTORE:
232                error = config->Restore(config->get_awar_value(awar_name));
233                if (!error) {
234                    config->set_awar_value("current", cfgName);
235                    if (loading) goto AUTOSTORE;
236                    reopen = false;
237                }
238                break;
239
240            case CM_SAVE: {
241                char *saveAs = GBS_global_string_copy("%s_%s", config->get_id(), cfgName);
242                filename     = aw_file_selection("Save config to file", "$(ARBCONFIG)", saveAs, ".arbcfg");
243                free(saveAs);
244                if (!filename) break;
245                // fall-through
246            }
247            case CM_STORE: {
248                AUTOSTORE :
249                remove_from_configs(cfgName, existing_configs); // remove existing config
250
251                if (existing_configs.length()) existing_configs = string(cfgName)+';'+existing_configs;
252                else existing_configs                           = cfgName;
253
254                {
255                    char *config_string = config->Store();
256                    config->set_awar_value(awar_name, config_string);
257                    free(config_string);
258                }
259                config->set_awar_value("current", cfgName);
260                config->set_awar_value("existing", existing_configs);
261
262                if (filename && !loading) error = config->Save(filename, awar_name); // CM_SAVE
263                break;
264            }
265
266            case CM_DELETE:
267                remove_from_configs(cfgName, existing_configs); // remove existing config
268                config->set_awar_value("current", "");
269                config->set_awar_value("existing", existing_configs);
270
271                // @@@ config is not really deleted from properties
272                break;
273
274            case CM_HELP:
275                AW_POPUP_HELP(aww, (AW_CL)"configurations.hlp");
276                break;
277            case CM_CLOSE:
278                reopen = false;
279                break;
280        }
281    }
282
283    free(title);
284    free(cfgName);
285
286    if (error) {
287        aw_popup_ok(error);
288        reopen = true;
289    }
290    if (reopen) AWT_start_config_manager(aww, cl_config);
291}
292
293void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, AWT_store_config_to_string store_cb,
294                               AWT_load_config_from_string load_cb, AW_CL cl1, AW_CL cl2, const char *macro_id)
295{
296    AWT_configuration *config = new AWT_configuration(aww, default_file_, id, store_cb, load_cb, cl1, cl2);
297    // config will not be freed!!!
298
299    aww->button_length(0); // -> autodetect size by size of graphic
300    aww->callback(AWT_start_config_manager, (AW_CL)config);
301    aww->create_button(macro_id ? macro_id : "SAVELOAD_CONFIG", "#conf_save.xpm");
302}
303
304static GB_ERROR decode_escapes(string& s) {
305    string::iterator f = s.begin();
306    string::iterator t = s.begin();
307
308    for (; f != s.end(); ++f, ++t) {
309        if (*f == '\\') {
310            ++f;
311            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
312            switch (*f) {
313                case 'n':
314                    *t = '\n';
315                    break;
316                case 'r':
317                    *t = '\r';
318                    break;
319                case 't':
320                    *t = '\t';
321                    break;
322                default:
323                    *t = *f;
324                    break;
325            }
326        }
327        else {
328            *t = *f;
329        }
330    }
331
332    s.erase(t, f);
333
334    return 0;
335}
336
337static void encode_escapes(string& s, const char *to_escape) {
338    string neu;
339    neu.reserve(s.length()*2+1);
340
341    for (string::iterator p = s.begin(); p != s.end(); ++p) {
342        if (*p == '\\' || strchr(to_escape, *p) != 0) {
343            neu = neu+'\\'+*p;
344        }
345        else if (*p == '\n') { neu = neu+"\\n"; }
346        else if (*p == '\r') { neu = neu+"\\r"; }
347        else if (*p == '\t') { neu = neu+"\\t"; }
348        else { neu = neu+*p; }
349    }
350    s = neu;
351}
352
353typedef map<string, string> config_map;
354struct AWT_config_mapping {
355    config_map cmap;
356
357    config_map::iterator entry(const string &e) { return cmap.find(e); }
358
359    config_map::iterator begin() { return cmap.begin(); }
360    config_map::const_iterator end() const { return cmap.end(); }
361    config_map::iterator end() { return cmap.end(); }
362};
363
364// -------------------
365//      AWT_config
366
367AWT_config::AWT_config(const char *config_char_ptr)
368    : mapping(new AWT_config_mapping)
369    , parse_error(0)
370{
371    // parse string in format "key1='value1';key2='value2'"..
372    // and put values into a map.
373    // assumes that keys are unique
374
375    string      configString(config_char_ptr);
376    config_map& cmap  = mapping->cmap;
377    size_t      pos   = 0;
378
379    while (!parse_error) {
380        size_t equal = configString.find('=', pos);
381        if (equal == string::npos) break;
382
383        if (configString[equal+1] != '\'') {
384            parse_error = "expected quote \"'\"";
385            break;
386        }
387        size_t start = equal+2;
388        size_t end   = configString.find('\'', start);
389        while (end != string::npos) {
390            if (configString[end-1] != '\\') break;
391            end = configString.find('\'', end+1);
392        }
393        if (end == string::npos) {
394            parse_error = "could not find matching quote \"'\"";
395            break;
396        }
397
398        string config_name = configString.substr(pos, equal-pos);
399        string value       = configString.substr(start, end-start);
400
401        parse_error = decode_escapes(value);
402        if (!parse_error) {
403            cmap[config_name] = value;
404        }
405
406        pos = end+2;            // skip ';'
407    }
408}
409AWT_config::AWT_config(const AWT_config_mapping *cfgname_2_awar, AW_root *root)
410    : mapping(new AWT_config_mapping)
411    , parse_error(0)
412{
413    const config_map& awarmap  = cfgname_2_awar->cmap;
414    config_map& valuemap = mapping->cmap;
415
416    for (config_map::const_iterator c = awarmap.begin(); c != awarmap.end(); ++c) {
417        const string& key(c->first);
418        const string& awar_name(c->second);
419
420        char *awar_value = root->awar(awar_name.c_str())->read_as_string();
421        valuemap[key]    = awar_value;
422        free(awar_value);
423    }
424
425    awt_assert(valuemap.size() == awarmap.size());
426}
427
428AWT_config::~AWT_config() {
429    delete mapping;
430}
431
432bool AWT_config::has_entry(const char *entry) const {
433    awt_assert(!parse_error);
434    return mapping->entry(entry) != mapping->end();
435}
436const char *AWT_config::get_entry(const char *entry) const {
437    awt_assert(!parse_error);
438    config_map::iterator found = mapping->entry(entry);
439    return (found == mapping->end()) ? 0 : found->second.c_str();
440}
441void AWT_config::set_entry(const char *entry, const char *value) {
442    awt_assert(!parse_error);
443    mapping->cmap[entry] = value;
444}
445void AWT_config::delete_entry(const char *entry) {
446    awt_assert(!parse_error);
447    mapping->cmap.erase(entry);
448}
449
450char *AWT_config::config_string() const {
451    awt_assert(!parse_error);
452    string result;
453    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
454        const string& config_name(e->first);
455        string        value(e->second);
456
457        encode_escapes(value, "\'");
458        string entry = config_name+"='"+value+'\'';
459        if (result.empty()) {
460            result = entry;
461        }
462        else {
463            result = result+';'+entry;
464        }
465    }
466    return strdup(result.c_str());
467}
468GB_ERROR AWT_config::write_to_awars(const AWT_config_mapping *cfgname_2_awar, AW_root *root) const {
469    GB_ERROR        error = 0;
470    GB_transaction *ta    = 0;
471    awt_assert(!parse_error);
472    for (config_map::iterator e = mapping->begin(); !error && e != mapping->end(); ++e) {
473        const string& config_name(e->first);
474        const string& value(e->second);
475
476        config_map::const_iterator found = cfgname_2_awar->cmap.find(config_name);
477        if (found == cfgname_2_awar->end()) {
478            error = GBS_global_string("config contains unmapped entry '%s'", config_name.c_str());
479        }
480        else {
481            const string&  awar_name(found->second);
482            AW_awar       *awar = root->awar(awar_name.c_str());
483            if (!ta) {
484                ta = new GB_transaction((GBDATA*)awar->gb_var); // do all awar changes in 1 transaction
485            }
486            awar->write_as_string(value.c_str());
487        }
488    }
489    if (ta) delete ta; // close transaction
490    return error;
491}
492
493// ------------------------------
494//      AWT_config_definition
495
496AWT_config_definition::AWT_config_definition(AW_root *aw_root)
497    : root(aw_root),  config_mapping(new AWT_config_mapping) {}
498
499AWT_config_definition::AWT_config_definition(AW_root *aw_root, AWT_config_mapping_def *mdef)
500    : root(aw_root),  config_mapping(new AWT_config_mapping)
501{
502    add(mdef);
503}
504
505AWT_config_definition::~AWT_config_definition() {
506    delete config_mapping;
507}
508
509void AWT_config_definition::add(const char *awar_name, const char *config_name) {
510    config_mapping->cmap[config_name] = awar_name;
511}
512void AWT_config_definition::add(const char *awar_name, const char *config_name, int counter) {
513    add(awar_name, GBS_global_string("%s%i", config_name, counter));
514}
515void AWT_config_definition::add(AWT_config_mapping_def *mdef) {
516    while (mdef->awar_name && mdef->config_name) {
517        add(mdef->awar_name, mdef->config_name);
518        mdef++;
519    }
520}
521
522char *AWT_config_definition::read() const {
523    // creates a string from awar values
524
525    AWT_config current_state(config_mapping, root);
526    return current_state.config_string();
527}
528void AWT_config_definition::write(const char *config_char_ptr) const {
529    // write values from string to awars
530    // if the string contains unknown settings, they are silently ignored
531
532    AWT_config wanted_state(config_char_ptr);
533    GB_ERROR   error  = wanted_state.parseError();
534    if (!error) error = wanted_state.write_to_awars(config_mapping, root);
535    if (error) aw_message(GBS_global_string("Error restoring configuration (%s)", error));
536}
Note: See TracBrowser for help on using the browser.