chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mupmate / Config.C
1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
3
4 // Code for the Config menu item on the main toolbar
5
6 #include "globals.H"
7 #include "Preferences.H"
8 #include "Config.H"
9 #include "Main.H"
10 #include "utils.H"
11 #include <FL/fl_ask.H>
12 #include <FL/Fl_Tooltip.H>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17
18
19 // Window to ask user where files and tools are located
20
21 FileLocations_dialog::FileLocations_dialog(void)
22         : Fl_Double_Window(620, 300, "Mupmate File Locations")
23 {
24         mup_documentation_p = new Fl_Input(200, 30, 400, 30, "Mup Documentation Folder");
25         mup_documentation_p->tooltip("Set where Mup documentation\n"
26                                 "files are installed on your system.\n"
27                                 "This folder must contain the \"uguide\"\n"
28                                 "folder that contains the HTML version\n"
29                                 "of the Mup User's Guide.");
30
31         mup_program_p = new Fl_Input(200, 65, 400, 30, "Mup Command Path");
32         mup_program_p->tooltip("Set where the Mup program\n"
33                                 "is installed on your system.");
34
35         music_files_p = new Fl_Input(200, 100, 400, 30, "Folder for Mup Files");
36         music_files_p->tooltip("Set the default folder for storing\n"
37                                 "your Mup files (.mup input files,\n"
38                                 "and .ps and .mid output files).");
39
40         muppath_p = new Fl_Input(200, 135, 400, 30, "Folder for Mup include Files");
41         static char include_tip_text[200];
42         (void) sprintf(include_tip_text,
43                         "Set the default folder (or list of folders,\n"
44                         "separated by %c characters) for\n"
45                         "storing your Mup \"include\" files.",
46                         path_separator());
47         muppath_p->tooltip(include_tip_text);
48
49         viewer_p = new Fl_Input(200, 170, 400, 30, "PostScript Viewer Path");
50         viewer_p->tooltip("Set which PostScript viewing program\n"
51                                 "to use for displaying Mup output.\n"
52 #ifdef OS_LIKE_WIN32
53                                 "This is typically GSview32.exe\n"
54                                 "which you can obtain from\n"
55                                 "http://www.cs.wisc.edu/~ghost/gsview/"
56 #else
57                                 "The \"gv\" program is a common choice."
58 #endif
59                         );
60
61         player_p = new Fl_Input(200, 205, 400, 30, "MIDI Player Path");
62         player_p->tooltip("Set which MIDI player program\n"
63                                 "to use for playing Mup MIDI output.\n"
64 #ifdef OS_LIKE_WIN32
65                                 "This is typically wmplayer.exe"
66 #else
67                                 "Common choices include xplaymidi or timidity."
68 #endif
69                         );
70
71         apply_p = new Fl_Return_Button(50, 255, 100, 30, "Apply");
72         apply_p->when(FL_WHEN_RELEASE);
73         apply_p->callback(Apply_cb, this);
74         
75         cancel_p = new Fl_Button(w() - 150, 255, 100, 30, "Cancel");
76         cancel_p->shortcut(FL_Escape);
77         cancel_p->when(FL_WHEN_RELEASE);
78         cancel_p->callback(Cancel_cb, this);
79
80         // Populate the fields
81         set_current_values();
82
83         // Arrange for destructor to clean up new-ed widgets
84         end();
85
86         // Arrange for window manager closes to do Cancel.
87         callback(Cancel_cb, this);
88         when(FL_WHEN_NEVER);
89 }
90
91
92 FileLocations_dialog::~FileLocations_dialog()
93 {
94 }
95
96
97 //--- Callback for when user clicks "Apply" on FileLocations dialog.
98 // Save values in preferences file.
99
100 CALL_BACK(FileLocations_dialog, Apply)
101 {
102         bool changes = false;           // if any changes made
103         bool error = false;             // if any errors found
104         char location[FL_PATH_MAX];
105
106         // Documentation location
107         if (mup_documentation_p->size() > 0) {
108                 (void) Preferences_p->set(Mup_documentation_location,
109                                                 mup_documentation_p->value());
110                 changes = true;
111                 // Documentation being wrong means User's Guide can't be
112                 // shown, which is bad, although not fatal.
113                 if ( ! fl_filename_isdir(mup_documentation_p->value()) ) {
114                         fl_alert("Location for Mup documentation is not a valid folder.");
115                         error = true;
116                 }
117                 else {
118                         if (access(users_guide_index_file(
119                                         mup_documentation_p->value()), F_OK)
120                                         != 0) {
121                                 fl_alert("Folder specified for Mup documentation it not correct:\n"
122                                         "it does not contain the Mup User's Guide.");
123                                 error = true;
124                         }
125                 }
126         }
127
128         // Location of Mup program
129         if (mup_program_p->size() > 0) {
130                 if (find_executable(mup_program_p->value(), location)) {
131                         (void) Preferences_p->set(Mup_program_location,
132                                                 mup_program_p->value());
133                         changes = true;
134                 }
135                 else {
136                         fl_alert("Location specified for Mup program is not valid.");
137                         error = true;
138                 }
139         }
140
141         // Default folder for Mup input files
142         if (music_files_p->size() > 0) {
143                 if (chdir(music_files_p->value()) != 0) {
144                         fl_alert("Value for \"Folder for Mup Files\" is not a valid folder.");
145                         error = true;
146                 }
147                 else {
148                         (void) Preferences_p->set(Music_files_location,
149                                                 music_files_p->value());
150                         changes = true;
151                 }
152         }
153
154         // $MUPPATH value
155         if (muppath_p->size() > 0) {
156                 (void) Preferences_p->set(MUPPATH_location, muppath_p->value());
157                 // Set $MUPPATH
158                 set_muppath(muppath_p->value());
159                 changes = true;
160                 // Setting MUPPATH correctly is only important if user
161                 // actually uses it, which many people won't, but if it is set
162                 // to something invalid, we give a warning.
163                 // Since MUPPATH can be a list, we check each component
164                 // in the list.
165                 char pathcopy[muppath_p->size() + 1];
166                 (void) strcpy(pathcopy, muppath_p->value());
167                 char * component_p = pathcopy;
168                 char * sep_p;   // where path separator appears in list
169                 do {
170                         if ((sep_p = strchr(component_p, path_separator())) != 0) {
171                                 *sep_p = '\0';
172                         }
173                         if (strlen(component_p) > 0 &&
174                                         ! fl_filename_isdir(component_p)) {
175                                 fl_alert("Location for Mup include files\n"
176                                         "\"%s\"\nis not a valid folder.",
177                                         component_p);
178                                 error = true;
179                         }
180                         component_p += strlen(component_p) + 1;
181                 } while (sep_p != 0);
182         }
183
184         // PostScript viewer program
185         if (viewer_p->size() > 0) {
186                 if (find_executable(viewer_p->value(), location)) {
187                         (void) Preferences_p->set(Viewer_location, viewer_p->value());
188                         changes = true;
189                 }
190                 else {
191                         fl_alert("Location specified for PostScript viewer is not valid.");
192                         error = true;
193                 }
194         }
195
196         // MIDI player
197         if (player_p->size() > 0) {
198                 if (find_executable(player_p->value(), location)) {
199                         (void) Preferences_p->set(MIDI_player_location, player_p->value());
200                         changes = true;
201                 }
202                 else {
203                         fl_alert("Location specified for MIDI player is not valid.");
204                         error =  true;
205                 }
206         }
207
208         // If any changes, persist the data.
209         if (changes) {
210                 Preferences_p->flush();
211         }
212
213         // If there were errors, leave form up so user can try to correct them.
214         if ( ! error ) {
215                 hide();
216         }
217 }
218
219
220 //--- callback for when user clicks "Cancel" on FileLocations dialog
221
222 CALL_BACK(FileLocations_dialog, Cancel)
223 {
224         hide();
225         // Put all the original settings back on the form
226         set_current_values();
227 }       
228
229
230 // Populate form with the current default values from user's preferences.
231
232 void
233 FileLocations_dialog::set_current_values(void)
234 {
235         char * val;
236         (void) Preferences_p->get(Mup_documentation_location, val,
237                                         Default_Mup_documentation_location);
238         mup_documentation_p->value(val);
239
240         (void) Preferences_p->get(Mup_program_location, val,
241                                         Default_Mup_program_location);
242         mup_program_p->value(val);
243
244         (void) Preferences_p->get(Music_files_location, val,
245                                         Default_music_files_location);
246         music_files_p->value(val);
247         (void) Preferences_p->get(MUPPATH_location, val,
248                                         Default_MUPPATH_location);
249         muppath_p->value(val);
250         (void) Preferences_p->get(Viewer_location, val,
251                                         Default_viewer_location);
252         viewer_p->value(val);
253         (void) Preferences_p->get(MIDI_player_location, val,
254                                         Default_MIDI_player_location);
255         player_p->value(val);
256 }
257
258
259 //-----------------------------------------------------------------
260
261 // List of standard FLTK fonts, and info to map name to menu entry.
262 static struct Font {
263         const char * name;
264         Fl_Font value;
265         int menu_offset;
266 } Fontlist[] = {
267         { "Courier",                    FL_COURIER },
268         { "Courier Bold",               FL_COURIER_BOLD },
269         { "Courier Italic",             FL_COURIER_ITALIC },
270         { "Courier Bold Italic",        FL_COURIER_BOLD_ITALIC },
271         { "Helvetica",                  FL_HELVETICA },
272         { "Helvetica Bold",             FL_HELVETICA_BOLD },
273         { "Helvetica Italic",           FL_HELVETICA_ITALIC },
274         { "Helvetica Bold Italic",      FL_HELVETICA_BOLD_ITALIC },
275         { "Times",                      FL_TIMES },
276         { "Times Bold",                 FL_TIMES_BOLD },
277         { "Times Italic",               FL_TIMES_ITALIC },
278         { "Times Bold Italic",          FL_TIMES_BOLD_ITALIC },
279 };
280 static const int Fontlistlength = sizeof(Fontlist) / sizeof(Fontlist[0]);
281
282 // Window to ask user preferences, like editor font, size, etc.
283
284 Preferences_dialog::Preferences_dialog(void)
285         : Fl_Double_Window(400, 280, "Mupmate Preferences")
286 {
287         // Make widget for user's editor font choice.
288         font_p = new Fl_Choice(20, 40, 210, 30, "Text Font");
289         font_p->tooltip("Select the font to be used\n"
290                         "in the editor window where you\n"
291                         "type in Mup input. It is also used\n"
292                         "for the Help and error report text.");
293         // Arrange to reset size menu if font selection changes
294         font_p->callback(fontchg_cb, this);
295         font_p->when(FL_WHEN_CHANGED);
296         font_p->align(FL_ALIGN_TOP_LEFT);
297
298         // Make widget for user's editor size choice.
299         size_p = new Fl_Choice(270, 40, 100, 30, "Text Size");
300         size_p->tooltip("Select the text size to be used\n"
301                         "in the editor window where you\n"
302                         "type in Mup input. It is also used\n"
303                         "for the Help and error report text.");
304         size_p->align(FL_ALIGN_TOP_LEFT);
305
306         auto_display_p = new Fl_Check_Button(20, 90, 180, 30,
307                                                 "Auto-Display on Save");
308         auto_display_p->tooltip("Set whether your music\n"
309                         "is displayed automatically\n"
310                         "whenever you save your Mup file.");
311
312         auto_save_p = new Fl_Check_Button(w() - 170, 90, 150, 30,
313                                                 "Auto-Save on Run");
314         auto_save_p->tooltip("Set whether your music is saved\n"
315                         "automatically whenever you do Display, Play,\n"
316                         "Write PostScript or Write MIDI from the Run menu.");
317
318         tooltips_delay_p = new Fl_Value_Input(150, 155, 100, 30, "Tool Tip Delay");
319         tooltips_delay_p->minimum(0.0);
320         tooltips_delay_p->precision(3);
321         tooltips_delay_p->tooltip("Set how long to delay before showing\n"
322                         "tool tips, in seconds.\n");
323         tooltips_delay_p->align(FL_ALIGN_TOP_LEFT);
324
325         // Create and configure widget for Apply button
326         apply_p = new Fl_Return_Button(60, 215, 100, 30, "Apply");
327         apply_p->when(FL_WHEN_RELEASE);
328         apply_p->callback(Apply_cb, this);
329
330         // Create and configure widget for Cancel button
331         cancel_p = new Fl_Button(w() - 160, 215, 100, 30, "Cancel");
332         cancel_p->shortcut(FL_Escape);
333         cancel_p->when(FL_WHEN_RELEASE);
334         cancel_p->callback(Cancel_cb, this);
335
336         // Populate the fields
337         set_current_values();
338
339         // Arrange for destructor to clean up new-ed widgets
340         end();
341
342         // Arrange for window manager closes to do Cancel.
343         callback(Cancel_cb, this);
344         when(FL_WHEN_NEVER);
345 }
346
347 Preferences_dialog::~Preferences_dialog()
348 {
349 }
350
351
352 //---- Callback for when user changes font selection.
353 // This re-creates the size menu to be what sizes are available
354 // for that font, since each font could have a different set of sizes.
355
356 CALL_BACK(Preferences_dialog, fontchg)
357 {
358         unsigned char size;
359         if (size_p->mvalue() != 0) {
360                 size = atoi(size_p->mvalue()->text);
361         }
362         else {
363                 // Shouldn't really be possible to get here,
364                 // but better to be safe.
365                 size = (unsigned char) atoi(Default_editor_size);
366         }
367
368         set_size_list(Config::fontvalue(font_p->mvalue()->text), size);
369 }
370
371
372 //--- Callback for when user clicks Apply in Preferences
373 // Save the new values.
374
375 CALL_BACK(Preferences_dialog, Apply)
376 {
377         Fl_Font font;
378         int n;
379
380         Preferences_p->set(Auto_display_preference, auto_display_p->value());
381         Preferences_p->set(Auto_save_preference, auto_save_p->value());
382         Preferences_p->set(Tooltips_delay_preference, tooltips_delay_p->value());
383         Fl_Tooltip::delay(tooltips_delay_p->value());
384
385         // Convert font menu selection into font value.
386         for (n = 0; n < Fontlistlength; n++) {
387                 if (Fontlist[n].menu_offset == font_p->value()) {
388                         Preferences_p->set(Editor_font_preference, Fontlist[n].name);
389                         font = Fontlist[n].value;
390                         break;
391                 }
392         }
393         if (n >= Fontlistlength) {
394                 // Selection not valid. Fall back to using the default.
395                 char * fontname;
396                 (void) Preferences_p->get(Editor_font_preference, fontname,
397                                                         Default_editor_font);
398                 font = Config::fontvalue(fontname);
399         }
400
401         // Save size value.
402         unsigned char size;
403         if (size_p->text() != 0) {
404                 (void) Preferences_p->set(Editor_size_preference, size_p->text());
405                 size = (unsigned char) atoi(size_p->text());
406         }
407         else {
408                 size = (unsigned char) atoi(Default_editor_size);
409         }
410
411         // Persist the data.
412         Preferences_p->flush();
413
414         // Actually change the font/size in all relevant windows.
415         // Windows that want to know about these changes register a callback,
416         // so we call them.
417         Font_change_registration::run_callbacks(font, size);
418
419         hide();
420 }
421
422 //--- callback for when user clicks Cancel in Preferences
423
424 CALL_BACK(Preferences_dialog, Cancel)
425 {
426         hide();
427         // Put all the original settings back on the form
428         set_current_values();
429 }
430
431
432 // Populate form with current values from user's preferences
433
434 void
435 Preferences_dialog::set_current_values(void)
436 {
437         int auto_display;
438         (void) Preferences_p->get(Auto_display_preference, auto_display,
439                                                 Default_auto_display);
440         auto_display_p->value(auto_display);
441
442         int auto_save;
443         (void) Preferences_p->get(Auto_save_preference, auto_save,
444                                                 Default_auto_save);
445         auto_save_p->value(auto_save);
446
447         double tooltips_delay;
448         (void) Preferences_p->get(Tooltips_delay_preference, tooltips_delay,
449                                                 Default_tooltips_delay);
450         tooltips_delay_p->value(tooltips_delay);
451
452         char * fontname;
453         (void) Preferences_p->get(Editor_font_preference, fontname,
454                                                 Default_editor_font);
455         Fl_Font font = Config::fontvalue(fontname);
456         // Populate font menu
457         font_p->clear();
458         for (int i = 0; i < Fontlistlength; i++) {
459                 Fontlist[i].menu_offset =
460                         font_p->add(Fontlist[i].name, 0, 0, 0, 0);
461                 // Set the current value
462                 if (Fontlist[i].value == font) {
463                         font_p->value(Fontlist[i].menu_offset);
464                 }
465         }
466
467         char * sizename;
468         (void) Preferences_p->get(Editor_size_preference, sizename,
469                                                 Default_editor_size);
470         unsigned char size = (unsigned char) atoi(sizename);
471         // Populate the size menu
472         set_size_list(font, size);
473 }
474
475
476 // When font selection changes, re-create the size menu,
477 // because each font could have different sizes available.
478
479 void
480 Preferences_dialog::set_size_list(Fl_Font font, uchar curr_size)
481 {
482         // Avoid really tiny sizes, or more importantly, zero, like if an atoi
483         // failed, because otherwise FLTK may try to divide by zero.
484         // Also limit to a maximum size.
485         if (curr_size < Min_size || curr_size > Max_size) {
486                 curr_size = (unsigned char) atoi(Default_editor_size);
487         }
488
489         // Clean out the current menu if any
490         size_p->clear();
491         
492         // Populate the menu
493         int * sizelist;
494         int numsizes = Fl::get_font_sizes(font, sizelist);
495         
496         // Set current value to ridiculous value, then find closest
497         int currvalue = 5000;
498
499         int i;  // index through sizelist
500         int menu_index; // index into menu
501         for (i = menu_index = 0; i < numsizes; i++) {
502                 if (sizelist[i] == 0) {
503                         // This means font is scaleable 
504                         continue;
505                 }
506                 if (sizelist[i] > Max_size) {
507                         break;
508                 }
509                 char num_as_string[4];
510                 (void) sprintf(num_as_string, "%d", sizelist[i]);
511                 size_p->add(num_as_string, 0, 0, 0, 0);
512                 // If this is closest index to desired size, mark as current
513                 if ( abs(sizelist[i] - currvalue) > abs(sizelist[i] - curr_size) ) {
514                         currvalue = sizelist[i];
515                         size_p->value(menu_index);
516                 }
517                 menu_index++;
518         }
519         if (numsizes == 0 || (numsizes == 1 && sizelist[0] == 0)) {
520                 // Either no available sizes at all, or only
521                 // scaleable, with no special "good" sizes,
522                 // so we pick some and hope for the best.
523                 size_p->add("10", 0, 0, 0, 0);
524                 if (curr_size <= 11) {
525                         size_p->value(0);
526                 }
527                 size_p->add("12", 0, 0, 0, 0);
528                 if (curr_size >= 12 && curr_size <= 13) {
529                         size_p->value(1);
530                 }
531                 size_p->add("14", 0, 0, 0, 0);
532                 if (curr_size >= 14 && curr_size <= 15) {
533                         size_p->value(2);
534                 }
535                 size_p->add("16", 0, 0, 0, 0);
536                 if (curr_size >= 16 && curr_size <= 17) {
537                         size_p->value(3);
538                 }
539                 size_p->add("18", 0, 0, 0, 0);
540                 if (curr_size >= 18) {
541                         size_p->value(4);
542                 }
543         }
544 }
545
546 //----------dialog to let user fill in the Registration form---------------
547
548 #define MULTI_OS "Note that if you wish to use Mup\n" \
549         "on multiple machines simultaneously,\n" \
550         "you need to purchase a registration\n" \
551         "for each."
552
553
554 RegistrationForm_dialog::RegistrationForm_dialog(void)
555         : Fl_Double_Window(500, 450, "Mup Registration")
556 {
557         name_p = new Fl_Input(80, 20, 400, 30, "Name");
558         name_p->tooltip("Enter your name.");
559
560         address_p = new Fl_Input(80, 60, 400, 30, "Address");
561         address_p->tooltip("Enter your street address.");
562
563         city_p = new Fl_Input(80, 100, 180, 30, "City");
564         city_p->tooltip("Enter the name of the city\n"
565                                 "in which you live.");
566         state_p = new Fl_Input(380, 100, 100, 30, "State/Province");
567         state_p->tooltip("Enter the state or province (if any)\n"
568                         "in which you live.");
569
570         postal_code_p = new Fl_Input(110, 140, 120, 30, "Postal Code");
571         postal_code_p->tooltip("Enter your zip code or postal code\n"
572                                 "as appropriate for your country.");
573
574         country_p = new Fl_Input(320, 140, 160, 30, "Country");
575         country_p->tooltip("Enter the name of the country in which\n"
576                         "you live (optional if in USA).");
577
578         email_p = new Fl_Input(110, 180, 370, 30, "Email address");
579         email_p->tooltip("Enter your email address. This will only be used\n"
580                         "to send you your registration key\n"
581                         "and announcements of future (free) Mup upgrades.");
582
583         how_heard_p = new Fl_Input(20, 240, 460, 30, "Where did you hear about Mup?");
584         how_heard_p->align(FL_ALIGN_TOP_LEFT);
585         how_heard_p->tooltip("Please let us know how you learned about Mup\n"
586                         "(a particular web link, magazine, book, etc.).");
587
588         // Checkboxes for OS types.
589         // If we are compiled for a particular OS,
590         // automatically check that box.
591         Windows_p = new Fl_Check_Button(20, 280, 80, 30, "Windows");
592 #ifdef __WIN32
593         Windows_p->value(1);
594 #endif
595         Windows_p->tooltip("Check here if you plan to run Mup\n"
596                         "under Microsoft Windows.\n"
597                         MULTI_OS);
598
599         Mac_p = new Fl_Check_Button(110, 280, 50, 30, "Mac");
600         Mac_p->tooltip("Check here if you plan to run Mup\n"
601                         "under Apple Mac OS.\n"
602                         MULTI_OS);
603 #ifdef __APPLE__
604         Mac_p->value(1);
605 #endif
606
607         Linux_p = new Fl_Check_Button(170, 280, 60, 30, "Linux");
608         Linux_p->tooltip("Check here if you plan to run Mup\n"
609                         "under Linux. "
610                         MULTI_OS);
611 #ifdef __linux
612         Linux_p->value(1);
613 #endif
614
615         other_p = new Fl_Check_Button(240, 280, 60, 30, "Other");
616         other_p->tooltip("Check here if you plan to run Mup\n"
617                         "under an OS not listed.\n"
618                         MULTI_OS);
619         other_OS_p = new Fl_Input(300, 280, 180, 30, "");
620         other_OS_p->tooltip("If you checked \"Other,\"\n"
621                         "specify the name of the Operating System.\n"
622                         MULTI_OS);
623
624         mailing_list_p = new Fl_Check_Button(100, 320, 350, 30,
625                                 "I would like to join the Mup user's mailing list");
626         mailing_list_p->tooltip("There is a email mailing list for registered\n"
627                         "Mup users, for sharing information about Mup.\n"
628                         "Check here if you want to join the list.\n"
629                         "There is no extra charge, and you can always\n"
630                         "subscribe/unsubscribe later if you change your mind.");
631
632         num_regs_p = new Positive_Int_Input(340, 355, 60, 30,
633                                         "Number of Registrations ($29 each)");
634         num_regs_p->value("1");
635         num_regs_p->tooltip("Enter the number of registrations\n"
636                         "you wish to purchase.\n"
637                         MULTI_OS);
638
639         save_form_p = new Fl_Return_Button(40, h() - 50, 120, 30, "Save Form");
640         save_form_p->callback(SaveForm_cb, this);
641         cancel_p = new Fl_Button(w() - 160, h() - 50, 120, 30, "Cancel");
642         cancel_p->shortcut(FL_Escape);
643         cancel_p->when(FL_WHEN_RELEASE);
644         cancel_p->callback(Cancel_cb, this);
645
646         // Arrange for destructor to clean up new-ed widgets
647         end();
648
649         // Arrange for window manager closes to do Cancel.
650         callback(Cancel_cb, this);
651         when(FL_WHEN_NEVER);
652 }
653
654
655 RegistrationForm_dialog::~RegistrationForm_dialog(void)
656 {
657 }
658
659
660 CALL_BACK(RegistrationForm_dialog, SaveForm)
661 {
662         generate_form();
663         hide();
664 }
665
666
667 CALL_BACK(RegistrationForm_dialog, Cancel)
668 {
669         hide();
670 }
671
672
673 // Generate the registration form with fields filled in.
674
675 extern const char * const registration_text;
676 static const char * const checkmark = "X";
677 static const char * const reg_file_name = "mup-reg.txt";
678
679 void
680 RegistrationForm_dialog::generate_form()
681 {
682         char * text = strdup(registration_text);
683
684         // First find all the fields in the registration form.
685         // Do this before filling anything is to avoid any chance
686         // of being confused by what we fill in.
687         char * name = strstr(text, "Name");
688         char * address = strstr(text, "Address");
689         char * city = strstr(text, "City");
690         char * state = strstr(text, "State");
691         char * postal_code = strstr(text, "Zip code");
692         char * country = strstr(text, "Country");
693         char * email = strstr(text, "Email");
694         char * how_heard = strstr(text, "How did you");
695         char * how_heard_line2 = strstr(how_heard, "\n\n");
696         char * Linux = strstr(text, "Linux");
697         char * Windows = strstr(text, "Windows/MS-DOS");
698         char * Mac = strstr(text, "Mac");
699         char * other = strstr(text, "Other");
700         char * mailing_list = strstr(text, "Yes");
701         char * regs = strstr(text, "Mup Version");
702
703         (void) fill_in(false, name, name_p->value());
704         (void) fill_in(false, address, address_p->value());
705         (void) fill_in(false, city, city_p->value());
706         (void) fill_in(false, state, state_p->value());
707         (void) fill_in(false, postal_code, postal_code_p->value());
708         (void) fill_in(false, country, country_p->value());
709         (void) fill_in(false, email, email_p->value());
710         const char * remaining;
711         if ((remaining = fill_in(false, how_heard, how_heard_p->value()))
712                                                                         != 0) {
713                 (void) fill_in(false, how_heard_line2, remaining);
714         }
715
716         if (Windows_p->value()) {
717                 (void) fill_in(true, Windows, checkmark);
718         }
719         if (Linux_p->value()) {
720                 (void) fill_in(true, Linux, checkmark);
721         }
722         if (Mac_p->value()) {
723                 (void) fill_in(true, Mac, checkmark);
724         }
725         if (other_p->value()) {
726                 (void) fill_in(true, other, checkmark);
727         }
728         if (other_OS_p->size() > 0) {
729                 (void) fill_in(false, other, other_OS_p->value());
730         }
731         (void) fill_in(mailing_list_p->value(), mailing_list, checkmark);
732
733         (void) fill_in(true, regs, num_regs_p->value());
734
735         // write to file
736         FILE * regfile;
737         if ((regfile = fopen(reg_file_name, "w")) == 0) {
738                 fl_alert("Unable to write registration form file.");
739         }
740         else {
741                 (void) fprintf(regfile, "%s", text);
742                 (void) fprintf(regfile, "\n\n\t Total due:  $%.2f\n",
743                         (29.0 + sales_tax()) * atoi(num_regs_p->value()));
744                 fclose(regfile);
745                 char currdir[FL_PATH_MAX];
746
747                 if (getcwd(currdir, sizeof(currdir)) == 0) {
748                         currdir[0] = '\0';
749                 }
750                 hide();
751                 fl_message("Your registration form has been saved in\n"
752                         "%s%c%s.", currdir, dir_separator(), reg_file_name);
753         }
754         free(text);
755 }
756
757
758 // Fills in value into blank either before or after the given place.
759 // Center the string in the blank.
760 // If it can't fit the entire value, it returns a pointer to
761 // what was left over, otherwise returns zero.
762
763 const char *
764 RegistrationForm_dialog::fill_in(bool before, char * place, const char * const value)
765
766         if (place == 0 || value == 0) {
767                 // Shouldn't happen, but better than core dump.
768                 return(value);
769         }
770
771         char * blanks_p;
772         // Find beginning of the blank.
773         if (before) {
774                 // back up to beginning of blanks, skipping any spaces
775                 for (blanks_p = place - 1; *blanks_p == ' '; blanks_p--)
776                         ;
777                 for (   ; *blanks_p == '_'; blanks_p--)
778                         ;
779                 blanks_p++;
780         }
781         else {
782                 blanks_p = strchr(place, '_');
783         }
784
785         // Figure out how much leading padding to leave
786         int count = strspn(blanks_p, "_");
787         int length = strlen(value);
788
789         // Fill in the blank.
790         int padding;
791         if (length > count) {
792                 // Try to split at white space.
793                 int used_length;
794                 for (used_length = count; used_length > 10; used_length--) {
795                         if (value[used_length] == ' ') {
796                                 break;
797                         }
798                 }
799                 padding = (count - used_length) / 2;
800                 strncpy(blanks_p + padding, value, used_length);
801                 return(value + used_length + 1);
802         }
803         else {
804                 padding = (count - length) / 2;
805                 strncpy(blanks_p + padding, value, length);
806                 return(0);
807         }
808 }
809
810
811 // Since Arkkra is based in Illinois, Illinois residents need to pay
812 // sales tax. So try to deduce if the address is Illinois.
813 // Simple minded--may sometimes guess wrong.
814
815 double
816 RegistrationForm_dialog::sales_tax(void)
817 {
818         const char * pattern;
819
820         if (state_p->size() > 0) {
821                 // Skip leading white space
822                 for (pattern = state_p->value(); *pattern == ' '; pattern++)
823                         ;
824                 if (*pattern == '\0') {
825                         return(0.0);
826                 }
827                 // Remove any trailing white space.
828                 // Can't remove in place, since string is const.
829                 // So remove from copy.
830                 char state[strlen(pattern) + 1];
831                 (void) strcpy(state, pattern);
832                 char * p;
833                 for (p = state + strlen(pattern) - 1; p > state ; p--) {
834                         if (*p == ' ') {
835                                 *p = '\0';
836                         }
837                         else {
838                                 break;
839                         }
840                 }
841                 
842                 if (strcmp(state, "IL") == 0
843                                 || strcasecmp(state, "Illinois") == 0
844                                 || strcasecmp(state, "Ill.") == 0) {
845                         // Sales tax is $2.18 per registration.
846                         return(2.18);
847                 }
848         }
849         return(0.0);
850 }
851
852
853 //----------dialog to let user type in the Registration Key---------------
854
855 RegistrationKey_dialog::RegistrationKey_dialog(void)
856         : Fl_Double_Window(250, 100, "Registration Key")
857 {
858         key_p = new Fl_Secret_Input(60, 20, 150, 30, "Key:");
859         key_p->tooltip("Enter the registration key\n"
860                 "you received from Arkkra Enterprises\n"
861                 "after you have paid your registration fee.");
862
863         OK_p = new Fl_Return_Button(25, 60, 80, 30, "OK:");
864         OK_p->when(FL_WHEN_RELEASE);
865         OK_p->callback(OK_cb, this);
866
867         cancel_p = new Fl_Button(120, 60, 90, 30, "Cancel");
868         cancel_p->shortcut(FL_Escape);
869         cancel_p->when(FL_WHEN_RELEASE);
870         cancel_p->callback(Cancel_cb, this);
871
872         // Arrange for destructor to clean up new-ed widgets
873         end();
874
875         // Arrange for window manager closes to do Cancel.
876         callback(Cancel_cb, this);
877         when(FL_WHEN_NEVER);
878 }
879
880
881 RegistrationKey_dialog::~RegistrationKey_dialog()
882 {
883 }
884
885 CALL_BACK(RegistrationKey_dialog, OK)
886 {
887         hide();
888         FILE * keyfile;
889         if ((keyfile = fopen(magic_file(), "w")) == 0) {
890                 fl_alert("Unable to open registration key file %s.",
891                                                         magic_file());
892         }
893         else {
894                 if (fprintf(keyfile, "%s", key_p->value()) != key_p->size()) {
895                         fl_alert("Unable to save registration key in %s.",
896                                                         magic_file());
897                 }
898                 fclose(keyfile);
899         }
900
901         // Blank out the field, for if it gets displayed again later.
902         key_p->value("");
903 }
904
905
906 // If user cancels entering registration key, we just hide the window
907
908 CALL_BACK(RegistrationKey_dialog, Cancel)
909 {
910         hide();
911 }
912
913
914 //-------the Config menu item on main toolbar-------------------------------
915
916 Config::Config()
917 {
918         locations_p = 0;
919         preferences_p = 0;
920         registrationform_p = 0;
921         registrationkey_p = 0;
922 }
923
924 Config::~Config()
925 {
926         if (locations_p != 0) {
927                 delete locations_p;
928                 locations_p = 0;
929         }
930         if (preferences_p != 0) {
931                 delete preferences_p;
932                 preferences_p = 0;
933         }
934         if (registrationform_p != 0) {
935                 delete registrationform_p;
936                 registrationform_p = 0;
937         }
938         if (registrationkey_p != 0) {
939                 delete registrationkey_p;
940                 registrationkey_p = 0;
941         }
942 }
943
944
945 // Bring up the dialog for "File Locations" menu item
946
947 CALL_BACK(Config, FileLocations)
948 {
949         if (locations_p == 0) {
950                 // first time, create widget
951                 locations_p = new FileLocations_dialog();
952         }
953         locations_p->show();
954 }
955
956
957 // Bring up the dialog for "Preferences" menu item
958
959 CALL_BACK(Config, Preferences)
960 {
961         if (preferences_p == 0) {
962                 // first time, create widget
963                 preferences_p = new Preferences_dialog();
964         }
965         preferences_p->show();
966 }
967
968
969 // Bring up dialog for filling in Registration Form
970
971 CALL_BACK(Config, RegistrationForm)
972 {
973         if (registrationform_p == 0) {
974                 // first time, create widget
975                 registrationform_p = new RegistrationForm_dialog();
976         }
977         registrationform_p->show();
978         fl_message("Note: For fastest service, you can register via\n"
979                 "credit card at http://www.arkkra.com/doc/credtcrd.html\n"
980                 "rather than filling out and mailing in a paper form.");
981 }
982
983
984 // Bring up dialog to let user enter their registration key after paying
985
986 CALL_BACK(Config, RegistrationKey)
987 {
988         if (registrationkey_p == 0) {
989                 // first time, create widget
990                 registrationkey_p = new RegistrationKey_dialog();
991         }
992         registrationkey_p->show();
993 }
994
995
996 // Translate font name to FL_Font value.
997
998 Fl_Font
999 Config::fontvalue(const char * fontname)
1000 {
1001         int n;
1002         // Linear search of the list (it is short).
1003         for (n = 0; n < Fontlistlength; n++) {
1004                 if (strcmp(Fontlist[n].name, fontname) == 0) {
1005                         return(Fontlist[n].value);
1006                 }
1007         }
1008         // Hmmm. Not found. Should not happen. Hunt for default
1009         for (n = 0; n < Fontlistlength; n++) {
1010                 if (strcmp(Fontlist[n].name, Default_editor_font) == 0) {
1011                         return(Fontlist[n].value);
1012                 }
1013         }
1014         // Wow. Can't find default either. Punt.
1015         return(FL_COURIER);
1016 }
1017
1018
1019 //--------------------- class that lets other classes register a callback
1020 // to be called for changes in font/size
1021
1022
1023 // List of callbacks for when font/size change
1024 Font_change_registration * Font_change_registration::list_p = 0;
1025
1026 Font_change_registration::Font_change_registration(Font_change_callback func, void * arg)
1027 {
1028         // Save callback information.
1029         callback = func;
1030         callback_arg = arg;
1031
1032         // Add to list of callbacks.
1033         next = list_p;
1034         list_p = this;
1035
1036         // Set the font and size on this newly registered widget.
1037         // Look up the current values and call the newly registered callback.
1038         char * fontstr;
1039         (void) Preferences_p->get(Editor_font_preference, fontstr,
1040                                                         Default_editor_font);
1041         Fl_Font font = Config::fontvalue(fontstr);
1042
1043         char * sizestr;
1044         (void) Preferences_p->get(Editor_size_preference, sizestr,
1045                                                         Default_editor_size);
1046         unsigned char size = (unsigned char) atoi(sizestr);
1047         if (size < Min_size) {
1048                 size = (unsigned char) atoi(Default_editor_size);
1049         }
1050         (*func)(arg, font, size);
1051 }
1052
1053 Font_change_registration::~Font_change_registration(void)
1054 {
1055         // Remove callback from linked list
1056         if (list_p == this) {
1057                 list_p = next;
1058         }
1059         else {
1060                 Font_change_registration * fcr_p;
1061                 for (fcr_p = list_p; fcr_p != 0; fcr_p = fcr_p->next) {
1062                         if (fcr_p->next == this) {
1063                                 fcr_p->next = next;
1064                                 return;
1065                         }
1066                 }
1067         }
1068 }
1069
1070
1071 // Notify all classes that want to know about font/size changes,
1072 // by calling the callback function they registered.
1073
1074 void
1075 Font_change_registration::run_callbacks(Fl_Font font, unsigned char size)
1076 {
1077         // Avoid unreadably small sizes and division by zero if
1078         // earlier atoi() of size failed due to bad data
1079         // (e.g., user hand-editing the preference file)
1080         if (size < Min_size || size > Max_size) {
1081                 size = (unsigned char) atoi(Default_editor_size);
1082         }
1083
1084         // Walk through list of registered callbacks, calling each.
1085         Font_change_registration * fcr_p;
1086         for (fcr_p = list_p; fcr_p != 0; fcr_p = fcr_p->next) {
1087                 (*(fcr_p->callback))(fcr_p->callback_arg, font, size);
1088         }
1089 }