1 /* Gui_messages.cpp 2 * 3 * Copyright (C) 1992-2018,2020,2021 Paul Boersma, 4 * 2008 Stefan de Konink, 2010 Franz Brausse, 2013 Tom Naughton 5 * 6 * This code is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or (at 9 * your option) any later version. 10 * 11 * This code is distributed in the hope that it will be useful, but 12 * WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 * See the GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this work. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include <time.h> 21 22 #include <assert.h> 23 #ifdef _WIN32 24 #include <windows.h> 25 #endif 26 27 #include "melder.h" 28 #include "Graphics.h" 29 #include "Gui.h" 30 31 /********** Exported variable. **********/ 32 33 static GuiWindow Melder_topShell; 34 35 /********** PROGRESS **********/ 36 37 static bool theProgressCancelled = false; 38 39 static bool waitWhileProgress (double progress, conststring32 message, GuiDialog dia, 40 GuiProgressBar scale, GuiLabel label1, GuiLabel label2, GuiButton cancelButton) 41 { 42 #if gtk 43 (void) cancelButton; // the Interrupt button has its own callback 44 // Wait for all pending events to be processed. If anybody knows how to inspect GTK's 45 // event queue for specific events, dump the code here, please. 46 // Until then, the button click attaches a g_object data key named "pressed" to the cancelButton 47 // which this function reads out in order to tell whether interruption has occurred 48 while (gtk_events_pending ()) { 49 trace (U"event pending"); 50 gtk_main_iteration (); 51 } 52 #elif motif 53 XEvent event; 54 while (PeekMessage (& event, 0, 0, 0, PM_REMOVE)) { 55 if (event. message == WM_KEYDOWN) { 56 /* 57 * Ignore all key-down messages, except Escape. 58 */ 59 if (LOWORD (event. wParam) == VK_ESCAPE) { 60 XtUnmanageChild (dia -> d_widget); 61 return false; // don't continue 62 } 63 } else if (event. message == WM_LBUTTONDOWN) { 64 /* 65 * Ignore all mouse-down messages, except click in Interrupt button. 66 */ 67 GuiObject me = (GuiObject) GetWindowLongPtr (event. hwnd, GWLP_USERDATA); 68 if (me == cancelButton -> d_widget) { 69 XtUnmanageChild (dia -> d_widget); 70 return false; // don't continue 71 } 72 } else if (event. message != WM_SYSKEYDOWN) { 73 /* 74 * Process paint messages etc. 75 */ 76 DispatchMessage (& event); 77 } 78 } 79 #elif cocoa 80 (void) cancelButton; // the Interrupt button has its own callback 81 while (NSEvent *nsEvent = [NSApp 82 nextEventMatchingMask: NSAnyEventMask 83 untilDate: [NSDate distantPast] 84 inMode: NSDefaultRunLoopMode 85 dequeue: YES 86 ]) 87 { 88 NSUInteger nsEventType = [nsEvent type]; 89 if (nsEventType == NSKeyDown) 90 NSBeep (); 91 [[nsEvent window] sendEvent: nsEvent]; 92 } 93 #endif 94 if (progress >= 1.0) { 95 GuiThing_hide (dia); 96 } else { 97 if (progress <= 0.0) 98 progress = 0.0; 99 GuiThing_show (dia); // TODO: prevent raising to the front 100 const char32 *newline = str32chr (message, U'\n'); 101 if (newline) { 102 static MelderString buffer; 103 MelderString_copy (& buffer, message); 104 buffer.string [newline - message] = U'\0'; 105 GuiLabel_setText (label1, buffer.string); 106 buffer.string [newline - message] = U'\n'; 107 GuiLabel_setText (label2, buffer.string + (newline - message) + 1); 108 } else { 109 GuiLabel_setText (label1, message); 110 GuiLabel_setText (label2, U""); 111 } 112 #if gtk 113 trace (U"update the progress bar"); 114 GuiProgressBar_setValue (scale, progress); 115 while (gtk_events_pending ()) { 116 trace (U"event pending"); 117 gtk_main_iteration (); 118 } 119 trace (U"check whether the cancel button has the \"pressed\" key set"); 120 if (g_object_steal_data (G_OBJECT (cancelButton -> d_widget), "pressed")) { 121 trace (U"the cancel button has been pressed"); 122 return false; // don't continue 123 } 124 #elif motif 125 GuiProgressBar_setValue (scale, progress); 126 GdiFlush (); 127 #elif cocoa 128 GuiProgressBar_setValue (scale, progress); 129 //[scale -> d_cocoaProgressBar displayIfNeeded]; 130 if (theProgressCancelled) { 131 theProgressCancelled = false; 132 return false; 133 } 134 #endif 135 } 136 trace (U"continue"); 137 return true; 138 } 139 140 static GuiButton theProgressCancelButton = nullptr; 141 142 #if gtk || cocoa 143 static void progress_dia_close (Thing /* boss */) { 144 theProgressCancelled = true; 145 #if gtk 146 g_object_set_data (G_OBJECT (theProgressCancelButton -> d_widget), "pressed", (gpointer) 1); 147 #endif 148 } 149 static void progress_cancel_btn_press (Thing /* boss */, GuiButtonEvent /* event */) { 150 theProgressCancelled = true; ExecOpenIndices(ResultRelInfo * resultRelInfo,bool speculative)151 #if gtk 152 g_object_set_data (G_OBJECT (theProgressCancelButton -> d_widget), "pressed", (gpointer) 1); 153 #endif 154 } 155 #endif 156 157 static void _Melder_dia_init (GuiDialog *dia, GuiProgressBar *scale, GuiLabel *label1, GuiLabel *label2, GuiButton *cancelButton, bool hasMonitor) { 158 trace (U"creating the dialog"); 159 *dia = GuiDialog_create (Melder_topShell, 200, 100, 400, hasMonitor ? 430 : 200, U"Work in progress", 160 #if gtk || cocoa 161 progress_dia_close, nullptr, 162 #else 163 nullptr, nullptr, 164 #endif 165 0); 166 167 trace (U"creating the labels"); 168 *label1 = GuiLabel_createShown (*dia, 3, 403, 0, Gui_LABEL_HEIGHT, U"label1", 0); 169 *label2 = GuiLabel_createShown (*dia, 3, 403, 30, 30 + Gui_LABEL_HEIGHT, U"label2", 0); 170 171 trace (U"creating the scale"); 172 *scale = GuiProgressBar_createShown (*dia, 3, -3, 70, 110, 0); 173 174 trace (U"creating the cancel button"); 175 *cancelButton = GuiButton_createShown (*dia, 0, 400, 170, 170 + Gui_PUSHBUTTON_HEIGHT, 176 U"Interrupt", 177 #if gtk 178 progress_cancel_btn_press, nullptr, 179 #elif cocoa 180 progress_cancel_btn_press, nullptr, 181 #else 182 nullptr, nullptr, 183 #endif 184 0); 185 trace (U"end"); 186 } 187 188 static void gui_progress (double progress, conststring32 message) { 189 static clock_t lastTime; 190 static GuiDialog dia = nullptr; 191 static GuiProgressBar scale = nullptr; 192 static GuiLabel label1 = nullptr, label2 = nullptr; 193 clock_t now = clock (); 194 if (progress <= 0.0 || progress >= 1.0 || 195 now - lastTime > CLOCKS_PER_SEC / 4) // this time step must be much longer than the null-event waiting time 196 { 197 if (! dia) 198 _Melder_dia_init (& dia, & scale, & label1, & label2, & theProgressCancelButton, false); 199 if (! waitWhileProgress (progress, message, dia, scale, label1, label2, theProgressCancelButton)) 200 Melder_throw (U"Interrupted!"); 201 lastTime = now; 202 } 203 } 204 205 static autoGraphics graphics; 206 207 static void gui_drawingarea_cb_expose (Thing /* boss */, GuiDrawingArea_ExposeEvent /* event */) { 208 if (! graphics) 209 return; 210 Graphics_play (graphics.get(), graphics.get()); 211 } 212 213 static void * gui_monitor (double progress, conststring32 message) { 214 static clock_t lastTime; 215 static GuiDialog dia = nullptr; 216 static GuiProgressBar scale = nullptr; 217 static GuiDrawingArea drawingArea = nullptr; 218 static GuiButton cancelButton = nullptr; 219 static GuiLabel label1 = nullptr, label2 = nullptr; 220 clock_t now = clock (); 221 if (progress <= 0.0 || progress >= 1.0 || 222 now - lastTime > CLOCKS_PER_SEC / 4) // this time step must be much longer than the null-event waiting time 223 { 224 if (! dia) { 225 _Melder_dia_init (& dia, & scale, & label1, & label2, & cancelButton, true); ExecCloseIndices(ResultRelInfo * resultRelInfo)226 drawingArea = GuiDrawingArea_createShown (dia, 0, 400, 230, 430, 227 gui_drawingarea_cb_expose, nullptr, nullptr, nullptr, nullptr, 0); 228 GuiThing_show (dia); 229 graphics = Graphics_create_xmdrawingarea (drawingArea); 230 } 231 if (progress <= 0.0 && graphics) { 232 Graphics_clearRecording (graphics.get()); 233 Graphics_startRecording (graphics.get()); 234 Graphics_clearWs (graphics.get()); 235 } 236 if (! waitWhileProgress (progress, message, dia, scale, label1, label2, cancelButton)) 237 Melder_throw (U"Interrupted!"); 238 lastTime = now; 239 if (progress == 0.0) 240 return graphics.get(); 241 } 242 return nullptr; 243 } 244 245 #if cocoa 246 static void mac_message (NSAlertStyle macAlertType, conststring32 message32) { 247 static char16 message16 [4000]; 248 const integer messageLength = str32len (message32); 249 uinteger j = 0; 250 for (int i = 0; i < messageLength && j <= 4000 - 3; i ++) { 251 char32 kar = message32 [i]; 252 if (kar <= 0x00'FFFF) { 253 message16 [j ++] = (char16) kar; 254 } else if (kar <= 0x10'FFFF) { 255 kar -= 0x01'0000; 256 message16 [j ++] = (char16) (0x00'D800 | (kar >> 10)); 257 message16 [j ++] = (char16) (0x00'DC00 | (kar & 0x00'03FF)); 258 } 259 } 260 message16 [j] = u'\0'; // append null byte because we are going to search this string 261 262 /* 263 Split up the message between header (will appear in bold) and rest. 264 The split is done at the first line break, except if the first line ends in a colon, 265 in which case the split is done at the second line break. 266 */ 267 const char16 *lineBreak = & message16 [0]; 268 for (; *lineBreak != u'\0'; lineBreak ++) 269 if (*lineBreak == u'\n') 270 break; 271 if (*lineBreak == u'\n' && lineBreak - message16 > 0 && lineBreak [-1] == u':') 272 for (lineBreak ++; *lineBreak != u'\0'; lineBreak ++) ExecInsertIndexTuples(TupleTableSlot * slot,EState * estate,bool noDupErr,bool * specConflict,List * arbiterIndexes)273 if (*lineBreak == u'\n') 274 break; 275 uinteger lengthOfFirstSentence = (uinteger) (lineBreak - message16); 276 /* 277 Create an alert dialog with an icon that is appropriate for the level. 278 */ 279 NSAlert *alert = [[NSAlert alloc] init]; 280 [alert setAlertStyle: macAlertType]; 281 if (macAlertType == NSCriticalAlertStyle) 282 [alert addButtonWithTitle: @"Crash"]; 283 /* 284 Add the header in bold. 285 */ 286 NSString *header = [[NSString alloc] initWithCharacters: (const unichar *) & message16 [0] length: lengthOfFirstSentence]; // note: init can change the object pointer! 287 if (header) { // make this very safe, because we can be at error time or at fatal time 288 [alert setMessageText: header]; 289 [header release]; 290 } 291 /* 292 Add the rest of the message in small type. 293 */ 294 if (lengthOfFirstSentence < j) { 295 NSString *rest = [[NSString alloc] initWithCharacters: (const unichar *) & lineBreak [1] length: j - 1 - lengthOfFirstSentence]; 296 if (rest) { // make this very safe, because we can be at error time or at crash time 297 [alert setInformativeText: rest]; 298 [rest release]; 299 } 300 } 301 /* 302 Display the alert dialog and synchronously wait for the user to click OK. 303 But: it is not impossible that the program crashes during `runModal`, 304 especially if `runModal` is called at expose time. 305 Write the message to stdout just in case. 306 */ 307 Melder_casual (message32); 308 [alert runModal]; 309 [alert release]; 310 } 311 #endif 312 313 #define theMessageFund_SIZE 100'000 314 static char * theMessageFund = nullptr; 315 316 static void gui_fatal (conststring32 message) { 317 free (theMessageFund); 318 #if gtk 319 GuiObject dialog = gtk_message_dialog_new (GTK_WINDOW (Melder_topShell -> d_gtkWindow), GTK_DIALOG_DESTROY_WITH_PARENT, 320 GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, "%s", Melder_peek32to8 (message)); 321 gtk_dialog_add_buttons (GTK_DIALOG (dialog), "Crash", GTK_RESPONSE_OK, nullptr); 322 gtk_dialog_run (GTK_DIALOG (dialog)); 323 gtk_widget_destroy (GTK_WIDGET (dialog)); 324 #elif motif 325 MessageBox (nullptr, Melder_peek32toW (message), L"Crashing bug", MB_OK | MB_TOPMOST | MB_ICONSTOP); 326 #elif cocoa 327 mac_message (NSCriticalAlertStyle, message); 328 SysError (11); 329 #endif 330 } 331 332 static void gui_error (conststring32 message) { 333 const bool memoryIsLow = str32str (message, U"Out of memory"); 334 if (memoryIsLow) 335 free (theMessageFund); 336 #if gtk 337 trace (U"create dialog"); 338 GuiObject dialog = gtk_message_dialog_new (GTK_WINDOW (Melder_topShell -> d_gtkWindow), GTK_DIALOG_DESTROY_WITH_PARENT, 339 GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", Melder_peek32to8 (message)); 340 trace (U"run dialog"); 341 gtk_dialog_run (GTK_DIALOG (dialog)); 342 trace (U"destroy dialog"); 343 gtk_widget_destroy (GTK_WIDGET (dialog)); 344 #elif motif 345 MessageBox (nullptr, Melder_peek32toW (message), L"Message", MB_OK | MB_TOPMOST | MB_ICONWARNING); // or (HWND) XtWindow ((GuiObject) Melder_topShell) 346 #elif cocoa 347 mac_message (NSWarningAlertStyle, message); 348 #endif 349 if (memoryIsLow) { 350 theMessageFund = (char *) malloc (theMessageFund_SIZE); 351 if (! theMessageFund) { 352 #if gtk 353 GuiObject dialog = gtk_message_dialog_new (GTK_WINDOW (Melder_topShell -> d_gtkWindow), GTK_DIALOG_DESTROY_WITH_PARENT, 354 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Praat is very low on memory.\nSave your work and quit Praat.\nIf you don't do that, Praat may crash."); 355 gtk_dialog_run (GTK_DIALOG (dialog)); 356 gtk_widget_destroy (GTK_WIDGET (dialog)); 357 #elif motif 358 MessageBox (nullptr, L"Praat is very low on memory.\nSave your work and quit Praat.\nIf you don't do that, Praat may crash.", L"Message", MB_OK); 359 #elif cocoa 360 mac_message (NSCriticalAlertStyle, U"Praat is very low on memory.\nSave your work and quit Praat.\nIf you don't do that, Praat may crash."); 361 #endif 362 } 363 } 364 } 365 366 static void gui_warning (conststring32 message) { 367 #if gtk 368 GuiObject dialog = gtk_message_dialog_new (GTK_WINDOW (Melder_topShell -> d_gtkWindow), GTK_DIALOG_DESTROY_WITH_PARENT, 369 GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", Melder_peek32to8 (message)); 370 gtk_dialog_run (GTK_DIALOG (dialog)); 371 gtk_widget_destroy (GTK_WIDGET (dialog)); 372 #elif motif 373 MessageBox (nullptr, Melder_peek32toW (message), L"Warning", MB_OK | MB_TOPMOST | MB_ICONINFORMATION); 374 #elif cocoa 375 mac_message (NSInformationalAlertStyle, message); 376 #endif 377 } 378 379 void Gui_injectMessageProcs (GuiWindow parent) { 380 theMessageFund = (char *) malloc (theMessageFund_SIZE); 381 assert (theMessageFund); 382 Melder_topShell = parent; 383 Melder_setCrashProc (gui_fatal); 384 Melder_setErrorProc (gui_error); 385 Melder_setWarningProc (gui_warning); 386 Melder_setProgressProc (gui_progress); 387 Melder_setMonitorProc (gui_monitor); 388 } 389 390 /* End of file Gui_messages.cpp */ 391