| 1 | // ==================================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : aw_advice.cpp // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // // |
|---|
| 7 | // Coded by Ralf Westram (coder@reallysoft.de) in May 2002 // |
|---|
| 8 | // Copyright Department of Microbiology (Technical University Munich) // |
|---|
| 9 | // // |
|---|
| 10 | // Visit our web site at: http://www.arb-home.de/ // |
|---|
| 11 | // // |
|---|
| 12 | // // |
|---|
| 13 | // ==================================================================== // |
|---|
| 14 | |
|---|
| 15 | #include "aw_advice.hxx" |
|---|
| 16 | #include "aw_window.hxx" |
|---|
| 17 | #include "aw_awar.hxx" |
|---|
| 18 | #include "aw_msg.hxx" |
|---|
| 19 | #include "aw_root.hxx" |
|---|
| 20 | |
|---|
| 21 | #include <arbdb.h> |
|---|
| 22 | |
|---|
| 23 | using namespace std; |
|---|
| 24 | |
|---|
| 25 | #define AWAR_ADVICE_TMP "/tmp/advices/" |
|---|
| 26 | |
|---|
| 27 | #define AWAR_ADVICE_TEXT AWAR_ADVICE_TMP "text" |
|---|
| 28 | #define AWAR_ADVICE_UNDERSTOOD AWAR_ADVICE_TMP "understood" |
|---|
| 29 | |
|---|
| 30 | #define AWAR_ADVICE_DISABLED "/advices/disabled" |
|---|
| 31 | #define AWAR_ADVICE_SHOWN "/tmp/advices/shown" |
|---|
| 32 | |
|---|
| 33 | // ------------------------- |
|---|
| 34 | // internal data : |
|---|
| 35 | |
|---|
| 36 | static bool initialized = false; |
|---|
| 37 | static AW_root *advice_root = NULp; |
|---|
| 38 | |
|---|
| 39 | // -------------------------------------------------------- |
|---|
| 40 | |
|---|
| 41 | void init_Advisor(AW_root *awr) { |
|---|
| 42 | if (!initialized) { |
|---|
| 43 | advice_root = awr; |
|---|
| 44 | |
|---|
| 45 | advice_root->awar_string(AWAR_ADVICE_TEXT, "<no advice>"); |
|---|
| 46 | advice_root->awar_int(AWAR_ADVICE_UNDERSTOOD, 0); |
|---|
| 47 | |
|---|
| 48 | initialized = true; |
|---|
| 49 | } |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | // ---------------------------- |
|---|
| 53 | // disabled advices : |
|---|
| 54 | |
|---|
| 55 | inline AW_awar *get_disabled_advices() { return advice_root->awar_string(AWAR_ADVICE_DISABLED, ""); } |
|---|
| 56 | inline AW_awar *get_shown_advices() { return advice_root->awar_string(AWAR_ADVICE_SHOWN, ""); } |
|---|
| 57 | |
|---|
| 58 | inline int advice_id_offset(const char* id, const char *idlist) { |
|---|
| 59 | const char *found = strstr(idlist, GBS_global_string(";%s;", id)); |
|---|
| 60 | return found ? found-idlist : -1; |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | inline bool advice_id_is_set(const char* id, const char *idlist) { return advice_id_offset(id, idlist) >= 0; } |
|---|
| 64 | |
|---|
| 65 | inline void set_advice_id(const char* id, AW_awar *var) { |
|---|
| 66 | const char *idlist = var->read_char_pntr(); |
|---|
| 67 | if (!advice_id_is_set(id, idlist)) { |
|---|
| 68 | if (idlist[0]) var->write_string(GBS_global_string("%s%s;", idlist, id)); |
|---|
| 69 | else var->write_string(GBS_global_string(";%s;", id)); |
|---|
| 70 | } |
|---|
| 71 | } |
|---|
| 72 | inline void remove_advice_id(const char* id, AW_awar *var) { |
|---|
| 73 | const char *idlist = var->read_char_pntr(); |
|---|
| 74 | if (advice_id_is_set(id, idlist)) { |
|---|
| 75 | int offset = advice_id_offset(id, idlist); |
|---|
| 76 | if (offset >= 0) { |
|---|
| 77 | char *newList = NULp; |
|---|
| 78 | if (offset == 0) { |
|---|
| 79 | newList = strdup(idlist+strlen(id)+1); |
|---|
| 80 | } |
|---|
| 81 | else { |
|---|
| 82 | newList = strdup(idlist); |
|---|
| 83 | char *idPos = newList+offset; |
|---|
| 84 | strcpy(idPos, idPos+strlen(id)+1); |
|---|
| 85 | } |
|---|
| 86 | var->write_string(newList); |
|---|
| 87 | free(newList); |
|---|
| 88 | } |
|---|
| 89 | } |
|---|
| 90 | } |
|---|
| 91 | inline void toggle_advice_id(const char* id, AW_awar *var) { |
|---|
| 92 | if (advice_id_is_set(id, var->read_char_pntr())) { |
|---|
| 93 | remove_advice_id(id, var); |
|---|
| 94 | } |
|---|
| 95 | else { |
|---|
| 96 | set_advice_id(id, var); |
|---|
| 97 | } |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | inline bool advice_disabled(const char* id) { return advice_id_is_set(id, get_disabled_advices()->read_char_pntr()); } |
|---|
| 101 | inline void disable_advice(const char* id) { set_advice_id(id, get_disabled_advices()); } |
|---|
| 102 | |
|---|
| 103 | inline bool advice_currently_shown(const char *id) { return advice_id_is_set(id, get_shown_advices()->read_char_pntr()); } |
|---|
| 104 | inline void toggle_advice_shown(const char *id) { toggle_advice_id(id, get_shown_advices()); } |
|---|
| 105 | |
|---|
| 106 | // ------------------------------------------- |
|---|
| 107 | |
|---|
| 108 | static void advice_close_cb(AW_window *aww, const char *id, AW_Advice_Type type) { |
|---|
| 109 | int understood = advice_root->awar(AWAR_ADVICE_UNDERSTOOD)->read_int(); |
|---|
| 110 | |
|---|
| 111 | // switch to 'not understood'. Has to be checked by user for each advice. |
|---|
| 112 | advice_root->awar(AWAR_ADVICE_UNDERSTOOD)->write_int(0); |
|---|
| 113 | aww->hide(); |
|---|
| 114 | |
|---|
| 115 | toggle_advice_shown(id); |
|---|
| 116 | |
|---|
| 117 | if (understood) { |
|---|
| 118 | disable_advice(id); |
|---|
| 119 | if (type & AW_ADVICE_TOGGLE) { |
|---|
| 120 | static bool in_advice = false; |
|---|
| 121 | if (!in_advice) { |
|---|
| 122 | in_advice = true; |
|---|
| 123 | AW_advice("You have disabled an advice.\n" |
|---|
| 124 | "In order to disable it PERMANENTLY, save properties.", AW_ADVICE_TOGGLE, "Advice disabled", "advice.hlp"); |
|---|
| 125 | in_advice = false; |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | } |
|---|
| 129 | } |
|---|
| 130 | static void advice_hide_and_close_cb(AW_window *aww, const char *id, AW_Advice_Type type) { |
|---|
| 131 | advice_root->awar(AWAR_ADVICE_UNDERSTOOD)->write_int(1); |
|---|
| 132 | advice_close_cb(aww, id, type); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | // ------------------------------------------- |
|---|
| 136 | |
|---|
| 137 | void AW_reactivate_all_advices(AW_window*) { |
|---|
| 138 | AW_awar *awar_disabled = get_disabled_advices(); |
|---|
| 139 | |
|---|
| 140 | char *disabled = awar_disabled->read_string(); |
|---|
| 141 | char *nosemi = GBS_string_eval(disabled, ";="); |
|---|
| 142 | int entries = strlen(disabled)-strlen(nosemi); |
|---|
| 143 | const char *msg = "No advices were disabled yet."; |
|---|
| 144 | |
|---|
| 145 | if (entries>0) { |
|---|
| 146 | aw_assert(entries>1); |
|---|
| 147 | entries--; |
|---|
| 148 | msg = GBS_global_string("Reactivated %i advices (for this session)\n" |
|---|
| 149 | "To reactivate them for future sessions, save properties.", |
|---|
| 150 | entries); |
|---|
| 151 | } |
|---|
| 152 | aw_message(msg); |
|---|
| 153 | |
|---|
| 154 | free(nosemi); |
|---|
| 155 | free(disabled); |
|---|
| 156 | |
|---|
| 157 | awar_disabled->write_string(""); |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | void AW_advice(const char *message, AW_Advice_Type type, const char *title, const char *corresponding_help) { |
|---|
| 161 | aw_assert(initialized); |
|---|
| 162 | size_t message_len = strlen(message); aw_assert(message_len>0); |
|---|
| 163 | long crc32 = GB_checksum(message, message_len, true, " .,-!"); // checksum is used to test if advice was shown |
|---|
| 164 | char *advice_id = GBS_global_string_copy("%lx", crc32); // do not delete (bound to cbs) |
|---|
| 165 | |
|---|
| 166 | bool show_advice = !advice_disabled(advice_id) && !advice_currently_shown(advice_id); |
|---|
| 167 | |
|---|
| 168 | if (show_advice) { |
|---|
| 169 | AW_awar *understood = advice_root->awar(AWAR_ADVICE_UNDERSTOOD); |
|---|
| 170 | understood->write_int(0); |
|---|
| 171 | |
|---|
| 172 | if (corresponding_help) type = AW_Advice_Type(type|AW_ADVICE_HELP); |
|---|
| 173 | #if defined(ASSERTION_USED) |
|---|
| 174 | else aw_assert((type & AW_ADVICE_HELP) == 0); |
|---|
| 175 | #endif // ASSERTION_USED |
|---|
| 176 | |
|---|
| 177 | AW_window_simple *aws = new AW_window_simple; // do not delete (ARB will crash) -- maybe reuse window for all advices? |
|---|
| 178 | |
|---|
| 179 | if (!title) title = "Please read carefully"; |
|---|
| 180 | aws->init(advice_root, "advice", GBS_global_string("ARB: %s", title)); |
|---|
| 181 | aws->load_xfig("window/advice.fig"); |
|---|
| 182 | |
|---|
| 183 | bool has_help = type & AW_ADVICE_HELP; |
|---|
| 184 | bool help_pops_up = false; |
|---|
| 185 | |
|---|
| 186 | if (has_help) { |
|---|
| 187 | aws->callback(makeHelpCallback(corresponding_help)); |
|---|
| 188 | aws->at("help"); |
|---|
| 189 | aws->create_button(NULp, "HELP", "H"); |
|---|
| 190 | |
|---|
| 191 | if (type & AW_ADVICE_HELP_POPUP) help_pops_up = true; |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | aws->at("advice"); |
|---|
| 195 | aws->create_text_field(AWAR_ADVICE_TEXT); |
|---|
| 196 | |
|---|
| 197 | advice_root->awar(AWAR_ADVICE_TEXT) |
|---|
| 198 | ->write_string(has_help && !help_pops_up |
|---|
| 199 | ? GBS_global_string("%s\n\nPlease refer to 'HELP' for more info.", message) |
|---|
| 200 | : message); |
|---|
| 201 | |
|---|
| 202 | if (help_pops_up) AW_help_popup(NULp, corresponding_help); |
|---|
| 203 | |
|---|
| 204 | if (type & AW_ADVICE_TOGGLE) { |
|---|
| 205 | aws->label("Do not advice me again"); |
|---|
| 206 | aws->at("understood"); |
|---|
| 207 | aws->create_toggle(AWAR_ADVICE_UNDERSTOOD); |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | aws->at("ok"); |
|---|
| 211 | if (type & AW_ADVICE_TOGGLE) { |
|---|
| 212 | aws->callback(makeWindowCallback(advice_close_cb, advice_id, type)); |
|---|
| 213 | aws->create_button(NULp, "OK", "O"); |
|---|
| 214 | } |
|---|
| 215 | else { |
|---|
| 216 | aws->callback(makeWindowCallback(advice_hide_and_close_cb, advice_id, type)); |
|---|
| 217 | aws->create_autosize_button(NULp, "I understand", "O", 2); |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | aws->window_fit(); |
|---|
| 221 | aws->allow_delete_window(false); // disable closing the window via 'X'-button |
|---|
| 222 | aws->show(); |
|---|
| 223 | toggle_advice_shown(advice_id); |
|---|
| 224 | } |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | // -------------------------------------------------------------------------------- |
|---|
| 228 | |
|---|
| 229 | #ifdef UNIT_TESTS |
|---|
| 230 | #include <test_unit.h> |
|---|
| 231 | |
|---|
| 232 | void TEST_advice_id_awar_handling() { |
|---|
| 233 | GB_shell shell; |
|---|
| 234 | AW_root root("min_ascii.arb", UNITTEST_MOCK); |
|---|
| 235 | init_Advisor(&root); |
|---|
| 236 | |
|---|
| 237 | const char *one = "one"; |
|---|
| 238 | const char *two = "second"; |
|---|
| 239 | |
|---|
| 240 | TEST_REJECT(advice_disabled(one)); |
|---|
| 241 | TEST_REJECT(advice_disabled(two)); |
|---|
| 242 | |
|---|
| 243 | disable_advice(one); |
|---|
| 244 | TEST_EXPECT(advice_disabled(one)); |
|---|
| 245 | TEST_REJECT(advice_disabled(two)); |
|---|
| 246 | |
|---|
| 247 | disable_advice(two); |
|---|
| 248 | TEST_EXPECT(advice_disabled(one)); |
|---|
| 249 | TEST_EXPECT(advice_disabled(two)); |
|---|
| 250 | |
|---|
| 251 | |
|---|
| 252 | TEST_REJECT(advice_currently_shown(one)); |
|---|
| 253 | TEST_REJECT(advice_currently_shown(two)); |
|---|
| 254 | |
|---|
| 255 | toggle_advice_shown(two); |
|---|
| 256 | TEST_REJECT(advice_currently_shown(one)); |
|---|
| 257 | TEST_EXPECT(advice_currently_shown(two)); |
|---|
| 258 | |
|---|
| 259 | toggle_advice_shown(one); |
|---|
| 260 | TEST_EXPECT(advice_currently_shown(one)); |
|---|
| 261 | TEST_EXPECT(advice_currently_shown(two)); |
|---|
| 262 | |
|---|
| 263 | toggle_advice_shown(two); |
|---|
| 264 | TEST_EXPECT(advice_currently_shown(one)); |
|---|
| 265 | TEST_REJECT(advice_currently_shown(two)); |
|---|
| 266 | |
|---|
| 267 | toggle_advice_shown(one); |
|---|
| 268 | TEST_REJECT(advice_currently_shown(one)); |
|---|
| 269 | TEST_REJECT(advice_currently_shown(two)); |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | void TEST_another_AW_root() { |
|---|
| 273 | GB_shell shell; |
|---|
| 274 | AW_root("min_ascii.arb", UNITTEST_MOCK); |
|---|
| 275 | } |
|---|
| 276 | TEST_PUBLISH(TEST_another_AW_root); |
|---|
| 277 | |
|---|
| 278 | #endif // UNIT_TESTS |
|---|