chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mupmate / File.C
CommitLineData
69695f33
MW
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
36extern char dir_separator();
37extern const char * const template_text;
38
39
40//-----------------Class to implement the File menu off the main menu bar
41
42File::File()
43{
44 filename = 0;
45 unsaved_changes = false;
46}
47
48File::~File()
49{
50 if (filename) {
51 delete filename;
52 filename = 0;
53 }
54}
55
56
57//--- the "New" menu item -------------
58
59CALL_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
90CALL_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
99CALL_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
133CALL_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
147CALL_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
211CALL_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
231void
232File::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
240void
241File::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
249void
250File::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
260void
261File::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
291Save_confirm_dialog::Answer
292File::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
307void
308File::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
349void
350File::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
362const char *
363File::effective_filename(void)
364{
365 return(filename == 0 ? "Untitled.mup" : filename);
366}
367
368void
369File::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
382static 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
443void
444File::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
456const char *
457File::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
492const char *
493File::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
530Save_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
559Save_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
570Save_confirm_dialog::Answer
571Save_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}