Gui_messages.cpp 13 KB

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