Commit | Line | Data |
---|---|---|
69695f33 MW |
1 | /* Copyright (c) 2006 by Arkkra Enterprises */ |
2 | /* All rights reserved */ | |
3 | ||
4 | // This file includes methods related to the "Run" menu on the main toolbar. | |
5 | ||
6 | #include "Run.H" | |
7 | #include "Preferences.H" | |
8 | #include "globals.H" | |
9 | #include "Config.H" | |
10 | #include "defines.h" | |
11 | #include <FL/fl_ask.H> | |
12 | #include <FL/filename.H> | |
13 | #include <string.h> | |
14 | #include <stdlib.h> | |
15 | #include <unistd.h> | |
16 | #include <fcntl.h> | |
17 | #include <ctype.h> | |
18 | #include <sys/stat.h> | |
19 | #ifdef OS_LIKE_UNIX | |
20 | #include <sys/wait.h> | |
21 | #include <errno.h> | |
22 | #endif | |
23 | ||
24 | // Message for when Mup fails, but we can't figure out why. | |
25 | const char * Unknown_Mup_failure = "Mup failed. Reason unknown."; | |
26 | ||
27 | // Describe use of numbers passed to -x option | |
28 | #define EXTRACT_NUM_DESCRIPTION "0 is used for pickup measure.\n" \ | |
29 | "Negative numbers are used to specify\n" \ | |
30 | "number of measures from the end.\n" | |
31 | ||
32 | // Tooltip is the same for all macro entries | |
33 | const char * macro_definition_tip = | |
34 | "Your Mup input can use macros to vary characteristics\n" | |
35 | "of the output for different runs.\n" | |
36 | "Enter the name of a macro you want to define,\n" | |
37 | "optionally followed by an equals sign and macro definition.\n" | |
38 | "Macro names must consist of upper case letters,\n" | |
39 | "numbers, and underscores, starting with an upper case letter."; | |
40 | ||
41 | //------ Class for user to set parameters to pass to Mup | |
42 | ||
43 | Run_parameters_dialog::Run_parameters_dialog(void) | |
44 | : Fl_Double_Window(0, 0, 700, 350, "Mup Options") | |
45 | { | |
46 | enable_combine_p = new Fl_Check_Button(20, 20, 165, 30, | |
47 | "Enable Auto Multirest"); | |
48 | enable_combine_p->tooltip("Set whether to automatically combine\n" | |
49 | "consecutive measures of rests into a multirest."); | |
50 | enable_combine_p->callback(combine_cb, this); | |
51 | enable_combine_p->when(FL_WHEN_CHANGED); | |
52 | ||
53 | rest_combine_p = new Positive_Int_Input(370, 20, 40, 30, | |
54 | "Min Measures to Combine"); | |
55 | rest_combine_p->tooltip("Minimum number of consecutive measures\n" | |
56 | "of rest that will be combined into a multirest."); | |
57 | // Only becomes active if auto-multirest combine becomes active | |
58 | rest_combine_p->deactivate(); | |
59 | ||
60 | // -p firstpage | |
61 | first_page_p = new Positive_Int_Input(300, 60, 50, 30, "First Page's Page Number"); | |
62 | first_page_p->tooltip("Set the page number to use\n" | |
63 | "for the first page of music output."); | |
64 | ||
65 | // -o pagelist | |
66 | pages_p = new Fl_Group(30, 100, 380, 70, "Pages to Display"); | |
67 | pages_p->box(FL_ENGRAVED_BOX); | |
68 | pages_p->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE); | |
69 | ||
70 | all_p = new Fl_Check_Button(170, 105, 55, 20, "All"); | |
71 | all_p->type(FL_RADIO_BUTTON); | |
72 | all_p->tooltip("You can restrict to displaying only a subset\n" | |
73 | "of the pages of music, but this shows all pages."); | |
74 | all_p->callback(selected_pages_cb, this); | |
75 | all_p->when(FL_WHEN_CHANGED); | |
76 | ||
77 | odd_p = new Fl_Check_Button(245, 105, 60, 20, "Odd"); | |
78 | odd_p->type(FL_RADIO_BUTTON); | |
79 | odd_p->tooltip("This restricts to displaying only\n" | |
80 | "odd numbered pages. This is generally\n" | |
81 | "most useful for printing to a\n" | |
82 | "single-sided printer."); | |
83 | odd_p->callback(selected_pages_cb, this); | |
84 | odd_p->when(FL_WHEN_CHANGED); | |
85 | ||
86 | even_p = new Fl_Check_Button(320, 105, 70, 20, "Even"); | |
87 | even_p->type(FL_RADIO_BUTTON); | |
88 | even_p->tooltip("This restricts to displaying only\n" | |
89 | "even numbered pages. This is generally\n" | |
90 | "most useful for printing to a\n" | |
91 | "single-sided printer."); | |
92 | even_p->callback(selected_pages_cb, this); | |
93 | even_p->when(FL_WHEN_CHANGED); | |
94 | ||
95 | selected_p = new Fl_Check_Button(50, 135, 85, 20, "Selected:"); | |
96 | selected_p->tooltip("This restricts to displaying only\n" | |
97 | "the pages you list in the blank to the right."); | |
98 | selected_p->type(FL_RADIO_BUTTON); | |
99 | selected_p->callback(selected_pages_cb, this); | |
100 | selected_p->when(FL_WHEN_CHANGED); | |
101 | ||
102 | page_list_p = new Fl_Input(150, 130, 230, 30, ""); | |
103 | page_list_p->tooltip("List the specific pages you want displayed.\n" | |
104 | "You can list individual pages separated by commas,\n" | |
105 | "and/or ranges of pages separated by dashes.\n" | |
106 | "Pages may be listed more than once and in any order."); | |
107 | page_list_p->deactivate(); | |
108 | saved_page_list = 0; | |
109 | pages_p->end(); | |
110 | ||
111 | // -s stafflist | |
112 | staff_list_p = new Fl_Input(150, 180, 260, 30, "Staffs to Display/Play"); | |
113 | staff_list_p->tooltip("If you wish to display or play only a subset\n" | |
114 | "of the staffs, or voices on a staff, list them here.\n" | |
115 | "Staffs are specified by comma-separated numbers\n" | |
116 | "or ranges, like 1,4 or 1-3,5-6. Staff numbers can be\n" | |
117 | "followed by v1, v2, or v3 to limit to a single voice.\n"); | |
118 | saved_staff_list = 0; | |
119 | ||
120 | // -x extract list | |
121 | extract_begin_p = new Int_Input(150, 220, 95, 30, "Extract Measures"); | |
122 | extract_begin_p->tooltip("If you wish to display or play only selected\n" | |
123 | "measures, enter the starting measure here.\n" | |
124 | EXTRACT_NUM_DESCRIPTION); | |
125 | extract_begin_p->callback(extract_cb, this); | |
126 | extract_begin_p->when(FL_WHEN_CHANGED); | |
127 | extract_end_p = new Int_Input(315, 220, 95, 30, "through"); | |
128 | extract_end_p->tooltip("If you wish to display or play only selected\n" | |
129 | "measures, enter the ending measure here.\n" | |
130 | EXTRACT_NUM_DESCRIPTION); | |
131 | extract_end_p->deactivate(); | |
132 | ||
133 | // Macros | |
134 | macros_group_p = new Fl_Group(430, 20, 250, 40 + MAX_MACROS * 40, "Macro Definitions"); | |
135 | macros_group_p->box(FL_ENGRAVED_BOX); | |
136 | macros_group_p->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE); | |
137 | int m; | |
138 | for (m = 0; m < MAX_MACROS; m++) { | |
139 | ||
140 | macro_definitions_p[m] = new Fl_Input(450, 50 + m * 40, 210, 30, ""); | |
141 | macro_definitions_p[m]->tooltip(macro_definition_tip); | |
142 | ||
143 | saved_macro_definitions[m] = 0; | |
144 | } | |
145 | macros_group_p->end(); | |
146 | ||
147 | save_p = new Fl_Return_Button(100, h() - 50, 80, 30, "Save"); | |
148 | save_p->callback(Save_cb, this); | |
149 | save_p->when(FL_WHEN_RELEASE); | |
150 | ||
151 | clear_form_p = new Fl_Button((w() - 150) / 2, h() - 50, 150, 30, | |
152 | "Clear Options"); | |
153 | clear_form_p->callback(clear_form_cb, this); | |
154 | clear_form_p->when(FL_WHEN_RELEASE); | |
155 | ||
156 | cancel_p = new Fl_Button(w() - 180, h() - 50, 80, 30, "Cancel"); | |
157 | cancel_p->shortcut(FL_Escape); | |
158 | cancel_p->callback(Cancel_cb, this); | |
159 | cancel_p->when(FL_WHEN_RELEASE); | |
160 | ||
161 | // Set everything to default values | |
162 | clear_form(); | |
163 | ||
164 | // Arrange to clean up all the new-ed widgets in destructor. | |
165 | end(); | |
166 | ||
167 | // Arrange for window manager closes to do Cancel. | |
168 | callback(Cancel_cb, this); | |
169 | when(FL_WHEN_NEVER); | |
170 | } | |
171 | ||
172 | ||
173 | Run_parameters_dialog::~Run_parameters_dialog(void) | |
174 | { | |
175 | int m; | |
176 | for (m = 0; m < MAX_MACROS; m++) { | |
177 | if (saved_macro_definitions[m] != 0) { | |
178 | delete saved_macro_definitions[m]; | |
179 | } | |
180 | } | |
181 | } | |
182 | ||
183 | ||
184 | //---- Callback for when user changes enablement of rest combining, | |
185 | // to gray or ungray the field for how many measures to combine. | |
186 | ||
187 | CALL_BACK(Run_parameters_dialog, combine) | |
188 | { | |
189 | if (enable_combine_p->value()) { | |
190 | rest_combine_p->activate(); | |
191 | } | |
192 | else { | |
193 | rest_combine_p->value(""); | |
194 | rest_combine_p->deactivate(); | |
195 | } | |
196 | } | |
197 | ||
198 | ||
199 | //---- Callback for when user changes setting of start of measure extraction, | |
200 | // to gray or ungray the field for ending measure of extraction. | |
201 | ||
202 | CALL_BACK(Run_parameters_dialog, extract) | |
203 | { | |
204 | if (extract_begin_p->size() > 0) { | |
205 | extract_end_p->activate(); | |
206 | } | |
207 | else { | |
208 | extract_end_p->value(""); | |
209 | extract_end_p->deactivate(); | |
210 | } | |
211 | } | |
212 | ||
213 | //---- Callback for when user changes setting of page selection, | |
214 | // to gray or ungray the field for listing the specific pages to display | |
215 | ||
216 | CALL_BACK(Run_parameters_dialog, selected_pages) | |
217 | { | |
218 | page_list_p->value(""); | |
219 | if (selected_p->value() == 1) { | |
220 | page_list_p->activate(); | |
221 | } | |
222 | else { | |
223 | page_list_p->deactivate(); | |
224 | } | |
225 | } | |
226 | ||
227 | ||
228 | //----Callback for button for clearing the form | |
229 | ||
230 | CALL_BACK(Run_parameters_dialog, clear_form) | |
231 | { | |
232 | // Set everything to default values | |
233 | saved_enable_combine = false; | |
234 | enable_combine_p->value(saved_enable_combine); | |
235 | ||
236 | (void) sprintf(saved_combine_measures, ""); | |
237 | rest_combine_p->value(saved_combine_measures); | |
238 | ||
239 | (void) sprintf(saved_first_page, "%d", MINFIRSTPAGE); | |
240 | first_page_p->value(saved_first_page); | |
241 | ||
242 | if (saved_page_list != 0) { | |
243 | delete saved_page_list; | |
244 | } | |
245 | saved_page_list = new char[1]; | |
246 | saved_page_list[0] = '\0'; | |
247 | page_list_p->value(saved_page_list); | |
248 | all_p->value(1); | |
249 | saved_pages = ALL_PAGES; | |
250 | ||
251 | if (saved_staff_list != 0) { | |
252 | delete saved_staff_list; | |
253 | } | |
254 | saved_staff_list = new char[1]; | |
255 | saved_staff_list[0] = '\0'; | |
256 | staff_list_p->value(saved_staff_list); | |
257 | ||
258 | saved_extract_begin[0] = '\0'; | |
259 | extract_begin_p->value(saved_extract_begin); | |
260 | saved_extract_end[0] = '\0'; | |
261 | extract_end_p->value(saved_extract_end); | |
262 | ||
263 | int m; | |
264 | for (m = 0; m < MAX_MACROS; m++) { | |
265 | macro_definitions_p[m]->value(""); | |
266 | if (saved_macro_definitions[m] != 0) { | |
267 | delete saved_macro_definitions[m]; | |
268 | saved_macro_definitions[m] = 0; | |
269 | } | |
270 | } | |
271 | } | |
272 | ||
273 | ||
274 | //---- callback for when user clicks "Save" on Set Options form | |
275 | ||
276 | CALL_BACK(Run_parameters_dialog, Save) | |
277 | { | |
278 | // -c rest combine option | |
279 | bool error = false; | |
280 | saved_enable_combine = enable_combine_p->value(); | |
281 | int num_meas = (int) atoi(rest_combine_p->value()); | |
282 | if (saved_enable_combine && (num_meas < MINRESTCOMBINE || num_meas > MAXRESTCOMBINE)) { | |
283 | fl_alert("\"Min Measures to Combine\" must be between\n" | |
284 | "%d and %d.", MINRESTCOMBINE, MAXRESTCOMBINE); | |
285 | error = true; | |
286 | } | |
287 | else { | |
288 | (void) strcpy(saved_combine_measures, rest_combine_p->value()); | |
289 | } | |
290 | ||
291 | // -p first page option | |
292 | int page_num = (int) atoi(first_page_p->value()); | |
293 | if (page_num < MINFIRSTPAGE || page_num > MAXFIRSTPAGE) { | |
294 | fl_alert("\"First Page\" number must be between\n" | |
295 | "%d and %d.", MINFIRSTPAGE, MAXFIRSTPAGE); | |
296 | error = true; | |
297 | } | |
298 | else { | |
299 | (void) strcpy(saved_first_page, first_page_p->value()); | |
300 | } | |
301 | ||
302 | // We don't really fully parse and error check the -o argument, | |
303 | // but make sure it at least is made up of only valid characters. | |
304 | if (page_list_p->size() > 0) { | |
305 | if (strspn(page_list_p->value(), "0123456789,- \t") | |
306 | != page_list_p->size()) { | |
307 | fl_alert("\"Pages to Display\" value is not valid."); | |
308 | error = true; | |
309 | } | |
310 | else { | |
311 | // Free existing, if any, and save new value. | |
312 | if (saved_page_list != 0) { | |
313 | delete saved_page_list; | |
314 | } | |
315 | saved_page_list = new char[page_list_p->size() + 1]; | |
316 | strcpy(saved_page_list, page_list_p->value()); | |
317 | } | |
318 | } | |
319 | else if (saved_page_list[0] != '\0') { | |
320 | // Had a list before but not anymore. | |
321 | // Null out the existing list. | |
322 | delete saved_page_list; | |
323 | saved_page_list = new char[1]; | |
324 | saved_page_list[0] = '\0'; | |
325 | } | |
326 | if (all_p->value() == 1) { | |
327 | saved_pages = ALL_PAGES; | |
328 | } | |
329 | if (odd_p->value() == 1) { | |
330 | saved_pages = ODD_PAGES; | |
331 | } | |
332 | if (even_p->value() == 1) { | |
333 | saved_pages = EVEN_PAGES; | |
334 | } | |
335 | if (selected_p->value() == 1) { | |
336 | saved_pages = SELECTED_PAGES; | |
337 | } | |
338 | if (saved_pages == SELECTED_PAGES && page_list_p->size() == 0) { | |
339 | fl_alert("You did not list which selected pages you want."); | |
340 | error = true; | |
341 | } | |
342 | ||
343 | // Similar for staff list (-s option) | |
344 | if (staff_list_p->size() > 0) { | |
345 | if (strspn(staff_list_p->value(), "0123456789,-v \t") | |
346 | != staff_list_p->size()) { | |
347 | fl_alert("\"Staffs to Display/Play\" is not valid"); | |
348 | error = true; | |
349 | } | |
350 | else { | |
351 | if (saved_staff_list != 0) { | |
352 | delete saved_staff_list; | |
353 | } | |
354 | saved_staff_list = new char[staff_list_p->size() + 1]; | |
355 | strcpy(saved_staff_list, staff_list_p->value()); | |
356 | } | |
357 | } | |
358 | else if (saved_staff_list[0] != '\0') { | |
359 | delete saved_staff_list; | |
360 | saved_staff_list = new char[1]; | |
361 | saved_staff_list[0] = '\0'; | |
362 | } | |
363 | ||
364 | // We can't really error check -x option values. | |
365 | // Widget will have already constrained to integer, | |
366 | // and we have no way to know what range is valid, | |
367 | // since we don't know how many measures the song has. | |
368 | // However, to keep things simple, we have fixed-size array of 8 bytes | |
369 | // for saving value, so can only allow up to 6 digits plus sign. | |
370 | if (abs(atoi(extract_begin_p->value())) >= 1000000) { | |
371 | fl_alert("\"Extract Measures\" start value is out of range."); | |
372 | error = true; | |
373 | } | |
374 | else { | |
375 | (void) strcpy(saved_extract_begin, extract_begin_p->value()); | |
376 | } | |
377 | if (abs(atoi(extract_end_p->value())) >= 1000000) { | |
378 | fl_alert("\"Extract Measures\" end value is out of range."); | |
379 | error = true; | |
380 | } | |
381 | else { | |
382 | (void) strcpy(saved_extract_end, extract_end_p->value()); | |
383 | } | |
384 | ||
385 | // Macros | |
386 | int m; | |
387 | int macsize; | |
388 | bool changed; | |
389 | for (m = 0; m < MAX_MACROS; m++) { | |
390 | changed = false; | |
391 | if ((macsize = macro_definitions_p[m]->size()) > 0) { | |
392 | if (macro_error(macro_definitions_p[m]->value())) { | |
393 | fl_alert("Macro %d has invalid format.", m + 1); | |
394 | error = true; | |
395 | } | |
396 | else if (saved_macro_definitions[m] == 0) { | |
397 | // no macro before, but is one now | |
398 | changed = true; | |
399 | } | |
400 | else if (strcmp(macro_definitions_p[m]->value(), | |
401 | saved_macro_definitions[m]) != 0) { | |
402 | // A different macro value than before | |
403 | delete saved_macro_definitions[m]; | |
404 | changed = true; | |
405 | } | |
406 | if (changed) { | |
407 | saved_macro_definitions[m] = new char[macsize + 1]; | |
408 | (void) strcpy(saved_macro_definitions[m], | |
409 | macro_definitions_p[m]->value()); | |
410 | } | |
411 | } | |
412 | else if (macsize == 0 && saved_macro_definitions[m] != 0) { | |
413 | // Used to be a macro value, but not anymore | |
414 | delete saved_macro_definitions[m]; | |
415 | saved_macro_definitions[m] = 0; | |
416 | } | |
417 | } | |
418 | ||
419 | ||
420 | // If there were user errors, we leave the window up for user to | |
421 | // correct the errors. User can, of course, cancel without correcting, | |
422 | // in which case if they try to run, Mup will complain about the | |
423 | // bad arguments. | |
424 | if ( ! error ) { | |
425 | hide(); | |
426 | } | |
427 | } | |
428 | ||
429 | ||
430 | //---- Callback for when user clicks "Cancel" on Set Options form | |
431 | ||
432 | CALL_BACK(Run_parameters_dialog, Cancel) | |
433 | { | |
434 | // Put back all the previous values | |
435 | enable_combine_p->value(saved_enable_combine); | |
436 | rest_combine_p->value(saved_combine_measures); | |
437 | first_page_p->value(saved_first_page); | |
438 | ||
439 | // It seems setting a radio button via value(1) doesn't reset the | |
440 | // others, so clear all, then set the one we want | |
441 | all_p->value(0); | |
442 | odd_p->value(0); | |
443 | even_p->value(0); | |
444 | selected_p->value(0); | |
445 | switch (saved_pages) { | |
446 | default: // default should be impossible | |
447 | case ALL_PAGES: | |
448 | all_p->value(1); | |
449 | break; | |
450 | case ODD_PAGES: | |
451 | odd_p->value(1); | |
452 | break; | |
453 | case EVEN_PAGES: | |
454 | even_p->value(1); | |
455 | break; | |
456 | case SELECTED_PAGES: | |
457 | selected_p->value(1); | |
458 | break; | |
459 | } | |
460 | page_list_p->value(saved_page_list); | |
461 | staff_list_p->value(saved_staff_list); | |
462 | extract_begin_p->value(saved_extract_begin); | |
463 | extract_end_p->value(saved_extract_end); | |
464 | ||
465 | int m; | |
466 | for (m = 0; m < MAX_MACROS; m++) { | |
467 | macro_definitions_p[m]->value( saved_macro_definitions[m] == 0 | |
468 | ? "" : saved_macro_definitions[m]); | |
469 | } | |
470 | hide(); | |
471 | } | |
472 | ||
473 | // Return true if check of macro finds an error. | |
474 | ||
475 | bool | |
476 | Run_parameters_dialog::macro_error(const char * macro) | |
477 | { | |
478 | // First character must be an upper case letter. | |
479 | if ( ! isupper(macro[0])) { | |
480 | return(true); | |
481 | } | |
482 | // Everything up to = or end of string, whichever comes first, | |
483 | // must be upper case letter, digit, or underscore. | |
484 | const char * m_p; | |
485 | for (m_p = macro; *m_p != '\0' && *m_p != '='; m_p++) { | |
486 | if ( ! isupper(*m_p) && ! isdigit(*m_p) && *m_p != '_') { | |
487 | return(true); | |
488 | } | |
489 | } | |
490 | return(false); | |
491 | } | |
492 | ||
493 | ||
494 | //--------class for Run menu ----------------------------------------------- | |
495 | ||
496 | Run::Run() | |
497 | { | |
498 | parameters_p = 0; | |
499 | report_p = 0; | |
500 | #ifdef OS_LIKE_WIN32 | |
501 | display_child.hProcess = 0; | |
502 | display_child.dwProcessId = 0; | |
503 | MIDI_child.hProcess = 0; | |
504 | MIDI_child.dwProcessId = 0; | |
505 | #else | |
506 | display_child = 0; | |
507 | MIDI_child = 0; | |
508 | #endif | |
509 | } | |
510 | ||
511 | Run::~Run() | |
512 | { | |
513 | if (parameters_p != 0) { | |
514 | delete parameters_p; | |
515 | parameters_p = 0; | |
516 | } | |
517 | if (report_p != 0) { | |
518 | delete report_p; | |
519 | report_p = 0; | |
520 | } | |
521 | // Kill off any child processes | |
522 | clean_up(); | |
523 | } | |
524 | ||
525 | ||
526 | ||
527 | //------------callback for Display menu item | |
528 | ||
529 | CALL_BACK(Run, Display) | |
530 | { | |
531 | Run_Mup(false, true); | |
532 | } | |
533 | ||
534 | ||
535 | //-----Callback for menu item to play MIDI file | |
536 | ||
537 | CALL_BACK(Run, Play) | |
538 | { | |
539 | Run_Mup(true, true); | |
540 | } | |
541 | ||
542 | ||
543 | //---------callback for menu item for writing PostScript output | |
544 | ||
545 | CALL_BACK(Run, WritePostScript) | |
546 | { | |
547 | Run_Mup(false, false); | |
548 | } | |
549 | ||
550 | //---------callback for menu item for writing MIDI output | |
551 | ||
552 | CALL_BACK(Run, WriteMIDI) | |
553 | { | |
554 | Run_Mup(true, false); | |
555 | } | |
556 | ||
557 | //---- callback for menu item to collect Mup command line parameters from user | |
558 | ||
559 | CALL_BACK(Run, Options) | |
560 | { | |
561 | if (parameters_p == 0) { | |
562 | parameters_p = new Run_parameters_dialog(); | |
563 | } | |
564 | parameters_p->show(); | |
565 | } | |
566 | ||
567 | ||
568 | //--------- This lets Run class know the file name and editor buffer, | |
569 | // which it needs access to, so it can run Mup on its contents. | |
570 | ||
571 | void | |
572 | Run::set_file(File * file_info_p) | |
573 | { | |
574 | file_p = file_info_p; | |
575 | } | |
576 | ||
577 | ||
578 | // Run Mup commands with user's parameters on the current file | |
579 | ||
580 | void | |
581 | Run::Run_Mup(bool midi, bool show_or_play) | |
582 | { | |
583 | ||
584 | const char * mup_input = file_p->effective_filename(); | |
585 | // Make sure file has been written. | |
586 | // The fiilename == 0 will be hit in the case where | |
587 | // user did New From Template but made no changes. | |
588 | if (file_p->unsaved_changes || file_p->filename == 0) { | |
589 | int auto_save; | |
590 | (void) Preferences_p->get(Auto_save_preference, auto_save, | |
591 | Default_auto_save); | |
592 | if (auto_save) { | |
593 | file_p->Save(false); | |
594 | if (file_p->unsaved_changes || file_p->filename == 0) { | |
595 | // User probably canceled a Save that got | |
596 | // turned into a SaveAs, | |
597 | // or maybe Save failed. If not properly | |
598 | // saved, we shouldn't try to process it. | |
599 | return; | |
600 | } | |
601 | } | |
602 | else { | |
603 | bool hide_the_No; | |
604 | const char * extra_text; | |
605 | // If there is a previous version of the file, | |
606 | // allow user to select "No," which means use that | |
607 | // previous version rather than writing out current. | |
608 | if (access(mup_input, F_OK) == 0) { | |
609 | hide_the_No = false; | |
610 | extra_text = "(If you select \"No,\" " | |
611 | "the most recently saved version " | |
612 | "of the Mup input file will be used.)"; | |
613 | } | |
614 | else { | |
615 | // No previous version | |
616 | hide_the_No = true; | |
617 | extra_text = ""; | |
618 | } | |
619 | switch (file_p->save_changes_check(extra_text, hide_the_No)) { | |
620 | default: // default case should be impossible | |
621 | case Save_confirm_dialog::Cancel: | |
622 | return; | |
623 | case Save_confirm_dialog::No: | |
624 | break; | |
625 | case Save_confirm_dialog::Yes: | |
626 | file_p->Save(false); | |
627 | if (file_p->unsaved_changes | |
628 | || file_p->filename == 0) { | |
629 | // User probably canceled the Save, | |
630 | // or maybe it failed. If not properly | |
631 | // saved, we shouldn't try to process it. | |
632 | return; | |
633 | } | |
634 | break; | |
635 | } | |
636 | } | |
637 | // If was "Untitled" before, it is now saved under a good | |
638 | // name, so get what that is. | |
639 | mup_input = file_p->effective_filename(); | |
640 | } | |
641 | ||
642 | // Get length of file name without the extension | |
643 | int base_length = strlen(mup_input) | |
644 | - strlen(fl_filename_ext(mup_input)); | |
645 | ||
646 | // Create output file name | |
647 | char mup_output[base_length + (midi ? 5 : 4)]; | |
648 | strncpy(mup_output, mup_input, base_length); | |
649 | strcpy(mup_output + base_length, (midi ? ".mid" : ".ps")); | |
650 | ||
651 | // Create error file name | |
652 | char mup_error[base_length + 5]; | |
653 | strncpy(mup_error, mup_input, base_length); | |
654 | strcpy(mup_error + base_length, ".err"); | |
655 | ||
656 | // Get Mup command to use. | |
657 | char * mup_command; | |
658 | (void) Preferences_p->get(Mup_program_location, mup_command, | |
659 | Default_Mup_program_location); | |
660 | char full_location[FL_PATH_MAX]; | |
661 | if ( ! find_executable(mup_command, full_location)) { | |
662 | fl_alert("Mup command not found.\n" | |
663 | "Check Config > File Locations setting."); | |
664 | return; | |
665 | } | |
666 | ||
667 | if (parameters_p == 0) { | |
668 | // User hasn't set any parameters, so we will use all | |
669 | // defaults. Creating the instance lets us deference it | |
670 | // below, so we don't have to care whether user ever | |
671 | // went to the Set Options page or not. | |
672 | parameters_p = new Run_parameters_dialog(); | |
673 | } | |
674 | ||
675 | // Build up list of arguments. | |
676 | // array slots needed for args: | |
677 | // 1 for Mup command itself | |
678 | // 2 for -e and arg | |
679 | // 2 for -f or -m and arg | |
680 | // 2 for -c and arg | |
681 | // 2 for -p and arg | |
682 | // 2 for -o and arg | |
683 | // 2 for -s and arg | |
684 | // 2 for -x and arg | |
685 | // 1 for Mup input file name | |
686 | // 2 for each -D and its macro definition arg | |
687 | // 1 for null terminator | |
688 | const char * command[17 + 2 * MAX_MACROS]; | |
689 | command[0] = full_location; | |
690 | command[1] = "-e"; | |
691 | command[2] = mup_error; | |
692 | command[3] = (midi ? "-m" : "-f"); | |
693 | command[4] = mup_output; | |
694 | int arg_offset = 5; | |
695 | ||
696 | // rest combine | |
697 | if (parameters_p->enable_combine_p->value() && | |
698 | parameters_p->rest_combine_p->size() > 0) { | |
699 | command[arg_offset++] = "-c"; | |
700 | command[arg_offset++] = parameters_p->rest_combine_p->value(); | |
701 | } | |
702 | ||
703 | // first page | |
704 | if (parameters_p->first_page_p->size() > 0) { | |
705 | command[arg_offset++] = "-p"; | |
706 | command[arg_offset++] = parameters_p->first_page_p->value(); | |
707 | } | |
708 | ||
709 | // page list | |
710 | switch (parameters_p->saved_pages) { | |
711 | default: // default should be impossible | |
712 | case Run_parameters_dialog::ALL_PAGES: | |
713 | break; | |
714 | case Run_parameters_dialog::ODD_PAGES: | |
715 | command[arg_offset++] = "-o"; | |
716 | command[arg_offset++] = "odd"; | |
717 | break; | |
718 | case Run_parameters_dialog::EVEN_PAGES: | |
719 | command[arg_offset++] = "-o"; | |
720 | command[arg_offset++] = "even"; | |
721 | case Run_parameters_dialog::SELECTED_PAGES: | |
722 | if (parameters_p->page_list_p->size() > 0) { | |
723 | command[arg_offset++] = "-o"; | |
724 | command[arg_offset++] = parameters_p->page_list_p->value(); | |
725 | } | |
726 | // Else they said selected, but then didn't list | |
727 | // any particular pages. We treat like ALL_PAGES, | |
728 | // since no pages is useless. | |
729 | // We would have already given error message, | |
730 | // but they must have ignored it. | |
731 | break; | |
732 | } | |
733 | ||
734 | // staff list | |
735 | if (parameters_p->staff_list_p->size() > 0) { | |
736 | command[arg_offset++] = "-s"; | |
737 | command[arg_offset++] = parameters_p->staff_list_p->value(); | |
738 | } | |
739 | ||
740 | // extract list | |
741 | char xoption[parameters_p->extract_begin_p->size() | |
742 | + parameters_p->extract_end_p->size() + 2]; | |
743 | if (parameters_p->extract_begin_p->size() > 0) { | |
744 | command[arg_offset++] = "-x"; | |
745 | (void) strcpy(xoption, parameters_p->extract_begin_p->value()); | |
746 | if (parameters_p->extract_end_p->size() > 0) { | |
747 | (void) strcat(xoption, ","); | |
748 | (void) strcat(xoption, parameters_p->extract_end_p->value()); | |
749 | } | |
750 | command[arg_offset++] = xoption; | |
751 | } | |
752 | ||
753 | // -D options | |
754 | int m; | |
755 | for (m = 0; m < MAX_MACROS; m++) { | |
756 | if (parameters_p->saved_macro_definitions[m] != 0) { | |
757 | command[arg_offset++] = "-D"; | |
758 | command[arg_offset++] = parameters_p->saved_macro_definitions[m]; | |
759 | } | |
760 | } | |
761 | ||
762 | // Mup input file name and null terminator | |
763 | command[arg_offset++] = mup_input; | |
764 | command[arg_offset++] = 0; | |
765 | ||
766 | static bool set_mupquiet = false; | |
767 | if ( ! set_mupquiet ) { | |
768 | putenv("MUPQUIET=1"); | |
769 | set_mupquiet = true; | |
770 | } | |
771 | ||
772 | // Look up the right (dis)player program to use. | |
773 | // On Windows we need this even if we are only writing the file, | |
774 | // not actually (dis)playing. To keep the code simpler, | |
775 | // we just always look it up. | |
776 | char * player_command; | |
777 | if (midi) { | |
778 | (void) Preferences_p->get(MIDI_player_location, | |
779 | player_command, | |
780 | Default_MIDI_player_location); | |
781 | } | |
782 | else { | |
783 | (void) Preferences_p->get(Viewer_location, | |
784 | player_command, | |
785 | Default_viewer_location); | |
786 | } | |
787 | #ifdef OS_LIKE_WIN32 | |
788 | // Media player locks the file it plays, so if we had | |
789 | // run it before, we have to make it release the lock | |
790 | // before we run Mup. Also, if it was playing a different file | |
791 | // before, it doesn't seem to want to play ours. | |
792 | // So if the MIDI player of choice is the media player, | |
793 | // we first try to make it close somewhat gracefully, | |
794 | // and wait a second for that to complete. | |
795 | // In any case we try to kill off any child MIDI players we | |
796 | // know of. If the graceful close already worked, | |
797 | // it should find the child already dead. | |
798 | // If they are using some other MIDI player, | |
799 | // we don't know what to do, so we'll just kill any child | |
800 | // MIDI player we know spawned previously, if any. | |
801 | // Would be nice to be able to do something less drastic than | |
802 | // kill the process, but we're not sure what else would be effective. | |
803 | if (midi) { | |
804 | if (is_mplayer(player_command)) { | |
805 | HWND window_handle; | |
806 | if ((window_handle = FindWindow(0, "Windows Media Player")) != 0) { | |
807 | SendMessage(window_handle, WM_CLOSE, 0, 0); | |
808 | } | |
809 | _sleep(1000); | |
810 | } | |
811 | if (has_MIDI_child()) { | |
812 | kill_process(&MIDI_child, "MIDI player"); | |
813 | } | |
814 | } | |
815 | // Somewhat similarly, we kill off any prior displayer, unless | |
816 | // the displayer is GSview, which we know we can pass -e argument | |
817 | // to make it reuse existing instance, if any. | |
818 | else if ( ! is_gsview(player_command) && has_display_child()) { | |
819 | kill_process(&display_child, "display"); | |
820 | } | |
821 | #endif | |
822 | ||
823 | // Run the command. | |
824 | int ret = execute_command(command, 0, true); | |
825 | ||
826 | // Report the errors, if any. | |
827 | struct stat info; | |
828 | if (stat(mup_error, &info) == 0) { | |
829 | if (info.st_size > 0) { | |
830 | if (report_p == 0) { | |
831 | report_p = new Error_report(); | |
832 | } | |
833 | report_p->loadfile(mup_error); | |
834 | report_p->show(); | |
835 | } | |
836 | unlink(mup_error); | |
837 | } | |
838 | else if (ret != 0) { | |
839 | // Exited with error, but left no error file. | |
840 | // Must have died badly (core dumped, execvp failed, etc) | |
841 | #if defined(WIFEXITED) && defined(WIFSIGNALED) | |
842 | if (WIFEXITED(ret)) { | |
843 | // Did exit(), so most likely exec failed | |
844 | // due to bad path to Mup program, | |
845 | // although we should have caught that above. | |
846 | fl_alert("Mup exited with return code %d but no error output.\n" | |
847 | "Check Config > File Locations setting.", | |
848 | WEXITSTATUS(ret)); | |
849 | } | |
850 | else if (WIFSIGNALED(ret)) { | |
851 | // Probably core dump :-( | |
852 | fl_alert("Mup exited due to signal %d.", WTERMSIG(ret)); | |
853 | } else { | |
854 | fl_alert(Unknown_Mup_failure); | |
855 | } | |
856 | #else // WIF... macros not defined | |
857 | if (ret == -1) { | |
858 | #ifdef OS_LIKE_WIN32 | |
859 | // Look up the error reason to include in message. | |
860 | DWORD format_retval; | |
861 | LPVOID error_string = 0; | |
862 | if (FormatMessage( | |
863 | FORMAT_MESSAGE_ALLOCATE_BUFFER | |
864 | | FORMAT_MESSAGE_FROM_SYSTEM | |
865 | | FORMAT_MESSAGE_ARGUMENT_ARRAY, | |
866 | 0, GetLastError(), | |
867 | LANG_NEUTRAL, (LPTSTR)&error_string, | |
868 | 0, 0)) { | |
869 | fl_alert("Failed to execute Mup program:\n%s" | |
870 | "Check settings in Config > File Locations.", | |
871 | (char *) error_string); | |
872 | LocalFree((HLOCAL)error_string); | |
873 | } | |
874 | else { | |
875 | fl_alert(Unknown_Mup_failure); | |
876 | } | |
877 | } | |
878 | #else // not Windows | |
879 | fl_alert(Unknown_Mup_failure); | |
880 | } | |
881 | #endif | |
882 | else { | |
883 | fl_alert(Unknown_Mup_failure); | |
884 | } | |
885 | #endif // WIF... macros | |
886 | } | |
887 | ||
888 | if (ret != 0) { | |
889 | // Something went wrong. We should not go on to | |
890 | // display/play even if user had asked for that. | |
891 | return; | |
892 | } | |
893 | ||
894 | // We wrote the output file successfully. | |
895 | // Hide any previous error window and | |
896 | // check if we need to (dis)play the results. | |
897 | if (report_p != 0) { | |
898 | report_p->hide(); | |
899 | } | |
900 | if ( ! show_or_play ) { | |
901 | // User just asked to generate a file, not (dis)play it. | |
902 | fl_message("%s output file\n\n %s\n\n" | |
903 | "has been successfully created.", | |
904 | (midi ? "MIDI" : "PostScript"), mup_output); | |
905 | return; | |
906 | } | |
907 | ||
908 | ||
909 | if ( ! find_executable(player_command, full_location)) { | |
910 | fl_alert("Unable to run %s command.\n" | |
911 | "Check Config > File Locations setting.", | |
912 | player_command); | |
913 | return; | |
914 | } | |
915 | ||
916 | #ifdef OS_LIKE_UNIX | |
917 | // For MIDI, we try to kill the previous player | |
918 | // if any, since we'll probably need the same | |
919 | // sound card. Note that for Windows, we did this | |
920 | // even before running Mup, since the player may lock | |
921 | // the file so Mup would be unable to write it. | |
922 | if (midi) { | |
923 | kill_process(&MIDI_child, "MIDI player"); | |
924 | } | |
925 | ||
926 | // If using gv as displayer and one already running on | |
927 | // this file, it would be nice to get send it SIGHUP | |
928 | // to make it re-read the file | |
929 | // rather than starting a new one. | |
930 | // But for other displayers, we don't know what signal | |
931 | // might work, if any. And even with gv, if we send | |
932 | // the signal, we have no way of knowing whether it | |
933 | // actually worked. And there are lots of cases to | |
934 | // consider. Suppose the displayer was gv, | |
935 | // and they brought up an instance, | |
936 | // then they changed to some other displayer, | |
937 | // and then back to gv. Should we have killed off | |
938 | // the first gv when bringing up the other viewer, | |
939 | // of should we keep it up and reuse it after they | |
940 | // change back? What if they kill the current | |
941 | // instance just after we decided to re-use it? | |
942 | // So just keep things simple for now. Always | |
943 | // kill off any existing viewer we know of and | |
944 | // start up a new one. | |
945 | else { // displaying | |
946 | kill_process(&display_child, "display"); | |
947 | } | |
948 | #endif | |
949 | ||
950 | // Fill in the argv array for displayer/player | |
951 | command[0] = full_location; | |
952 | #ifdef OS_LIKE_WIN32 | |
953 | // Media player appears to ignore its argument | |
954 | // if it isn't a full path name, including drive. | |
955 | // So ensure we have everything. | |
956 | char fullpath[FL_PATH_MAX]; | |
957 | if (mup_output[1] != ':' ) { | |
958 | // We don't have complete path. | |
959 | // Get current directory including drive. | |
960 | if (getcwd(fullpath, sizeof(fullpath)) == 0) { | |
961 | fl_alert("Unable to determine current folder."); | |
962 | return; | |
963 | } | |
964 | if (mup_output[0] != dir_separator()) { | |
965 | // Relative path | |
966 | (void) sprintf(fullpath + strlen(fullpath), | |
967 | "%c%s", dir_separator(), | |
968 | mup_output); | |
969 | } | |
970 | else { | |
971 | // Was full path except for drive. | |
972 | (void) strcpy(fullpath + 2, mup_output); | |
973 | } | |
974 | } | |
975 | else { | |
976 | // Can use existing path as is. | |
977 | (void) strcpy(fullpath, mup_output); | |
978 | } | |
979 | ||
980 | if ( ! midi && is_gsview(player_command)) { | |
981 | // For gsview, use -e to make it use | |
982 | // existing instance if any. | |
983 | command[1] = "-e"; | |
984 | command[2] = fullpath; | |
985 | command[3] = '\0'; | |
986 | } | |
987 | else { | |
988 | command[1] = fullpath; | |
989 | command[2] = '\0'; | |
990 | ||
991 | } | |
992 | #else | |
993 | command[1] = mup_output; | |
994 | command[2] = '\0'; | |
995 | #endif | |
996 | ||
997 | if (execute_command(command, | |
998 | (midi ? &MIDI_child : &display_child)) != 0) { | |
999 | fl_alert("Unable to run %s command.\n" | |
1000 | "Check settings under Config > File Locations.", | |
1001 | command[0]); | |
1002 | } | |
1003 | } | |
1004 | ||
1005 | ||
1006 | // Execute given command with the given argv. | |
1007 | // If proc_info_p is zero, wait for the process to complete, | |
1008 | // otherwise save information about the spawned process in what it points to, | |
1009 | // so that the caller can keep track of it while it runs independently. | |
1010 | // The hide_window parameter is only used for Windows and causes the | |
1011 | // spawned process to be created with flags to not create a window, | |
1012 | // This lets us use a console mode version of Mup, so traditional users | |
1013 | // can continue to run Mup in a console without mupmate, but we can | |
1014 | // run the same .exe without the annoyance of a console popping up. | |
1015 | // Returns 0 on success, -1 on failure to create process, | |
1016 | // or what the process returned if it had non-zero exit code, | |
1017 | // and was waited for. | |
1018 | ||
1019 | int | |
1020 | Run::execute_command(const char ** argv, Proc_Info *proc_info_p, bool hide_window) | |
1021 | { | |
1022 | int ret = -1; | |
1023 | ||
1024 | #ifdef OS_LIKE_UNIX | |
1025 | pid_t child; | |
1026 | switch (child = fork()) { | |
1027 | case 0: | |
1028 | execvp(argv[0], (char * const *)argv); | |
1029 | // If here, the exec failed. Child must die. | |
1030 | exit(1); | |
1031 | case -1: | |
1032 | // failed | |
1033 | if (proc_info_p != 0) { | |
1034 | *proc_info_p = 0; | |
1035 | } | |
1036 | break; | |
1037 | default: | |
1038 | if (proc_info_p == 0) { | |
1039 | // wait for child to complete | |
1040 | if (waitpid(child, &ret, 0) < 0) { | |
1041 | ret = -1; | |
1042 | } | |
1043 | } | |
1044 | else { | |
1045 | *proc_info_p = child; | |
1046 | ret = 0; | |
1047 | } | |
1048 | break; | |
1049 | } | |
1050 | #else | |
1051 | ||
1052 | #ifdef OS_LIKE_WIN32 | |
1053 | ||
1054 | // Convert the argv array into a string with each argument quoted. | |
1055 | // First calculate how much space is needed. | |
1056 | int a; | |
1057 | int length = 0; | |
1058 | bool has_quote = false; // to optimize normal case | |
1059 | for (a = 0; argv[a] != 0; a++) { | |
1060 | length += strlen(argv[a]); | |
1061 | // A quoted argv[0] won't work; just quote the other args. | |
1062 | if (a > 0) { | |
1063 | // Add space before the arg and quotes on each end. | |
1064 | length += 3; | |
1065 | const char * s; | |
1066 | // If embedded quote, add space for escaping it | |
1067 | for (s = argv[a]; *s != '\0'; s++) { | |
1068 | if (*s == '"') { | |
1069 | length++; | |
1070 | has_quote = true; | |
1071 | } | |
1072 | } | |
1073 | } | |
1074 | } | |
1075 | ||
1076 | // Get space and fill in all the arguments, properly quoted. | |
1077 | char command[length + 1]; | |
1078 | (void) strcpy(command, argv[0]); | |
1079 | char * dest = command + strlen(command); | |
1080 | for (a = 1; argv[a] != 0; a++) { | |
1081 | *dest++ = ' '; | |
1082 | *dest++ = '"'; | |
1083 | if (has_quote) { | |
1084 | int i; | |
1085 | for (i = 0; argv[a][i] != '\0'; i++) { | |
1086 | if (argv[a][i] == '"') { | |
1087 | *dest++ = '\\'; | |
1088 | } | |
1089 | *dest++ = argv[a][i]; | |
1090 | } | |
1091 | } | |
1092 | else { | |
1093 | (void) strcpy(dest, argv[a]); | |
1094 | dest += strlen(dest); | |
1095 | } | |
1096 | *dest++ = '"'; | |
1097 | } | |
1098 | *dest = '\0'; | |
1099 | ||
1100 | // Fill in information for starting up the process | |
1101 | PROCESS_INFORMATION process_info; | |
1102 | STARTUPINFO startup_info; | |
1103 | memset( &startup_info, 0, sizeof(startup_info)); | |
1104 | startup_info.cb = sizeof(startup_info); | |
1105 | DWORD create_flags; // flags to control creation aspects | |
1106 | if (hide_window) { | |
1107 | startup_info.dwFlags = STARTF_USESHOWWINDOW; | |
1108 | startup_info.wShowWindow = SW_HIDE; | |
1109 | create_flags = CREATE_NO_WINDOW; | |
1110 | } | |
1111 | else { | |
1112 | create_flags = 0; | |
1113 | } | |
1114 | ||
1115 | // Run the process | |
1116 | BOOL proc = CreateProcess(NULL, command, NULL, NULL, | |
1117 | TRUE, create_flags, NULL, NULL, | |
1118 | &startup_info, &process_info); | |
1119 | ||
1120 | if (proc) { | |
1121 | // It was successfully created. | |
1122 | if (proc_info_p == 0) { | |
1123 | // wait for child to complete | |
1124 | DWORD result = WaitForSingleObject( | |
1125 | process_info.hProcess, INFINITE); | |
1126 | switch (result) { | |
1127 | case WAIT_FAILED: | |
1128 | case WAIT_ABANDONED: | |
1129 | case WAIT_TIMEOUT: | |
1130 | ret = -1; | |
1131 | break; | |
1132 | default: | |
1133 | GetExitCodeProcess(process_info.hProcess, &result); | |
1134 | ret = (int) result; | |
1135 | } | |
1136 | } | |
1137 | else { | |
1138 | *proc_info_p = process_info; | |
1139 | ret = 0; | |
1140 | } | |
1141 | } | |
1142 | else { | |
1143 | proc_info_p->hProcess = 0; | |
1144 | proc_info_p->dwProcessId = 0; | |
1145 | ret = -1; | |
1146 | } | |
1147 | ||
1148 | #else | |
1149 | fl_alert("Process execution only implemented\n" | |
1150 | "for Linux (and similar) and Windows so far..."); | |
1151 | ret = -1; | |
1152 | #endif | |
1153 | ||
1154 | #endif | |
1155 | return(ret); | |
1156 | } | |
1157 | ||
1158 | ||
1159 | // Kill the specified process, if it exists. | |
1160 | // The description is used in error messages | |
1161 | ||
1162 | void | |
1163 | Run::kill_process(const Proc_Info * const proc_info_p, const char * const description) | |
1164 | { | |
1165 | #ifdef OS_LIKE_UNIX | |
1166 | int exitstatus; | |
1167 | if (*proc_info_p == 0 || waitpid(*proc_info_p, &exitstatus, WNOHANG) | |
1168 | == *proc_info_p) { | |
1169 | // No process spawned or the one we had already died | |
1170 | return; | |
1171 | } | |
1172 | // Not clear how hard to try to kill process. | |
1173 | // SIGTERM should usually work, and is preferable if it works. | |
1174 | // We wait a little while and try SIGKILL if it hasn't died yet. | |
1175 | // We don't check for errors, because the | |
1176 | // only errors that should be possible are bad signal (should be | |
1177 | // impossible, since we hard-code SIGTERM), process doesn't exist | |
1178 | // (already dead, so no need to kill it), or bad permission (we | |
1179 | // spawned it ourself, so ought to have permission to kill it). | |
1180 | (void) kill(*proc_info_p, SIGTERM); | |
1181 | // Wait for up to 3 seconds for it to die | |
1182 | int w; | |
1183 | int ret; | |
1184 | for (w = 0; w < 3; w++) { | |
1185 | if ((ret = waitpid(*proc_info_p, &exitstatus, WNOHANG)) | |
1186 | == *proc_info_p) { | |
1187 | // It died. | |
1188 | // *** Especially in the case of MIDI, there is a | |
1189 | // chance the player has already written stuff out | |
1190 | // to the device driver that it won't clear out when | |
1191 | // it dies, so that if we try to start a new one, | |
1192 | // the device will still be busy. But we have no way | |
1193 | // to know how long to wait, if at all, and don't | |
1194 | // want to always sleep a long time just to cover | |
1195 | // the case of trying to play again while in the middle | |
1196 | // of playing. | |
1197 | return; | |
1198 | } | |
1199 | if (ret == -1 && errno == ECHILD) { | |
1200 | // Child doesn't exist, so nothing to kill | |
1201 | return; | |
1202 | } | |
1203 | sleep(1); | |
1204 | } | |
1205 | // Okay. Resort to SIGKILL and wait 1 more second to let it die. | |
1206 | kill(*proc_info_p, SIGKILL); | |
1207 | sleep(1); | |
1208 | #endif | |
1209 | #ifdef OS_LIKE_WIN32 | |
1210 | if (proc_info_p->hProcess == 0 || WaitForSingleObject( | |
1211 | proc_info_p->hProcess, 0) != WAIT_TIMEOUT) { | |
1212 | // No process spawned or the one we had must have already died. | |
1213 | return; | |
1214 | } | |
1215 | HANDLE handle; | |
1216 | if ( (handle = OpenProcess(PROCESS_TERMINATE, | |
1217 | FALSE, proc_info_p->dwProcessId)) == 0 || | |
1218 | TerminateProcess(handle, 0) == 0) { | |
1219 | // Warn user we were unable to kill the old child. | |
1220 | DWORD format_retval; | |
1221 | LPVOID error_string = 0; | |
1222 | if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | |
1223 | | FORMAT_MESSAGE_FROM_SYSTEM | |
1224 | | FORMAT_MESSAGE_ARGUMENT_ARRAY, | |
1225 | 0, GetLastError(), | |
1226 | LANG_NEUTRAL, (LPTSTR)&error_string, | |
1227 | 0, 0)) { | |
1228 | fl_alert("Unable to terminate %s process.\n%s", | |
1229 | description, (char *) error_string); | |
1230 | LocalFree((HLOCAL)error_string); | |
1231 | } | |
1232 | } | |
1233 | if (handle != 0) { | |
1234 | CloseHandle(handle); | |
1235 | } | |
1236 | #endif | |
1237 | } | |
1238 | ||
1239 | ||
1240 | #ifdef OS_LIKE_WIN32 | |
1241 | // Return true if the given command is for Windows Media Player. | |
1242 | ||
1243 | bool | |
1244 | Run::is_mplayer(const char * command) | |
1245 | { | |
1246 | int length = strlen(command); | |
1247 | if (strcasecmp(command + length - 12, "wmplayer.exe") == 0 || | |
1248 | strcasecmp(command + length - 11, "mplayer.exe") == 0 || | |
1249 | strcasecmp(command + length - 8, "wmplayer") == 0 || | |
1250 | strcasecmp(command + length - 7, "mplayer") == 0) { | |
1251 | return(true); | |
1252 | } | |
1253 | return(false); | |
1254 | } | |
1255 | ||
1256 | ||
1257 | // Return true if the given command is for GSview. | |
1258 | ||
1259 | bool | |
1260 | Run::is_gsview(const char * command) | |
1261 | { | |
1262 | int length = strlen(command); | |
1263 | if (strcasecmp(command + length - 12, "gsview32.exe") == 0 || | |
1264 | strcasecmp(command + length - 10, "gsview.exe") == 0 || | |
1265 | strcasecmp(command + length - 8, "gsview32") == 0 || | |
1266 | strcasecmp(command + length - 6, "gsview") == 0) { | |
1267 | return(true); | |
1268 | } | |
1269 | return(false); | |
1270 | } | |
1271 | #endif | |
1272 | ||
1273 | ||
1274 | // Called when user begins a new file, or when exiting, | |
1275 | // to kill off child proceses that were spawned. | |
1276 | // We start new processes for new file, | |
1277 | // even if one already up for current file. | |
1278 | // Not sure if that's what user wants, but... | |
1279 | ||
1280 | void | |
1281 | Run::clean_up(void) | |
1282 | { | |
1283 | kill_process(&display_child, "display"); | |
1284 | kill_process(&MIDI_child, "MIDI player"); | |
1285 | } | |
1286 | ||
1287 | ||
1288 | // Report whether we spawned a display child and think it is still alive. | |
1289 | ||
1290 | bool | |
1291 | Run::has_display_child(void) | |
1292 | { | |
1293 | #ifdef OS_LIKE_WIN32 | |
1294 | return(display_child.hProcess != 0); | |
1295 | #else | |
1296 | return(display_child != 0); | |
1297 | #endif | |
1298 | } | |
1299 | ||
1300 | ||
1301 | // Report whether we spawned a MIDI child and think it is still alive. | |
1302 | ||
1303 | bool | |
1304 | Run::has_MIDI_child(void) | |
1305 | { | |
1306 | #ifdef OS_LIKE_WIN32 | |
1307 | return(MIDI_child.hProcess != 0); | |
1308 | #else | |
1309 | return(MIDI_child != 0); | |
1310 | #endif | |
1311 | } | |
1312 | ||
1313 | ||
1314 | //------------ Class for displaying errors from Mup | |
1315 | ||
1316 | Error_report::Error_report(void) | |
1317 | : Fl_Double_Window(Default_width, Default_height, "Error report") | |
1318 | { | |
1319 | text_p = new Fl_Text_Display(20, 20, w() - 40, h() - 90); | |
1320 | resizable((Fl_Widget *) text_p); | |
1321 | text_p->buffer( new Fl_Text_Buffer() ); | |
1322 | ||
1323 | // Set font/size and arrange to get notified of changes in them | |
1324 | font_change_reg_p = new Font_change_registration( | |
1325 | font_change_cb, (void *) this); | |
1326 | ||
1327 | ok_p = new Fl_Return_Button(w() / 2 - 40, h() - 50, 80, 30, "OK"); | |
1328 | ok_p->callback(OK_cb, this); | |
1329 | show(); | |
1330 | ||
1331 | // Arrange to clean up new-ed widgets in destructor. | |
1332 | end(); | |
1333 | } | |
1334 | ||
1335 | Error_report::~Error_report() | |
1336 | { | |
1337 | } | |
1338 | ||
1339 | ||
1340 | // Load the error file (from -e of Mup) into window to show user. | |
1341 | ||
1342 | int | |
1343 | Error_report::loadfile(const char * filename) | |
1344 | { | |
1345 | return text_p->buffer()->loadfile(filename); | |
1346 | } | |
1347 | ||
1348 | ||
1349 | // Callback for when user clicks OK after reading Mup error report | |
1350 | ||
1351 | CALL_BACK(Error_report, OK) | |
1352 | { | |
1353 | hide(); | |
1354 | } | |
1355 | ||
1356 | ||
1357 | // Callback for change in font/size | |
1358 | ||
1359 | void | |
1360 | Error_report::font_change_cb(void * data, Fl_Font font, unsigned char size) | |
1361 | { | |
1362 | ((Error_report *)data)->font_change(font, size); | |
1363 | } | |
1364 | ||
1365 | void | |
1366 | Error_report::font_change(Fl_Font font, unsigned char size) | |
1367 | { | |
1368 | text_p->textfont(font); | |
1369 | text_p->textsize(size); | |
1370 | text_p->redisplay_range(0, text_p->buffer()->length()); | |
1371 | } |