chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mupmate / Config.C
diff --git a/mup/mupmate/Config.C b/mup/mupmate/Config.C
new file mode 100644 (file)
index 0000000..446238a
--- /dev/null
@@ -0,0 +1,1089 @@
+/* Copyright (c) 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+// Code for the Config menu item on the main toolbar
+
+#include "globals.H"
+#include "Preferences.H"
+#include "Config.H"
+#include "Main.H"
+#include "utils.H"
+#include <FL/fl_ask.H>
+#include <FL/Fl_Tooltip.H>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+
+// Window to ask user where files and tools are located
+
+FileLocations_dialog::FileLocations_dialog(void)
+       : Fl_Double_Window(620, 300, "Mupmate File Locations")
+{
+       mup_documentation_p = new Fl_Input(200, 30, 400, 30, "Mup Documentation Folder");
+       mup_documentation_p->tooltip("Set where Mup documentation\n"
+                               "files are installed on your system.\n"
+                               "This folder must contain the \"uguide\"\n"
+                               "folder that contains the HTML version\n"
+                               "of the Mup User's Guide.");
+
+       mup_program_p = new Fl_Input(200, 65, 400, 30, "Mup Command Path");
+       mup_program_p->tooltip("Set where the Mup program\n"
+                               "is installed on your system.");
+
+       music_files_p = new Fl_Input(200, 100, 400, 30, "Folder for Mup Files");
+       music_files_p->tooltip("Set the default folder for storing\n"
+                               "your Mup files (.mup input files,\n"
+                               "and .ps and .mid output files).");
+
+       muppath_p = new Fl_Input(200, 135, 400, 30, "Folder for Mup include Files");
+       static char include_tip_text[200];
+       (void) sprintf(include_tip_text,
+                       "Set the default folder (or list of folders,\n"
+                       "separated by %c characters) for\n"
+                       "storing your Mup \"include\" files.",
+                       path_separator());
+       muppath_p->tooltip(include_tip_text);
+
+       viewer_p = new Fl_Input(200, 170, 400, 30, "PostScript Viewer Path");
+       viewer_p->tooltip("Set which PostScript viewing program\n"
+                               "to use for displaying Mup output.\n"
+#ifdef OS_LIKE_WIN32
+                               "This is typically GSview32.exe\n"
+                               "which you can obtain from\n"
+                               "http://www.cs.wisc.edu/~ghost/gsview/"
+#else
+                               "The \"gv\" program is a common choice."
+#endif
+                       );
+
+       player_p = new Fl_Input(200, 205, 400, 30, "MIDI Player Path");
+       player_p->tooltip("Set which MIDI player program\n"
+                               "to use for playing Mup MIDI output.\n"
+#ifdef OS_LIKE_WIN32
+                               "This is typically wmplayer.exe"
+#else
+                               "Common choices include xplaymidi or timidity."
+#endif
+                       );
+
+       apply_p = new Fl_Return_Button(50, 255, 100, 30, "Apply");
+       apply_p->when(FL_WHEN_RELEASE);
+       apply_p->callback(Apply_cb, this);
+       
+       cancel_p = new Fl_Button(w() - 150, 255, 100, 30, "Cancel");
+       cancel_p->shortcut(FL_Escape);
+       cancel_p->when(FL_WHEN_RELEASE);
+       cancel_p->callback(Cancel_cb, this);
+
+       // Populate the fields
+       set_current_values();
+
+       // Arrange for destructor to clean up new-ed widgets
+       end();
+
+       // Arrange for window manager closes to do Cancel.
+       callback(Cancel_cb, this);
+       when(FL_WHEN_NEVER);
+}
+
+
+FileLocations_dialog::~FileLocations_dialog()
+{
+}
+
+
+//--- Callback for when user clicks "Apply" on FileLocations dialog.
+// Save values in preferences file.
+
+CALL_BACK(FileLocations_dialog, Apply)
+{
+       bool changes = false;           // if any changes made
+       bool error = false;             // if any errors found
+       char location[FL_PATH_MAX];
+
+       // Documentation location
+       if (mup_documentation_p->size() > 0) {
+               (void) Preferences_p->set(Mup_documentation_location,
+                                               mup_documentation_p->value());
+               changes = true;
+               // Documentation being wrong means User's Guide can't be
+               // shown, which is bad, although not fatal.
+               if ( ! fl_filename_isdir(mup_documentation_p->value()) ) {
+                       fl_alert("Location for Mup documentation is not a valid folder.");
+                       error = true;
+               }
+               else {
+                       if (access(users_guide_index_file(
+                                       mup_documentation_p->value()), F_OK)
+                                       != 0) {
+                               fl_alert("Folder specified for Mup documentation it not correct:\n"
+                                       "it does not contain the Mup User's Guide.");
+                               error = true;
+                       }
+               }
+       }
+
+       // Location of Mup program
+       if (mup_program_p->size() > 0) {
+               if (find_executable(mup_program_p->value(), location)) {
+                       (void) Preferences_p->set(Mup_program_location,
+                                               mup_program_p->value());
+                       changes = true;
+               }
+               else {
+                       fl_alert("Location specified for Mup program is not valid.");
+                       error = true;
+               }
+       }
+
+       // Default folder for Mup input files
+       if (music_files_p->size() > 0) {
+               if (chdir(music_files_p->value()) != 0) {
+                       fl_alert("Value for \"Folder for Mup Files\" is not a valid folder.");
+                       error = true;
+               }
+               else {
+                       (void) Preferences_p->set(Music_files_location,
+                                               music_files_p->value());
+                       changes = true;
+               }
+       }
+
+       // $MUPPATH value
+       if (muppath_p->size() > 0) {
+               (void) Preferences_p->set(MUPPATH_location, muppath_p->value());
+               // Set $MUPPATH
+               set_muppath(muppath_p->value());
+               changes = true;
+               // Setting MUPPATH correctly is only important if user
+               // actually uses it, which many people won't, but if it is set
+               // to something invalid, we give a warning.
+               // Since MUPPATH can be a list, we check each component
+               // in the list.
+               char pathcopy[muppath_p->size() + 1];
+               (void) strcpy(pathcopy, muppath_p->value());
+               char * component_p = pathcopy;
+               char * sep_p;   // where path separator appears in list
+               do {
+                       if ((sep_p = strchr(component_p, path_separator())) != 0) {
+                               *sep_p = '\0';
+                       }
+                       if (strlen(component_p) > 0 &&
+                                       ! fl_filename_isdir(component_p)) {
+                               fl_alert("Location for Mup include files\n"
+                                       "\"%s\"\nis not a valid folder.",
+                                       component_p);
+                               error = true;
+                       }
+                       component_p += strlen(component_p) + 1;
+               } while (sep_p != 0);
+       }
+
+       // PostScript viewer program
+       if (viewer_p->size() > 0) {
+               if (find_executable(viewer_p->value(), location)) {
+                       (void) Preferences_p->set(Viewer_location, viewer_p->value());
+                       changes = true;
+               }
+               else {
+                       fl_alert("Location specified for PostScript viewer is not valid.");
+                       error = true;
+               }
+       }
+
+       // MIDI player
+       if (player_p->size() > 0) {
+               if (find_executable(player_p->value(), location)) {
+                       (void) Preferences_p->set(MIDI_player_location, player_p->value());
+                       changes = true;
+               }
+               else {
+                       fl_alert("Location specified for MIDI player is not valid.");
+                       error =  true;
+               }
+       }
+
+       // If any changes, persist the data.
+       if (changes) {
+               Preferences_p->flush();
+       }
+
+       // If there were errors, leave form up so user can try to correct them.
+       if ( ! error ) {
+               hide();
+       }
+}
+
+
+//--- callback for when user clicks "Cancel" on FileLocations dialog
+
+CALL_BACK(FileLocations_dialog, Cancel)
+{
+       hide();
+       // Put all the original settings back on the form
+       set_current_values();
+}      
+
+
+// Populate form with the current default values from user's preferences.
+
+void
+FileLocations_dialog::set_current_values(void)
+{
+       char * val;
+       (void) Preferences_p->get(Mup_documentation_location, val,
+                                       Default_Mup_documentation_location);
+       mup_documentation_p->value(val);
+
+       (void) Preferences_p->get(Mup_program_location, val,
+                                       Default_Mup_program_location);
+       mup_program_p->value(val);
+
+       (void) Preferences_p->get(Music_files_location, val,
+                                       Default_music_files_location);
+       music_files_p->value(val);
+       (void) Preferences_p->get(MUPPATH_location, val,
+                                       Default_MUPPATH_location);
+       muppath_p->value(val);
+       (void) Preferences_p->get(Viewer_location, val,
+                                       Default_viewer_location);
+       viewer_p->value(val);
+       (void) Preferences_p->get(MIDI_player_location, val,
+                                       Default_MIDI_player_location);
+       player_p->value(val);
+}
+
+
+//-----------------------------------------------------------------
+
+// List of standard FLTK fonts, and info to map name to menu entry.
+static struct Font {
+       const char * name;
+       Fl_Font value;
+       int menu_offset;
+} Fontlist[] = {
+       { "Courier",                    FL_COURIER },
+       { "Courier Bold",               FL_COURIER_BOLD },
+       { "Courier Italic",             FL_COURIER_ITALIC },
+       { "Courier Bold Italic",        FL_COURIER_BOLD_ITALIC },
+       { "Helvetica",                  FL_HELVETICA },
+       { "Helvetica Bold",             FL_HELVETICA_BOLD },
+       { "Helvetica Italic",           FL_HELVETICA_ITALIC },
+       { "Helvetica Bold Italic",      FL_HELVETICA_BOLD_ITALIC },
+       { "Times",                      FL_TIMES },
+       { "Times Bold",                 FL_TIMES_BOLD },
+       { "Times Italic",               FL_TIMES_ITALIC },
+       { "Times Bold Italic",          FL_TIMES_BOLD_ITALIC },
+};
+static const int Fontlistlength = sizeof(Fontlist) / sizeof(Fontlist[0]);
+
+// Window to ask user preferences, like editor font, size, etc.
+
+Preferences_dialog::Preferences_dialog(void)
+       : Fl_Double_Window(400, 280, "Mupmate Preferences")
+{
+       // Make widget for user's editor font choice.
+       font_p = new Fl_Choice(20, 40, 210, 30, "Text Font");
+       font_p->tooltip("Select the font to be used\n"
+                       "in the editor window where you\n"
+                       "type in Mup input. It is also used\n"
+                       "for the Help and error report text.");
+       // Arrange to reset size menu if font selection changes
+       font_p->callback(fontchg_cb, this);
+       font_p->when(FL_WHEN_CHANGED);
+       font_p->align(FL_ALIGN_TOP_LEFT);
+
+       // Make widget for user's editor size choice.
+       size_p = new Fl_Choice(270, 40, 100, 30, "Text Size");
+       size_p->tooltip("Select the text size to be used\n"
+                       "in the editor window where you\n"
+                       "type in Mup input. It is also used\n"
+                       "for the Help and error report text.");
+       size_p->align(FL_ALIGN_TOP_LEFT);
+
+       auto_display_p = new Fl_Check_Button(20, 90, 180, 30,
+                                               "Auto-Display on Save");
+       auto_display_p->tooltip("Set whether your music\n"
+                       "is displayed automatically\n"
+                       "whenever you save your Mup file.");
+
+       auto_save_p = new Fl_Check_Button(w() - 170, 90, 150, 30,
+                                               "Auto-Save on Run");
+       auto_save_p->tooltip("Set whether your music is saved\n"
+                       "automatically whenever you do Display, Play,\n"
+                       "Write PostScript or Write MIDI from the Run menu.");
+
+       tooltips_delay_p = new Fl_Value_Input(150, 155, 100, 30, "Tool Tip Delay");
+       tooltips_delay_p->minimum(0.0);
+       tooltips_delay_p->precision(3);
+       tooltips_delay_p->tooltip("Set how long to delay before showing\n"
+                       "tool tips, in seconds.\n");
+       tooltips_delay_p->align(FL_ALIGN_TOP_LEFT);
+
+       // Create and configure widget for Apply button
+       apply_p = new Fl_Return_Button(60, 215, 100, 30, "Apply");
+       apply_p->when(FL_WHEN_RELEASE);
+       apply_p->callback(Apply_cb, this);
+
+       // Create and configure widget for Cancel button
+       cancel_p = new Fl_Button(w() - 160, 215, 100, 30, "Cancel");
+       cancel_p->shortcut(FL_Escape);
+       cancel_p->when(FL_WHEN_RELEASE);
+       cancel_p->callback(Cancel_cb, this);
+
+       // Populate the fields
+       set_current_values();
+
+       // Arrange for destructor to clean up new-ed widgets
+       end();
+
+       // Arrange for window manager closes to do Cancel.
+       callback(Cancel_cb, this);
+       when(FL_WHEN_NEVER);
+}
+
+Preferences_dialog::~Preferences_dialog()
+{
+}
+
+
+//---- Callback for when user changes font selection.
+// This re-creates the size menu to be what sizes are available
+// for that font, since each font could have a different set of sizes.
+
+CALL_BACK(Preferences_dialog, fontchg)
+{
+       unsigned char size;
+       if (size_p->mvalue() != 0) {
+               size = atoi(size_p->mvalue()->text);
+       }
+       else {
+               // Shouldn't really be possible to get here,
+               // but better to be safe.
+               size = (unsigned char) atoi(Default_editor_size);
+       }
+
+       set_size_list(Config::fontvalue(font_p->mvalue()->text), size);
+}
+
+
+//--- Callback for when user clicks Apply in Preferences
+// Save the new values.
+
+CALL_BACK(Preferences_dialog, Apply)
+{
+       Fl_Font font;
+       int n;
+
+       Preferences_p->set(Auto_display_preference, auto_display_p->value());
+       Preferences_p->set(Auto_save_preference, auto_save_p->value());
+       Preferences_p->set(Tooltips_delay_preference, tooltips_delay_p->value());
+       Fl_Tooltip::delay(tooltips_delay_p->value());
+
+       // Convert font menu selection into font value.
+       for (n = 0; n < Fontlistlength; n++) {
+               if (Fontlist[n].menu_offset == font_p->value()) {
+                       Preferences_p->set(Editor_font_preference, Fontlist[n].name);
+                       font = Fontlist[n].value;
+                       break;
+               }
+       }
+       if (n >= Fontlistlength) {
+               // Selection not valid. Fall back to using the default.
+               char * fontname;
+               (void) Preferences_p->get(Editor_font_preference, fontname,
+                                                       Default_editor_font);
+               font = Config::fontvalue(fontname);
+       }
+
+       // Save size value.
+       unsigned char size;
+       if (size_p->text() != 0) {
+               (void) Preferences_p->set(Editor_size_preference, size_p->text());
+               size = (unsigned char) atoi(size_p->text());
+       }
+       else {
+               size = (unsigned char) atoi(Default_editor_size);
+       }
+
+       // Persist the data.
+       Preferences_p->flush();
+
+       // Actually change the font/size in all relevant windows.
+       // Windows that want to know about these changes register a callback,
+       // so we call them.
+       Font_change_registration::run_callbacks(font, size);
+
+       hide();
+}
+
+//--- callback for when user clicks Cancel in Preferences
+
+CALL_BACK(Preferences_dialog, Cancel)
+{
+       hide();
+       // Put all the original settings back on the form
+       set_current_values();
+}
+
+
+// Populate form with current values from user's preferences
+
+void
+Preferences_dialog::set_current_values(void)
+{
+       int auto_display;
+       (void) Preferences_p->get(Auto_display_preference, auto_display,
+                                               Default_auto_display);
+       auto_display_p->value(auto_display);
+
+       int auto_save;
+       (void) Preferences_p->get(Auto_save_preference, auto_save,
+                                               Default_auto_save);
+       auto_save_p->value(auto_save);
+
+       double tooltips_delay;
+       (void) Preferences_p->get(Tooltips_delay_preference, tooltips_delay,
+                                               Default_tooltips_delay);
+       tooltips_delay_p->value(tooltips_delay);
+
+       char * fontname;
+       (void) Preferences_p->get(Editor_font_preference, fontname,
+                                               Default_editor_font);
+       Fl_Font font = Config::fontvalue(fontname);
+       // Populate font menu
+       font_p->clear();
+       for (int i = 0; i < Fontlistlength; i++) {
+               Fontlist[i].menu_offset =
+                       font_p->add(Fontlist[i].name, 0, 0, 0, 0);
+               // Set the current value
+               if (Fontlist[i].value == font) {
+                       font_p->value(Fontlist[i].menu_offset);
+               }
+       }
+
+       char * sizename;
+       (void) Preferences_p->get(Editor_size_preference, sizename,
+                                               Default_editor_size);
+       unsigned char size = (unsigned char) atoi(sizename);
+       // Populate the size menu
+       set_size_list(font, size);
+}
+
+
+// When font selection changes, re-create the size menu,
+// because each font could have different sizes available.
+
+void
+Preferences_dialog::set_size_list(Fl_Font font, uchar curr_size)
+{
+       // Avoid really tiny sizes, or more importantly, zero, like if an atoi
+       // failed, because otherwise FLTK may try to divide by zero.
+       // Also limit to a maximum size.
+       if (curr_size < Min_size || curr_size > Max_size) {
+               curr_size = (unsigned char) atoi(Default_editor_size);
+       }
+
+       // Clean out the current menu if any
+       size_p->clear();
+       
+       // Populate the menu
+       int * sizelist;
+       int numsizes = Fl::get_font_sizes(font, sizelist);
+       
+       // Set current value to ridiculous value, then find closest
+       int currvalue = 5000;
+
+       int i;  // index through sizelist
+       int menu_index; // index into menu
+       for (i = menu_index = 0; i < numsizes; i++) {
+               if (sizelist[i] == 0) {
+                       // This means font is scaleable 
+                       continue;
+               }
+               if (sizelist[i] > Max_size) {
+                       break;
+               }
+               char num_as_string[4];
+               (void) sprintf(num_as_string, "%d", sizelist[i]);
+               size_p->add(num_as_string, 0, 0, 0, 0);
+               // If this is closest index to desired size, mark as current
+               if ( abs(sizelist[i] - currvalue) > abs(sizelist[i] - curr_size) ) {
+                       currvalue = sizelist[i];
+                       size_p->value(menu_index);
+               }
+               menu_index++;
+       }
+       if (numsizes == 0 || (numsizes == 1 && sizelist[0] == 0)) {
+               // Either no available sizes at all, or only
+               // scaleable, with no special "good" sizes,
+               // so we pick some and hope for the best.
+               size_p->add("10", 0, 0, 0, 0);
+               if (curr_size <= 11) {
+                       size_p->value(0);
+               }
+               size_p->add("12", 0, 0, 0, 0);
+               if (curr_size >= 12 && curr_size <= 13) {
+                       size_p->value(1);
+               }
+               size_p->add("14", 0, 0, 0, 0);
+               if (curr_size >= 14 && curr_size <= 15) {
+                       size_p->value(2);
+               }
+               size_p->add("16", 0, 0, 0, 0);
+               if (curr_size >= 16 && curr_size <= 17) {
+                       size_p->value(3);
+               }
+               size_p->add("18", 0, 0, 0, 0);
+               if (curr_size >= 18) {
+                       size_p->value(4);
+               }
+       }
+}
+
+//----------dialog to let user fill in the Registration form---------------
+
+#define MULTI_OS "Note that if you wish to use Mup\n" \
+       "on multiple machines simultaneously,\n" \
+       "you need to purchase a registration\n" \
+       "for each."
+
+
+RegistrationForm_dialog::RegistrationForm_dialog(void)
+       : Fl_Double_Window(500, 450, "Mup Registration")
+{
+       name_p = new Fl_Input(80, 20, 400, 30, "Name");
+       name_p->tooltip("Enter your name.");
+
+       address_p = new Fl_Input(80, 60, 400, 30, "Address");
+       address_p->tooltip("Enter your street address.");
+
+       city_p = new Fl_Input(80, 100, 180, 30, "City");
+       city_p->tooltip("Enter the name of the city\n"
+                               "in which you live.");
+       state_p = new Fl_Input(380, 100, 100, 30, "State/Province");
+       state_p->tooltip("Enter the state or province (if any)\n"
+                       "in which you live.");
+
+       postal_code_p = new Fl_Input(110, 140, 120, 30, "Postal Code");
+       postal_code_p->tooltip("Enter your zip code or postal code\n"
+                               "as appropriate for your country.");
+
+       country_p = new Fl_Input(320, 140, 160, 30, "Country");
+       country_p->tooltip("Enter the name of the country in which\n"
+                       "you live (optional if in USA).");
+
+       email_p = new Fl_Input(110, 180, 370, 30, "Email address");
+       email_p->tooltip("Enter your email address. This will only be used\n"
+                       "to send you your registration key\n"
+                       "and announcements of future (free) Mup upgrades.");
+
+       how_heard_p = new Fl_Input(20, 240, 460, 30, "Where did you hear about Mup?");
+       how_heard_p->align(FL_ALIGN_TOP_LEFT);
+       how_heard_p->tooltip("Please let us know how you learned about Mup\n"
+                       "(a particular web link, magazine, book, etc.).");
+
+       // Checkboxes for OS types.
+       // If we are compiled for a particular OS,
+       // automatically check that box.
+       Windows_p = new Fl_Check_Button(20, 280, 80, 30, "Windows");
+#ifdef __WIN32
+       Windows_p->value(1);
+#endif
+       Windows_p->tooltip("Check here if you plan to run Mup\n"
+                       "under Microsoft Windows.\n"
+                       MULTI_OS);
+
+       Mac_p = new Fl_Check_Button(110, 280, 50, 30, "Mac");
+       Mac_p->tooltip("Check here if you plan to run Mup\n"
+                       "under Apple Mac OS.\n"
+                       MULTI_OS);
+#ifdef __APPLE__
+       Mac_p->value(1);
+#endif
+
+       Linux_p = new Fl_Check_Button(170, 280, 60, 30, "Linux");
+       Linux_p->tooltip("Check here if you plan to run Mup\n"
+                       "under Linux. "
+                       MULTI_OS);
+#ifdef __linux
+       Linux_p->value(1);
+#endif
+
+       other_p = new Fl_Check_Button(240, 280, 60, 30, "Other");
+       other_p->tooltip("Check here if you plan to run Mup\n"
+                       "under an OS not listed.\n"
+                       MULTI_OS);
+       other_OS_p = new Fl_Input(300, 280, 180, 30, "");
+       other_OS_p->tooltip("If you checked \"Other,\"\n"
+                       "specify the name of the Operating System.\n"
+                       MULTI_OS);
+
+       mailing_list_p = new Fl_Check_Button(100, 320, 350, 30,
+                               "I would like to join the Mup user's mailing list");
+       mailing_list_p->tooltip("There is a email mailing list for registered\n"
+                       "Mup users, for sharing information about Mup.\n"
+                       "Check here if you want to join the list.\n"
+                       "There is no extra charge, and you can always\n"
+                       "subscribe/unsubscribe later if you change your mind.");
+
+       num_regs_p = new Positive_Int_Input(340, 355, 60, 30,
+                                       "Number of Registrations ($29 each)");
+       num_regs_p->value("1");
+       num_regs_p->tooltip("Enter the number of registrations\n"
+                       "you wish to purchase.\n"
+                       MULTI_OS);
+
+       save_form_p = new Fl_Return_Button(40, h() - 50, 120, 30, "Save Form");
+       save_form_p->callback(SaveForm_cb, this);
+       cancel_p = new Fl_Button(w() - 160, h() - 50, 120, 30, "Cancel");
+       cancel_p->shortcut(FL_Escape);
+       cancel_p->when(FL_WHEN_RELEASE);
+       cancel_p->callback(Cancel_cb, this);
+
+       // Arrange for destructor to clean up new-ed widgets
+       end();
+
+       // Arrange for window manager closes to do Cancel.
+       callback(Cancel_cb, this);
+       when(FL_WHEN_NEVER);
+}
+
+
+RegistrationForm_dialog::~RegistrationForm_dialog(void)
+{
+}
+
+
+CALL_BACK(RegistrationForm_dialog, SaveForm)
+{
+       generate_form();
+       hide();
+}
+
+
+CALL_BACK(RegistrationForm_dialog, Cancel)
+{
+       hide();
+}
+
+
+// Generate the registration form with fields filled in.
+
+extern const char * const registration_text;
+static const char * const checkmark = "X";
+static const char * const reg_file_name = "mup-reg.txt";
+
+void
+RegistrationForm_dialog::generate_form()
+{
+       char * text = strdup(registration_text);
+
+       // First find all the fields in the registration form.
+       // Do this before filling anything is to avoid any chance
+       // of being confused by what we fill in.
+       char * name = strstr(text, "Name");
+       char * address = strstr(text, "Address");
+       char * city = strstr(text, "City");
+       char * state = strstr(text, "State");
+       char * postal_code = strstr(text, "Zip code");
+       char * country = strstr(text, "Country");
+       char * email = strstr(text, "Email");
+       char * how_heard = strstr(text, "How did you");
+       char * how_heard_line2 = strstr(how_heard, "\n\n");
+       char * Linux = strstr(text, "Linux");
+       char * Windows = strstr(text, "Windows/MS-DOS");
+       char * Mac = strstr(text, "Mac");
+       char * other = strstr(text, "Other");
+       char * mailing_list = strstr(text, "Yes");
+       char * regs = strstr(text, "Mup Version");
+
+       (void) fill_in(false, name, name_p->value());
+       (void) fill_in(false, address, address_p->value());
+       (void) fill_in(false, city, city_p->value());
+       (void) fill_in(false, state, state_p->value());
+       (void) fill_in(false, postal_code, postal_code_p->value());
+       (void) fill_in(false, country, country_p->value());
+       (void) fill_in(false, email, email_p->value());
+       const char * remaining;
+       if ((remaining = fill_in(false, how_heard, how_heard_p->value()))
+                                                                       != 0) {
+               (void) fill_in(false, how_heard_line2, remaining);
+       }
+
+       if (Windows_p->value()) {
+               (void) fill_in(true, Windows, checkmark);
+       }
+       if (Linux_p->value()) {
+               (void) fill_in(true, Linux, checkmark);
+       }
+       if (Mac_p->value()) {
+               (void) fill_in(true, Mac, checkmark);
+       }
+       if (other_p->value()) {
+               (void) fill_in(true, other, checkmark);
+       }
+       if (other_OS_p->size() > 0) {
+               (void) fill_in(false, other, other_OS_p->value());
+       }
+       (void) fill_in(mailing_list_p->value(), mailing_list, checkmark);
+
+       (void) fill_in(true, regs, num_regs_p->value());
+
+       // write to file
+       FILE * regfile;
+       if ((regfile = fopen(reg_file_name, "w")) == 0) {
+               fl_alert("Unable to write registration form file.");
+       }
+       else {
+               (void) fprintf(regfile, "%s", text);
+               (void) fprintf(regfile, "\n\n\t Total due:  $%.2f\n",
+                       (29.0 + sales_tax()) * atoi(num_regs_p->value()));
+               fclose(regfile);
+               char currdir[FL_PATH_MAX];
+
+               if (getcwd(currdir, sizeof(currdir)) == 0) {
+                       currdir[0] = '\0';
+               }
+               hide();
+               fl_message("Your registration form has been saved in\n"
+                       "%s%c%s.", currdir, dir_separator(), reg_file_name);
+       }
+       free(text);
+}
+
+
+// Fills in value into blank either before or after the given place.
+// Center the string in the blank.
+// If it can't fit the entire value, it returns a pointer to
+// what was left over, otherwise returns zero.
+
+const char *
+RegistrationForm_dialog::fill_in(bool before, char * place, const char * const value)
+{ 
+       if (place == 0 || value == 0) {
+               // Shouldn't happen, but better than core dump.
+               return(value);
+       }
+
+       char * blanks_p;
+       // Find beginning of the blank.
+       if (before) {
+               // back up to beginning of blanks, skipping any spaces
+               for (blanks_p = place - 1; *blanks_p == ' '; blanks_p--)
+                       ;
+               for (   ; *blanks_p == '_'; blanks_p--)
+                       ;
+               blanks_p++;
+       }
+       else {
+               blanks_p = strchr(place, '_');
+       }
+
+       // Figure out how much leading padding to leave
+       int count = strspn(blanks_p, "_");
+       int length = strlen(value);
+
+       // Fill in the blank.
+       int padding;
+       if (length > count) {
+               // Try to split at white space.
+               int used_length;
+               for (used_length = count; used_length > 10; used_length--) {
+                       if (value[used_length] == ' ') {
+                               break;
+                       }
+               }
+               padding = (count - used_length) / 2;
+               strncpy(blanks_p + padding, value, used_length);
+               return(value + used_length + 1);
+       }
+       else {
+               padding = (count - length) / 2;
+               strncpy(blanks_p + padding, value, length);
+               return(0);
+       }
+}
+
+
+// Since Arkkra is based in Illinois, Illinois residents need to pay
+// sales tax. So try to deduce if the address is Illinois.
+// Simple minded--may sometimes guess wrong.
+
+double
+RegistrationForm_dialog::sales_tax(void)
+{
+       const char * pattern;
+
+       if (state_p->size() > 0) {
+               // Skip leading white space
+               for (pattern = state_p->value(); *pattern == ' '; pattern++)
+                       ;
+               if (*pattern == '\0') {
+                       return(0.0);
+               }
+               // Remove any trailing white space.
+               // Can't remove in place, since string is const.
+               // So remove from copy.
+               char state[strlen(pattern) + 1];
+               (void) strcpy(state, pattern);
+               char * p;
+               for (p = state + strlen(pattern) - 1; p > state ; p--) {
+                       if (*p == ' ') {
+                               *p = '\0';
+                       }
+                       else {
+                               break;
+                       }
+               }
+               
+               if (strcmp(state, "IL") == 0
+                               || strcasecmp(state, "Illinois") == 0
+                               || strcasecmp(state, "Ill.") == 0) {
+                       // Sales tax is $2.18 per registration.
+                       return(2.18);
+               }
+       }
+       return(0.0);
+}
+
+
+//----------dialog to let user type in the Registration Key---------------
+
+RegistrationKey_dialog::RegistrationKey_dialog(void)
+       : Fl_Double_Window(250, 100, "Registration Key")
+{
+       key_p = new Fl_Secret_Input(60, 20, 150, 30, "Key:");
+       key_p->tooltip("Enter the registration key\n"
+               "you received from Arkkra Enterprises\n"
+               "after you have paid your registration fee.");
+
+       OK_p = new Fl_Return_Button(25, 60, 80, 30, "OK:");
+       OK_p->when(FL_WHEN_RELEASE);
+       OK_p->callback(OK_cb, this);
+
+       cancel_p = new Fl_Button(120, 60, 90, 30, "Cancel");
+       cancel_p->shortcut(FL_Escape);
+       cancel_p->when(FL_WHEN_RELEASE);
+       cancel_p->callback(Cancel_cb, this);
+
+       // Arrange for destructor to clean up new-ed widgets
+       end();
+
+       // Arrange for window manager closes to do Cancel.
+       callback(Cancel_cb, this);
+       when(FL_WHEN_NEVER);
+}
+
+
+RegistrationKey_dialog::~RegistrationKey_dialog()
+{
+}
+
+CALL_BACK(RegistrationKey_dialog, OK)
+{
+       hide();
+       FILE * keyfile;
+       if ((keyfile = fopen(magic_file(), "w")) == 0) {
+               fl_alert("Unable to open registration key file %s.",
+                                                       magic_file());
+       }
+       else {
+               if (fprintf(keyfile, "%s", key_p->value()) != key_p->size()) {
+                       fl_alert("Unable to save registration key in %s.",
+                                                       magic_file());
+               }
+               fclose(keyfile);
+       }
+
+       // Blank out the field, for if it gets displayed again later.
+       key_p->value("");
+}
+
+
+// If user cancels entering registration key, we just hide the window
+
+CALL_BACK(RegistrationKey_dialog, Cancel)
+{
+       hide();
+}
+
+
+//-------the Config menu item on main toolbar-------------------------------
+
+Config::Config()
+{
+       locations_p = 0;
+       preferences_p = 0;
+       registrationform_p = 0;
+       registrationkey_p = 0;
+}
+
+Config::~Config()
+{
+       if (locations_p != 0) {
+               delete locations_p;
+               locations_p = 0;
+       }
+       if (preferences_p != 0) {
+               delete preferences_p;
+               preferences_p = 0;
+       }
+       if (registrationform_p != 0) {
+               delete registrationform_p;
+               registrationform_p = 0;
+       }
+       if (registrationkey_p != 0) {
+               delete registrationkey_p;
+               registrationkey_p = 0;
+       }
+}
+
+
+// Bring up the dialog for "File Locations" menu item
+
+CALL_BACK(Config, FileLocations)
+{
+       if (locations_p == 0) {
+               // first time, create widget
+               locations_p = new FileLocations_dialog();
+       }
+       locations_p->show();
+}
+
+
+// Bring up the dialog for "Preferences" menu item
+
+CALL_BACK(Config, Preferences)
+{
+       if (preferences_p == 0) {
+               // first time, create widget
+               preferences_p = new Preferences_dialog();
+       }
+       preferences_p->show();
+}
+
+
+// Bring up dialog for filling in Registration Form
+
+CALL_BACK(Config, RegistrationForm)
+{
+       if (registrationform_p == 0) {
+               // first time, create widget
+               registrationform_p = new RegistrationForm_dialog();
+       }
+       registrationform_p->show();
+       fl_message("Note: For fastest service, you can register via\n"
+               "credit card at http://www.arkkra.com/doc/credtcrd.html\n"
+               "rather than filling out and mailing in a paper form.");
+}
+
+
+// Bring up dialog to let user enter their registration key after paying
+
+CALL_BACK(Config, RegistrationKey)
+{
+       if (registrationkey_p == 0) {
+               // first time, create widget
+               registrationkey_p = new RegistrationKey_dialog();
+       }
+       registrationkey_p->show();
+}
+
+
+// Translate font name to FL_Font value.
+
+Fl_Font
+Config::fontvalue(const char * fontname)
+{
+       int n;
+       // Linear search of the list (it is short).
+       for (n = 0; n < Fontlistlength; n++) {
+               if (strcmp(Fontlist[n].name, fontname) == 0) {
+                       return(Fontlist[n].value);
+               }
+       }
+       // Hmmm. Not found. Should not happen. Hunt for default
+       for (n = 0; n < Fontlistlength; n++) {
+               if (strcmp(Fontlist[n].name, Default_editor_font) == 0) {
+                       return(Fontlist[n].value);
+               }
+       }
+       // Wow. Can't find default either. Punt.
+       return(FL_COURIER);
+}
+
+
+//--------------------- class that lets other classes register a callback
+// to be called for changes in font/size
+
+
+// List of callbacks for when font/size change
+Font_change_registration * Font_change_registration::list_p = 0;
+
+Font_change_registration::Font_change_registration(Font_change_callback func, void * arg)
+{
+       // Save callback information.
+       callback = func;
+       callback_arg = arg;
+
+       // Add to list of callbacks.
+       next = list_p;
+       list_p = this;
+
+       // Set the font and size on this newly registered widget.
+       // Look up the current values and call the newly registered callback.
+       char * fontstr;
+       (void) Preferences_p->get(Editor_font_preference, fontstr,
+                                                       Default_editor_font);
+       Fl_Font font = Config::fontvalue(fontstr);
+
+       char * sizestr;
+       (void) Preferences_p->get(Editor_size_preference, sizestr,
+                                                       Default_editor_size);
+       unsigned char size = (unsigned char) atoi(sizestr);
+       if (size < Min_size) {
+               size = (unsigned char) atoi(Default_editor_size);
+       }
+       (*func)(arg, font, size);
+}
+
+Font_change_registration::~Font_change_registration(void)
+{
+       // Remove callback from linked list
+       if (list_p == this) {
+               list_p = next;
+       }
+       else {
+               Font_change_registration * fcr_p;
+               for (fcr_p = list_p; fcr_p != 0; fcr_p = fcr_p->next) {
+                       if (fcr_p->next == this) {
+                               fcr_p->next = next;
+                               return;
+                       }
+               }
+       }
+}
+
+
+// Notify all classes that want to know about font/size changes,
+// by calling the callback function they registered.
+
+void
+Font_change_registration::run_callbacks(Fl_Font font, unsigned char size)
+{
+       // Avoid unreadably small sizes and division by zero if
+       // earlier atoi() of size failed due to bad data
+       // (e.g., user hand-editing the preference file)
+       if (size < Min_size || size > Max_size) {
+               size = (unsigned char) atoi(Default_editor_size);
+       }
+
+       // Walk through list of registered callbacks, calling each.
+       Font_change_registration * fcr_p;
+       for (fcr_p = list_p; fcr_p != 0; fcr_p = fcr_p->next) {
+               (*(fcr_p->callback))(fcr_p->callback_arg, font, size);
+       }
+}