Make SaveAs not require unsaved changes (bgo#628449)
[pdfmod:pdfmod.git] / src / PdfMod / Gui / Actions.cs
1 // Copyright (C) 2009-2010 Novell, Inc.
2 // Copyright (C) 2009 Julien Rebetez
3 // Copyright (C) 2009 Łukasz Jernaś
4 // Copyright (C) 2009 Andreu Correa Casablanca
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 using System;
21 using System.IO;
22 using System.Collections.Generic;
23 using System.Runtime.InteropServices;
24 using System.Linq;
25
26 using Mono.Unix;
27 using Gtk;
28
29 using Hyena;
30 using Hyena.Gui;
31
32 using PdfSharp.Pdf;
33
34 using PdfMod.Pdf;
35 using PdfMod.Pdf.Actions;
36
37 namespace PdfMod.Gui
38 {
39     public class Actions : HyenaActionGroup
40     {
41         Client app;
42         UndoManager undo_manager;
43         const string WIKI_URL = "http://live.gnome.org/PdfMod";
44         const string DOCS_URL = "http://library.gnome.org/users/pdfmod/";
45
46         static string [] require_doc_actions = new string[] {
47             "Save", "SaveAs", "Properties", "Undo", "Redo", "ZoomFit", "OpenInViewer",
48             "SelectAll", "SelectEvens", "SelectOdds", "SelectMatching", "SelectInverse", "InsertFrom", "ExportImages"
49         };
50
51         static string [] require_page_actions = new string[] {
52             "Remove", "Extract", "RotateRight", "RotateLeft"
53         };
54
55         public UndoManager UndoManager { get { return undo_manager; } }
56
57         public Actions (Client app, ActionManager action_manager) : base (action_manager, "Global")
58         {
59             this.app = app;
60             undo_manager = new UndoManager ();
61
62             AddImportant (
63                 new ActionEntry ("Open",   Gtk.Stock.Open,   null, "<control>O", Catalog.GetString ("Open a document"), OnOpen),
64                 new ActionEntry ("InsertFrom", Gtk.Stock.Add, Catalog.GetString("_Insert From..."), null, Catalog.GetString("Insert pages from another document"), OnInsertFrom),
65                 new ActionEntry ("Save",   Gtk.Stock.Save,   null, "<control>S", Catalog.GetString ("Save changes to this document, overwriting the existing file"), OnSave),
66                 new ActionEntry ("SaveAs", Gtk.Stock.SaveAs, null, "<control><shift>S", Catalog.GetString ("Save this document to a new file"), OnSaveAs),
67
68                 new ActionEntry ("FileMenu", null, Catalog.GetString ("_File"), null, null, null),
69                 new ActionEntry ("RecentMenu", null, Catalog.GetString ("Recent _Files"), null, null, null),
70                 new ActionEntry ("Close", Gtk.Stock.Close, null, "<control>W", null, OnClose),
71                 new ActionEntry ("Remove", Gtk.Stock.Remove, null, "Delete", null, OnRemove),
72                 new ActionEntry ("Extract", Gtk.Stock.New, null, null, null, OnExtractPages),
73                 new ActionEntry ("RotateRight", null, Catalog.GetString ("Rotate Right"), "bracketright", Catalog.GetString ("Rotate right"), OnRotateRight),
74                 new ActionEntry ("RotateLeft", null, Catalog.GetString ("Rotate Left"), "bracketleft", Catalog.GetString ("Rotate left"), OnRotateLeft),
75                 new ActionEntry ("ExportImages", null, Catalog.GetString ("Export Images"), null, Catalog.GetString ("Save all images in this document to a new folder"), OnExportImages),
76
77                 new ActionEntry ("EditMenu", null, Catalog.GetString ("_Edit"), null, null, null),
78                 new ActionEntry ("SelectAll", Stock.SelectAll, null, "<control>A", null, OnSelectAll),
79                 new ActionEntry ("SelectEvens", null, Catalog.GetString ("Select Even Pages"), null, null, OnSelectEvens),
80                 new ActionEntry ("SelectOdds", null, Catalog.GetString ("Select Odd Pages"), null, null, OnSelectOdds),
81                 new ActionEntry ("SelectMatching", null, Catalog.GetString ("Select Matching..."), "<control>F", null, OnSelectMatching),
82                 new ActionEntry ("SelectInverse", null, Catalog.GetString ("_Invert Selection"), "<shift><control>I", null, OnSelectInverse),
83                 new ActionEntry ("Undo", Stock.Undo, null, "<control>z", null, OnUndo),
84                 new ActionEntry ("Redo", Stock.Redo, null, "<control>y", null, OnRedo),
85
86                 new ActionEntry ("ViewMenu", null, Catalog.GetString ("_View"), null, null, null),
87                 new ActionEntry ("ZoomIn", Stock.ZoomIn, null, "<control>plus", null, OnZoomIn),
88                 new ActionEntry ("ZoomOut", Stock.ZoomOut, null, "<control>minus", null, OnZoomOut),
89
90                 new ActionEntry ("HelpMenu", null, Catalog.GetString ("_Help"), null, null, null),
91                 new ActionEntry ("Help", Stock.Help, Catalog.GetString ("_Contents"), "F1", null, OnHelp),
92                 new ActionEntry ("About", Stock.About, null, null, null, OnAbout),
93
94                 new ActionEntry ("PageContextMenu", null, "", null, null, OnPageContextMenu),
95
96                 new ActionEntry ("OpenInViewer", null, Catalog.GetString ("Open in Viewer"), "F5", Catalog.GetString ("Open in viewer"), OnOpenInViewer)
97             );
98
99             AddImportant (
100                 new ToggleActionEntry ("Properties", Stock.Properties, null, "<alt>Return", Catalog.GetString ("View and edit the title, keywords, and more for this document"), OnProperties, false),
101                 new ToggleActionEntry ("ZoomFit", Stock.ZoomFit, null, "<control>0", null, OnZoomFit, true),
102                 new ToggleActionEntry ("ViewToolbar", null, Catalog.GetString ("Toolbar"), null, null, OnViewToolbar, Client.Configuration.ShowToolbar),
103                 new ToggleActionEntry ("ViewBookmarks", null, Catalog.GetString ("Bookmarks"), "F9", null, OnViewBookmarks, Client.Configuration.ShowBookmarks),
104                 new ToggleActionEntry ("FullScreenView", null, Catalog.GetString ("Fullscreen"), "F11", null, OnFullScreenView, false)
105             );
106
107             this["RotateRight"].IconName = "object-rotate-right";
108             this["RotateLeft"].IconName = "object-rotate-left";
109             this["ExportImages"].IconName = "image-x-generic";
110
111             UpdateAction ("Help", true);
112
113             Update ();
114             app.IconView.SelectionChanged += OnChanged;
115             app.IconView.ZoomChanged += delegate { Update (); };
116             app.DocumentLoaded += (o, a) => {
117                 app.Document.Changed += () => Update ();
118                 Update ();
119             };
120             undo_manager.UndoChanged += OnChanged;
121
122             AddUiFromFile ("UIManager.xml");
123             Register ();
124
125             // Add additional menu item keybindings
126             var item = ActionManager.UIManager.GetWidget ("/MainMenu/ViewMenu/ZoomIn");
127             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.KP_Add, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
128             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.equal, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
129
130             item = ActionManager.UIManager.GetWidget ("/MainMenu/ViewMenu/ZoomOut");
131             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.KP_Subtract, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
132             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.underscore, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
133
134             item = ActionManager.UIManager.GetWidget ("/MainMenu/FileMenu/Close");
135             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.q, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
136
137             item = ActionManager.UIManager.GetWidget ("/MainMenu/EditMenu/Redo");
138             item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.z, Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask, Gtk.AccelFlags.Visible);
139
140             // Set up recent documents menu
141             MenuItem recent_item = ActionManager.UIManager.GetWidget ("/MainMenu/FileMenu/RecentMenu") as MenuItem;
142             var recent_chooser_item = new RecentChooserMenu (RecentManager.Default) {
143                 Filter = new RecentFilter (),
144                 SortType = RecentSortType.Mru
145             };
146             recent_chooser_item.Filter.AddPattern ("*.pdf");
147             recent_chooser_item.ItemActivated += delegate {
148                 Client.RunIdle (delegate { app.LoadPath (recent_chooser_item.CurrentUri); });
149             };
150             recent_item.Submenu = recent_chooser_item;
151         }
152
153         void OnChanged (object o, EventArgs args)
154         {
155             Update ();
156         }
157
158         void Update ()
159         {
160             bool have_doc = app.Document != null;
161             foreach (string action in require_doc_actions)
162                 UpdateAction (action, true, have_doc);
163
164             bool have_page = have_doc && app.IconView.SelectedItems.Length > 0;
165             foreach (string action in require_page_actions)
166                 UpdateAction (action, true, have_page);
167
168             UpdateAction ("Undo", true, have_doc && undo_manager.CanUndo);
169             UpdateAction ("Redo", true, have_doc && undo_manager.CanRedo);
170
171             var undo = undo_manager.UndoAction as IDescribedUndoAction;
172             this["Undo"].Label = undo == null
173                 ? Catalog.GetString ("_Undo")
174                 : String.Format (Catalog.GetString ("Undo {0}"), undo.Description);
175
176             var redo = undo_manager.RedoAction as IDescribedUndoAction;
177             this["Redo"].Label = redo == null
178                 ? Catalog.GetString ("_Redo")
179                 : String.Format (Catalog.GetString ("Redo {0}"), redo.Description);
180
181             UpdateAction ("Save", true, have_doc && app.Document.HasUnsavedChanges);
182             UpdateAction ("ZoomIn", true, have_doc && app.IconView.CanZoomIn);
183             UpdateAction ("ZoomOut", true, have_doc && app.IconView.CanZoomOut);
184
185             int selection_count = app.IconView.SelectedItems.Length;
186             this["Remove"].Label = String.Format (Catalog.GetPluralString (
187                 "Remove Page", "Remove {0} Pages", selection_count),
188                 selection_count);
189             this["Remove"].Tooltip = String.Format (Catalog.GetPluralString (
190                 "Remove the selected page", "Remove the {0} selected pages", selection_count),
191                 selection_count);
192             this["Extract"].Label = String.Format (Catalog.GetPluralString (
193                 "Extract Page", "Extract {0} Pages", selection_count),
194                 selection_count);
195             this["Extract"].Tooltip = String.Format (Catalog.GetPluralString (
196                 "Extract the selected page", "Extract the {0} selected pages", selection_count),
197                 selection_count);
198         }
199
200 #region Action Handlers
201
202         void OnOpen (object o, EventArgs args)
203         {
204             var chooser = app.CreateChooser (Catalog.GetString ("Select PDF"), FileChooserAction.Open);
205             chooser.SelectMultiple = true;
206             chooser.AddButton (Stock.Open, ResponseType.Ok);
207
208             if (app.Document != null) {
209                 chooser.SetCurrentFolder (System.IO.Path.GetDirectoryName (app.Document.SuggestedSavePath));
210             } else {
211                 chooser.SetCurrentFolder (Client.Configuration.LastOpenFolder);
212             }
213
214             var response = chooser.Run ();
215             var filenames = chooser.Filenames;
216             chooser.Destroy ();
217
218             if (response == (int)ResponseType.Ok) {
219                 Client.RunIdle (delegate {
220                     foreach (var file in filenames) {
221                         app.LoadPath (file);
222                     }
223                 });
224             }
225         }
226
227         void OnOpenInViewer (object o, EventArgs args)
228         {
229             System.Diagnostics.Process.Start (app.Document.CurrentStateUri);
230         }
231
232         void OnFullScreenView (object o, EventArgs args)
233         {
234             bool fullscreen = (this["FullScreenView"] as ToggleAction).Active;
235
236             if (fullscreen) {
237                 app.Window.Fullscreen ();
238             } else {
239                 app.Window.Unfullscreen ();
240             }
241         }
242
243         void OnInsertFrom (object o, EventArgs args)
244         {
245             var chooser = app.CreateChooser (Catalog.GetString ("Select PDF"), FileChooserAction.Open);
246             chooser.SelectMultiple = false;
247             chooser.SetCurrentFolder (System.IO.Path.GetDirectoryName (app.Document.SuggestedSavePath));
248             chooser.AddButton (Stock.Open, ResponseType.Ok);
249             // TODO will uncomment this later; don't want to break string freeze now
250             //chooser.AddButton (Catalog.GetString ("_Insert"), ResponseType.Ok);
251
252             var response = chooser.Run ();
253             string filename = chooser.Filename;
254             chooser.Destroy();
255
256             if (response == (int)ResponseType.Ok) {
257                 try {
258                     app.Document.AddFromUri (new Uri (filename));
259                 } catch (Exception e) {
260                     Hyena.Log.Exception (e);
261                     Hyena.Log.Error (
262                         Catalog.GetString ("Error Loading Document"),
263                         String.Format (Catalog.GetString ("There was an error loading {0}"), GLib.Markup.EscapeText (filename ?? "")), true
264                     );
265                 }
266             }
267         }
268
269         void OnSave (object o, EventArgs args)
270         {
271             app.Document.Save (app.Document.SuggestedSavePath);
272             undo_manager.Clear ();
273         }
274
275         void OnSaveAs (object o, EventArgs args)
276         {
277             var chooser = app.CreateChooser (Catalog.GetString ("Save as..."), FileChooserAction.Save);
278             chooser.SelectMultiple = false;
279             chooser.DoOverwriteConfirmation = true;
280             chooser.CurrentName = System.IO.Path.GetFileName (app.Document.SuggestedSavePath);
281             chooser.AddButton (Stock.SaveAs, ResponseType.Ok);
282             chooser.SetCurrentFolder (System.IO.Path.GetDirectoryName (app.Document.SuggestedSavePath));
283
284             var response = chooser.Run ();
285             string filename = chooser.Filename;
286             chooser.Destroy ();
287
288             if (response == (int)ResponseType.Ok) {
289                 Log.DebugFormat ("Saving {0} to {1}", app.Document.Uri, filename);
290                 app.Document.Save (filename);
291                 undo_manager.Clear ();
292             }
293         }
294
295         void OnProperties (object o, EventArgs args)
296         {
297             app.EditorBox.Visible = (this["Properties"] as ToggleAction).Active;
298             if (app.EditorBox.Visible) {
299                 app.EditorBox.GrabFocus ();
300             }
301         }
302
303         void OnRemove (object o, EventArgs args)
304         {
305             var action = new RemoveAction (app.Document, app.IconView.SelectedPages);
306             action.Do ();
307             // Undo isn't working yet
308             //undo_manager.AddUndoAction (action);
309         }
310
311         void OnExtractPages (object o, EventArgs args)
312         {
313             var doc = new PdfDocument ();
314             var pages = new List<Page> (app.IconView.SelectedPages);
315             foreach (var page in pages) {
316                 doc.AddPage (page.Pdf);
317             }
318
319             var path = Client.GetTmpFilename ();
320             doc.Save (path);
321             doc.Dispose ();
322
323             app.LoadPath (path, Path.Combine (
324                 Path.GetDirectoryName (app.Document.SuggestedSavePath),
325                 String.Format ("[{0}] {1}",
326                     GLib.Markup.EscapeText (Document.GetPageSummary (pages, 10)),
327                     Path.GetFileName (app.Document.SuggestedSavePath))
328             ));
329         }
330
331         void OnExportImages (object o, EventArgs args)
332         {
333             var action = new ExportImagesAction (app.Document, app.Document.Pages);
334             if (action.ExportableImageCount == 0) {
335                 Log.Information ("Found zero exportable images in the selected pages");
336                 return;
337             }
338
339             var export_path_base = Path.Combine (
340                 Path.GetDirectoryName (app.Document.SuggestedSavePath),
341                 Hyena.StringUtil.EscapeFilename (
342                     app.Document.Title ?? System.IO.Path.GetFileNameWithoutExtension (app.Document.Filename))
343             );
344
345             var export_path = export_path_base;
346             int i = 1;
347             while (Directory.Exists (export_path) && i < 100) {
348                 export_path = String.Format ("{0} ({1})", export_path_base, i++);
349             }
350
351             try {
352                 Directory.CreateDirectory (export_path);
353             } catch (Exception e) {
354                 Hyena.Log.Exception (e);
355             }
356
357             action.Do (export_path);
358             System.Diagnostics.Process.Start (export_path);
359         }
360
361         void OnUndo (object o, EventArgs args)
362         {
363             undo_manager.Undo ();
364         }
365
366         void OnRedo (object o, EventArgs args)
367         {
368             undo_manager.Redo ();
369         }
370
371         [DllImport ("glib-2.0.dll")]
372         static extern IntPtr g_get_language_names ();
373
374         string CombinePaths (params string [] parts)
375         {
376             string path = parts[0];
377             for (int i = 1; i < parts.Length; i++) {
378                 path = System.IO.Path.Combine (path, parts[i]);
379             }
380             return path;
381         }
382
383         void OnHelp (object o, EventArgs args)
384         {
385             bool shown = false;
386             try {
387                 IntPtr lang_ptr = g_get_language_names ();
388                 var langs = GLib.Marshaller.NullTermPtrToStringArray (lang_ptr, false);
389
390                 string help_dir = null;
391                 foreach (var dir in new string [] { Core.Defines.PREFIX + "/share/gnome/help/", "/usr/local/share/gnome/help/", "docs/" }) {
392                     help_dir = dir;
393                     if (System.IO.Directory.Exists (dir + "pdfmod/")) {
394                         break;
395                     }
396                 }
397
398                 foreach (var lang in langs) {
399                     var help_path = CombinePaths (help_dir, "pdfmod", lang, "pdfmod.xml");
400                     if (System.IO.File.Exists (help_path)) {
401                         System.Diagnostics.Process.Start (String.Format ("ghelp://{0}", help_path));
402                         shown = true;
403                         break;
404                     }
405                 }
406             } catch (Exception e) {
407                 Hyena.Log.Exception ("Error opening help", e);
408             }
409
410             if (!shown) {
411                 var message_dialog = new Hyena.Widgets.HigMessageDialog (
412                     app.Window, DialogFlags.Modal, MessageType.Warning, ButtonsType.None,
413                     Catalog.GetString ("Error opening help"),
414                     Catalog.GetString ("Would you like to open PDF Mod's online documentation?")
415                 );
416                 message_dialog.AddButton (Stock.No, ResponseType.No, false);
417                 message_dialog.AddButton (Stock.Yes, ResponseType.Yes, true);
418
419                 var response = (ResponseType) message_dialog.Run ();
420                 message_dialog.Destroy ();
421                 if (response == ResponseType.Yes) {
422                     System.Diagnostics.Process.Start (DOCS_URL);
423                 }
424             }
425         }
426
427         void OnAbout (object o, EventArgs args)
428         {
429             Gtk.AboutDialog.SetUrlHook ((dlg, link) => { System.Diagnostics.Process.Start (link); });
430
431             var dialog = new Gtk.AboutDialog () {
432                 ProgramName = "PDF Mod",
433                 Version = Core.Defines.VERSION,
434                 Website = WIKI_URL,
435                 WebsiteLabel = Catalog.GetString ("Visit Website"),
436                 Authors = new string [] {
437                     Catalog.GetString ("Primary Development:"),
438                     "\tGabriel Burt",
439                     "",
440                     Catalog.GetString ("Contributors:"),
441                     "\tSandy Armstrong",
442                     "\tAaron Bockover",
443                     "\tOlivier Le Thanh Duong",
444                     "\tJulien Rebetez",
445                     "\tIgor Vatavuk",
446                     "\tBertrand Lorentz",
447                     "\tMichael McKinley",
448                     "\tŁukasz Jernaś",
449                     "\tRomain Tartière",
450                     "\tRobert Dyer",
451                     "\tAndreu Correa Casablanca"
452                 },
453                 Documenters = new string [] { "Gabriel Burt" },
454                 Artists = new string [] { "Kalle Persson" },
455                 Copyright = String.Format (
456                     // Translators: {0} and {1} are the years the copyright assertion covers; put into
457                     // variables so you don't have to re-translate this every year
458                     Catalog.GetString ("Copyright {0} Novell Inc.\nCopyright {1} Other PDF Mod Contributors"),
459                     "2009-2010", "2009"
460                 ),
461                 TranslatorCredits = Catalog.GetString ("translator-credits")
462             };
463
464             try {
465                 dialog.Logo = Gtk.IconTheme.Default.LoadIcon ("pdfmod", 256, 0);
466             } catch {}
467
468             string [] license_paths = new string [] {
469                 Core.Defines.PREFIX + "/share/doc/packages/pdfmod/COPYING",
470                 "/usr/local/share/doc/packages/pdfmod/COPYING",
471                 "COPYING",
472                 "../COPYING"
473             };
474
475             foreach (var path in license_paths) {
476                 try {
477                     dialog.License = System.IO.File.ReadAllText (path);
478                     break;
479                 } catch {}
480             }
481
482             dialog.Run ();
483             dialog.Destroy ();
484         }
485
486         void OnPageContextMenu (object o, EventArgs args)
487         {
488             ShowContextMenu ("/PageContextMenu");
489         }
490
491         void OnSelectAll (object o, EventArgs args)
492         {
493             app.IconView.SetPageSelectionMode (PageSelectionMode.All);
494         }
495
496         void OnSelectEvens (object o, EventArgs args)
497         {
498             app.IconView.SetPageSelectionMode (PageSelectionMode.Evens);
499         }
500
501         void OnSelectOdds (object o, EventArgs args)
502         {
503             app.IconView.SetPageSelectionMode (PageSelectionMode.Odds);
504         }
505
506         void OnSelectMatching (object o, EventArgs args)
507         {
508             app.ToggleMatchQuery ();
509         }
510
511         void OnSelectInverse (object o, EventArgs args)
512         {
513             app.IconView.SetPageSelectionMode (PageSelectionMode.Inverse);
514         }
515
516         void OnZoomIn (object o, EventArgs args)
517         {
518             app.IconView.Zoom (10);
519         }
520
521         void OnZoomOut (object o, EventArgs args)
522         {
523             app.IconView.Zoom (-10);
524         }
525
526         void OnZoomFit (object o, EventArgs args)
527         {
528             app.IconView.ZoomFit ();
529         }
530
531         void OnViewToolbar (object o, EventArgs args)
532         {
533             bool show = (this["ViewToolbar"] as ToggleAction).Active;
534             Client.Configuration.ShowToolbar = app.HeaderToolbar.Visible = show;
535         }
536
537         void OnViewBookmarks (object o, EventArgs args)
538         {
539             bool show = (this["ViewBookmarks"] as ToggleAction).Active;
540             Client.Configuration.ShowBookmarks = app.BookmarkView.Visible = show;
541         }
542
543         void OnRotateRight (object o, EventArgs args)
544         {
545             Rotate (90);
546         }
547
548         void OnRotateLeft (object o, EventArgs args)
549         {
550             Rotate (-90);
551         }
552
553         void Rotate (int degrees)
554         {
555             if (!(app.Window.Focus is Gtk.Entry)) {
556                 var action = new RotateAction (app.Document, app.IconView.SelectedPages, degrees);
557                 action.Do ();
558                 undo_manager.AddUndoAction (action);
559             }
560         }
561
562         void OnClose (object o, EventArgs args)
563         {
564             app.Quit ();
565         }
566
567 #endregion
568
569     }
570 }