chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mupmate / File.C
1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
3
4 // Code for the File menu off the main toolbar
5
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/stat.h>
11 #include <FL/Fl.H>
12 #include <FL/fl_ask.H>
13 #ifndef OS_LIKE_WIN32
14 #include <FL/Fl_File_Icon.H>
15 #endif
16 #include <FL/Fl_Shared_Image.H>
17 #include <FL/Fl_Input.H>
18 #include <FL/filename.H>
19 #include <FL/Fl_Bitmap.H>
20 #include <FL/Fl_File_Chooser.H>
21 #include "globals.H"
22 #include "File.H"
23 #include "Main.H"
24 #include "Preferences.H"
25 #include "utils.H"
26 #include <time.h>
27 #ifdef OS_LIKE_WIN32
28 #include <FL/x.H>
29 #include <commdlg.h>
30 #endif
31
32 // The file name filters
33 #define Mup_filter "*.mup"
34 #define All_filter "*.*"
35
36 extern char dir_separator();
37 extern const char * const template_text;
38
39
40 //-----------------Class to implement the File menu off the main menu bar
41
42 File::File()
43 {
44         filename = 0;
45         unsaved_changes = false;
46 }
47
48 File::~File()
49 {
50         if (filename) {
51                 delete filename;
52                 filename = 0;
53         }
54 }
55
56
57 //--- the "New" menu item -------------
58
59 CALL_BACK(File, New)
60 {
61         // Ask user if they want to save any unsaved change on
62         // current file first.
63         switch (save_changes_check()) {
64         default: // default case should be impossible
65         case Save_confirm_dialog::Cancel:
66                 return;
67         case Save_confirm_dialog::No:
68                 break;
69         case Save_confirm_dialog::Yes:
70                 Save(false);
71                 break;
72         }
73
74         // Clear the current edit buffer
75         Fl_Text_Buffer * buffer_p = editor_p->buffer();
76         buffer_p->replace(0, buffer_p->length(), "");
77
78         if (filename) {
79                 delete filename;
80                 filename = 0;
81         }
82         set_window_label();
83         // Reset state information
84         begin_new_file();
85 }
86
87
88 // --- the "New From Template" menu item
89
90 CALL_BACK(File, NewFromTemplate)
91 {
92         New();
93         editor_p->buffer()->append(template_text);
94         unsaved_changes = false;
95 }
96
97 //--- the "Open" menu item -------------
98
99 CALL_BACK(File, Open)
100 {
101         // If already editing a file, ask user if they want to
102         // save any unsaved changes first
103         switch (save_changes_check()) {
104         default: // default case should be impossible
105         case Save_confirm_dialog::Cancel:
106                 return;
107         case Save_confirm_dialog::No:
108                 break;
109         case Save_confirm_dialog::Yes:
110                 Save(false);
111                 break;
112         }
113
114         // Clear out label to "Untitled" in case the load fails
115         set_window_label();
116
117         // Ask user for filename. If they give one, load it into editor.
118         const char * newfile = open_ask_user();
119         if (newfile != 0 && newfile[0] != '\0') {
120                 load_file(newfile);
121         }
122 }
123
124
125 //--- the "Save" menu item -------------
126
127 // Save the current buffer contents.
128 // If honor_auto_display is true and user has requested auto display,
129 // do the Run>Display action. If the save was due to finishing up an old
130 // file to start on a new or due to saving before exiting, we don't do that;
131 // it is only done if user did an explicit Save or Save As.
132
133 CALL_BACK_A(File, Save, bool honor_auto_display)
134 {
135         if (filename == 0) {
136                 // No file name given yet, so change into a Save As
137                 SaveAs(honor_auto_display);
138         }
139         else {
140                 save_file(honor_auto_display);
141         }
142 }
143
144
145 //--- the "SaveAs" menu item -------------
146
147 CALL_BACK_A(File, SaveAs, bool honor_auto_display)
148 {
149         // Ask user for name of file to save to
150         const char * newfile = save_as_ask_user();
151         if (newfile != 0 && newfile[0] != '\0') {
152
153                 // If user didn't give a suffix, add .mup suffix.
154                 // If they used .ps or .mid or .err suffix, don't allow that.
155                 const char * suffix = fl_filename_ext(newfile);
156                 char * suffixed_filename = 0;
157                 if (*suffix == '\0') {
158                         // User did not supply a suffix, so we add .mup
159                         suffixed_filename = new char[strlen(newfile) + 5];
160                         (void) sprintf(suffixed_filename, "%s.mup", newfile);
161                         newfile = suffixed_filename;
162                 }
163                 else if (strcasecmp(suffix, ".ps") == 0
164                                         || strcasecmp(suffix, ".mid") == 0
165                                         || strcasecmp(suffix, ".err") == 0) {
166                         fl_alert("A filename extension of .ps .mid or .err\n"
167                                 "is not allowed for Mup input files,\n"
168                                 "since those extensions are used for\n"
169                                 "PostScript, MIDI, and error output files.");
170                         return;
171                 }
172
173                 if (access(newfile, F_OK) == 0) {
174                         const char * ask_replace = " already exists. Do you want to replace it?";
175                         char question[strlen(newfile) + strlen(ask_replace) + 1];
176                         (void) sprintf(question, "%s%s\n", newfile, ask_replace);
177                         switch (Save_confirm_dialog::confirm_save(question)) {
178                         default: // default case should be impossible.
179                         case Save_confirm_dialog::Cancel:
180                         case Save_confirm_dialog::No:
181                                 if (suffixed_filename != 0) {
182                                         delete suffixed_filename;
183                                 }
184                                 return;
185                         case Save_confirm_dialog::Yes:
186                                 break;
187                         }
188                 }
189
190                 // Save the name of the new file
191                 if (filename != 0) {
192                         // forget previous file name
193                         delete filename;
194                 }
195                 if (suffixed_filename != 0) {
196                         filename = suffixed_filename;
197                 }
198                 else {
199                         filename = new char[strlen(newfile) + 1];
200                         (void) sprintf(filename, newfile);
201                 }
202
203                 set_window_label();
204                 save_file(honor_auto_display);
205         }
206 }
207
208
209 //--- the "Exit" menu item -------------
210
211 CALL_BACK(File, Exit)
212 {
213         switch (save_changes_check()) {
214         default: // default case should be impossible
215         case Save_confirm_dialog::Cancel:
216                 return;
217         case Save_confirm_dialog::No:
218                 break;
219         case Save_confirm_dialog::Yes:
220                 Save(false);
221                 break;
222         }
223         Main::clean_exit();
224 }
225
226 //------------------------------------------------------------------------
227
228 // This class needs access to editor window, so whoever creates an instance
229 // of this class needs to call this function to give it access.
230
231 void
232 File::set_editor(Fl_Text_Editor * ed)
233 {
234         editor_p = ed;
235 }
236
237  
238 // We need to set parent's label, etc. Save info about parent.
239
240 void
241 File::set_parent(Main * win_p)
242 {
243         parent_window_p = win_p;
244 }
245
246 // Called when user makes a change in editor window.
247 // Let's us know if we need to prompt user about unsaved changes.
248
249 void
250 File::modify_cb(int, int num_inserted, int num_deleted, int, const char *, void * data)
251 {
252         if (num_inserted > 0 || num_deleted > 0) {
253                 ((File *)data)->unsaved_changes = true;
254         }
255 }
256
257
258 // Utility method that does the details of saving the current file
259
260 void
261 File::save_file(bool honor_auto_display)
262 {
263         if (editor_p->buffer()->savefile(filename) != 0) {
264                 fl_alert("failed to save file %s", filename);
265         }
266         else {
267                 // All unsaved changes have now been saved
268                 unsaved_changes = false;
269
270                 // If user wants auto-display on save, do that
271                 if (honor_auto_display) {
272                         int auto_display;
273                         (void) Preferences_p->get(Auto_display_preference, auto_display,
274                                                         Default_auto_display);
275                         if (auto_display) {
276                                 parent_window_p->runmenu_p->Display();
277                         }
278                 }
279         }
280 }
281
282
283 // Utility method to ask user if they want to save changes.
284 // Returns:
285 //      Cancel  don't do anything
286 //      No      do action without saving changes
287 //      Yes     save changes before doing action
288 // The extra_text argument allows caller to enhance the question
289 // asked of the user. The "No" button can be hidden.
290
291 Save_confirm_dialog::Answer
292 File::save_changes_check(const char * extra_text, bool hide_the_No)
293 {
294         if ( ! unsaved_changes ) {
295                 return Save_confirm_dialog::No;
296         }
297         const char * name = effective_filename();
298         char question[200 + strlen(name) + strlen(extra_text)];
299         (void) sprintf(question, "The text in the %s file has changed. "
300                         "Do you want to save the changes? %s", name, extra_text);
301         return(Save_confirm_dialog::confirm_save(question, hide_the_No));
302 }
303
304
305 // Read the given file into the editor window.
306
307 void
308 File::load_file(const char * name)
309 {
310         // Free up existing file name, if any
311         if (filename != 0) {
312                 delete filename;
313                 filename = 0;
314         }
315
316         // If name supplied doesn't have .mup suffix,
317         // try again with that suffix added.
318         char * newname = 0;
319         if (access(name, F_OK) != 0) {
320                 if (strlen(name) < 5 ||
321                                 strcasecmp(name + strlen(name) - 4, ".mup") != 0) {
322                         newname = new char[strlen(name) + 5];
323                         (void) sprintf(newname, "%s.mup", name);
324                 }
325         }
326         if (newname == 0) {
327                 newname = new char[strlen(name) + 1];
328                 (void) strcpy(newname, name);
329         }
330
331         // Mup files are typically only a few Kbytes,
332         // so we probably don't need the default 128 K buffer,
333         // but memory is cheap enough these days, so just go with that.
334         if (editor_p->buffer()->loadfile(newname) != 0) {
335                 fl_alert("Unable to load file \"%s\"", newname);
336                 delete newname;
337         }
338         else {
339                 filename = newname;
340         }
341
342         // Reset state information for a new file
343         begin_new_file();
344
345         // add file name to window label
346         set_window_label();
347 }
348
349 void
350 File::set_window_label()
351 {
352         if (parent_window_p == 0) {
353                 return;
354         }
355         const char * name = effective_filename();
356         char label[strlen(name) + 11];
357         (void) sprintf(label, "%s - Mupmate", name);
358         parent_window_p->copy_label(label);
359 }
360
361
362 const char *
363 File::effective_filename(void)
364 {
365         return(filename == 0 ? "Untitled.mup" : filename);
366 }
367
368 void
369 File::begin_new_file(void)
370 {
371         parent_window_p->begin_new_file();
372         unsaved_changes = false;
373 }
374
375 #ifndef OS_LIKE_WIN32
376 // Add Icon for Mup files.
377
378 // Figure out note polygon one time and just add these offsets for the other...
379 #define RIGHT_NOTE_X    5800
380 #define RIGHT_NOTE_Y    -1200
381
382 static short Mup_icon_data[] = {
383         Fl_File_Icon::COLOR,    0, FL_RED,
384
385         // left stem
386         Fl_File_Icon::POLYGON,
387         Fl_File_Icon::VERTEX, 600, 1200, 
388         Fl_File_Icon::VERTEX, 600, 8200,
389         Fl_File_Icon::VERTEX, 1200, 8200,
390         Fl_File_Icon::VERTEX, 1200, 1200,
391         Fl_File_Icon::VERTEX, 600, 1200,
392         Fl_File_Icon::END,
393
394         // right stem
395         Fl_File_Icon::POLYGON,
396         Fl_File_Icon::VERTEX, 6400, 600, 
397         Fl_File_Icon::VERTEX, 6400, 7000,
398         Fl_File_Icon::VERTEX, 7000, 7000,
399         Fl_File_Icon::VERTEX, 7000, 600,
400         Fl_File_Icon::VERTEX, 6400, 600,
401         Fl_File_Icon::END,
402
403         // beam
404         Fl_File_Icon::POLYGON,
405         Fl_File_Icon::VERTEX, 600, 1200, 
406         Fl_File_Icon::VERTEX, 600, 2700,
407         Fl_File_Icon::VERTEX, 7000, 2100,
408         Fl_File_Icon::VERTEX, 7000, 600,
409         Fl_File_Icon::VERTEX, 600, 2000,
410         Fl_File_Icon::END,
411
412         // left note
413         Fl_File_Icon::POLYGON,
414         Fl_File_Icon::VERTEX, 600, 8200, 
415         Fl_File_Icon::VERTEX, 1600, 9400,
416         Fl_File_Icon::VERTEX, 3200, 9400,
417         Fl_File_Icon::VERTEX, 3800, 8800,
418         Fl_File_Icon::VERTEX, 3800, 8000,
419         Fl_File_Icon::VERTEX, 3000, 7200,
420         Fl_File_Icon::VERTEX, 1600, 7200,
421         Fl_File_Icon::VERTEX, 800, 7900,
422         Fl_File_Icon::VERTEX, 800, 8200,
423         Fl_File_Icon::VERTEX, 500, 9000,
424         Fl_File_Icon::END,
425
426         // right note
427         Fl_File_Icon::POLYGON,
428         Fl_File_Icon::VERTEX, 600 + RIGHT_NOTE_X, 8200 + RIGHT_NOTE_Y, 
429         Fl_File_Icon::VERTEX, 1600 + RIGHT_NOTE_X, 9400 + RIGHT_NOTE_Y,
430         Fl_File_Icon::VERTEX, 3200 + RIGHT_NOTE_X, 9400 + RIGHT_NOTE_Y,
431         Fl_File_Icon::VERTEX, 3800 + RIGHT_NOTE_X, 8800 + RIGHT_NOTE_Y,
432         Fl_File_Icon::VERTEX, 3800 + RIGHT_NOTE_X, 8000 + RIGHT_NOTE_Y,
433         Fl_File_Icon::VERTEX, 3000 + RIGHT_NOTE_X, 7200 + RIGHT_NOTE_Y,
434         Fl_File_Icon::VERTEX, 1600 + RIGHT_NOTE_X, 7200 + RIGHT_NOTE_Y,
435         Fl_File_Icon::VERTEX, 800 + RIGHT_NOTE_X, 7900 + RIGHT_NOTE_Y,
436         Fl_File_Icon::VERTEX, 800 + RIGHT_NOTE_X, 8200 + RIGHT_NOTE_Y,
437         Fl_File_Icon::VERTEX, 500 + RIGHT_NOTE_X, 9000 + RIGHT_NOTE_Y,
438         Fl_File_Icon::END,
439
440         Fl_File_Icon::END
441 };
442
443 void
444 File::add_mup_icon(void)
445 {
446         new Fl_File_Icon("*.mup", Fl_File_Icon::PLAIN,
447                         sizeof(Mup_icon_data) / sizeof(Mup_icon_data[0]),
448                         Mup_icon_data);
449 }
450 #endif
451
452
453
454 // Ask user for name of file to open, and return their choice
455
456 const char *
457 File::open_ask_user(void)
458 {
459 #ifdef OS_LIKE_WIN32
460         OPENFILENAME openfilename;
461         static CHAR path[FL_PATH_MAX] = "*.mup";
462         CHAR dir[FL_PATH_MAX];
463         memset(&openfilename, 0, sizeof(openfilename));
464         GetCurrentDirectory(sizeof(dir), dir);
465         openfilename.lStructSize = sizeof(openfilename);
466         parent_window_p->make_current();
467         openfilename.hwndOwner = fl_window;
468         openfilename.hInstance = fl_display;
469         openfilename.lpstrFilter = "Mup files (*.mup)\0*.mup\0All files (*.*)\0*.*\0";
470         openfilename.lpstrFile = path;
471         openfilename.nMaxFile = sizeof(path);
472         openfilename.lpstrInitialDir = dir;
473         openfilename.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
474         openfilename.lpstrDefExt = ".mup";
475
476         if (GetOpenFileName(&openfilename)) {
477                 return(path);
478         }
479         else {
480                 return(0);
481         }
482
483 #else
484         fl_file_chooser_ok_label("Open");
485         return (fl_file_chooser("Open Mup file", "Mup files (*.mup)\tAll files (*)", "*.mup"));
486 #endif
487 }
488
489
490 // Ask user for name of file to save to, and return their choice
491
492 const char *
493 File::save_as_ask_user(void)
494 {
495 #ifdef OS_LIKE_WIN32
496         OPENFILENAME openfilename;
497         static CHAR path[FL_PATH_MAX] = "*.mup";
498         CHAR dir[FL_PATH_MAX];
499         memset(&openfilename, 0, sizeof(openfilename));
500         GetCurrentDirectory(sizeof(dir), dir);
501         openfilename.lStructSize = sizeof(openfilename);
502         parent_window_p->make_current();
503         openfilename.hwndOwner = fl_window;
504         openfilename.hInstance = fl_display;
505         openfilename.lpstrFilter = "Mup files (*.mup)\0*.mup\0All files (*.*)\0*.*\0";
506         openfilename.lpstrFile = path;
507         openfilename.nMaxFile = sizeof(path);
508         openfilename.lpstrInitialDir = dir;
509         openfilename.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
510         openfilename.lpstrDefExt = ".mup";
511         if (GetSaveFileName(&openfilename)) {
512                 return(path);
513         }
514         else {
515                 return(0);
516         }
517 #else
518         fl_file_chooser_ok_label("Save");
519         return (fl_file_chooser("Save Mup file", "Mup files (*.mup)\tAll files (*)", "*.mup"));
520 #endif
521 }
522
523
524 //---------------------------------------------------------------------
525 // The fl_choice function has the middle button as the default,
526 // but we want the left button (Yes) to be the default for whether
527 // to save before exiting, so we have a special class for it.
528 // It is based on the fl_choice code.
529
530 Save_confirm_dialog::Save_confirm_dialog(const char * text)
531         : Fl_Double_Window(500, 130, "Confirm")
532 {
533         // Make question mark "icon"
534         icon_p = new Fl_Box(10, 10, 50, 50);
535         icon_p->box(FL_THIN_UP_BOX);
536         icon_p->labelfont(FL_TIMES_BOLD);
537         icon_p->labelsize(30);
538         icon_p->color(FL_WHITE);
539         icon_p->labelcolor(FL_BLUE);
540         icon_p->label("?");
541
542         // Print the question
543         message_p = new Fl_Box(90, 20, 400, 40);
544         message_p->label(text);
545         message_p->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT | FL_ALIGN_WRAP);
546
547         yes_p = new Fl_Return_Button(210, h() - 50, 70, 30, "Yes");
548
549         no_p = new Fl_Button(300, h() - 50, 70, 30, "No");
550
551         cancel_p = new Fl_Button(390, h() - 50, 70, 30, "Cancel");
552         cancel_p->shortcut(FL_Escape);
553         show();
554
555         // Arrange for destructor to free new-ed widgets
556         end();
557 }
558
559 Save_confirm_dialog::~Save_confirm_dialog()
560 {
561 }
562
563
564 // Method to bring up dialog to ask user if they want to save changes.
565 // Returns:
566 //      Cancel  don't do anything
567 //      No      do action without saving changes
568 //      Yes     save changes before doing action
569
570 Save_confirm_dialog::Answer
571 Save_confirm_dialog::confirm_save(const char * text, bool hide_the_No)
572 {
573         // Create dialog window
574         Save_confirm_dialog * confirm_p = new Save_confirm_dialog(text);
575
576         // Only show desired buttons
577         if (hide_the_No) {
578                 confirm_p->no_p->hide();
579         }
580
581         // Wait for user to select a button
582         Answer ret;
583         for ( ; ; ) {
584                 Fl_Widget *widget_p = Fl::readqueue();
585                 if (widget_p == 0) {
586                         Fl::wait();
587                 }
588                 else if (widget_p == confirm_p->cancel_p ||
589                                 widget_p == confirm_p) {
590                         ret = Cancel;
591                         break;
592                 }
593                 else if (widget_p == confirm_p->no_p) {
594                         ret = No;
595                         break;
596                 }
597                 else if (widget_p == confirm_p->yes_p) {
598                         ret = Yes;
599                         break;
600                 }
601         }
602
603         // Clean up the dialog window
604         confirm_p->hide();
605         delete confirm_p;
606
607         return ret;
608 }