--- /dev/null
+/* Copyright (c) 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+// Code for the File menu off the main toolbar
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <FL/Fl.H>
+#include <FL/fl_ask.H>
+#ifndef OS_LIKE_WIN32
+#include <FL/Fl_File_Icon.H>
+#endif
+#include <FL/Fl_Shared_Image.H>
+#include <FL/Fl_Input.H>
+#include <FL/filename.H>
+#include <FL/Fl_Bitmap.H>
+#include <FL/Fl_File_Chooser.H>
+#include "globals.H"
+#include "File.H"
+#include "Main.H"
+#include "Preferences.H"
+#include "utils.H"
+#include <time.h>
+#ifdef OS_LIKE_WIN32
+#include <FL/x.H>
+#include <commdlg.h>
+#endif
+
+// The file name filters
+#define Mup_filter "*.mup"
+#define All_filter "*.*"
+
+extern char dir_separator();
+extern const char * const template_text;
+
+
+//-----------------Class to implement the File menu off the main menu bar
+
+File::File()
+{
+ filename = 0;
+ unsaved_changes = false;
+}
+
+File::~File()
+{
+ if (filename) {
+ delete filename;
+ filename = 0;
+ }
+}
+
+
+//--- the "New" menu item -------------
+
+CALL_BACK(File, New)
+{
+ // Ask user if they want to save any unsaved change on
+ // current file first.
+ switch (save_changes_check()) {
+ default: // default case should be impossible
+ case Save_confirm_dialog::Cancel:
+ return;
+ case Save_confirm_dialog::No:
+ break;
+ case Save_confirm_dialog::Yes:
+ Save(false);
+ break;
+ }
+
+ // Clear the current edit buffer
+ Fl_Text_Buffer * buffer_p = editor_p->buffer();
+ buffer_p->replace(0, buffer_p->length(), "");
+
+ if (filename) {
+ delete filename;
+ filename = 0;
+ }
+ set_window_label();
+ // Reset state information
+ begin_new_file();
+}
+
+
+// --- the "New From Template" menu item
+
+CALL_BACK(File, NewFromTemplate)
+{
+ New();
+ editor_p->buffer()->append(template_text);
+ unsaved_changes = false;
+}
+
+//--- the "Open" menu item -------------
+
+CALL_BACK(File, Open)
+{
+ // If already editing a file, ask user if they want to
+ // save any unsaved changes first
+ switch (save_changes_check()) {
+ default: // default case should be impossible
+ case Save_confirm_dialog::Cancel:
+ return;
+ case Save_confirm_dialog::No:
+ break;
+ case Save_confirm_dialog::Yes:
+ Save(false);
+ break;
+ }
+
+ // Clear out label to "Untitled" in case the load fails
+ set_window_label();
+
+ // Ask user for filename. If they give one, load it into editor.
+ const char * newfile = open_ask_user();
+ if (newfile != 0 && newfile[0] != '\0') {
+ load_file(newfile);
+ }
+}
+
+
+//--- the "Save" menu item -------------
+
+// Save the current buffer contents.
+// If honor_auto_display is true and user has requested auto display,
+// do the Run>Display action. If the save was due to finishing up an old
+// file to start on a new or due to saving before exiting, we don't do that;
+// it is only done if user did an explicit Save or Save As.
+
+CALL_BACK_A(File, Save, bool honor_auto_display)
+{
+ if (filename == 0) {
+ // No file name given yet, so change into a Save As
+ SaveAs(honor_auto_display);
+ }
+ else {
+ save_file(honor_auto_display);
+ }
+}
+
+
+//--- the "SaveAs" menu item -------------
+
+CALL_BACK_A(File, SaveAs, bool honor_auto_display)
+{
+ // Ask user for name of file to save to
+ const char * newfile = save_as_ask_user();
+ if (newfile != 0 && newfile[0] != '\0') {
+
+ // If user didn't give a suffix, add .mup suffix.
+ // If they used .ps or .mid or .err suffix, don't allow that.
+ const char * suffix = fl_filename_ext(newfile);
+ char * suffixed_filename = 0;
+ if (*suffix == '\0') {
+ // User did not supply a suffix, so we add .mup
+ suffixed_filename = new char[strlen(newfile) + 5];
+ (void) sprintf(suffixed_filename, "%s.mup", newfile);
+ newfile = suffixed_filename;
+ }
+ else if (strcasecmp(suffix, ".ps") == 0
+ || strcasecmp(suffix, ".mid") == 0
+ || strcasecmp(suffix, ".err") == 0) {
+ fl_alert("A filename extension of .ps .mid or .err\n"
+ "is not allowed for Mup input files,\n"
+ "since those extensions are used for\n"
+ "PostScript, MIDI, and error output files.");
+ return;
+ }
+
+ if (access(newfile, F_OK) == 0) {
+ const char * ask_replace = " already exists. Do you want to replace it?";
+ char question[strlen(newfile) + strlen(ask_replace) + 1];
+ (void) sprintf(question, "%s%s\n", newfile, ask_replace);
+ switch (Save_confirm_dialog::confirm_save(question)) {
+ default: // default case should be impossible.
+ case Save_confirm_dialog::Cancel:
+ case Save_confirm_dialog::No:
+ if (suffixed_filename != 0) {
+ delete suffixed_filename;
+ }
+ return;
+ case Save_confirm_dialog::Yes:
+ break;
+ }
+ }
+
+ // Save the name of the new file
+ if (filename != 0) {
+ // forget previous file name
+ delete filename;
+ }
+ if (suffixed_filename != 0) {
+ filename = suffixed_filename;
+ }
+ else {
+ filename = new char[strlen(newfile) + 1];
+ (void) sprintf(filename, newfile);
+ }
+
+ set_window_label();
+ save_file(honor_auto_display);
+ }
+}
+
+
+//--- the "Exit" menu item -------------
+
+CALL_BACK(File, Exit)
+{
+ switch (save_changes_check()) {
+ default: // default case should be impossible
+ case Save_confirm_dialog::Cancel:
+ return;
+ case Save_confirm_dialog::No:
+ break;
+ case Save_confirm_dialog::Yes:
+ Save(false);
+ break;
+ }
+ Main::clean_exit();
+}
+
+//------------------------------------------------------------------------
+
+// This class needs access to editor window, so whoever creates an instance
+// of this class needs to call this function to give it access.
+
+void
+File::set_editor(Fl_Text_Editor * ed)
+{
+ editor_p = ed;
+}
+
+
+// We need to set parent's label, etc. Save info about parent.
+
+void
+File::set_parent(Main * win_p)
+{
+ parent_window_p = win_p;
+}
+
+// Called when user makes a change in editor window.
+// Let's us know if we need to prompt user about unsaved changes.
+
+void
+File::modify_cb(int, int num_inserted, int num_deleted, int, const char *, void * data)
+{
+ if (num_inserted > 0 || num_deleted > 0) {
+ ((File *)data)->unsaved_changes = true;
+ }
+}
+
+
+// Utility method that does the details of saving the current file
+
+void
+File::save_file(bool honor_auto_display)
+{
+ if (editor_p->buffer()->savefile(filename) != 0) {
+ fl_alert("failed to save file %s", filename);
+ }
+ else {
+ // All unsaved changes have now been saved
+ unsaved_changes = false;
+
+ // If user wants auto-display on save, do that
+ if (honor_auto_display) {
+ int auto_display;
+ (void) Preferences_p->get(Auto_display_preference, auto_display,
+ Default_auto_display);
+ if (auto_display) {
+ parent_window_p->runmenu_p->Display();
+ }
+ }
+ }
+}
+
+
+// Utility method to ask user if they want to save changes.
+// Returns:
+// Cancel don't do anything
+// No do action without saving changes
+// Yes save changes before doing action
+// The extra_text argument allows caller to enhance the question
+// asked of the user. The "No" button can be hidden.
+
+Save_confirm_dialog::Answer
+File::save_changes_check(const char * extra_text, bool hide_the_No)
+{
+ if ( ! unsaved_changes ) {
+ return Save_confirm_dialog::No;
+ }
+ const char * name = effective_filename();
+ char question[200 + strlen(name) + strlen(extra_text)];
+ (void) sprintf(question, "The text in the %s file has changed. "
+ "Do you want to save the changes? %s", name, extra_text);
+ return(Save_confirm_dialog::confirm_save(question, hide_the_No));
+}
+
+
+// Read the given file into the editor window.
+
+void
+File::load_file(const char * name)
+{
+ // Free up existing file name, if any
+ if (filename != 0) {
+ delete filename;
+ filename = 0;
+ }
+
+ // If name supplied doesn't have .mup suffix,
+ // try again with that suffix added.
+ char * newname = 0;
+ if (access(name, F_OK) != 0) {
+ if (strlen(name) < 5 ||
+ strcasecmp(name + strlen(name) - 4, ".mup") != 0) {
+ newname = new char[strlen(name) + 5];
+ (void) sprintf(newname, "%s.mup", name);
+ }
+ }
+ if (newname == 0) {
+ newname = new char[strlen(name) + 1];
+ (void) strcpy(newname, name);
+ }
+
+ // Mup files are typically only a few Kbytes,
+ // so we probably don't need the default 128 K buffer,
+ // but memory is cheap enough these days, so just go with that.
+ if (editor_p->buffer()->loadfile(newname) != 0) {
+ fl_alert("Unable to load file \"%s\"", newname);
+ delete newname;
+ }
+ else {
+ filename = newname;
+ }
+
+ // Reset state information for a new file
+ begin_new_file();
+
+ // add file name to window label
+ set_window_label();
+}
+
+void
+File::set_window_label()
+{
+ if (parent_window_p == 0) {
+ return;
+ }
+ const char * name = effective_filename();
+ char label[strlen(name) + 11];
+ (void) sprintf(label, "%s - Mupmate", name);
+ parent_window_p->copy_label(label);
+}
+
+
+const char *
+File::effective_filename(void)
+{
+ return(filename == 0 ? "Untitled.mup" : filename);
+}
+
+void
+File::begin_new_file(void)
+{
+ parent_window_p->begin_new_file();
+ unsaved_changes = false;
+}
+
+#ifndef OS_LIKE_WIN32
+// Add Icon for Mup files.
+
+// Figure out note polygon one time and just add these offsets for the other...
+#define RIGHT_NOTE_X 5800
+#define RIGHT_NOTE_Y -1200
+
+static short Mup_icon_data[] = {
+ Fl_File_Icon::COLOR, 0, FL_RED,
+
+ // left stem
+ Fl_File_Icon::POLYGON,
+ Fl_File_Icon::VERTEX, 600, 1200,
+ Fl_File_Icon::VERTEX, 600, 8200,
+ Fl_File_Icon::VERTEX, 1200, 8200,
+ Fl_File_Icon::VERTEX, 1200, 1200,
+ Fl_File_Icon::VERTEX, 600, 1200,
+ Fl_File_Icon::END,
+
+ // right stem
+ Fl_File_Icon::POLYGON,
+ Fl_File_Icon::VERTEX, 6400, 600,
+ Fl_File_Icon::VERTEX, 6400, 7000,
+ Fl_File_Icon::VERTEX, 7000, 7000,
+ Fl_File_Icon::VERTEX, 7000, 600,
+ Fl_File_Icon::VERTEX, 6400, 600,
+ Fl_File_Icon::END,
+
+ // beam
+ Fl_File_Icon::POLYGON,
+ Fl_File_Icon::VERTEX, 600, 1200,
+ Fl_File_Icon::VERTEX, 600, 2700,
+ Fl_File_Icon::VERTEX, 7000, 2100,
+ Fl_File_Icon::VERTEX, 7000, 600,
+ Fl_File_Icon::VERTEX, 600, 2000,
+ Fl_File_Icon::END,
+
+ // left note
+ Fl_File_Icon::POLYGON,
+ Fl_File_Icon::VERTEX, 600, 8200,
+ Fl_File_Icon::VERTEX, 1600, 9400,
+ Fl_File_Icon::VERTEX, 3200, 9400,
+ Fl_File_Icon::VERTEX, 3800, 8800,
+ Fl_File_Icon::VERTEX, 3800, 8000,
+ Fl_File_Icon::VERTEX, 3000, 7200,
+ Fl_File_Icon::VERTEX, 1600, 7200,
+ Fl_File_Icon::VERTEX, 800, 7900,
+ Fl_File_Icon::VERTEX, 800, 8200,
+ Fl_File_Icon::VERTEX, 500, 9000,
+ Fl_File_Icon::END,
+
+ // right note
+ Fl_File_Icon::POLYGON,
+ Fl_File_Icon::VERTEX, 600 + RIGHT_NOTE_X, 8200 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 1600 + RIGHT_NOTE_X, 9400 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 3200 + RIGHT_NOTE_X, 9400 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 3800 + RIGHT_NOTE_X, 8800 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 3800 + RIGHT_NOTE_X, 8000 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 3000 + RIGHT_NOTE_X, 7200 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 1600 + RIGHT_NOTE_X, 7200 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 800 + RIGHT_NOTE_X, 7900 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 800 + RIGHT_NOTE_X, 8200 + RIGHT_NOTE_Y,
+ Fl_File_Icon::VERTEX, 500 + RIGHT_NOTE_X, 9000 + RIGHT_NOTE_Y,
+ Fl_File_Icon::END,
+
+ Fl_File_Icon::END
+};
+
+void
+File::add_mup_icon(void)
+{
+ new Fl_File_Icon("*.mup", Fl_File_Icon::PLAIN,
+ sizeof(Mup_icon_data) / sizeof(Mup_icon_data[0]),
+ Mup_icon_data);
+}
+#endif
+
+
+
+// Ask user for name of file to open, and return their choice
+
+const char *
+File::open_ask_user(void)
+{
+#ifdef OS_LIKE_WIN32
+ OPENFILENAME openfilename;
+ static CHAR path[FL_PATH_MAX] = "*.mup";
+ CHAR dir[FL_PATH_MAX];
+ memset(&openfilename, 0, sizeof(openfilename));
+ GetCurrentDirectory(sizeof(dir), dir);
+ openfilename.lStructSize = sizeof(openfilename);
+ parent_window_p->make_current();
+ openfilename.hwndOwner = fl_window;
+ openfilename.hInstance = fl_display;
+ openfilename.lpstrFilter = "Mup files (*.mup)\0*.mup\0All files (*.*)\0*.*\0";
+ openfilename.lpstrFile = path;
+ openfilename.nMaxFile = sizeof(path);
+ openfilename.lpstrInitialDir = dir;
+ openfilename.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
+ openfilename.lpstrDefExt = ".mup";
+
+ if (GetOpenFileName(&openfilename)) {
+ return(path);
+ }
+ else {
+ return(0);
+ }
+
+#else
+ fl_file_chooser_ok_label("Open");
+ return (fl_file_chooser("Open Mup file", "Mup files (*.mup)\tAll files (*)", "*.mup"));
+#endif
+}
+
+
+// Ask user for name of file to save to, and return their choice
+
+const char *
+File::save_as_ask_user(void)
+{
+#ifdef OS_LIKE_WIN32
+ OPENFILENAME openfilename;
+ static CHAR path[FL_PATH_MAX] = "*.mup";
+ CHAR dir[FL_PATH_MAX];
+ memset(&openfilename, 0, sizeof(openfilename));
+ GetCurrentDirectory(sizeof(dir), dir);
+ openfilename.lStructSize = sizeof(openfilename);
+ parent_window_p->make_current();
+ openfilename.hwndOwner = fl_window;
+ openfilename.hInstance = fl_display;
+ openfilename.lpstrFilter = "Mup files (*.mup)\0*.mup\0All files (*.*)\0*.*\0";
+ openfilename.lpstrFile = path;
+ openfilename.nMaxFile = sizeof(path);
+ openfilename.lpstrInitialDir = dir;
+ openfilename.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
+ openfilename.lpstrDefExt = ".mup";
+ if (GetSaveFileName(&openfilename)) {
+ return(path);
+ }
+ else {
+ return(0);
+ }
+#else
+ fl_file_chooser_ok_label("Save");
+ return (fl_file_chooser("Save Mup file", "Mup files (*.mup)\tAll files (*)", "*.mup"));
+#endif
+}
+
+
+//---------------------------------------------------------------------
+// The fl_choice function has the middle button as the default,
+// but we want the left button (Yes) to be the default for whether
+// to save before exiting, so we have a special class for it.
+// It is based on the fl_choice code.
+
+Save_confirm_dialog::Save_confirm_dialog(const char * text)
+ : Fl_Double_Window(500, 130, "Confirm")
+{
+ // Make question mark "icon"
+ icon_p = new Fl_Box(10, 10, 50, 50);
+ icon_p->box(FL_THIN_UP_BOX);
+ icon_p->labelfont(FL_TIMES_BOLD);
+ icon_p->labelsize(30);
+ icon_p->color(FL_WHITE);
+ icon_p->labelcolor(FL_BLUE);
+ icon_p->label("?");
+
+ // Print the question
+ message_p = new Fl_Box(90, 20, 400, 40);
+ message_p->label(text);
+ message_p->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT | FL_ALIGN_WRAP);
+
+ yes_p = new Fl_Return_Button(210, h() - 50, 70, 30, "Yes");
+
+ no_p = new Fl_Button(300, h() - 50, 70, 30, "No");
+
+ cancel_p = new Fl_Button(390, h() - 50, 70, 30, "Cancel");
+ cancel_p->shortcut(FL_Escape);
+ show();
+
+ // Arrange for destructor to free new-ed widgets
+ end();
+}
+
+Save_confirm_dialog::~Save_confirm_dialog()
+{
+}
+
+
+// Method to bring up dialog to ask user if they want to save changes.
+// Returns:
+// Cancel don't do anything
+// No do action without saving changes
+// Yes save changes before doing action
+
+Save_confirm_dialog::Answer
+Save_confirm_dialog::confirm_save(const char * text, bool hide_the_No)
+{
+ // Create dialog window
+ Save_confirm_dialog * confirm_p = new Save_confirm_dialog(text);
+
+ // Only show desired buttons
+ if (hide_the_No) {
+ confirm_p->no_p->hide();
+ }
+
+ // Wait for user to select a button
+ Answer ret;
+ for ( ; ; ) {
+ Fl_Widget *widget_p = Fl::readqueue();
+ if (widget_p == 0) {
+ Fl::wait();
+ }
+ else if (widget_p == confirm_p->cancel_p ||
+ widget_p == confirm_p) {
+ ret = Cancel;
+ break;
+ }
+ else if (widget_p == confirm_p->no_p) {
+ ret = No;
+ break;
+ }
+ else if (widget_p == confirm_p->yes_p) {
+ ret = Yes;
+ break;
+ }
+ }
+
+ // Clean up the dialog window
+ confirm_p->hide();
+ delete confirm_p;
+
+ return ret;
+}