chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mupmate / Run.C
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 }