chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mupmate / Run.C
CommitLineData
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.
25const 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
33const 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
43Run_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
173Run_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
187CALL_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
202CALL_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
216CALL_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
230CALL_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
276CALL_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
432CALL_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
475bool
476Run_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
496Run::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
511Run::~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
529CALL_BACK(Run, Display)
530{
531 Run_Mup(false, true);
532}
533
534
535//-----Callback for menu item to play MIDI file
536
537CALL_BACK(Run, Play)
538{
539 Run_Mup(true, true);
540}
541
542
543//---------callback for menu item for writing PostScript output
544
545CALL_BACK(Run, WritePostScript)
546{
547 Run_Mup(false, false);
548}
549
550//---------callback for menu item for writing MIDI output
551
552CALL_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
559CALL_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
571void
572Run::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
580void
581Run::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
1019int
1020Run::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
1162void
1163Run::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
1243bool
1244Run::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
1259bool
1260Run::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
1280void
1281Run::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
1290bool
1291Run::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
1303bool
1304Run::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
1316Error_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
1335Error_report::~Error_report()
1336{
1337}
1338
1339
1340// Load the error file (from -e of Mup) into window to show user.
1341
1342int
1343Error_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
1351CALL_BACK(Error_report, OK)
1352{
1353 hide();
1354}
1355
1356
1357// Callback for change in font/size
1358
1359void
1360Error_report::font_change_cb(void * data, Fl_Font font, unsigned char size)
1361{
1362 ((Error_report *)data)->font_change(font, size);
1363}
1364
1365void
1366Error_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}