| 1 | // =============================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : AW_awar.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // =============================================================== // |
|---|
| 10 | |
|---|
| 11 | #include "aw_nawar.hxx" |
|---|
| 12 | #include "aw_awar.hxx" |
|---|
| 13 | #include "aw_root.hxx" |
|---|
| 14 | #include "aw_msg.hxx" |
|---|
| 15 | #include "aw_window.hxx" |
|---|
| 16 | #include "aw_select.hxx" |
|---|
| 17 | #include <arb_str.h> |
|---|
| 18 | #include <arb_file.h> |
|---|
| 19 | #include <ad_cb.h> |
|---|
| 20 | #include <list> |
|---|
| 21 | #include <sys/stat.h> |
|---|
| 22 | #include <climits> |
|---|
| 23 | #include <cfloat> |
|---|
| 24 | |
|---|
| 25 | #if defined(DEBUG) |
|---|
| 26 | // uncomment next line to dump all awar-changes to stderr |
|---|
| 27 | // #define DUMP_AWAR_CHANGES |
|---|
| 28 | #endif // DEBUG |
|---|
| 29 | |
|---|
| 30 | #define AWAR_EPS 0.00000001 |
|---|
| 31 | |
|---|
| 32 | #if defined(DUMP_AWAR_CHANGES) |
|---|
| 33 | #define AWAR_CHANGE_DUMP(name, where, format) fprintf(stderr, "change awar '%s' " where "(" format ")\n", name, para) |
|---|
| 34 | #else |
|---|
| 35 | #define AWAR_CHANGE_DUMP(name, where, format) |
|---|
| 36 | #endif // DEBUG |
|---|
| 37 | |
|---|
| 38 | #if defined(ASSERTION_USED) |
|---|
| 39 | bool AW_awar::deny_read = false; |
|---|
| 40 | bool AW_awar::deny_write = false; |
|---|
| 41 | #endif |
|---|
| 42 | |
|---|
| 43 | struct AW_widget_refresh_cb : virtual Noncopyable { |
|---|
| 44 | AW_widget_refresh_cb(AW_widget_refresh_cb *previous, AW_awar *vs, AW_CL cd1, Widget w, AW_widget_type type, AW_window *awi); |
|---|
| 45 | ~AW_widget_refresh_cb(); |
|---|
| 46 | |
|---|
| 47 | AW_CL cd; |
|---|
| 48 | AW_awar *awar; |
|---|
| 49 | Widget widget; |
|---|
| 50 | AW_widget_type widget_type; |
|---|
| 51 | AW_window *aw; |
|---|
| 52 | |
|---|
| 53 | AW_widget_refresh_cb *next; |
|---|
| 54 | }; |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | static void aw_cp_awar_2_widget_cb(AW_root *root, AW_widget_refresh_cb *widgetlist) { |
|---|
| 58 | if (widgetlist->widget == root->changer_of_variable) { |
|---|
| 59 | root->changer_of_variable = NULp; |
|---|
| 60 | root->value_changed = false; |
|---|
| 61 | return; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | { |
|---|
| 65 | char *var_value; |
|---|
| 66 | var_value = widgetlist->awar->read_as_string(); |
|---|
| 67 | |
|---|
| 68 | // und benachrichtigen der anderen |
|---|
| 69 | switch (widgetlist->widget_type) { |
|---|
| 70 | |
|---|
| 71 | case AW_WIDGET_INPUT_FIELD: |
|---|
| 72 | widgetlist->aw->update_input_field(widgetlist->widget, var_value); |
|---|
| 73 | break; |
|---|
| 74 | case AW_WIDGET_TEXT_FIELD: |
|---|
| 75 | widgetlist->aw->update_text_field(widgetlist->widget, var_value); |
|---|
| 76 | break; |
|---|
| 77 | case AW_WIDGET_TOGGLE: |
|---|
| 78 | widgetlist->aw->update_toggle(widgetlist->widget, var_value, widgetlist->cd); |
|---|
| 79 | break; |
|---|
| 80 | case AW_WIDGET_LABEL_FIELD: |
|---|
| 81 | widgetlist->aw->update_label(widgetlist->widget, var_value); |
|---|
| 82 | break; |
|---|
| 83 | case AW_WIDGET_CHOICE_MENU: |
|---|
| 84 | widgetlist->aw->refresh_option_menu((AW_option_menu_struct*)widgetlist->cd); |
|---|
| 85 | break; |
|---|
| 86 | case AW_WIDGET_TOGGLE_FIELD: |
|---|
| 87 | widgetlist->aw->refresh_toggle_field((int)widgetlist->cd); |
|---|
| 88 | break; |
|---|
| 89 | case AW_WIDGET_SELECTION_LIST: |
|---|
| 90 | ((AW_selection_list *)widgetlist->cd)->refresh(); |
|---|
| 91 | break; |
|---|
| 92 | case AW_WIDGET_SCALER: |
|---|
| 93 | widgetlist->aw->update_scaler(widgetlist->widget, widgetlist->awar, (AW_ScalerType)widgetlist->cd); |
|---|
| 94 | break; |
|---|
| 95 | default: |
|---|
| 96 | break; |
|---|
| 97 | } |
|---|
| 98 | free(var_value); |
|---|
| 99 | } |
|---|
| 100 | root->value_changed = false; // Maybe value changed is set because Motif calls me |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | |
|---|
| 104 | AW_widget_refresh_cb::AW_widget_refresh_cb(AW_widget_refresh_cb *previous, AW_awar *vs, AW_CL cd1, Widget w, AW_widget_type type, AW_window *awi) { |
|---|
| 105 | cd = cd1; |
|---|
| 106 | widget = w; |
|---|
| 107 | widget_type = type; |
|---|
| 108 | awar = vs; |
|---|
| 109 | aw = awi; |
|---|
| 110 | next = previous; |
|---|
| 111 | |
|---|
| 112 | awar->add_callback(makeRootCallback(aw_cp_awar_2_widget_cb, this)); |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | AW_widget_refresh_cb::~AW_widget_refresh_cb() { |
|---|
| 116 | if (next) delete next; |
|---|
| 117 | awar->remove_callback(makeRootCallback(aw_cp_awar_2_widget_cb, this)); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | AW_var_target::AW_var_target(void* pntr, AW_var_target *nexti) { |
|---|
| 121 | next = nexti; |
|---|
| 122 | pointer = pntr; |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | |
|---|
| 126 | bool AW_awar::allowed_to_run_callbacks = true; |
|---|
| 127 | |
|---|
| 128 | static GB_ERROR AW_MSG_UNMAPPED_AWAR = "Error (unmapped AWAR):\n" |
|---|
| 129 | "You cannot write to this field because it is either deleted or\n" |
|---|
| 130 | "unmapped. Try to select a different item, reselect this and retry."; |
|---|
| 131 | |
|---|
| 132 | #define WRITE_BODY(self,format,func) \ |
|---|
| 133 | if (!gb_var) return AW_MSG_UNMAPPED_AWAR; \ |
|---|
| 134 | aw_assert(!deny_write); \ |
|---|
| 135 | GB_transaction ta(gb_var); \ |
|---|
| 136 | AWAR_CHANGE_DUMP(awar_name, #self, format); \ |
|---|
| 137 | GB_ERROR error = func(gb_var, para); \ |
|---|
| 138 | if (!error) update_tmp_state_during_change() |
|---|
| 139 | |
|---|
| 140 | #define WRITE_SKELETON(self,type,format,func) \ |
|---|
| 141 | GB_ERROR AW_awar::self(type para) { \ |
|---|
| 142 | WRITE_BODY(self, format, func); \ |
|---|
| 143 | return error; \ |
|---|
| 144 | } \ |
|---|
| 145 | GB_ERROR AW_awar::concatenate(re,self)(type para) { \ |
|---|
| 146 | WRITE_BODY(self, format, func); \ |
|---|
| 147 | GB_touch(gb_var); \ |
|---|
| 148 | return error; \ |
|---|
| 149 | } |
|---|
| 150 | |
|---|
| 151 | WRITE_SKELETON(write_string, const char*, "%s", GB_write_string) // defines rewrite_string |
|---|
| 152 | WRITE_SKELETON(write_int, long, "%li", GB_write_int) // defines rewrite_int |
|---|
| 153 | WRITE_SKELETON(write_float, float, "%f", GB_write_float) // defines rewrite_float |
|---|
| 154 | WRITE_SKELETON(write_as_string, const char*, "%s", GB_write_autoconv_string) // defines rewrite_as_string // @@@ rename -> write_autoconvert_string? |
|---|
| 155 | WRITE_SKELETON(write_pointer, GBDATA*, "%p", GB_write_pointer) // defines rewrite_pointer |
|---|
| 156 | |
|---|
| 157 | #undef WRITE_SKELETON |
|---|
| 158 | #undef AWAR_CHANGE_DUMP |
|---|
| 159 | |
|---|
| 160 | |
|---|
| 161 | char *AW_awar::read_as_string() const { |
|---|
| 162 | aw_assert(!deny_read); |
|---|
| 163 | if (!gb_var) return strdup(""); |
|---|
| 164 | GB_transaction ta(gb_var); |
|---|
| 165 | return GB_read_as_string(gb_var); |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | const char *AW_awar::read_char_pntr() const { |
|---|
| 169 | aw_assert(!deny_read); |
|---|
| 170 | aw_assert(variable_type == AW_STRING); |
|---|
| 171 | |
|---|
| 172 | if (!gb_var) return ""; |
|---|
| 173 | GB_transaction ta(gb_var); |
|---|
| 174 | return GB_read_char_pntr(gb_var); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | float AW_awar::read_float() const { |
|---|
| 178 | aw_assert(!deny_read); |
|---|
| 179 | if (!gb_var) return 0.0; |
|---|
| 180 | GB_transaction ta(gb_var); |
|---|
| 181 | return GB_read_float(gb_var); |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | long AW_awar::read_int() const { |
|---|
| 185 | aw_assert(!deny_read); |
|---|
| 186 | if (!gb_var) return 0; |
|---|
| 187 | GB_transaction ta(gb_var); |
|---|
| 188 | return GB_read_int(gb_var); |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | GBDATA *AW_awar::read_pointer() const { |
|---|
| 192 | aw_assert(!deny_read); |
|---|
| 193 | if (!gb_var) return NULp; |
|---|
| 194 | GB_transaction ta(gb_var); |
|---|
| 195 | return GB_read_pointer(gb_var); |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | char *AW_awar::read_string() const { |
|---|
| 199 | aw_assert(!deny_read); |
|---|
| 200 | aw_assert(variable_type == AW_STRING); |
|---|
| 201 | |
|---|
| 202 | if (!gb_var) return strdup(""); |
|---|
| 203 | GB_transaction ta(gb_var); |
|---|
| 204 | return GB_read_string(gb_var); |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | void AW_awar::touch() { |
|---|
| 208 | aw_assert(!deny_write); |
|---|
| 209 | if (gb_var) { |
|---|
| 210 | GB_transaction ta(gb_var); |
|---|
| 211 | GB_touch(gb_var); |
|---|
| 212 | } |
|---|
| 213 | } |
|---|
| 214 | GB_ERROR AW_awar::reset_to_default() { |
|---|
| 215 | aw_assert(!deny_write); |
|---|
| 216 | GB_ERROR error = NULp; |
|---|
| 217 | switch (variable_type) { |
|---|
| 218 | case AW_STRING: error = write_string (default_value.s); break; |
|---|
| 219 | case AW_INT: error = write_int (default_value.l); break; |
|---|
| 220 | case AW_FLOAT: error = write_float (default_value.f); break; |
|---|
| 221 | case AW_POINTER: error = write_pointer(default_value.p); break; |
|---|
| 222 | default: aw_assert(0); break; |
|---|
| 223 | } |
|---|
| 224 | return error; |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | void AW_awar::untie_all_widgets() { |
|---|
| 228 | delete refresh_list; refresh_list = NULp; |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | AW_awar *AW_awar::add_callback(const RootCallback& rcb) { |
|---|
| 232 | AW_root_cblist::add(callback_list, rcb); |
|---|
| 233 | return this; |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | AW_awar *AW_awar::add_target_var(char **ppchr) { |
|---|
| 237 | assert_var_type(AW_STRING); |
|---|
| 238 | target_list = new AW_var_target((void *)ppchr, target_list); |
|---|
| 239 | update_target(target_list); |
|---|
| 240 | return this; |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | AW_awar *AW_awar::add_target_var(float *pfloat) { |
|---|
| 244 | assert_var_type(AW_FLOAT); |
|---|
| 245 | target_list = new AW_var_target((void *)pfloat, target_list); |
|---|
| 246 | update_target(target_list); |
|---|
| 247 | return this; |
|---|
| 248 | } |
|---|
| 249 | |
|---|
| 250 | AW_awar *AW_awar::add_target_var(long *pint) { |
|---|
| 251 | assert_var_type(AW_INT); |
|---|
| 252 | target_list = new AW_var_target((void *)pint, target_list); |
|---|
| 253 | update_target(target_list); |
|---|
| 254 | return this; |
|---|
| 255 | } |
|---|
| 256 | |
|---|
| 257 | void AW_awar::tie_widget(AW_CL cd1, Widget widget, AW_widget_type type, AW_window *aww) { |
|---|
| 258 | refresh_list = new AW_widget_refresh_cb(refresh_list, this, cd1, widget, type, aww); |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | |
|---|
| 262 | AW_VARIABLE_TYPE AW_awar::get_type() const { |
|---|
| 263 | return this->variable_type; |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | // extern "C" |
|---|
| 268 | static void AW_var_gbdata_callback(GBDATA*, AW_awar *awar) { |
|---|
| 269 | awar->update(); |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | static void AW_var_gbdata_callback_delete_intern(GBDATA *gbd, AW_awar *awar) { |
|---|
| 274 | if (awar->gb_origin == gbd) { |
|---|
| 275 | // make awar zombie |
|---|
| 276 | awar->gb_var = NULp; |
|---|
| 277 | awar->gb_origin = NULp; |
|---|
| 278 | } |
|---|
| 279 | else { |
|---|
| 280 | aw_assert(awar->gb_var == gbd); // forgot to remove a callback ? |
|---|
| 281 | awar->gb_var = awar->gb_origin; // unmap |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | awar->update(); |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | AW_awar::AW_awar(AW_VARIABLE_TYPE var_type, const char *var_name, |
|---|
| 288 | const char *var_value, float var_float_value, |
|---|
| 289 | AW_default default_file, AW_root *rooti) |
|---|
| 290 | : callback_list(NULp), |
|---|
| 291 | target_list(NULp), |
|---|
| 292 | refresh_list(NULp), |
|---|
| 293 | callback_time_sum(0.0), |
|---|
| 294 | callback_time_count(0), |
|---|
| 295 | #if defined(DEBUG) |
|---|
| 296 | is_global(false), |
|---|
| 297 | #endif |
|---|
| 298 | root(rooti), |
|---|
| 299 | gb_var(NULp) |
|---|
| 300 | { |
|---|
| 301 | pp.f.min = 0; |
|---|
| 302 | pp.f.max = 0; |
|---|
| 303 | pp.srt = NULp; |
|---|
| 304 | |
|---|
| 305 | GB_transaction ta(default_file); |
|---|
| 306 | |
|---|
| 307 | aw_assert(var_name && var_name[0] != 0); |
|---|
| 308 | |
|---|
| 309 | #if defined(DEBUG) |
|---|
| 310 | GB_ERROR err = GB_check_hkey(var_name); |
|---|
| 311 | aw_assert(!err); |
|---|
| 312 | #endif // DEBUG |
|---|
| 313 | |
|---|
| 314 | awar_name = strdup(var_name); |
|---|
| 315 | GBDATA *gb_def = GB_search(default_file, var_name, GB_FIND); |
|---|
| 316 | |
|---|
| 317 | in_tmp_branch = strncmp(var_name, "tmp/", 4) == 0; |
|---|
| 318 | |
|---|
| 319 | GB_TYPES wanted_gbtype = (GB_TYPES)var_type; |
|---|
| 320 | |
|---|
| 321 | if (gb_def) { // use value stored in DB |
|---|
| 322 | GB_TYPES gbtype = GB_read_type(gb_def); |
|---|
| 323 | |
|---|
| 324 | if (gbtype != wanted_gbtype) { |
|---|
| 325 | GB_warningf("Existing awar '%s' has wrong type (%i instead of %i) - recreating\n", |
|---|
| 326 | var_name, int(gbtype), int(wanted_gbtype)); |
|---|
| 327 | GB_delete(gb_def); |
|---|
| 328 | gb_def = NULp; |
|---|
| 329 | } |
|---|
| 330 | } |
|---|
| 331 | |
|---|
| 332 | // store default-value in member |
|---|
| 333 | switch (var_type) { |
|---|
| 334 | case AW_STRING: default_value.s = nulldup(var_value); break; |
|---|
| 335 | case AW_INT: default_value.l = (long)var_value; break; |
|---|
| 336 | case AW_FLOAT: default_value.f = var_float_value; break; |
|---|
| 337 | case AW_POINTER: default_value.p = (GBDATA*)var_value; break; |
|---|
| 338 | default: aw_assert(0); break; |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | if (!gb_def) { // set AWAR to default value |
|---|
| 342 | gb_def = GB_search(default_file, var_name, wanted_gbtype); |
|---|
| 343 | |
|---|
| 344 | switch (var_type) { |
|---|
| 345 | case AW_STRING: |
|---|
| 346 | #if defined(DUMP_AWAR_CHANGES) |
|---|
| 347 | fprintf(stderr, "creating awar_string '%s' with default value '%s'\n", var_name, default_value.s); |
|---|
| 348 | #endif // DUMP_AWAR_CHANGES |
|---|
| 349 | GB_write_string(gb_def, default_value.s); |
|---|
| 350 | break; |
|---|
| 351 | |
|---|
| 352 | case AW_INT: { |
|---|
| 353 | #if defined(DUMP_AWAR_CHANGES) |
|---|
| 354 | fprintf(stderr, "creating awar_int '%s' with default value '%li'\n", var_name, default_value.l); |
|---|
| 355 | #endif // DUMP_AWAR_CHANGES |
|---|
| 356 | GB_write_int(gb_def, default_value.l); |
|---|
| 357 | break; |
|---|
| 358 | } |
|---|
| 359 | case AW_FLOAT: |
|---|
| 360 | #if defined(DUMP_AWAR_CHANGES) |
|---|
| 361 | fprintf(stderr, "creating awar_float '%s' with default value '%f'\n", var_name, default_value.f); |
|---|
| 362 | #endif // DUMP_AWAR_CHANGES |
|---|
| 363 | GB_write_float(gb_def, default_value.f); |
|---|
| 364 | break; |
|---|
| 365 | |
|---|
| 366 | case AW_POINTER: { |
|---|
| 367 | #if defined(DUMP_AWAR_CHANGES) |
|---|
| 368 | fprintf(stderr, "creating awar_pointer '%s' with default value '%p'\n", var_name, default_value.p); |
|---|
| 369 | #endif // DUMP_AWAR_CHANGES |
|---|
| 370 | GB_write_pointer(gb_def, default_value.p); |
|---|
| 371 | break; |
|---|
| 372 | } |
|---|
| 373 | default: |
|---|
| 374 | GB_warningf("AWAR '%s' cannot be created because of disallowed type", var_name); |
|---|
| 375 | break; |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | GB_ERROR error = GB_set_temporary(gb_def); |
|---|
| 379 | if (error) GB_warningf("AWAR '%s': failed to set temporary on creation (Reason: %s)", var_name, error); |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | variable_type = var_type; |
|---|
| 383 | gb_origin = gb_def; |
|---|
| 384 | this->map(gb_def); |
|---|
| 385 | |
|---|
| 386 | aw_assert(is_valid()); |
|---|
| 387 | } |
|---|
| 388 | |
|---|
| 389 | |
|---|
| 390 | void AW_awar::update() { |
|---|
| 391 | bool fix_value = false; |
|---|
| 392 | |
|---|
| 393 | aw_assert(is_valid()); |
|---|
| 394 | |
|---|
| 395 | if (gb_var && ((pp.f.min != pp.f.max) || pp.srt)) { |
|---|
| 396 | switch (variable_type) { |
|---|
| 397 | case AW_INT: { |
|---|
| 398 | long lo = read_int(); |
|---|
| 399 | if (lo < pp.f.min -.5) { |
|---|
| 400 | fix_value = true; |
|---|
| 401 | lo = (int)(pp.f.min + 0.5); |
|---|
| 402 | } |
|---|
| 403 | if (lo>pp.f.max + .5) { |
|---|
| 404 | fix_value = true; |
|---|
| 405 | lo = (int)(pp.f.max + 0.5); |
|---|
| 406 | } |
|---|
| 407 | if (fix_value) { |
|---|
| 408 | if (root) root->changer_of_variable = NULp; |
|---|
| 409 | write_int(lo); |
|---|
| 410 | } |
|---|
| 411 | break; |
|---|
| 412 | } |
|---|
| 413 | case AW_FLOAT: { |
|---|
| 414 | float fl = read_float(); |
|---|
| 415 | if (fl < pp.f.min) { |
|---|
| 416 | fix_value = true; |
|---|
| 417 | fl = pp.f.min+AWAR_EPS; |
|---|
| 418 | } |
|---|
| 419 | if (fl>pp.f.max) { |
|---|
| 420 | fix_value = true; |
|---|
| 421 | fl = pp.f.max-AWAR_EPS; |
|---|
| 422 | } |
|---|
| 423 | if (fix_value) { |
|---|
| 424 | if (root) root->changer_of_variable = NULp; |
|---|
| 425 | write_float(fl); |
|---|
| 426 | } |
|---|
| 427 | break; |
|---|
| 428 | } |
|---|
| 429 | case AW_STRING: { |
|---|
| 430 | arb_assert(pp.srt); // otherwise call to GBS_string_eval is expensive |
|---|
| 431 | char *str = read_string(); |
|---|
| 432 | char *n = GBS_string_eval(str, pp.srt); |
|---|
| 433 | |
|---|
| 434 | if (!n) GBK_terminatef("SRT ERROR %s %s", pp.srt, GB_await_error()); |
|---|
| 435 | |
|---|
| 436 | if (strcmp(n, str) != 0) { |
|---|
| 437 | fix_value = true; |
|---|
| 438 | if (root) root->changer_of_variable = NULp; |
|---|
| 439 | write_string(n); |
|---|
| 440 | } |
|---|
| 441 | free(n); |
|---|
| 442 | free(str); |
|---|
| 443 | break; |
|---|
| 444 | } |
|---|
| 445 | default: |
|---|
| 446 | break; |
|---|
| 447 | } |
|---|
| 448 | } |
|---|
| 449 | |
|---|
| 450 | if (!fix_value) { // function was already recalled by write_xxx() above |
|---|
| 451 | this->update_targets(); |
|---|
| 452 | this->run_callbacks(); |
|---|
| 453 | } |
|---|
| 454 | |
|---|
| 455 | aw_assert(is_valid()); |
|---|
| 456 | } |
|---|
| 457 | |
|---|
| 458 | |
|---|
| 459 | void AW_awar::update_target(AW_var_target *pntr) { |
|---|
| 460 | // send data to all variables |
|---|
| 461 | if (!pntr->pointer) return; |
|---|
| 462 | switch (variable_type) { |
|---|
| 463 | case AW_STRING: this->get((char **)pntr->pointer); break; |
|---|
| 464 | case AW_FLOAT: this->get((float *)pntr->pointer); break; |
|---|
| 465 | case AW_INT: this->get((long *)pntr->pointer); break; |
|---|
| 466 | default: aw_assert(0); GB_warning("Unknown awar type"); break; |
|---|
| 467 | } |
|---|
| 468 | } |
|---|
| 469 | |
|---|
| 470 | |
|---|
| 471 | |
|---|
| 472 | void AW_awar::assert_var_type(AW_VARIABLE_TYPE wanted_type) { |
|---|
| 473 | if (wanted_type != variable_type) { |
|---|
| 474 | GBK_terminatef("AWAR '%s' has wrong type (got=%i, expected=%i)", |
|---|
| 475 | awar_name, variable_type, wanted_type); |
|---|
| 476 | } |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | // extern "C" |
|---|
| 480 | static void AW_var_gbdata_callback_delete(GBDATA *gbd, AW_awar *awar) { |
|---|
| 481 | AW_var_gbdata_callback_delete_intern(gbd, awar); |
|---|
| 482 | } |
|---|
| 483 | |
|---|
| 484 | AW_awar *AW_awar::map(AW_default gbd) { |
|---|
| 485 | if (gb_var) { // remove old mapping |
|---|
| 486 | GB_remove_callback((GBDATA *)gb_var, GB_CB_CHANGED, makeDatabaseCallback(AW_var_gbdata_callback, this)); |
|---|
| 487 | if (gb_var != gb_origin) { // keep callback if pointing to origin! |
|---|
| 488 | GB_remove_callback((GBDATA *)gb_var, GB_CB_DELETE, makeDatabaseCallback(AW_var_gbdata_callback_delete, this)); |
|---|
| 489 | } |
|---|
| 490 | gb_var = NULp; |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | if (!gbd) { // attempt to remap to NULp -> unmap |
|---|
| 494 | gbd = gb_origin; |
|---|
| 495 | } |
|---|
| 496 | |
|---|
| 497 | if (gbd) { |
|---|
| 498 | GB_transaction ta(gbd); |
|---|
| 499 | |
|---|
| 500 | GB_ERROR error = GB_add_callback((GBDATA *) gbd, GB_CB_CHANGED, makeDatabaseCallback(AW_var_gbdata_callback, this)); |
|---|
| 501 | if (!error && gbd != gb_origin) { // do not add delete callback if mapping to origin |
|---|
| 502 | error = GB_add_callback((GBDATA *) gbd, GB_CB_DELETE, makeDatabaseCallback(AW_var_gbdata_callback_delete, this)); |
|---|
| 503 | } |
|---|
| 504 | if (error) aw_message(error); |
|---|
| 505 | |
|---|
| 506 | gb_var = gbd; |
|---|
| 507 | update(); |
|---|
| 508 | } |
|---|
| 509 | else { |
|---|
| 510 | update(); |
|---|
| 511 | } |
|---|
| 512 | |
|---|
| 513 | aw_assert(is_valid()); |
|---|
| 514 | |
|---|
| 515 | return this; |
|---|
| 516 | } |
|---|
| 517 | |
|---|
| 518 | AW_awar *AW_awar::map(AW_awar *dest) { |
|---|
| 519 | return this->map(dest->gb_var); |
|---|
| 520 | } |
|---|
| 521 | AW_awar *AW_awar::map(const char *awarn) { |
|---|
| 522 | return map(root->awar(awarn)); |
|---|
| 523 | } |
|---|
| 524 | |
|---|
| 525 | AW_awar *AW_awar::remove_callback(const RootCallback& rcb) { |
|---|
| 526 | AW_root_cblist::remove(callback_list, rcb); |
|---|
| 527 | return this; |
|---|
| 528 | } |
|---|
| 529 | |
|---|
| 530 | AW_awar *AW_awar::set_minmax(float min, float max) { |
|---|
| 531 | if (variable_type == AW_STRING) GBK_terminatef("set_minmax does not apply to string AWAR '%s'", awar_name); |
|---|
| 532 | if (min>=max) { |
|---|
| 533 | // 'min>max' would set impossible limits |
|---|
| 534 | // 'min==max' would remove limits, which is not allowed! |
|---|
| 535 | GBK_terminatef("illegal values in set_minmax for AWAR '%s'", awar_name); |
|---|
| 536 | } |
|---|
| 537 | |
|---|
| 538 | pp.f.min = min; |
|---|
| 539 | pp.f.max = max; |
|---|
| 540 | update(); // corrects wrong default value |
|---|
| 541 | return this; |
|---|
| 542 | } |
|---|
| 543 | |
|---|
| 544 | float AW_awar::get_min() const { |
|---|
| 545 | if (variable_type == AW_STRING) GBK_terminatef("get_min does not apply to string AWAR '%s'", awar_name); |
|---|
| 546 | bool isSet = (pp.f.min != pp.f.max); // as used in AW_awar::update |
|---|
| 547 | if (!isSet) { |
|---|
| 548 | aw_assert(float(INT_MIN)>=-FLT_MAX); |
|---|
| 549 | if (variable_type == AW_INT) return float(INT_MIN); |
|---|
| 550 | aw_assert(variable_type == AW_FLOAT); |
|---|
| 551 | return -FLT_MAX; |
|---|
| 552 | } |
|---|
| 553 | return pp.f.min; |
|---|
| 554 | } |
|---|
| 555 | float AW_awar::get_max() const { |
|---|
| 556 | if (variable_type == AW_STRING) GBK_terminatef("get_max does not apply to string AWAR '%s'", awar_name); |
|---|
| 557 | bool isSet = (pp.f.min != pp.f.max); // as used in AW_awar::update |
|---|
| 558 | if (!isSet) { |
|---|
| 559 | aw_assert(float(INT_MAX)<=FLT_MAX); |
|---|
| 560 | if (variable_type == AW_INT) return float(INT_MAX); |
|---|
| 561 | aw_assert(variable_type == AW_FLOAT); |
|---|
| 562 | return FLT_MAX; |
|---|
| 563 | } |
|---|
| 564 | return pp.f.max; |
|---|
| 565 | } |
|---|
| 566 | |
|---|
| 567 | AW_awar *AW_awar::set_srt(const char *srt) { |
|---|
| 568 | assert_var_type(AW_STRING); |
|---|
| 569 | pp.srt = srt; |
|---|
| 570 | return this; |
|---|
| 571 | } |
|---|
| 572 | |
|---|
| 573 | GB_ERROR AW_awar::toggle_toggle() { |
|---|
| 574 | char *var = this->read_as_string(); |
|---|
| 575 | GB_ERROR error = NULp; |
|---|
| 576 | if (var[0] == '0' || var[0] == 'n') { |
|---|
| 577 | switch (this->variable_type) { |
|---|
| 578 | case AW_STRING: error = this->write_string("yes"); break; |
|---|
| 579 | case AW_INT: error = this->write_int(1); break; |
|---|
| 580 | case AW_FLOAT: error = this->write_float(1.0); break; |
|---|
| 581 | default: break; |
|---|
| 582 | } |
|---|
| 583 | } |
|---|
| 584 | else { |
|---|
| 585 | switch (this->variable_type) { |
|---|
| 586 | case AW_STRING: error = this->write_string("no"); break; |
|---|
| 587 | case AW_INT: error = this->write_int(0); break; |
|---|
| 588 | case AW_FLOAT: error = this->write_float(0.0); break; |
|---|
| 589 | default: break; |
|---|
| 590 | } |
|---|
| 591 | } |
|---|
| 592 | free(var); |
|---|
| 593 | return error; |
|---|
| 594 | } |
|---|
| 595 | |
|---|
| 596 | AW_awar *AW_awar::unmap() { |
|---|
| 597 | return this->map(gb_origin); |
|---|
| 598 | } |
|---|
| 599 | |
|---|
| 600 | void AW_awar::run_callbacks() { |
|---|
| 601 | if (allowed_to_run_callbacks) { |
|---|
| 602 | clock_t start = clock(); |
|---|
| 603 | |
|---|
| 604 | AW_root_cblist::call(callback_list, root); |
|---|
| 605 | |
|---|
| 606 | clock_t end = clock(); |
|---|
| 607 | |
|---|
| 608 | if (start<end) { // ignore overflown |
|---|
| 609 | double cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; |
|---|
| 610 | |
|---|
| 611 | if (callback_time_count>19 && (callback_time_count%2) == 0) { |
|---|
| 612 | callback_time_count = callback_time_count/2; |
|---|
| 613 | callback_time_sum = callback_time_sum/2.0; |
|---|
| 614 | } |
|---|
| 615 | |
|---|
| 616 | callback_time_sum += cpu_time_used; |
|---|
| 617 | callback_time_count++; |
|---|
| 618 | } |
|---|
| 619 | } |
|---|
| 620 | } |
|---|
| 621 | |
|---|
| 622 | |
|---|
| 623 | void AW_awar::update_targets() { |
|---|
| 624 | // send data to all variables |
|---|
| 625 | AW_var_target*pntr; |
|---|
| 626 | for (pntr = target_list; pntr; pntr = pntr->next) { |
|---|
| 627 | update_target(pntr); |
|---|
| 628 | } |
|---|
| 629 | } |
|---|
| 630 | |
|---|
| 631 | |
|---|
| 632 | void AW_awar::remove_all_callbacks() { |
|---|
| 633 | AW_root_cblist::clear(callback_list); |
|---|
| 634 | } |
|---|
| 635 | |
|---|
| 636 | // -------------------------------------------------------------------------------- |
|---|
| 637 | |
|---|
| 638 | inline bool member_of_DB(GBDATA *gbd, GBDATA *gb_main) { |
|---|
| 639 | return gbd && GB_get_root(gbd) == gb_main;; |
|---|
| 640 | } |
|---|
| 641 | |
|---|
| 642 | void AW_awar::unlink() { |
|---|
| 643 | aw_assert(this->is_valid()); |
|---|
| 644 | remove_all_callbacks(); |
|---|
| 645 | remove_all_target_vars(); |
|---|
| 646 | gb_origin = NULp; // make zombie awar |
|---|
| 647 | unmap(); // unmap to nothing |
|---|
| 648 | aw_assert(is_valid()); |
|---|
| 649 | } |
|---|
| 650 | |
|---|
| 651 | bool AW_awar::unlink_from_DB(GBDATA *gb_main) { |
|---|
| 652 | bool make_zombie = false; |
|---|
| 653 | bool mapped_to_DB = member_of_DB(gb_var, gb_main); |
|---|
| 654 | bool origin_in_DB = member_of_DB(gb_origin, gb_main); |
|---|
| 655 | |
|---|
| 656 | aw_assert(is_valid()); |
|---|
| 657 | |
|---|
| 658 | if (mapped_to_DB) { |
|---|
| 659 | if (origin_in_DB) make_zombie = true; |
|---|
| 660 | else unmap(); |
|---|
| 661 | } |
|---|
| 662 | else if (origin_in_DB) { |
|---|
| 663 | // origin is in DB, current mapping is not |
|---|
| 664 | // -> remap permanentely |
|---|
| 665 | gb_origin = gb_var; |
|---|
| 666 | } |
|---|
| 667 | |
|---|
| 668 | if (make_zombie) unlink(); |
|---|
| 669 | |
|---|
| 670 | aw_assert(is_valid()); |
|---|
| 671 | |
|---|
| 672 | return make_zombie; |
|---|
| 673 | } |
|---|
| 674 | |
|---|
| 675 | // -------------------------------------------------------------------------------- |
|---|
| 676 | |
|---|
| 677 | |
|---|
| 678 | |
|---|
| 679 | void AW_awar::remove_all_target_vars() { |
|---|
| 680 | while (target_list) { |
|---|
| 681 | AW_var_target *tar = target_list; |
|---|
| 682 | target_list = tar->next; |
|---|
| 683 | delete tar; |
|---|
| 684 | } |
|---|
| 685 | } |
|---|
| 686 | |
|---|
| 687 | |
|---|
| 688 | void AW_awar::update_tmp_state_during_change() { |
|---|
| 689 | // here it's known that the awar is inside the correct DB (main-DB or prop-DB) |
|---|
| 690 | |
|---|
| 691 | if (has_managed_tmp_state()) { |
|---|
| 692 | aw_assert(GB_get_transaction_level(gb_origin) != 0); |
|---|
| 693 | // still should be inside change-TA (or inside TA opened in update_tmp_state()) |
|---|
| 694 | // (or in no-TA-mode; as true for properties) |
|---|
| 695 | |
|---|
| 696 | bool has_default_value = false; |
|---|
| 697 | switch (variable_type) { |
|---|
| 698 | case AW_STRING: has_default_value = ARB_strNULLcmp(GB_read_char_pntr(gb_origin), default_value.s) == 0; break; |
|---|
| 699 | case AW_INT: has_default_value = GB_read_int(gb_origin) == default_value.l; break; |
|---|
| 700 | case AW_FLOAT: has_default_value = GB_read_float(gb_origin) == default_value.f; break; |
|---|
| 701 | case AW_POINTER: has_default_value = GB_read_pointer(gb_origin) == default_value.p; break; |
|---|
| 702 | default: aw_assert(0); GB_warning("Unknown awar type"); break; |
|---|
| 703 | } |
|---|
| 704 | |
|---|
| 705 | if (GB_is_temporary(gb_origin) != has_default_value) { |
|---|
| 706 | GB_ERROR error = has_default_value ? GB_set_temporary(gb_origin) : GB_clear_temporary(gb_origin); |
|---|
| 707 | if (error) GB_warning(GBS_global_string("Failed to set temporary for AWAR '%s' (Reason: %s)", awar_name, error)); |
|---|
| 708 | } |
|---|
| 709 | } |
|---|
| 710 | } |
|---|
| 711 | |
|---|
| 712 | void AW_awar::set_temp_if_is_default(GBDATA *gb_db) { |
|---|
| 713 | if (has_managed_tmp_state() && member_of_DB(gb_origin, gb_db)) { // ignore awars in "other" DB (main-DB vs properties) |
|---|
| 714 | aw_assert(GB_get_transaction_level(gb_origin)<1); // make sure allowed_to_run_callbacks works as expected |
|---|
| 715 | |
|---|
| 716 | allowed_to_run_callbacks = false; // avoid AWAR-change-callbacks |
|---|
| 717 | { |
|---|
| 718 | GB_transaction ta(gb_origin); |
|---|
| 719 | update_tmp_state_during_change(); |
|---|
| 720 | } |
|---|
| 721 | allowed_to_run_callbacks = true; |
|---|
| 722 | } |
|---|
| 723 | } |
|---|
| 724 | |
|---|
| 725 | AW_awar::~AW_awar() { |
|---|
| 726 | unlink(); |
|---|
| 727 | untie_all_widgets(); |
|---|
| 728 | if (variable_type == AW_STRING) free(default_value.s); |
|---|
| 729 | free(awar_name); |
|---|
| 730 | } |
|---|
| 731 | |
|---|
| 732 | // -------------------------------------------------------------------------------- |
|---|
| 733 | |
|---|
| 734 | #ifdef UNIT_TESTS |
|---|
| 735 | #include <test_unit.h> |
|---|
| 736 | |
|---|
| 737 | static int test_cb1_called; |
|---|
| 738 | static int test_cb2_called; |
|---|
| 739 | |
|---|
| 740 | static void test_cb1(AW_root *, AW_CL cd1, AW_CL cd2) { test_cb1_called += (cd1+cd2); } |
|---|
| 741 | static void test_cb2(AW_root *, AW_CL cd1, AW_CL cd2) { test_cb2_called += (cd1+cd2); } |
|---|
| 742 | |
|---|
| 743 | #define TEST_EXPECT_CBS_CALLED(cbl, c1,c2) \ |
|---|
| 744 | do { \ |
|---|
| 745 | test_cb1_called = test_cb2_called = 0; \ |
|---|
| 746 | AW_root_cblist::call(cbl, NULp); \ |
|---|
| 747 | TEST_EXPECT_EQUAL(test_cb1_called, c1); \ |
|---|
| 748 | TEST_EXPECT_EQUAL(test_cb2_called, c2); \ |
|---|
| 749 | } while(0) |
|---|
| 750 | |
|---|
| 751 | void TEST_AW_root_cblist() { |
|---|
| 752 | AW_root_cblist *cb_list = NULp; |
|---|
| 753 | |
|---|
| 754 | RootCallback tcb1(test_cb1, 1, 0); |
|---|
| 755 | RootCallback tcb2(test_cb2, 0, 1); |
|---|
| 756 | RootCallback wrong_tcb2(test_cb2, 1, 0); |
|---|
| 757 | |
|---|
| 758 | AW_root_cblist::add(cb_list, tcb1); TEST_EXPECT_CBS_CALLED(cb_list, 1, 0); |
|---|
| 759 | AW_root_cblist::add(cb_list, tcb2); TEST_EXPECT_CBS_CALLED(cb_list, 1, 1); |
|---|
| 760 | |
|---|
| 761 | AW_root_cblist::remove(cb_list, tcb1); TEST_EXPECT_CBS_CALLED(cb_list, 0, 1); |
|---|
| 762 | AW_root_cblist::remove(cb_list, wrong_tcb2); TEST_EXPECT_CBS_CALLED(cb_list, 0, 1); |
|---|
| 763 | AW_root_cblist::remove(cb_list, tcb2); TEST_EXPECT_CBS_CALLED(cb_list, 0, 0); |
|---|
| 764 | |
|---|
| 765 | AW_root_cblist::add(cb_list, tcb1); |
|---|
| 766 | AW_root_cblist::add(cb_list, tcb1); // add callback twice |
|---|
| 767 | TEST_EXPECT_CBS_CALLED(cb_list, 1, 0); // should only be called once |
|---|
| 768 | |
|---|
| 769 | AW_root_cblist::add(cb_list, tcb2); |
|---|
| 770 | AW_root_cblist::clear(cb_list); |
|---|
| 771 | TEST_EXPECT_CBS_CALLED(cb_list, 0, 0); // list clear - nothing should be called |
|---|
| 772 | } |
|---|
| 773 | TEST_PUBLISH(TEST_AW_root_cblist); |
|---|
| 774 | |
|---|
| 775 | #endif // UNIT_TESTS |
|---|
| 776 | |
|---|
| 777 | |
|---|