chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mupmate / Main.C
1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
3
4 // Code for the main window for Mupmate, a front end program for
5 // the Mup music publisher program from Arkkra Enterprises.
6 // It uses the FLTK toolkit for OS independence.
7
8 // This file contains code for the toolbar and editor window,
9 // as well as general startup and showing the license.
10
11 // We only support editing a single file at a time, so most classes
12 // as really effectively singletons, but the code is written to be
13 // able to support multiple instances, in case we ever want to do that.
14 // That means callback functions are always passed a pointer to
15 // a class instance as their second argument, and all they do is cast 
16 // that to the appropriate type, and call the corresponding class method.
17
18 // For the most part, widgets are only allocated when needed, but then
19 // stay around for the life of the process, in case they are needed again.
20
21 // Callbacks are named with _cb suffix.
22 // Pointers are named with _p suffix, except for (char *) types
23 // that are pointing to text strings, which don't have any special suffix.
24
25
26 #include <string.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <fcntl.h>
31
32 #include <FL/Fl.H>
33 #include <FL/fl_ask.H>
34 #include <FL/Fl_Tooltip.H>
35
36 #include "globals.H"
37 #include "Main.H"
38 #include "Preferences.H"
39 #include "utils.H"
40
41 #include <FL/x.H>
42 #ifdef OS_LIKE_WIN32
43 #include "resource.h"
44 #else
45 #include <FL/Fl_File_Icon.H>
46 #ifdef OS_LIKE_UNIX
47 #include <X11/xpm.h>
48 #include "mup32.xpm"
49 #endif
50 #endif
51
52 // Height of the tool bar on the main window
53 #define TOOLBAR_HEIGHT 30
54
55 // How often to blink the cursor.
56 #define BLINK_RATE 0.5
57
58 // If file indicating user has agreed to license terms doesn't exist,
59 // we ask them to agree. This is the text of the license.
60 extern const char * const license_text;
61
62 //----------------------------------------------------------------------
63
64 // Define the toolbar and its submenus.
65 //  Indented lines indicate the submenus.
66 // The & indicates shortcut key
67
68 const char * File_label = "&File";
69  const char * New_label = "&New";
70  const char * NewFromTemplate_label = "New From &Template";
71  const char * Open_label = "&Open...";
72  const char * Save_label = "&Save";
73  const char * SaveAs_label = "Save &As...";
74  const char * Exit_label = "E&xit";
75 const char * Edit_label = "&Edit";
76  const char * Undo_label = "&Undo";
77  const char * Cut_label = "Cu&t";
78  const char * Copy_label = "&Copy";
79  const char * Paste_label = "&Paste";
80  const char * Delete_label = "&Delete";
81  const char * Find_label = "&Find...";
82  const char * FindNext_label = "Find &Next";
83  const char * Replace_label = "&Replace...";
84  const char * GoTo_label = "&Go To...";
85  const char * SelectAll_label = "&Select All";
86 const char * Run_label = "&Run";
87  const char * Display_label = "&Display";
88  const char * Play_label = "&Play";
89  const char * WritePostScript_label = "&Write PostScript File";
90  const char * WriteMIDI_label = "Write &MIDI File";
91  const char * Options_label = "&Set Options...";
92 const char * Config_label = "&Config";
93  const char * FileLocations_label = "&File Locations...";
94  const char * Preferences_label = "&Preferences...";
95  const char * RegistrationForm_label = "&Registration Form...";
96  const char * RegistrationKey_label = "Registration &Key...";
97 const char * Help_label = "&Help";
98  const char * UserGuide_label = "Mup &User's Guide";
99  const char * StartupHints_label = "&Startup Hints";
100  const char * AboutMupmate_label = "&About Mupmate";
101
102 Fl_Menu_Item Toolbar_menu[] = {
103         { File_label,   0,      0,      0,      FL_SUBMENU },
104                 { New_label,    FL_CTRL + 'n',  File::New_cb },
105                 { NewFromTemplate_label,        FL_CTRL + 't', File::NewFromTemplate_cb },
106                 { Open_label,   FL_CTRL + 'o',  File::Open_cb },
107                 { Save_label,   FL_CTRL + 's',  File::Save_cb },
108                 { SaveAs_label,         0,      File::SaveAs_cb,        0,      FL_MENU_DIVIDER },
109                 { Exit_label,   0,      File::Exit_cb },
110                 { 0 },
111         { Edit_label,   0,      0,      0,      FL_SUBMENU },
112                 { Undo_label,   FL_CTRL + 'z',  Edit::Undo_cb,  0,      FL_MENU_INACTIVE | FL_MENU_DIVIDER },
113                 { Cut_label,    FL_CTRL + 'x',  Edit::Cut_cb,   0,      FL_MENU_INACTIVE },
114                 { Copy_label,   FL_CTRL + 'c',  Edit::Copy_cb,  0,      FL_MENU_INACTIVE },
115                 { Paste_label,  FL_CTRL + 'v',  Edit::Paste_cb, 0,      FL_MENU_INACTIVE },
116                 { Delete_label, FL_Delete,      Edit::Delete_cb,        0,      FL_MENU_INACTIVE | FL_MENU_DIVIDER },
117                 { Find_label,   FL_CTRL + 'f',  Edit::Find_cb,  0,      FL_MENU_INACTIVE },
118                 { FindNext_label,       FL_F + 3,       Edit::FindNext_cb,      0,      FL_MENU_INACTIVE },
119                 { Replace_label,        FL_CTRL + 'h',  Edit::Replace_cb,       0,      FL_MENU_INACTIVE },
120                 { GoTo_label,   FL_CTRL + 'g',  Edit::GoTo_cb,  0,      FL_MENU_DIVIDER },
121                 { SelectAll_label,      FL_CTRL + 'a',  Edit::SelectAll_cb,     0, FL_MENU_INACTIVE },
122                 { 0 },
123         { Run_label,    0,      0,      0,      FL_SUBMENU },
124                 { Display_label,                        0,      Run::Display_cb,        0,      FL_MENU_INACTIVE },
125                 { Play_label,                   0,      Run::Play_cb,   0,      FL_MENU_INACTIVE },
126                 { WritePostScript_label,        0,      Run::WritePostScript_cb,        0, FL_MENU_INACTIVE },
127                 { WriteMIDI_label,              0,      Run::WriteMIDI_cb,      0,       FL_MENU_DIVIDER | FL_MENU_INACTIVE },
128                 { Options_label,                0,      Run::Options_cb },
129                 { 0 },
130         { Config_label, 0,      0,      0,      FL_SUBMENU },
131                 { FileLocations_label,          0,      Config::FileLocations_cb },
132                 { Preferences_label,            0,      Config::Preferences_cb, 0,      FL_MENU_DIVIDER },
133                 { RegistrationForm_label,       0,      Config::RegistrationForm_cb },
134                 { RegistrationKey_label,        0,      Config::RegistrationKey_cb },
135                 { 0 },
136         { Help_label,   0,      0,      0,      FL_SUBMENU },
137                 { UserGuide_label,              0,      Help::Uguide_cb },
138                 { StartupHints_label,           0,      Help::Startup_Hints_cb },
139                 { AboutMupmate_label,           0,      Help::About_cb },
140                 { 0 },
141         { 0 }
142 };
143
144
145 //----------------------------------------------------------------------
146
147 // Linked list of main windows, in case we ever support more than one
148 // at a time. (Currently we don't, because we're not sure if it
149 // might be more confusing than useful.)
150 Main * Main::list_p;
151
152
153 // Constructor for main window. It contains toolbar and editor window.
154
155 Main::Main(const char * title)
156         : Fl_Double_Window(Default_width, Default_height, title)
157 {
158         xclass("mup");
159 #ifdef OS_LIKE_WIN32
160         icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON)));
161 #else
162 #ifdef OS_LIKE_UNIX
163         fl_open_display();
164         Pixmap p, mask;
165         XpmCreatePixmapFromData(fl_display, DefaultRootWindow(fl_display),
166                                  mup32_xpm, &p, &mask, NULL);
167         icon((char *)p);
168 #endif
169 #endif
170         // Try to use user's default foreground/background
171         Fl::get_system_colors();
172
173         void * data = 0;
174
175         // Create class instances for each toolbar item
176         filemenu_p = new File();
177         editmenu_p = new Edit();
178         configmenu_p = new Config();
179         helpmenu_p = new Help();
180         runmenu_p = new Run();
181
182         // Add to list of windows 
183         next = list_p;
184         list_p = this;
185
186         // Create the toolbar and populate its menu items
187         toolbar_p = new Fl_Menu_Bar(0, 0, w(), TOOLBAR_HEIGHT);
188         int numitems = sizeof(Toolbar_menu) / sizeof(Toolbar_menu[0]);
189         for (int i = 0; i < numitems; i++) {
190                 if (Toolbar_menu[i].text != 0) {
191                         // As we move to each top-level menu item,
192                         // keep a pointer to that item, which is then
193                         // used as the argument to callback functions,
194                         // so they know what object to act on.
195                         if (strcmp(Toolbar_menu[i].text, File_label) == 0) {
196                                 data = (void *) filemenu_p;
197                         }
198                         else if (strcmp(Toolbar_menu[i].text, Edit_label) == 0) {
199                                 data = (void *) editmenu_p;
200                         }
201                         else if (strcmp(Toolbar_menu[i].text, Config_label) == 0) {
202                                 data = (void *) configmenu_p;
203                         }
204                         else if (strcmp(Toolbar_menu[i].text, Run_label) == 0) {
205                                 data = (void *) runmenu_p;
206                         }
207                         else if (strcmp(Toolbar_menu[i].text, Help_label) == 0) {
208                                 data = (void *) helpmenu_p;
209                         }
210                 }
211                 Toolbar_menu[i].user_data(data);
212         }
213         toolbar_p->copy(Toolbar_menu);
214
215         // Create and configure the editor window
216         editor_p = new Fl_Text_Editor(0, TOOLBAR_HEIGHT, w(),
217                                         h() - TOOLBAR_HEIGHT, "");
218         editor_p->buffer( new Fl_Text_Buffer );
219
220         // Set font/size and arrange to be notified of changes in them
221         font_change_reg_p = new Font_change_registration(font_change_cb,
222                                                                 (void *) this);
223
224         // Several objects need to be notified of changes in the
225         // editor window, so they can do things like gray-ungray menu items.
226         editor_p->buffer()->add_modify_callback(modify_cb, (void*) this);
227         editor_p->buffer()->add_modify_callback(File::modify_cb,
228                                         (void*) filemenu_p);
229         editor_p->buffer()->add_modify_callback(Edit::modify_cb,
230                                         (void*) editmenu_p);
231
232         // Initialize state information.
233         have_selection = false;
234         can_paste = false;
235         prev_bufflength = 0;
236         // Undo is inactive until user does something that can be undone.
237         undo_active = false;
238         undo_active_on_next_change = true;
239
240         // Arrange to make cursor blink
241         Fl::add_timeout(BLINK_RATE, blinker, this);
242         cursor_state = 1;
243
244         // Let editor take as much space as is available
245         // if the user resizes the main window. 
246         size_range(Min_width, Min_height, 0, 0);
247         resizable((Fl_Widget *) editor_p);
248
249         // Other classes need to have access to editor and such
250         filemenu_p->set_editor(editor_p);
251         filemenu_p->set_parent(this);
252         editmenu_p->set_editor(editor_p);
253         runmenu_p->set_file(filemenu_p);
254
255         // Arrange for destructor to free the new-ed child widgets
256         end();
257
258         show();
259
260         // Arrange for window manager closes to do Exit.
261         callback(atclose_cb, this);
262         when(FL_WHEN_NEVER);
263
264 #ifdef OS_LIKE_UNIX
265         // Arrange for icon to be associated with window
266         XWMHints hints;
267         hints.flags = IconPixmapHint | IconMaskHint ;
268         hints.icon_pixmap = p;
269         hints.icon_mask = mask;
270         XSetWMHints(fl_display, fl_xid((Fl_Window *)this), &hints);
271 #endif
272 }
273
274
275 // Destructor for main window
276
277 Main::~Main()
278 {
279         delete font_change_reg_p;
280         font_change_reg_p = 0;
281         Fl::remove_timeout(blinker, this);
282         // Remove from list of Main windows 
283         if (list_p == this) {
284                 list_p = next;
285         }
286         else {
287                 for (Main * m = list_p; m != 0; m = m->next) {
288                         if (m->next == this) {
289                                 m->next = this->next;
290                                 break;
291                         }
292                 }
293         }
294         delete filemenu_p;
295         filemenu_p = 0;
296         delete editmenu_p;
297         editmenu_p = 0;
298         delete configmenu_p;
299         configmenu_p = 0;
300         delete helpmenu_p;
301         helpmenu_p = 0;
302         delete runmenu_p;
303         runmenu_p = 0;
304 }
305
306
307 // Callback for when user changes font/size
308
309 void
310 Main::font_change_cb(void * data, Fl_Font font, unsigned char size)
311 {
312         ((Main *)data)->font_change(font, size);
313 }
314
315
316 void
317 Main::font_change(Fl_Font font, unsigned char size)
318 {
319         // Get shorter name for buffer, as we'll be using it a lot.
320         Fl_Text_Buffer * buffer_p = editor_p->buffer();
321
322         // Don't want this change to count as something that can be undone
323         buffer_p->canUndo(false);
324
325         // We want to change the entire text buffer, so need to
326         // select its whole contents. If there was already a selection,
327         // save that and put it back when we are done
328         int sel_start, sel_end;
329         int had_selection;
330         int cursorplace = editor_p->insert_position();
331         if ((had_selection = buffer_p->selected()) != 0) {
332                 buffer_p->selection_position(&sel_start, &sel_end);
333                 buffer_p->unselect();
334         }
335
336         // set new font and size
337         buffer_p->select(0, editor_p->buffer()->length() - 1);
338         editor_p->textfont(font);
339         editor_p->textsize(size);
340         buffer_p->unselect();
341
342         // Put selection and cursor back as they were before font change
343         if (had_selection) {
344                 buffer_p->select(sel_start, sel_end);
345         }
346         editor_p->insert_position(cursorplace);
347         buffer_p->canUndo(true);
348 }
349
350
351 // Callback for when editor window changes.
352 // This arranges to gray/ungray toolbar menu items.
353
354 void
355 Main::modify_cb(int, int, int, int, const char *, void * data)
356 {
357         ((Main *)data)->modify();
358 }
359
360 void
361 Main::modify()
362 {
363         int bufflength = editor_p->buffer()->length();
364         // See if what changed is something we might care about.
365         if (editor_p->buffer()->selected() != have_selection ||
366                         editmenu_p->can_paste() != can_paste
367                         || undo_active != undo_active_on_next_change
368                         || bufflength < 2 || prev_bufflength == 0) {
369
370                 // Something changed, and we may need to
371                 // gray or ungray menu items in response.
372                 have_selection = editor_p->buffer()->selected();
373                 const Fl_Menu_Item * menu_p = toolbar_p->menu();
374                 Fl_Menu_Item * item_p;
375                 // Walk through toolbar and submenus, checking
376                 // if anything needs to be grayed/ungrayed.
377                 for (int i = 0; i < toolbar_p->size(); i++) {
378                         const char * mtext = toolbar_p->text(i);
379                         if (mtext == 0) {
380                                 continue;
381                         }
382                         // Can only Copy, Cut, and Delete if something
383                         // is selected.
384                         if (strcmp(mtext, Copy_label) == 0 ||
385                                         strcmp(mtext, Cut_label) == 0 ||
386                                         strcmp(mtext, Delete_label) == 0) {
387                                 // have to un-const so we can (de)activate
388                                 item_p = (Fl_Menu_Item *) &(menu_p[i]);
389                                 if (have_selection) {
390                                         item_p->activate();
391                                 }
392                                 else {
393                                         item_p->deactivate();
394                                 }
395                         }
396
397                         // Paste is different. It becomes active when there
398                         // is something in clipboard, and never again becomes
399                         // inactive.
400                         if (strcmp(mtext, Paste_label) == 0 &&
401                                                 editmenu_p->can_paste()) {
402                                 ((Fl_Menu_Item *)&(menu_p[i]))->activate();
403                                 can_paste = true;
404                         }
405
406                         // Undo is also different. On first change of any
407                         // kind it become active, and stays that way,
408                         // except it gets reset on new file.
409                         if (strcmp(mtext, Undo_label) == 0) {
410                                 if (undo_active && ! undo_active_on_next_change) {
411                                         ((Fl_Menu_Item *)&(menu_p[i]))->deactivate();
412                                         undo_active = false;
413                                         undo_active_on_next_change = true;
414                                 }
415                                 else if ( ! undo_active && undo_active_on_next_change) {
416                                         ((Fl_Menu_Item *)&(menu_p[i]))->activate();
417                                         undo_active = true;
418                                 }
419                         }
420
421                         // Find and FindNext are inactive when file is empty,
422                         // because obviously there is nothing to find.
423                         // Similar for Replace and Select All.
424                         // Also for all the Run things
425                         if (strcmp(mtext, Find_label) == 0 ||
426                                         strcmp(mtext, FindNext_label) == 0 ||
427                                         strcmp(mtext, Replace_label) == 0 ||
428                                         strcmp(mtext, SelectAll_label) == 0 ||
429                                         strcmp(mtext, Display_label) == 0 ||
430                                         strcmp(mtext, Play_label) == 0 ||
431                                         strcmp(mtext, WritePostScript_label) == 0 ||
432                                         strcmp(mtext, WriteMIDI_label) == 0) {
433                                 if (bufflength == 0) {
434                                         ((Fl_Menu_Item *)&(menu_p[i]))->deactivate();
435                                 }
436                                 else {
437                                         ((Fl_Menu_Item *)&(menu_p[i]))->activate();
438                                 }
439                         }
440                 }
441         }
442         prev_bufflength = bufflength;
443 }
444
445
446 // This method gets called when user starts working on a new file.
447 // It sets state information so we can know how to gray menu items properly.
448
449 void
450 Main::begin_new_file()
451 {
452         // transition Undo-ability state from true to false
453         undo_active = true;
454         undo_active_on_next_change = false;
455         editor_p->buffer()->call_modify_callbacks();
456         runmenu_p->clean_up();
457 }
458
459
460 // Handle some special cases.
461 // 1. By default fltk will exit the main window upon getting escape.
462 // That seems bad, since a vi user will be used to hitting escape all the
463 // time when editing, because that is always a "safe" thing to do,
464 // and if they did it here by mistake, they would lose all
465 // their text entry since the last save. So we ignore the escape in this window.
466 // I suppose we could ask if they really want to quit...
467 // 2. If user does cut or copy via keyboard accelerator, the normal code
468 // for ungraying the Paste button doesn't get called, so we catch that case
469 // here and ungray it.
470
471 int
472 Main::handle_events(int e)
473 {
474         // If escape is received while on main window,
475         // return 1 to show that we consumed the event.
476         if (e == FL_SHORTCUT && Fl::event_key() == FL_Escape) {
477                 for (Main * m_p = list_p; m_p != 0; m_p = m_p->next) {
478                         if (Fl::first_window() == m_p) {
479                                 return(1);
480                         }
481                 }
482         }
483
484         // If user did cut or copy via cntl-c or cntl-x,
485         // arrange to ungray Paste.
486         if (e == FL_KEYUP && (Fl::event_state() & FL_CTRL)  &&
487                         (Fl::event_key() == 'v' || Fl::event_key() == 'x')) {
488                 for (Main * m_p = list_p; m_p != 0; m_p = m_p->next) {
489                         if (Fl::first_window() == m_p) {
490                                 m_p->editmenu_p->set_can_paste();
491                                 break;
492                         }
493                 }
494         }
495
496         return(0);
497 }
498
499
500 // If user tries to close the main window via the window manager
501 // while having unsaved changes, we ask user if they want to save
502 // the changes first. 
503
504 CALL_BACK(Main, atclose)
505 {
506         File::Exit_cb(0, filemenu_p);
507 }
508
509
510 // Blink the cursor. It can be hard to see if next to selected text if not
511 // blinking. We could potentially optimize to only do this while
512 // the window has focus, but it doesn't seem worth the complication...
513
514 void
515 Main::blinker(void * data)
516 {
517         Main * obj_p = (Main *) data;
518         // Put cursor into opposite of its current state
519         obj_p->cursor_state ^= 1;
520         obj_p->editor_p->show_cursor(obj_p->cursor_state);
521         // Reset timer to call ourselves again.
522         Fl::repeat_timeout(BLINK_RATE, blinker, data);
523 }
524
525
526 // Give user hints if we haven't already done that before.
527
528 void
529 Main::hints(void)
530 {
531         int did_startup;
532         (void) Preferences_p->get(Showed_startup_hints, did_startup,
533                                         Default_startup_hints_flag);
534         if ( ! did_startup) { 
535                 Help::Startup_Hints_cb(0, (void *) helpmenu_p);
536         }
537 }
538
539 //----------------------------------------------------------------------
540
541 int
542 main(int argc, char **argv, const char **arge)
543 {
544         // The arge value may get changed when we set new environment
545         // variables, so look up PATH first thing.
546         get_path(arge);
547
548         // Uguide browser needs to show images
549         fl_register_images();
550
551 #ifndef OS_LIKE_WIN32
552         // On Windows we use the native Open/Save As dialogs.
553         // On other platforms we use FLTK's, but add icon for Mup files.
554         Fl_File_Icon::load_system_icons();
555         File::add_mup_icon();
556 #endif
557
558         // Try to get best hardware support for graphics
559         Fl::visual(FL_DOUBLE|FL_INDEX);
560
561         // Get the user's preferences that persists across sessions
562         Preferences_p = new Fl_Preferences(Fl_Preferences::USER,
563                                         "arkkra.com", "mupmate");
564
565         // Enable tips when user hovers their mouse over a widget.
566         // If user doesn't like them, they can set delay to huge value.
567         Fl_Tooltip::enable();
568         double tooltips_delay;
569         (void) Preferences_p->get(Tooltips_delay_preference, tooltips_delay,
570                                         Default_tooltips_delay);
571         Fl_Tooltip::delay(tooltips_delay);
572
573         // Set $MUPPATH
574         char * val;
575         (void) Preferences_p->get(MUPPATH_location, val,
576                                                 Default_MUPPATH_location);
577         set_muppath(val);
578
579         // Tell Mup that it is being run via mupmate,
580         // so it can give more appropriate error messages.
581         putenv("MUPMATE=1");
582
583         // Create main window
584         Main *main_p = new Main("Mupmate");
585
586         // Ensure "escape" key doesn't kill main window,
587         // and make sure Paste ungraying works
588         Fl::add_handler(Main::handle_events);
589
590         // Try to find some reasonable defaults for configuration items
591         // that aren't already set.
592         deduce_helper_locations();
593
594         // If magic file indicating license agreement isn't there,
595         // ask user to agree to Mup license.
596         if (access(magic_file(argv[0]), F_OK) != 0) {
597                 // Show the license and get agreement before continuing
598                 new License(main_p, magic_file(argv[0]));
599         }
600         else {
601                 // Display the main window.
602                 main_p->show(1, argv);
603
604                 // The first time, we show user some hints.
605                 main_p->hints();
606         }
607
608         // Go to where user said they want to store their Mup files by default.
609         // Need to wait to do this until after we have deduced locations
610         // of executable, in case they were in current directory.
611         char * mup_dir;
612         (void) Preferences_p->get(Music_files_location, mup_dir,
613                                                 Default_music_files_location);
614         if (strcmp(mup_dir, ".") != 0) {
615                 if (chdir(mup_dir) != 0) {
616                         char curr_dir[FL_PATH_MAX] = "current";
617                         char message[2 * FL_PATH_MAX + 100];
618                         (void *) getcwd(curr_dir, sizeof(curr_dir));
619                         sprintf(message, "Unable to change to folder\n"
620                                 "\"%s.\"\nStaying in \"%s\" folder.\n"
621                                 "Fix setting of \"Folder for Mup Files\"\n"
622                                 "in Config->File Locations.",
623                                 mup_dir, curr_dir);
624                         fl_alert(message);
625                 }
626         }
627
628         // Expect 0 or 1 args. If 1, should be name of file to load
629         if (argc > 1) {
630                 main_p->filemenu_p->load_file(argv[1]);
631         }
632         if (argc > 2) {
633                 fl_alert("Only expecting one file; extra arguments are being ignored.");
634         }
635
636         // Go into main event-handler loop
637         int exitvalue = Fl::run();
638         Main::clean_exit(exitvalue);
639         /*NOTREACHED*/
640         return(exitvalue);
641 }
642
643
644 // Clean up all the windows and their children and exit.
645
646 void
647 Main::clean_exit(int exitval)
648 {
649         Main * m_p;
650         Main * nextwin_p;
651
652         for (m_p = list_p; m_p != 0; m_p = nextwin_p) {
653                 nextwin_p = m_p->next;
654                 delete m_p;
655         }
656         exit(exitval);
657 }
658
659 //---------- class to show Mup license and get user's agreement
660
661
662 License::License(Main *m_p, const char * magic)
663         : Fl_Double_Window(Default_width, Default_height, "Mup License")
664 {
665         // save passed-in info in object data
666         main_p = m_p;
667         magic_file_name = magic;
668
669         // widget for displaying the license text
670         text_p = new Fl_Text_Display(20, 20, w() - 40, h() - 90);
671         resizable((Fl_Widget *) text_p);
672         text_p->buffer( new Fl_Text_Buffer () );
673         text_p->textsize(18);
674         text_p->buffer()->text(license_text);
675
676         i_agree_p = new Fl_Return_Button(100, h() - 50, 100, 30, "I Agree");
677         i_agree_p->callback(IAgree_cb, this);
678
679         cancel_p = new Fl_Button(w() - 200, h() - 50, 100, 30, "Cancel");
680         cancel_p->callback(Cancel_cb, this);
681         cancel_p->shortcut(FL_Escape);
682
683         show();
684
685         // Arrange for destructor to free the new-ed child widgets
686         end();
687
688         // Arrange for window manager closes to do Cancel.
689         callback(Cancel_cb, this);
690         when(FL_WHEN_NEVER);
691 }
692
693
694 License::~License()
695 {
696 }
697
698
699 // Callback for when user clicks that they agree to the license.
700
701 CALL_BACK(License, IAgree)
702 {
703         // Create the magic file
704         int fd;
705         if ((fd = open(magic_file_name, O_WRONLY | O_CREAT, 0644)) < 0) {
706                 fl_alert("Unable to create file indicating license agreement.");
707         }
708         else {
709                 close(fd);
710         }
711
712         hide();
713
714         // Bring up the normal main window
715         main_p->show();
716         main_p->hints();
717 }
718
719 // Callback if user refuses to accept license. We just exit if they refuse.
720
721 CALL_BACK(License, Cancel)
722 {
723         exit(0);
724 }