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