chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mupmate / Main.C
diff --git a/mup/mupmate/Main.C b/mup/mupmate/Main.C
new file mode 100644 (file)
index 0000000..906f6d4
--- /dev/null
@@ -0,0 +1,724 @@
+/* Copyright (c) 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+// Code for the main window for Mupmate, a front end program for
+// the Mup music publisher program from Arkkra Enterprises.
+// It uses the FLTK toolkit for OS independence.
+
+// This file contains code for the toolbar and editor window,
+// as well as general startup and showing the license.
+
+// We only support editing a single file at a time, so most classes
+// as really effectively singletons, but the code is written to be
+// able to support multiple instances, in case we ever want to do that.
+// That means callback functions are always passed a pointer to
+// a class instance as their second argument, and all they do is cast 
+// that to the appropriate type, and call the corresponding class method.
+
+// For the most part, widgets are only allocated when needed, but then
+// stay around for the life of the process, in case they are needed again.
+
+// Callbacks are named with _cb suffix.
+// Pointers are named with _p suffix, except for (char *) types
+// that are pointing to text strings, which don't have any special suffix.
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include <FL/Fl.H>
+#include <FL/fl_ask.H>
+#include <FL/Fl_Tooltip.H>
+
+#include "globals.H"
+#include "Main.H"
+#include "Preferences.H"
+#include "utils.H"
+
+#include <FL/x.H>
+#ifdef OS_LIKE_WIN32
+#include "resource.h"
+#else
+#include <FL/Fl_File_Icon.H>
+#ifdef OS_LIKE_UNIX
+#include <X11/xpm.h>
+#include "mup32.xpm"
+#endif
+#endif
+
+// Height of the tool bar on the main window
+#define TOOLBAR_HEIGHT 30
+
+// How often to blink the cursor.
+#define BLINK_RATE 0.5
+
+// If file indicating user has agreed to license terms doesn't exist,
+// we ask them to agree. This is the text of the license.
+extern const char * const license_text;
+
+//----------------------------------------------------------------------
+
+// Define the toolbar and its submenus.
+//  Indented lines indicate the submenus.
+// The & indicates shortcut key
+
+const char * File_label = "&File";
+ const char * New_label = "&New";
+ const char * NewFromTemplate_label = "New From &Template";
+ const char * Open_label = "&Open...";
+ const char * Save_label = "&Save";
+ const char * SaveAs_label = "Save &As...";
+ const char * Exit_label = "E&xit";
+const char * Edit_label = "&Edit";
+ const char * Undo_label = "&Undo";
+ const char * Cut_label = "Cu&t";
+ const char * Copy_label = "&Copy";
+ const char * Paste_label = "&Paste";
+ const char * Delete_label = "&Delete";
+ const char * Find_label = "&Find...";
+ const char * FindNext_label = "Find &Next";
+ const char * Replace_label = "&Replace...";
+ const char * GoTo_label = "&Go To...";
+ const char * SelectAll_label = "&Select All";
+const char * Run_label = "&Run";
+ const char * Display_label = "&Display";
+ const char * Play_label = "&Play";
+ const char * WritePostScript_label = "&Write PostScript File";
+ const char * WriteMIDI_label = "Write &MIDI File";
+ const char * Options_label = "&Set Options...";
+const char * Config_label = "&Config";
+ const char * FileLocations_label = "&File Locations...";
+ const char * Preferences_label = "&Preferences...";
+ const char * RegistrationForm_label = "&Registration Form...";
+ const char * RegistrationKey_label = "Registration &Key...";
+const char * Help_label = "&Help";
+ const char * UserGuide_label = "Mup &User's Guide";
+ const char * StartupHints_label = "&Startup Hints";
+ const char * AboutMupmate_label = "&About Mupmate";
+
+Fl_Menu_Item Toolbar_menu[] = {
+       { File_label,   0,      0,      0,      FL_SUBMENU },
+               { New_label,    FL_CTRL + 'n',  File::New_cb },
+               { NewFromTemplate_label,        FL_CTRL + 't', File::NewFromTemplate_cb },
+               { Open_label,   FL_CTRL + 'o',  File::Open_cb },
+               { Save_label,   FL_CTRL + 's',  File::Save_cb },
+               { SaveAs_label,         0,      File::SaveAs_cb,        0,      FL_MENU_DIVIDER },
+               { Exit_label,   0,      File::Exit_cb },
+               { 0 },
+       { Edit_label,   0,      0,      0,      FL_SUBMENU },
+               { Undo_label,   FL_CTRL + 'z',  Edit::Undo_cb,  0,      FL_MENU_INACTIVE | FL_MENU_DIVIDER },
+               { Cut_label,    FL_CTRL + 'x',  Edit::Cut_cb,   0,      FL_MENU_INACTIVE },
+               { Copy_label,   FL_CTRL + 'c',  Edit::Copy_cb,  0,      FL_MENU_INACTIVE },
+               { Paste_label,  FL_CTRL + 'v',  Edit::Paste_cb, 0,      FL_MENU_INACTIVE },
+               { Delete_label, FL_Delete,      Edit::Delete_cb,        0,      FL_MENU_INACTIVE | FL_MENU_DIVIDER },
+               { Find_label,   FL_CTRL + 'f',  Edit::Find_cb,  0,      FL_MENU_INACTIVE },
+               { FindNext_label,       FL_F + 3,       Edit::FindNext_cb,      0,      FL_MENU_INACTIVE },
+               { Replace_label,        FL_CTRL + 'h',  Edit::Replace_cb,       0,      FL_MENU_INACTIVE },
+               { GoTo_label,   FL_CTRL + 'g',  Edit::GoTo_cb,  0,      FL_MENU_DIVIDER },
+               { SelectAll_label,      FL_CTRL + 'a',  Edit::SelectAll_cb,     0, FL_MENU_INACTIVE },
+               { 0 },
+       { Run_label,    0,      0,      0,      FL_SUBMENU },
+               { Display_label,                        0,      Run::Display_cb,        0,      FL_MENU_INACTIVE },
+               { Play_label,                   0,      Run::Play_cb,   0,      FL_MENU_INACTIVE },
+               { WritePostScript_label,        0,      Run::WritePostScript_cb,        0, FL_MENU_INACTIVE },
+               { WriteMIDI_label,              0,      Run::WriteMIDI_cb,      0,       FL_MENU_DIVIDER | FL_MENU_INACTIVE },
+               { Options_label,                0,      Run::Options_cb },
+               { 0 },
+       { Config_label, 0,      0,      0,      FL_SUBMENU },
+               { FileLocations_label,          0,      Config::FileLocations_cb },
+               { Preferences_label,            0,      Config::Preferences_cb, 0,      FL_MENU_DIVIDER },
+               { RegistrationForm_label,       0,      Config::RegistrationForm_cb },
+               { RegistrationKey_label,        0,      Config::RegistrationKey_cb },
+               { 0 },
+       { Help_label,   0,      0,      0,      FL_SUBMENU },
+               { UserGuide_label,              0,      Help::Uguide_cb },
+               { StartupHints_label,           0,      Help::Startup_Hints_cb },
+               { AboutMupmate_label,           0,      Help::About_cb },
+               { 0 },
+       { 0 }
+};
+
+
+//----------------------------------------------------------------------
+
+// Linked list of main windows, in case we ever support more than one
+// at a time. (Currently we don't, because we're not sure if it
+// might be more confusing than useful.)
+Main * Main::list_p;
+
+
+// Constructor for main window. It contains toolbar and editor window.
+
+Main::Main(const char * title)
+       : Fl_Double_Window(Default_width, Default_height, title)
+{
+       xclass("mup");
+#ifdef OS_LIKE_WIN32
+       icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON)));
+#else
+#ifdef OS_LIKE_UNIX
+       fl_open_display();
+       Pixmap p, mask;
+       XpmCreatePixmapFromData(fl_display, DefaultRootWindow(fl_display),
+                                 mup32_xpm, &p, &mask, NULL);
+       icon((char *)p);
+#endif
+#endif
+       // Try to use user's default foreground/background
+       Fl::get_system_colors();
+
+       void * data = 0;
+
+       // Create class instances for each toolbar item
+       filemenu_p = new File();
+       editmenu_p = new Edit();
+       configmenu_p = new Config();
+       helpmenu_p = new Help();
+       runmenu_p = new Run();
+
+       // Add to list of windows 
+       next = list_p;
+       list_p = this;
+
+       // Create the toolbar and populate its menu items
+       toolbar_p = new Fl_Menu_Bar(0, 0, w(), TOOLBAR_HEIGHT);
+       int numitems = sizeof(Toolbar_menu) / sizeof(Toolbar_menu[0]);
+       for (int i = 0; i < numitems; i++) {
+               if (Toolbar_menu[i].text != 0) {
+                       // As we move to each top-level menu item,
+                       // keep a pointer to that item, which is then
+                       // used as the argument to callback functions,
+                       // so they know what object to act on.
+                       if (strcmp(Toolbar_menu[i].text, File_label) == 0) {
+                               data = (void *) filemenu_p;
+                       }
+                       else if (strcmp(Toolbar_menu[i].text, Edit_label) == 0) {
+                               data = (void *) editmenu_p;
+                       }
+                       else if (strcmp(Toolbar_menu[i].text, Config_label) == 0) {
+                               data = (void *) configmenu_p;
+                       }
+                       else if (strcmp(Toolbar_menu[i].text, Run_label) == 0) {
+                               data = (void *) runmenu_p;
+                       }
+                       else if (strcmp(Toolbar_menu[i].text, Help_label) == 0) {
+                               data = (void *) helpmenu_p;
+                       }
+               }
+               Toolbar_menu[i].user_data(data);
+       }
+       toolbar_p->copy(Toolbar_menu);
+
+       // Create and configure the editor window
+       editor_p = new Fl_Text_Editor(0, TOOLBAR_HEIGHT, w(),
+                                       h() - TOOLBAR_HEIGHT, "");
+       editor_p->buffer( new Fl_Text_Buffer );
+
+       // Set font/size and arrange to be notified of changes in them
+       font_change_reg_p = new Font_change_registration(font_change_cb,
+                                                               (void *) this);
+
+       // Several objects need to be notified of changes in the
+       // editor window, so they can do things like gray-ungray menu items.
+       editor_p->buffer()->add_modify_callback(modify_cb, (void*) this);
+       editor_p->buffer()->add_modify_callback(File::modify_cb,
+                                       (void*) filemenu_p);
+       editor_p->buffer()->add_modify_callback(Edit::modify_cb,
+                                       (void*) editmenu_p);
+
+       // Initialize state information.
+       have_selection = false;
+       can_paste = false;
+       prev_bufflength = 0;
+       // Undo is inactive until user does something that can be undone.
+       undo_active = false;
+       undo_active_on_next_change = true;
+
+       // Arrange to make cursor blink
+       Fl::add_timeout(BLINK_RATE, blinker, this);
+       cursor_state = 1;
+
+       // Let editor take as much space as is available
+       // if the user resizes the main window. 
+       size_range(Min_width, Min_height, 0, 0);
+       resizable((Fl_Widget *) editor_p);
+
+       // Other classes need to have access to editor and such
+       filemenu_p->set_editor(editor_p);
+       filemenu_p->set_parent(this);
+       editmenu_p->set_editor(editor_p);
+       runmenu_p->set_file(filemenu_p);
+
+       // Arrange for destructor to free the new-ed child widgets
+       end();
+
+       show();
+
+       // Arrange for window manager closes to do Exit.
+       callback(atclose_cb, this);
+       when(FL_WHEN_NEVER);
+
+#ifdef OS_LIKE_UNIX
+       // Arrange for icon to be associated with window
+       XWMHints hints;
+       hints.flags = IconPixmapHint | IconMaskHint ;
+       hints.icon_pixmap = p;
+       hints.icon_mask = mask;
+       XSetWMHints(fl_display, fl_xid((Fl_Window *)this), &hints);
+#endif
+}
+
+
+// Destructor for main window
+
+Main::~Main()
+{
+       delete font_change_reg_p;
+       font_change_reg_p = 0;
+       Fl::remove_timeout(blinker, this);
+       // Remove from list of Main windows 
+       if (list_p == this) {
+               list_p = next;
+       }
+       else {
+               for (Main * m = list_p; m != 0; m = m->next) {
+                       if (m->next == this) {
+                               m->next = this->next;
+                               break;
+                       }
+               }
+       }
+       delete filemenu_p;
+       filemenu_p = 0;
+       delete editmenu_p;
+       editmenu_p = 0;
+       delete configmenu_p;
+       configmenu_p = 0;
+       delete helpmenu_p;
+       helpmenu_p = 0;
+       delete runmenu_p;
+       runmenu_p = 0;
+}
+
+
+// Callback for when user changes font/size
+
+void
+Main::font_change_cb(void * data, Fl_Font font, unsigned char size)
+{
+       ((Main *)data)->font_change(font, size);
+}
+
+
+void
+Main::font_change(Fl_Font font, unsigned char size)
+{
+       // Get shorter name for buffer, as we'll be using it a lot.
+       Fl_Text_Buffer * buffer_p = editor_p->buffer();
+
+       // Don't want this change to count as something that can be undone
+       buffer_p->canUndo(false);
+
+       // We want to change the entire text buffer, so need to
+       // select its whole contents. If there was already a selection,
+       // save that and put it back when we are done
+       int sel_start, sel_end;
+       int had_selection;
+       int cursorplace = editor_p->insert_position();
+       if ((had_selection = buffer_p->selected()) != 0) {
+               buffer_p->selection_position(&sel_start, &sel_end);
+               buffer_p->unselect();
+       }
+
+       // set new font and size
+       buffer_p->select(0, editor_p->buffer()->length() - 1);
+       editor_p->textfont(font);
+       editor_p->textsize(size);
+       buffer_p->unselect();
+
+       // Put selection and cursor back as they were before font change
+       if (had_selection) {
+               buffer_p->select(sel_start, sel_end);
+       }
+       editor_p->insert_position(cursorplace);
+       buffer_p->canUndo(true);
+}
+
+
+// Callback for when editor window changes.
+// This arranges to gray/ungray toolbar menu items.
+
+void
+Main::modify_cb(int, int, int, int, const char *, void * data)
+{
+       ((Main *)data)->modify();
+}
+
+void
+Main::modify()
+{
+       int bufflength = editor_p->buffer()->length();
+       // See if what changed is something we might care about.
+       if (editor_p->buffer()->selected() != have_selection ||
+                       editmenu_p->can_paste() != can_paste
+                       || undo_active != undo_active_on_next_change
+                       || bufflength < 2 || prev_bufflength == 0) {
+
+               // Something changed, and we may need to
+               // gray or ungray menu items in response.
+               have_selection = editor_p->buffer()->selected();
+               const Fl_Menu_Item * menu_p = toolbar_p->menu();
+               Fl_Menu_Item * item_p;
+               // Walk through toolbar and submenus, checking
+               // if anything needs to be grayed/ungrayed.
+               for (int i = 0; i < toolbar_p->size(); i++) {
+                       const char * mtext = toolbar_p->text(i);
+                       if (mtext == 0) {
+                               continue;
+                       }
+                       // Can only Copy, Cut, and Delete if something
+                       // is selected.
+                       if (strcmp(mtext, Copy_label) == 0 ||
+                                       strcmp(mtext, Cut_label) == 0 ||
+                                       strcmp(mtext, Delete_label) == 0) {
+                               // have to un-const so we can (de)activate
+                               item_p = (Fl_Menu_Item *) &(menu_p[i]);
+                               if (have_selection) {
+                                       item_p->activate();
+                               }
+                               else {
+                                       item_p->deactivate();
+                               }
+                       }
+
+                       // Paste is different. It becomes active when there
+                       // is something in clipboard, and never again becomes
+                       // inactive.
+                       if (strcmp(mtext, Paste_label) == 0 &&
+                                               editmenu_p->can_paste()) {
+                               ((Fl_Menu_Item *)&(menu_p[i]))->activate();
+                               can_paste = true;
+                       }
+
+                       // Undo is also different. On first change of any
+                       // kind it become active, and stays that way,
+                       // except it gets reset on new file.
+                       if (strcmp(mtext, Undo_label) == 0) {
+                               if (undo_active && ! undo_active_on_next_change) {
+                                       ((Fl_Menu_Item *)&(menu_p[i]))->deactivate();
+                                       undo_active = false;
+                                       undo_active_on_next_change = true;
+                               }
+                               else if ( ! undo_active && undo_active_on_next_change) {
+                                       ((Fl_Menu_Item *)&(menu_p[i]))->activate();
+                                       undo_active = true;
+                               }
+                       }
+
+                       // Find and FindNext are inactive when file is empty,
+                       // because obviously there is nothing to find.
+                       // Similar for Replace and Select All.
+                       // Also for all the Run things
+                       if (strcmp(mtext, Find_label) == 0 ||
+                                       strcmp(mtext, FindNext_label) == 0 ||
+                                       strcmp(mtext, Replace_label) == 0 ||
+                                       strcmp(mtext, SelectAll_label) == 0 ||
+                                       strcmp(mtext, Display_label) == 0 ||
+                                       strcmp(mtext, Play_label) == 0 ||
+                                       strcmp(mtext, WritePostScript_label) == 0 ||
+                                       strcmp(mtext, WriteMIDI_label) == 0) {
+                               if (bufflength == 0) {
+                                       ((Fl_Menu_Item *)&(menu_p[i]))->deactivate();
+                               }
+                               else {
+                                       ((Fl_Menu_Item *)&(menu_p[i]))->activate();
+                               }
+                       }
+               }
+       }
+       prev_bufflength = bufflength;
+}
+
+
+// This method gets called when user starts working on a new file.
+// It sets state information so we can know how to gray menu items properly.
+
+void
+Main::begin_new_file()
+{
+       // transition Undo-ability state from true to false
+       undo_active = true;
+       undo_active_on_next_change = false;
+       editor_p->buffer()->call_modify_callbacks();
+       runmenu_p->clean_up();
+}
+
+
+// Handle some special cases.
+// 1. By default fltk will exit the main window upon getting escape.
+// That seems bad, since a vi user will be used to hitting escape all the
+// time when editing, because that is always a "safe" thing to do,
+// and if they did it here by mistake, they would lose all
+// their text entry since the last save. So we ignore the escape in this window.
+// I suppose we could ask if they really want to quit...
+// 2. If user does cut or copy via keyboard accelerator, the normal code
+// for ungraying the Paste button doesn't get called, so we catch that case
+// here and ungray it.
+
+int
+Main::handle_events(int e)
+{
+       // If escape is received while on main window,
+       // return 1 to show that we consumed the event.
+       if (e == FL_SHORTCUT && Fl::event_key() == FL_Escape) {
+               for (Main * m_p = list_p; m_p != 0; m_p = m_p->next) {
+                       if (Fl::first_window() == m_p) {
+                               return(1);
+                       }
+               }
+       }
+
+       // If user did cut or copy via cntl-c or cntl-x,
+       // arrange to ungray Paste.
+       if (e == FL_KEYUP && (Fl::event_state() & FL_CTRL)  &&
+                       (Fl::event_key() == 'v' || Fl::event_key() == 'x')) {
+               for (Main * m_p = list_p; m_p != 0; m_p = m_p->next) {
+                       if (Fl::first_window() == m_p) {
+                               m_p->editmenu_p->set_can_paste();
+                               break;
+                       }
+               }
+       }
+
+       return(0);
+}
+
+
+// If user tries to close the main window via the window manager
+// while having unsaved changes, we ask user if they want to save
+// the changes first. 
+
+CALL_BACK(Main, atclose)
+{
+       File::Exit_cb(0, filemenu_p);
+}
+
+
+// Blink the cursor. It can be hard to see if next to selected text if not
+// blinking. We could potentially optimize to only do this while
+// the window has focus, but it doesn't seem worth the complication...
+
+void
+Main::blinker(void * data)
+{
+       Main * obj_p = (Main *) data;
+       // Put cursor into opposite of its current state
+       obj_p->cursor_state ^= 1;
+       obj_p->editor_p->show_cursor(obj_p->cursor_state);
+       // Reset timer to call ourselves again.
+       Fl::repeat_timeout(BLINK_RATE, blinker, data);
+}
+
+
+// Give user hints if we haven't already done that before.
+
+void
+Main::hints(void)
+{
+       int did_startup;
+       (void) Preferences_p->get(Showed_startup_hints, did_startup,
+                                       Default_startup_hints_flag);
+       if ( ! did_startup) { 
+               Help::Startup_Hints_cb(0, (void *) helpmenu_p);
+       }
+}
+
+//----------------------------------------------------------------------
+
+int
+main(int argc, char **argv, const char **arge)
+{
+       // The arge value may get changed when we set new environment
+       // variables, so look up PATH first thing.
+       get_path(arge);
+
+       // Uguide browser needs to show images
+       fl_register_images();
+
+#ifndef OS_LIKE_WIN32
+       // On Windows we use the native Open/Save As dialogs.
+       // On other platforms we use FLTK's, but add icon for Mup files.
+       Fl_File_Icon::load_system_icons();
+       File::add_mup_icon();
+#endif
+
+       // Try to get best hardware support for graphics
+       Fl::visual(FL_DOUBLE|FL_INDEX);
+
+       // Get the user's preferences that persists across sessions
+       Preferences_p = new Fl_Preferences(Fl_Preferences::USER,
+                                       "arkkra.com", "mupmate");
+
+       // Enable tips when user hovers their mouse over a widget.
+       // If user doesn't like them, they can set delay to huge value.
+       Fl_Tooltip::enable();
+       double tooltips_delay;
+       (void) Preferences_p->get(Tooltips_delay_preference, tooltips_delay,
+                                       Default_tooltips_delay);
+       Fl_Tooltip::delay(tooltips_delay);
+
+       // Set $MUPPATH
+       char * val;
+       (void) Preferences_p->get(MUPPATH_location, val,
+                                               Default_MUPPATH_location);
+       set_muppath(val);
+
+       // Tell Mup that it is being run via mupmate,
+       // so it can give more appropriate error messages.
+       putenv("MUPMATE=1");
+
+       // Create main window
+       Main *main_p = new Main("Mupmate");
+
+       // Ensure "escape" key doesn't kill main window,
+       // and make sure Paste ungraying works
+       Fl::add_handler(Main::handle_events);
+
+       // Try to find some reasonable defaults for configuration items
+       // that aren't already set.
+       deduce_helper_locations();
+
+       // If magic file indicating license agreement isn't there,
+       // ask user to agree to Mup license.
+       if (access(magic_file(argv[0]), F_OK) != 0) {
+               // Show the license and get agreement before continuing
+               new License(main_p, magic_file(argv[0]));
+       }
+       else {
+               // Display the main window.
+               main_p->show(1, argv);
+
+               // The first time, we show user some hints.
+               main_p->hints();
+       }
+
+       // Go to where user said they want to store their Mup files by default.
+       // Need to wait to do this until after we have deduced locations
+       // of executable, in case they were in current directory.
+       char * mup_dir;
+       (void) Preferences_p->get(Music_files_location, mup_dir,
+                                               Default_music_files_location);
+       if (strcmp(mup_dir, ".") != 0) {
+               if (chdir(mup_dir) != 0) {
+                       char curr_dir[FL_PATH_MAX] = "current";
+                       char message[2 * FL_PATH_MAX + 100];
+                       (void *) getcwd(curr_dir, sizeof(curr_dir));
+                       sprintf(message, "Unable to change to folder\n"
+                               "\"%s.\"\nStaying in \"%s\" folder.\n"
+                               "Fix setting of \"Folder for Mup Files\"\n"
+                               "in Config->File Locations.",
+                               mup_dir, curr_dir);
+                       fl_alert(message);
+               }
+       }
+
+       // Expect 0 or 1 args. If 1, should be name of file to load
+       if (argc > 1) {
+               main_p->filemenu_p->load_file(argv[1]);
+       }
+       if (argc > 2) {
+               fl_alert("Only expecting one file; extra arguments are being ignored.");
+       }
+
+       // Go into main event-handler loop
+       int exitvalue = Fl::run();
+       Main::clean_exit(exitvalue);
+       /*NOTREACHED*/
+       return(exitvalue);
+}
+
+
+// Clean up all the windows and their children and exit.
+
+void
+Main::clean_exit(int exitval)
+{
+       Main * m_p;
+       Main * nextwin_p;
+
+       for (m_p = list_p; m_p != 0; m_p = nextwin_p) {
+               nextwin_p = m_p->next;
+               delete m_p;
+       }
+       exit(exitval);
+}
+
+//---------- class to show Mup license and get user's agreement
+
+
+License::License(Main *m_p, const char * magic)
+       : Fl_Double_Window(Default_width, Default_height, "Mup License")
+{
+       // save passed-in info in object data
+       main_p = m_p;
+       magic_file_name = magic;
+
+       // widget for displaying the license text
+       text_p = new Fl_Text_Display(20, 20, w() - 40, h() - 90);
+       resizable((Fl_Widget *) text_p);
+       text_p->buffer( new Fl_Text_Buffer () );
+       text_p->textsize(18);
+       text_p->buffer()->text(license_text);
+
+       i_agree_p = new Fl_Return_Button(100, h() - 50, 100, 30, "I Agree");
+       i_agree_p->callback(IAgree_cb, this);
+
+       cancel_p = new Fl_Button(w() - 200, h() - 50, 100, 30, "Cancel");
+       cancel_p->callback(Cancel_cb, this);
+       cancel_p->shortcut(FL_Escape);
+
+       show();
+
+       // Arrange for destructor to free the new-ed child widgets
+       end();
+
+       // Arrange for window manager closes to do Cancel.
+       callback(Cancel_cb, this);
+       when(FL_WHEN_NEVER);
+}
+
+
+License::~License()
+{
+}
+
+
+// Callback for when user clicks that they agree to the license.
+
+CALL_BACK(License, IAgree)
+{
+       // Create the magic file
+       int fd;
+       if ((fd = open(magic_file_name, O_WRONLY | O_CREAT, 0644)) < 0) {
+               fl_alert("Unable to create file indicating license agreement.");
+       }
+       else {
+               close(fd);
+       }
+
+       hide();
+
+       // Bring up the normal main window
+       main_p->show();
+       main_p->hints();
+}
+
+// Callback if user refuses to accept license. We just exit if they refuse.
+
+CALL_BACK(License, Cancel)
+{
+       exit(0);
+}