| 1 | // ==================================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : AW_question.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Coded by Ralf Westram (coder@reallysoft.de) in January 2002 // |
|---|
| 7 | // Copyright Department of Microbiology (Technical University Munich) // |
|---|
| 8 | // // |
|---|
| 9 | // Visit our web site at: http://www.arb-home.de/ // |
|---|
| 10 | // ==================================================================== // |
|---|
| 11 | |
|---|
| 12 | #include <arbdb.h> |
|---|
| 13 | #include <aw_msg.hxx> |
|---|
| 14 | #include <aw_question.hxx> |
|---|
| 15 | #include "aw_root.hxx" |
|---|
| 16 | #include "aw_awar.hxx" |
|---|
| 17 | #include "aw_global.hxx" |
|---|
| 18 | #include "aw_window.hxx" |
|---|
| 19 | #include "aw_window_Xm.hxx" |
|---|
| 20 | #include "aw_advice.hxx" |
|---|
| 21 | |
|---|
| 22 | using namespace std; |
|---|
| 23 | |
|---|
| 24 | #define AWAR_QUESTION "tmp/question" |
|---|
| 25 | |
|---|
| 26 | int aw_question(const char *unique_id, const char *question, const char *buttons, bool sameSizeButtons, const char *helpfile) { |
|---|
| 27 | /*! Ask the user a question. Blocks all UI input until the answer is provided. |
|---|
| 28 | * |
|---|
| 29 | * @param unique_id Unique ID to identify the question. Must be valid hkey. |
|---|
| 30 | * @param question The question. |
|---|
| 31 | * @param buttons Comma separated list of button names. A button named starting |
|---|
| 32 | * with "^" will begin a new row of buttons. A button named "EXIT" |
|---|
| 33 | * will cause abnormal (EXIT_FAILURE) termination of program. |
|---|
| 34 | * @param sameSizeButtons Make all buttons have the same size. |
|---|
| 35 | * @param helpfile Adds a "HELP" button. May be NULp. (currently ignored) |
|---|
| 36 | * @return the index of the selected answer |
|---|
| 37 | */ |
|---|
| 38 | |
|---|
| 39 | aw_assert(buttons); |
|---|
| 40 | |
|---|
| 41 | AW_awar *awar_neverAskAgain = NULp; |
|---|
| 42 | if (unique_id) { |
|---|
| 43 | GB_ERROR error = GB_check_key(unique_id); |
|---|
| 44 | if (error) { |
|---|
| 45 | aw_message(error); |
|---|
| 46 | unique_id = NULp; |
|---|
| 47 | } |
|---|
| 48 | else { |
|---|
| 49 | awar_neverAskAgain = AW_root::SINGLETON->awar_int(GBS_global_string("answers/%s", unique_id), 0, AW_ROOT_DEFAULT); |
|---|
| 50 | } |
|---|
| 51 | } |
|---|
| 52 | |
|---|
| 53 | int result = awar_neverAskAgain ? awar_neverAskAgain->read_int() : 0; |
|---|
| 54 | if (result>0) { // have auto-answer |
|---|
| 55 | --result; |
|---|
| 56 | } |
|---|
| 57 | else { // no auto-answer |
|---|
| 58 | if (!question) question = "No question?! Please report this as a bug."; |
|---|
| 59 | |
|---|
| 60 | char *button_list = strdup(buttons ? buttons : "OK"); |
|---|
| 61 | if (button_list[0] == 0) { |
|---|
| 62 | freedup(button_list, "Maybe ok,EXIT"); |
|---|
| 63 | GBK_dump_backtrace(stderr, "Empty buttonlist"); |
|---|
| 64 | question = GBS_global_string_copy("%s\n" |
|---|
| 65 | "(Program error - Unsure what happens when you click ok\n" |
|---|
| 66 | " Check console for backtrace and report error)", |
|---|
| 67 | question); |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | AW_root *root = AW_root::SINGLETON; |
|---|
| 71 | AW_awar *awar_quest = root->awar_string(AWAR_QUESTION); |
|---|
| 72 | awar_quest->write_string(question); |
|---|
| 73 | |
|---|
| 74 | size_t question_length, question_lines; |
|---|
| 75 | aw_detect_text_size(question, question_length, question_lines); |
|---|
| 76 | |
|---|
| 77 | // hash key to find matching window |
|---|
| 78 | char *hindex = GBS_global_string_copy("%s$%s$%zu$%zu$%i$%s", |
|---|
| 79 | button_list, unique_id ? unique_id : "<NOID>", |
|---|
| 80 | question_length, question_lines, int(sameSizeButtons), |
|---|
| 81 | helpfile ? helpfile : ""); |
|---|
| 82 | |
|---|
| 83 | static GB_HASH *hash_windows = NULp; |
|---|
| 84 | if (!hash_windows) hash_windows = GBS_create_hash(256, GB_MIND_CASE); |
|---|
| 85 | AW_window_message *aw_msg = (AW_window_message *)GBS_read_hash(hash_windows, hindex); |
|---|
| 86 | |
|---|
| 87 | #if defined(DEBUG) |
|---|
| 88 | printf("hindex='%s'\n", hindex); |
|---|
| 89 | #endif // DEBUG |
|---|
| 90 | |
|---|
| 91 | if (!aw_msg) { |
|---|
| 92 | aw_msg = new AW_window_message; |
|---|
| 93 | GBS_write_hash(hash_windows, hindex, (long)aw_msg); |
|---|
| 94 | { |
|---|
| 95 | char *wid = GBS_string_2_key(GBS_global_string("QUESTION BOX %s", unique_id)); |
|---|
| 96 | aw_msg->init(root, wid, "QUESTION BOX", false); |
|---|
| 97 | free(wid); |
|---|
| 98 | } |
|---|
| 99 | aw_msg->recalc_size_atShow(AW_RESIZE_DEFAULT); // force size recalc (ignores user size) |
|---|
| 100 | |
|---|
| 101 | aw_msg->label_length(10); |
|---|
| 102 | |
|---|
| 103 | aw_msg->at(10, 10); |
|---|
| 104 | aw_msg->auto_space(10, 10); |
|---|
| 105 | |
|---|
| 106 | aw_msg->button_length(question_length+3); |
|---|
| 107 | aw_msg->button_height(question_lines+1); |
|---|
| 108 | |
|---|
| 109 | aw_msg->create_button(NULp, AWAR_QUESTION); |
|---|
| 110 | |
|---|
| 111 | aw_msg->button_height(0); |
|---|
| 112 | |
|---|
| 113 | aw_msg->at_newline(); |
|---|
| 114 | |
|---|
| 115 | if (sameSizeButtons) { |
|---|
| 116 | size_t max_button_length = helpfile ? 4 : 0; |
|---|
| 117 | char *pos = button_list; |
|---|
| 118 | |
|---|
| 119 | while (1) { |
|---|
| 120 | char *comma = strchr(pos, ','); |
|---|
| 121 | if (!comma) comma = strchr(pos, 0); |
|---|
| 122 | |
|---|
| 123 | size_t len = comma-pos; |
|---|
| 124 | if (len>max_button_length) max_button_length = len; |
|---|
| 125 | |
|---|
| 126 | if (!comma[0]) break; |
|---|
| 127 | pos = comma+1; |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | aw_msg->button_length(max_button_length+2); |
|---|
| 131 | } |
|---|
| 132 | else { |
|---|
| 133 | aw_msg->button_length(0); |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | // insert the buttons: |
|---|
| 137 | char *ret = strtok(button_list, ","); |
|---|
| 138 | bool help_button_done = false; |
|---|
| 139 | int counter = 0; |
|---|
| 140 | |
|---|
| 141 | while (ret) { |
|---|
| 142 | if (ret[0] == '^') { |
|---|
| 143 | if (helpfile && !help_button_done) { |
|---|
| 144 | aw_msg->callback(makeHelpCallback(helpfile)); |
|---|
| 145 | aw_msg->create_button("HELP", "HELP", "H"); |
|---|
| 146 | help_button_done = true; |
|---|
| 147 | } |
|---|
| 148 | aw_msg->at_newline(); |
|---|
| 149 | ++ret; |
|---|
| 150 | } |
|---|
| 151 | if (strcmp(ret, "EXIT") == 0) { |
|---|
| 152 | aw_msg->callback(makeWindowCallback(message_cb, -1)); |
|---|
| 153 | } |
|---|
| 154 | else { |
|---|
| 155 | aw_msg->callback(makeWindowCallback(message_cb, counter++)); |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | if (sameSizeButtons) { |
|---|
| 159 | aw_msg->create_button(NULp, ret); |
|---|
| 160 | } |
|---|
| 161 | else { |
|---|
| 162 | aw_msg->create_autosize_button(NULp, ret); |
|---|
| 163 | } |
|---|
| 164 | ret = strtok(NULp, ","); |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | if (helpfile && !help_button_done) { // if not done above |
|---|
| 168 | aw_msg->callback(makeHelpCallback(helpfile)); |
|---|
| 169 | aw_msg->create_button("HELP", "HELP", "H"); |
|---|
| 170 | help_button_done = true; |
|---|
| 171 | } |
|---|
| 172 | |
|---|
| 173 | // create no-repeat checkbox if we have a unique-id |
|---|
| 174 | if (awar_neverAskAgain) { |
|---|
| 175 | aw_msg->at_newline(); |
|---|
| 176 | const char *label = counter>1 ? "Never ask again" : "Never notify me again"; |
|---|
| 177 | aw_msg->label_length(strlen(label)); |
|---|
| 178 | aw_msg->label(label); |
|---|
| 179 | aw_msg->create_toggle(awar_neverAskAgain->awar_name); |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | aw_msg->window_fit(); |
|---|
| 183 | } |
|---|
| 184 | else { |
|---|
| 185 | #if defined(DEBUG) |
|---|
| 186 | printf("[Reusing existing aw_question-window]\n"); |
|---|
| 187 | #endif |
|---|
| 188 | } |
|---|
| 189 | free(hindex); |
|---|
| 190 | aw_msg->show_modal(); |
|---|
| 191 | |
|---|
| 192 | free(button_list); |
|---|
| 193 | aw_message_cb_result = -13; |
|---|
| 194 | |
|---|
| 195 | #if defined(TRACE_STATUS_MORE) |
|---|
| 196 | fprintf(stderr, "add aw_message_timer_listen_event with delay = %i\n", AW_MESSAGE_LISTEN_DELAY); fflush(stdout); |
|---|
| 197 | #endif // TRACE_STATUS_MORE |
|---|
| 198 | root->add_timed_callback_never_disabled(AW_MESSAGE_LISTEN_DELAY, makeTimedCallback(aw_message_timer_listen_event, static_cast<AW_window*>(aw_msg))); |
|---|
| 199 | |
|---|
| 200 | { |
|---|
| 201 | LocallyModify<bool> flag(root->disable_callbacks, true); |
|---|
| 202 | while (aw_message_cb_result == -13) { |
|---|
| 203 | root->process_events(); |
|---|
| 204 | } |
|---|
| 205 | } |
|---|
| 206 | aw_msg->hide(); |
|---|
| 207 | |
|---|
| 208 | // if we have an awar and no-repeat got checked, |
|---|
| 209 | // store the result and warn user about what he's done. |
|---|
| 210 | if (awar_neverAskAgain && awar_neverAskAgain->read_int()) { |
|---|
| 211 | int answerCode = aw_message_cb_result >= 0 ? aw_message_cb_result+1 : 0; |
|---|
| 212 | awar_neverAskAgain->write_int(answerCode); |
|---|
| 213 | |
|---|
| 214 | if (answerCode>0) { |
|---|
| 215 | const char *appname = AW_root::SINGLETON->program_name; |
|---|
| 216 | char *advice = GBS_global_string_copy |
|---|
| 217 | ("You will not be asked that question again in this session.\n" |
|---|
| 218 | "%s will always assume the answer you just gave.\n" |
|---|
| 219 | "\n" |
|---|
| 220 | "After restarting %s that question will be asked again.\n" |
|---|
| 221 | "To disable that question permanently for future sessions,\n" |
|---|
| 222 | "you need to save properties.\n" |
|---|
| 223 | "\n" |
|---|
| 224 | "Depending on the type of question, disabling it might be\n" |
|---|
| 225 | "helpful or obstructive.\n" |
|---|
| 226 | "Disabled questions can be reactivated from the properties menu.\n", |
|---|
| 227 | appname, appname); |
|---|
| 228 | |
|---|
| 229 | AW_advice(advice, AW_ADVICE_TOGGLE, "Disabling questions", "questions.hlp"); |
|---|
| 230 | free(advice); |
|---|
| 231 | } |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | result = aw_message_cb_result; |
|---|
| 235 | } |
|---|
| 236 | |
|---|
| 237 | switch (result) { |
|---|
| 238 | case -1: // exit with core |
|---|
| 239 | fprintf(stderr, "Core dump requested\n"); |
|---|
| 240 | ARB_SIGSEGV(1); |
|---|
| 241 | break; |
|---|
| 242 | case -2: // exit without core |
|---|
| 243 | exit(-1); |
|---|
| 244 | break; |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | return result; |
|---|
| 248 | } |
|---|
| 249 | |
|---|
| 250 | bool aw_ask_sure(const char *unique_id, const char *msg) { |
|---|
| 251 | /*! |
|---|
| 252 | * pop up a modal yes/no question |
|---|
| 253 | * @param unique_id If given, the dialog will get an "do not show again" checkbox |
|---|
| 254 | * @param msg The question. |
|---|
| 255 | * @return True if the answer was "Yes" |
|---|
| 256 | */ |
|---|
| 257 | return aw_question(unique_id, msg, "Yes,No", true, NULp) == 0; |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | void aw_popup_ok(const char *msg) { |
|---|
| 261 | /*! |
|---|
| 262 | * Pop up a modal message with an Ok button |
|---|
| 263 | * @param msg The message. |
|---|
| 264 | */ |
|---|
| 265 | aw_question(NULp, msg, "Ok", true, NULp); |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | __ATTR__NORETURN void aw_popup_exit(const char *msg) { |
|---|
| 269 | /*! |
|---|
| 270 | * Pop up a modal message with an Exit button. |
|---|
| 271 | * Won't return but exit with "EXIT_FAILURE" |
|---|
| 272 | */ |
|---|
| 273 | aw_question(NULp, msg, "EXIT", true, NULp); |
|---|
| 274 | aw_assert(0); // should not be reached |
|---|
| 275 | exit(EXIT_FAILURE); |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | void AW_reactivate_all_questions(AW_window*) { |
|---|
| 279 | GB_transaction ta(AW_ROOT_DEFAULT); |
|---|
| 280 | GBDATA *gb_neverAskedAgain = GB_search(AW_ROOT_DEFAULT, "answers", GB_FIND); |
|---|
| 281 | const char *msg = "No questions were disabled yet."; |
|---|
| 282 | |
|---|
| 283 | if (gb_neverAskedAgain) { |
|---|
| 284 | int reactivated = 0; |
|---|
| 285 | for (GBDATA *gb_q = GB_child(gb_neverAskedAgain); gb_q; gb_q = GB_nextChild(gb_q)) { |
|---|
| 286 | if (GB_read_int(gb_q)) { |
|---|
| 287 | GB_write_int(gb_q, 0); |
|---|
| 288 | reactivated++; |
|---|
| 289 | } |
|---|
| 290 | } |
|---|
| 291 | if (reactivated) { |
|---|
| 292 | msg = GBS_global_string("Reactivated %i questions (for this session)\n" |
|---|
| 293 | "To reactivate them for future sessions, save properties.", |
|---|
| 294 | reactivated); |
|---|
| 295 | } |
|---|
| 296 | } |
|---|
| 297 | aw_message(msg); |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | |
|---|
| 301 | void AW_repeated_question::add_help(const char *help_file) { |
|---|
| 302 | freedup(helpfile, help_file); |
|---|
| 303 | } |
|---|
| 304 | |
|---|
| 305 | int AW_repeated_question::get_answer(const char *unique_id, const char *question, const char *buttons, const char *to_all, bool add_abort) { |
|---|
| 306 | if (!buttons_used) { |
|---|
| 307 | buttons_used = strdup(buttons); |
|---|
| 308 | } |
|---|
| 309 | else { |
|---|
| 310 | // do not use the same instance of AW_repeated_question with different buttons! |
|---|
| 311 | assert_or_exit(strcmp(buttons_used, buttons) == 0); |
|---|
| 312 | } |
|---|
| 313 | |
|---|
| 314 | if (answer == -1 || !dont_ask_again) { |
|---|
| 315 | |
|---|
| 316 | char *all = GBS_global_string_copy(" (%s)", to_all); |
|---|
| 317 | int all_len = strlen(all); |
|---|
| 318 | size_t but_len = strlen(buttons); |
|---|
| 319 | size_t new_buttons_len = but_len*3+1+(add_abort ? 6 : 0)+all_len*3; |
|---|
| 320 | char *new_buttons = ARB_alloc<char>(new_buttons_len); |
|---|
| 321 | int button_count = 0; // number of buttons in 'buttons' |
|---|
| 322 | |
|---|
| 323 | { // transform "YES,NO" -> "YES,YES (to_all),^NO,NO (to_all)" or "YES (to_all),NO (to_all)" |
|---|
| 324 | char *w = new_buttons; |
|---|
| 325 | const char *r = buttons; |
|---|
| 326 | |
|---|
| 327 | while (1) { |
|---|
| 328 | const char *comma = strchr(r, ','); |
|---|
| 329 | if (!comma) comma = strchr(r, 0); |
|---|
| 330 | int len = comma-r; |
|---|
| 331 | |
|---|
| 332 | if (!dont_ask_again) { |
|---|
| 333 | if (w>new_buttons) *w++ = '^'; // not in front of first button |
|---|
| 334 | memcpy(w, r, len); w += len; |
|---|
| 335 | *w++ = ','; |
|---|
| 336 | } |
|---|
| 337 | memcpy(w, r, len); w += len; |
|---|
| 338 | memcpy(w, all, all_len); w += all_len; |
|---|
| 339 | *w++ = ','; |
|---|
| 340 | |
|---|
| 341 | button_count++; |
|---|
| 342 | |
|---|
| 343 | if (!comma[0]) break; |
|---|
| 344 | r = comma+1; |
|---|
| 345 | } |
|---|
| 346 | if (add_abort) { |
|---|
| 347 | const char *abort = "^ABORT"; |
|---|
| 348 | strcpy(w, abort); w += strlen(abort); |
|---|
| 349 | } |
|---|
| 350 | else { |
|---|
| 351 | --w; // delete comma at end |
|---|
| 352 | } |
|---|
| 353 | w[0] = 0; |
|---|
| 354 | |
|---|
| 355 | aw_assert(size_t(w-new_buttons) < new_buttons_len); // oops buffer overflow |
|---|
| 356 | |
|---|
| 357 | free(all); |
|---|
| 358 | } |
|---|
| 359 | |
|---|
| 360 | int user_answer = aw_question(unique_id, question, new_buttons, true, helpfile); |
|---|
| 361 | |
|---|
| 362 | if (dont_ask_again) { // ask question as normal when called first (dont_ask_again later) |
|---|
| 363 | answer = user_answer; |
|---|
| 364 | } |
|---|
| 365 | else { |
|---|
| 366 | answer = user_answer/2; |
|---|
| 367 | dont_ask_again = (user_answer%2) || (user_answer == (button_count*2)); |
|---|
| 368 | } |
|---|
| 369 | |
|---|
| 370 | free(new_buttons); |
|---|
| 371 | |
|---|
| 372 | aw_assert(answer<(button_count+(add_abort ? 1 : 0))); |
|---|
| 373 | } |
|---|
| 374 | |
|---|
| 375 | aw_assert(answer != -1); |
|---|
| 376 | |
|---|
| 377 | return answer; |
|---|
| 378 | } |
|---|
| 379 | |
|---|