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