diff options
author | Alex Merry <alex.merry@cs.ox.ac.uk> | 2012-12-03 19:02:53 +0000 |
---|---|---|
committer | Alex Merry <alex.merry@cs.ox.ac.uk> | 2012-12-04 11:12:28 +0000 |
commit | 94ff07fc9d728d97dde159e0c3e6ab80e29e0855 (patch) | |
tree | 2e74ff3d709db87e2e2b27aa501ed46727c054d7 /tikzit/src/gtk | |
parent | 0d715a7f17b8e5570b42d7802c502e4aa234a3c1 (diff) |
Refactor MainWindow into Application and Window
Basic multiple-window support, but no tools.
Diffstat (limited to 'tikzit/src/gtk')
-rw-r--r-- | tikzit/src/gtk/Application.h | 139 | ||||
-rw-r--r-- | tikzit/src/gtk/Application.m | 268 | ||||
-rw-r--r-- | tikzit/src/gtk/GraphInputHandler.h | 5 | ||||
-rw-r--r-- | tikzit/src/gtk/GraphInputHandler.m | 55 | ||||
-rw-r--r-- | tikzit/src/gtk/Menu.h | 33 | ||||
-rw-r--r-- | tikzit/src/gtk/Menu.m | 156 | ||||
-rw-r--r-- | tikzit/src/gtk/Window.h (renamed from tikzit/src/gtk/MainWindow.h) | 113 | ||||
-rw-r--r-- | tikzit/src/gtk/Window.m (renamed from tikzit/src/gtk/MainWindow.m) | 463 | ||||
-rw-r--r-- | tikzit/src/gtk/main.m | 43 |
9 files changed, 738 insertions, 537 deletions
diff --git a/tikzit/src/gtk/Application.h b/tikzit/src/gtk/Application.h new file mode 100644 index 0000000..1967132 --- /dev/null +++ b/tikzit/src/gtk/Application.h @@ -0,0 +1,139 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" + +@class Application; +@class Configuration; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class TikzDocument; +@class Window; + +extern Application* app; + +/** + * Manages the main application window + */ +@interface Application: NSObject { + // the main application configuration + Configuration *configFile; + // maintains the known (user-defined) styles + StyleManager *styleManager; + // maintains the preambles used for previews + Preambles *preambles; + // the last-accessed folders (for open and save dialogs) + NSString *lastOpenFolder; + NSString *lastSaveAsFolder; + + // the open (active) document + TikzDocument *activeDocument; + + PreambleEditor *preambleWindow; + PreviewWindow *previewWindow; + SettingsDialog *settingsDialog; + + // the open windows (array of Window*) + NSMutableArray *openWindows; +} + +/** + * The main application configuration file + */ +@property (readonly) Configuration *mainConfiguration; + +/** + * The app-wide style manager instance + */ +@property (readonly) StyleManager *styleManager; + +/** + * The app-wide preambles registry + */ +@property (readonly) Preambles *preambles; + +/** + * The document the user is currently editing + */ +@property (retain) TikzDocument *activeDocument; + +/** + * The folder last actively chosen by a user for opening a file + */ +@property (copy) NSString *lastOpenFolder; + +/** + * The folder last actively chosen by a user for saving a file + */ +@property (copy) NSString *lastSaveAsFolder; + +/** + * The application instance. + */ ++ (Application*) app; + +/** + * Starts the application with a single window containing an empty file + */ +- (id) init; +/** + * Starts the application with the given files open + */ +- (id) initWithFiles:(NSArray*)files; + +/** + * Loads a new, empty document in a new window + */ +- (void) newWindow; +/** + * Loads an existing document from a file in a new window + * + * @param doc the document the new window should show + */ +- (void) newWindowWithDocument:(TikzDocument*)doc; +/** + * Quit the application, confirming with the user if there are + * changes to any open documents. + */ +- (void) quit; + +/** + * Show the dialog for editing preambles. + */ +- (void) showPreamblesEditor; +/** + * Show or update the preview window. + */ +- (void) showPreviewForDocument:(TikzDocument*)doc; +/** + * Show the settings dialog. + */ +- (void) showSettingsDialog; + +/** + * Save the application configuration to permanent storage + * + * Should be called just before the application exits + */ +- (void) saveConfiguration; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/Application.m b/tikzit/src/gtk/Application.m new file mode 100644 index 0000000..2631bb3 --- /dev/null +++ b/tikzit/src/gtk/Application.m @@ -0,0 +1,268 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Application.h" + +#import "Configuration.h" +#import "PreambleEditor.h" +#ifdef HAVE_POPPLER +#import "Preambles.h" +#import "Preambles+Storage.h" +#import "PreviewWindow.h" +#endif +#ifdef HAVE_POPPLER +#import "SettingsDialog.h" +#endif +#import "StyleManager.h" +#import "StyleManager+Storage.h" +#import "SupportDir.h" +#import "TikzDocument.h" +#import "Window.h" + +// used for args to g_mkdir_with_parents +#import "stat.h" + +Application* app = nil; + +@interface Application (Notifications) +- (void) windowClosed:(NSNotification*)notification; +@end + +@implementation Application + +@synthesize mainConfiguration=configFile; +@synthesize styleManager, preambles; +@synthesize lastOpenFolder, lastSaveAsFolder; +@synthesize activeDocument; + ++ (Application*) app { + if (app == nil) { + [[[self alloc] init] release]; + } + return app; +} + +- (id) _initCommon { + if (app != nil) { + [self release]; + self = app; + return app; + } + self = [super init]; + + if (self) { + NSError *error = nil; + configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error]; + if (error != nil) { + logError (error, @"WARNING: Failed to load configuration"); + } + + styleManager = [[StyleManager alloc] init]; + [styleManager loadStylesUsingConfigurationName:@"styles"]; // FIXME: error message? + +#ifdef HAVE_POPPLER + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + preambles = [[Preambles alloc] initFromDirectory:preamblesDir]; // FIXME: error message? + [preambles setStyleManager:styleManager]; + NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"]; + if (selectedPreamble != nil) { + [preambles setSelectedPreambleName:selectedPreamble]; + } +#endif + + lastOpenFolder = [[configFile stringEntry:@"lastOpenFolder" inGroup:@"Paths"] retain]; + if (lastOpenFolder == nil) + lastOpenFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + lastSaveAsFolder = [[configFile stringEntry:@"lastSaveAsFolder" inGroup:@"Paths"] retain]; + if (lastSaveAsFolder == nil) + lastSaveAsFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + + openWindows = [[NSMutableArray alloc] init]; + + // FIXME: toolboxes + + app = [self retain]; + } + + return self; +} + +- (id) init { + self = [self _initCommon]; + + if (self) { + [self newWindow]; + } + + return self; +} + +- (id) initWithFiles:(NSArray*)files { + self = [self _initCommon]; + + if (self) { + int fileOpenCount = 0; + for (NSString *file in files) { + NSError *error = nil; + TikzDocument *doc = [TikzDocument documentFromFile:file styleManager:styleManager error:&error]; + if (doc != nil) { + [self newWindowWithDocument:doc]; + ++fileOpenCount; + } else { + logError(error, @"WARNING: failed to open file"); + } + } + if (fileOpenCount == 0) { + [self newWindow]; + } + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [configFile release]; + [styleManager release]; + [preambles release]; + [lastOpenFolder release]; + [lastSaveAsFolder release]; + [activeDocument release]; + [preambleWindow release]; + [previewWindow release]; + [settingsDialog release]; + [openWindows release]; + + [super dealloc]; +} + +- (void) newWindow { + [self newWindowWithDocument:nil]; +} + +- (void) newWindowWithDocument:(TikzDocument*)doc { + Window *window = (doc == nil) + ? [Window window] + : [Window windowWithDocument:doc]; + [openWindows addObject:window]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowClosed:) + name:@"WindowClosed" + object:window]; + // FIXME: focus? +} + +- (void) quit { + NSMutableArray *unsavedDocs = [NSMutableArray arrayWithCapacity:[openWindows count]]; + for (Window *window in openWindows) { + TikzDocument *doc = [window document]; + if ([doc hasUnsavedChanges]) { + [unsavedDocs addObject:doc]; + } + } + if ([unsavedDocs count] > 0) { + // FIXME: show a dialog + return; + } + gtk_main_quit(); +} + +- (void) showPreamblesEditor { +#ifdef HAVE_POPPLER + if (preambleWindow == nil) { + preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles]; + //[preambleWindow setParentWindow:mainWindow]; + } + [preambleWindow show]; +#endif +} + +- (void) showPreviewForDocument:(TikzDocument*)doc { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:preambles config:configFile]; + //[previewWindow setParentWindow:mainWindow]; + [previewWindow setDocument:doc]; + } + [previewWindow show]; +#endif +} + +- (void) showSettingsDialog { +#ifdef HAVE_POPPLER + if (settingsDialog == nil) { + settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile]; + //[settingsDialog setParentWindow:mainWindow]; + } + [settingsDialog show]; +#endif +} + +- (Configuration*) mainConfiguration { + return configFile; +} + +- (TikzDocument*) activeDocument { + return activeDocument; +} + +- (void) saveConfiguration { + NSError *error = nil; + +#ifdef HAVE_POPPLER + if (preambles != nil) { + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([preamblesDir UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); + [preambles storeToDirectory:preamblesDir]; + [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; + } +#endif + + [styleManager saveStylesUsingConfigurationName:@"styles"]; + + if (lastOpenFolder != nil) { + [configFile setStringEntry:@"lastOpenFolder" inGroup:@"Paths" value:lastOpenFolder]; + } + if (lastSaveAsFolder != nil) { + [configFile setStringEntry:@"lastSaveAsFolder" inGroup:@"Paths" value:lastSaveAsFolder]; + } + + if (![configFile writeToStoreWithError:&error]) { + logError (error, @"Could not write config file"); + } +} + +@end + +@implementation Application (Notifications) +- (void) windowClosed:(NSNotification*)notification { + Window *window = [notification object]; + [openWindows removeObjectIdenticalTo:window]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:nil + object:window]; + if ([openWindows count] == 0) { + gtk_main_quit(); + } else if ([[window document] isEqual:activeDocument]) { + [self setActiveDocument:[[openWindows objectAtIndex:0] document]]; + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/gtk/GraphInputHandler.h b/tikzit/src/gtk/GraphInputHandler.h index 7277e82..caf1528 100644 --- a/tikzit/src/gtk/GraphInputHandler.h +++ b/tikzit/src/gtk/GraphInputHandler.h @@ -20,8 +20,6 @@ #import "InputDelegate.h" #import "StyleManager.h" -@class MainWindow; - typedef enum { SelectMode, CreateNodeMode, @@ -43,7 +41,6 @@ typedef enum { } MouseState; @interface GraphInputHandler: NSObject { - MainWindow *window; GraphRenderer *renderer; InputMode mode; MouseState state; @@ -60,7 +57,7 @@ typedef enum { @property (assign) float edgeFuzz; @property (assign) InputMode mode; -- (id) initWithGraphRenderer:(GraphRenderer*)r window:(MainWindow*)w; +- (id) initWithGraphRenderer:(GraphRenderer*)r; - (void) resetState; diff --git a/tikzit/src/gtk/GraphInputHandler.m b/tikzit/src/gtk/GraphInputHandler.m index 02d39a1..788dfeb 100644 --- a/tikzit/src/gtk/GraphInputHandler.m +++ b/tikzit/src/gtk/GraphInputHandler.m @@ -18,25 +18,15 @@ #import "GraphInputHandler.h" #import <gdk/gdkkeysyms.h> -#import "MainWindow.h" #import "Edge+Render.h" static const InputMask unionSelectMask = ShiftMask; -@interface GraphInputHandler (Notifications) -- (void) nodeSelectionChanged:(NSNotification*)n; -- (void) edgeSelectionChanged:(NSNotification*)n; -@end - @implementation GraphInputHandler - (id) initWithGraphRenderer:(GraphRenderer*)r { - return [self initWithGraphRenderer:r window:nil]; -} -- (id) initWithGraphRenderer:(GraphRenderer*)r window:(MainWindow*)w { self = [super init]; if (self) { - window = w; renderer = r; mode = SelectMode; state = QuietState; @@ -47,12 +37,6 @@ static const InputMask unionSelectMask = ShiftMask; currentResizeHandle = NoHandle; // FIXME: listen only to the doc's PickSupport // (need to track document changes) - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(nodeSelectionChanged:) - name:@"NodeSelectionChanged" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(edgeSelectionChanged:) - name:@"EdgeSelectionChanged" object:nil]; } return self; @@ -125,21 +109,6 @@ static const InputMask unionSelectMask = ShiftMask; [self deselectAll]; if (mode == BoundingBoxMode) { [renderer setBoundingBoxHandlesShown:YES]; - [window favourGraphControls]; - } else if (mode == CreateNodeMode) { - [window favourNodeControls]; - } else if (mode == DrawEdgeMode) { - [window favourEdgeControls]; - } else if (mode == HandMode) { - [window favourGraphControls]; - } else if (mode == SelectMode) { - // FIXME: also change on selection change - if ([[[[self doc] pickSupport] selectedNodes] count]) - [window favourNodeControls]; - else if ([[[[self doc] pickSupport] selectedEdges] count]) - [window favourEdgeControls]; - else - [window favourGraphControls]; } } } @@ -497,28 +466,4 @@ static const InputMask unionSelectMask = ShiftMask; @end -@implementation GraphInputHandler (Notifications) -- (void) nodeSelectionChanged:(NSNotification*)n { - if (mode == SelectMode) { - if ([[[[self doc] pickSupport] selectedNodes] count]) - [window favourNodeControls]; - else if ([[[[self doc] pickSupport] selectedEdges] count]) - [window favourEdgeControls]; - else - [window favourGraphControls]; - } -} - -- (void) edgeSelectionChanged:(NSNotification*)n { - if (mode == SelectMode) { - if ([[[[self doc] pickSupport] selectedNodes] count]) - [window favourNodeControls]; - else if ([[[[self doc] pickSupport] selectedEdges] count]) - [window favourEdgeControls]; - else - [window favourGraphControls]; - } -} -@end - // vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/Menu.h b/tikzit/src/gtk/Menu.h index 5a364e4..f195765 100644 --- a/tikzit/src/gtk/Menu.h +++ b/tikzit/src/gtk/Menu.h @@ -1,10 +1,6 @@ /* * Copyright 2011 Alex Merry <alex.merry@kdemail.net> * - * Stuff stolen from glade-window.c in Glade: - * Copyright (C) 2001 Ximian, Inc. - * Copyright (C) 2007 Vincent Geddes. - * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of @@ -22,14 +18,14 @@ #import "TZFoundation.h" #import <gtk/gtk.h> -@class MainWindow; +@class Window; @class PickSupport; /** - * Manages the menu and toolbar for the main window. + * Manages the menu */ @interface Menu: NSObject { - MainWindow *mainWindow; + Window *window; GtkUIManager *ui; GtkActionGroup *staticActions; GtkActionGroup *documentActions; @@ -46,25 +42,20 @@ } /** - * Constructs the menu and toolbar for @p window - * - * @param window the mainwindow that will be acted upon by the various - * menu items and toolbar buttons. + * The menubar widget, to be inserted into the window */ -- (id) initForMainWindow:(MainWindow*)window; - +@property (readonly) GtkWidget *menubar; /** - * The menubar widget, to be inserted into the main window + * The window object passed to initForWindow */ -- (GtkWidget*) menubar; -/** - * The toolbar widget, to be inserted into the main window - */ -- (GtkWidget*) toolbar; +@property (readonly) Window *window; + /** - * The main window object passed to initForMainWindow + * Constructs the menu for @p window + * + * @param window the window that will be acted upon */ -- (MainWindow*) mainWindow; +- (id) initForWindow:(Window*)window; /** * Enables or disables the undo action diff --git a/tikzit/src/gtk/Menu.m b/tikzit/src/gtk/Menu.m index b99336d..c6f2fde 100644 --- a/tikzit/src/gtk/Menu.m +++ b/tikzit/src/gtk/Menu.m @@ -21,7 +21,8 @@ #import "Menu.h" -#import "MainWindow.h" +#import "Application.h" +#import "Window.h" #import "GraphInputHandler.h" #import "Configuration.h" #import "PickSupport.h" @@ -51,49 +52,49 @@ // {{{ Callbacks -static void new_cb (GtkAction *action, MainWindow *window) { +static void new_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window loadEmptyDocument]; + [app newWindow]; [pool drain]; } -static void open_cb (GtkAction *action, MainWindow *window) { +static void open_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window openFile]; [pool drain]; } -static void save_cb (GtkAction *action, MainWindow *window) { +static void save_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window saveActiveDocument]; [pool drain]; } -static void save_as_cb (GtkAction *action, MainWindow *window) { +static void save_as_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window saveActiveDocumentAs]; [pool drain]; } -static void save_as_shape_cb (GtkAction *action, MainWindow *window) { +static void save_as_shape_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window saveActiveDocumentAsShape]; [pool drain]; } -static void refresh_shapes_cb (GtkAction *action, MainWindow *window) { +static void refresh_shapes_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [Shape refreshShapeDictionary]; [pool drain]; } -static void quit_cb (GtkAction *action, MainWindow *window) { +static void quit_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window quit]; + [app quit]; [pool drain]; } -static void help_cb (GtkAction *action, MainWindow *window) { +static void help_cb (GtkAction *action, Window *window) { GError *gerror = NULL; gtk_show_uri (NULL, "http://tikzit.sourceforge.net/manual.html", GDK_CURRENT_TIME, &gerror); if (gerror != NULL) { @@ -103,7 +104,7 @@ static void help_cb (GtkAction *action, MainWindow *window) { } } -static void about_cb (GtkAction *action, MainWindow *window) { +static void about_cb (GtkAction *action, Window *window) { static const gchar * const authors[] = { "Aleks Kissinger <aleks0@gmail.com>", "Chris Heunen <chrisheunen@gmail.com>", @@ -145,10 +146,10 @@ static void about_cb (GtkAction *action, MainWindow *window) { g_object_unref (logo); } -static void undo_cb (GtkAction *action, MainWindow *window) { +static void undo_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - TikzDocument *document = [window activeDocument]; + TikzDocument *document = [window document]; if ([document canUndo]) { [document undo]; } else { @@ -159,10 +160,10 @@ static void undo_cb (GtkAction *action, MainWindow *window) { [pool drain]; } -static void redo_cb (GtkAction *action, MainWindow *window) { +static void redo_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - TikzDocument *document = [window activeDocument]; + TikzDocument *document = [window document]; if ([document canRedo]) { [document redo]; } else { @@ -173,132 +174,133 @@ static void redo_cb (GtkAction *action, MainWindow *window) { [pool drain]; } -static void cut_cb (GtkAction *action, MainWindow *window) { +static void cut_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window cut]; [pool drain]; } -static void copy_cb (GtkAction *action, MainWindow *window) { +static void copy_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window copy]; [pool drain]; } -static void paste_cb (GtkAction *action, MainWindow *window) { +static void paste_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window paste]; [pool drain]; } -static void delete_cb (GtkAction *action, MainWindow *window) { +static void delete_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] removeSelected]; + [[window document] removeSelected]; [pool drain]; } -static void select_all_cb (GtkAction *action, MainWindow *window) { +static void select_all_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - TikzDocument *document = [window activeDocument]; + TikzDocument *document = [window document]; [[document pickSupport] selectAllNodes:[NSSet setWithArray:[[document graph] nodes]]]; [pool drain]; } -static void deselect_all_cb (GtkAction *action, MainWindow *window) { +static void deselect_all_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - TikzDocument *document = [window activeDocument]; + TikzDocument *document = [window document]; [[document pickSupport] deselectAllNodes]; [[document pickSupport] deselectAllEdges]; [pool drain]; } -static void flip_horiz_cb (GtkAction *action, MainWindow *window) { +static void flip_horiz_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] flipSelectedNodesHorizontally]; + [[window document] flipSelectedNodesHorizontally]; [pool drain]; } -static void flip_vert_cb (GtkAction *action, MainWindow *window) { +static void flip_vert_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] flipSelectedNodesVertically]; + [[window document] flipSelectedNodesVertically]; [pool drain]; } -static void reverse_edges_cb (GtkAction *action, MainWindow *window) { +static void reverse_edges_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] reverseSelectedEdges]; + [[window document] reverseSelectedEdges]; [pool drain]; } -static void bring_forward_cb (GtkAction *action, MainWindow *window) { +static void bring_forward_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] bringSelectionForward]; + [[window document] bringSelectionForward]; [pool drain]; } -static void send_backward_cb (GtkAction *action, MainWindow *window) { +static void send_backward_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] sendSelectionBackward]; + [[window document] sendSelectionBackward]; [pool drain]; } -static void bring_to_front_cb (GtkAction *action, MainWindow *window) { +static void bring_to_front_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] bringSelectionToFront]; + [[window document] bringSelectionToFront]; [pool drain]; } -static void send_to_back_cb (GtkAction *action, MainWindow *window) { +static void send_to_back_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[window activeDocument] sendSelectionToBack]; + [[window document] sendSelectionToBack]; [pool drain]; } #ifdef HAVE_POPPLER -static void show_preferences_cb (GtkAction *action, MainWindow *window) { +static void show_preferences_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window showSettingsDialog]; + [app showSettingsDialog]; [pool drain]; } -static void show_preamble_cb (GtkAction *action, MainWindow *window) { +static void show_preamble_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window editPreambles]; + [app showPreamblesEditor]; [pool drain]; } -static void show_preview_cb (GtkAction *action, MainWindow *window) { +static void show_preview_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window showPreview]; + [app showPreviewForDocument:[window document]]; [pool drain]; } #endif -static void zoom_in_cb (GtkAction *action, MainWindow *window) { +static void zoom_in_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window zoomIn]; [pool drain]; } -static void zoom_out_cb (GtkAction *action, MainWindow *window) { +static void zoom_out_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window zoomOut]; [pool drain]; } -static void zoom_reset_cb (GtkAction *action, MainWindow *window) { +static void zoom_reset_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window zoomReset]; [pool drain]; } -static void input_mode_change_cb (GtkRadioAction *action, GtkRadioAction *current, MainWindow *window) { +static void input_mode_change_cb (GtkRadioAction *action, GtkRadioAction *current, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; InputMode mode = (InputMode)gtk_radio_action_get_current_value (action); [[window graphInputHandler] setMode:mode]; [pool drain]; } +/* static void toolbar_style_change_cb (GtkRadioAction *action, GtkRadioAction *current, Menu *menu) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @@ -308,8 +310,9 @@ static void toolbar_style_change_cb (GtkRadioAction *action, GtkRadioAction *cur [pool drain]; } +*/ -static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, MainWindow *window) { +static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; gchar *uri, *path; @@ -324,13 +327,7 @@ static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, MainWin return; } - NSString *nspath = [NSString stringWithGlibFilename:path]; - if (error) { - g_warning ("Could not convert filename \"%s\" to an NSString: %s", path, error->message); - g_error_free (error); - return; - } - [window loadDocumentFromFile:nspath]; + [window openFileAtPath:[NSString stringWithGlibFilename:path]]; g_free (uri); g_free (path); @@ -396,12 +393,14 @@ static const gchar ui_info[] = #endif " </menu>" " <menu action='ViewMenu'>" +/* " <menu action='ToolbarStyle'>" " <menuitem action='ToolbarIconsOnly'/>" " <menuitem action='ToolbarTextOnly'/>" " <menuitem action='ToolbarTextIcons'/>" " <menuitem action='ToolbarTextIconsHoriz'/>" " </menu>" +*/ /* " <menuitem action='ToolbarVisible'/>" " <menuitem action='StatusbarVisible'/>" @@ -427,6 +426,7 @@ static const gchar ui_info[] = " <menuitem action='About'/>" " </menu>" " </menubar>" +/* " <toolbar name='ToolBar'>" " <toolitem action='New'/>" " <toolitem action='Open'/>" @@ -442,6 +442,7 @@ static const gchar ui_info[] = " <toolitem action='BoundingBoxMode'/>" " <toolitem action='HandMode'/>" " </toolbar>" +*/ "</ui>"; @@ -493,7 +494,7 @@ static GtkActionEntry static_entries[] = { #endif /* ViewMenu */ - { "ToolbarStyle", NULL, N_("_Toolbar style") }, + //{ "ToolbarStyle", NULL, N_("_Toolbar style") }, #ifdef HAVE_POPPLER { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL, @@ -694,14 +695,19 @@ create_recent_chooser_menu () @implementation Menu -- (id) initForMainWindow:(MainWindow*)window -{ +- (id) init { + [self release]; + self = nil; + return nil; +} + +- (id) initForWindow:(Window*)w { self = [super init]; if (!self) { return nil; } - mainWindow = window; + window = w; GError *error = NULL; @@ -715,11 +721,13 @@ create_recent_chooser_menu () gtk_action_group_add_radio_actions (staticActions, mode_entries, n_mode_entries, (gint)SelectMode, G_CALLBACK (input_mode_change_cb), window); + /* GtkToolbarStyle style; g_object_get (G_OBJECT (gtk_settings_get_default ()), "gtk-toolbar-style", &style, NULL); gtk_action_group_add_radio_actions (staticActions, toolbar_style_entries, n_toolbar_style_entries, style, G_CALLBACK (toolbar_style_change_cb), self); + */ documentActions = gtk_action_group_new (ACTION_GROUP_DOCUMENT); //gtk_action_group_set_translation_domain (documentActions, GETTEXT_PACKAGE); @@ -752,11 +760,13 @@ create_recent_chooser_menu () } /* Set custom images for tool mode buttons */ + /* set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/SelectMode")), &select_rectangular); set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/CreateNodeMode")), &draw_ellipse); set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/DrawEdgeMode")), &draw_path); set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/BoundingBoxMode")), &transform_crop_and_resize); set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/HandMode")), &transform_move); + */ /* Save the undo and redo actions so they can be updated */ undoAction = gtk_action_group_get_action (documentActions, "Undo"); @@ -783,28 +793,37 @@ create_recent_chooser_menu () selBasedActions[0] = gtk_action_group_get_action (documentActions, "Delete"); selBasedActions[1] = gtk_action_group_get_action (documentActions, "DeselectAll"); - Configuration *configFile = [window mainConfiguration]; + /* + Configuration *configFile = [app mainConfiguration]; if ([configFile hasKey:@"toolbarStyle" inGroup:@"UI"]) { int value = [configFile integerEntry:@"toolbarStyle" inGroup:@"UI"]; gtk_radio_action_set_current_value ( GTK_RADIO_ACTION (gtk_action_group_get_action (staticActions, "ToolbarIconsOnly")), value); } + */ return self; } +- (void) dealloc { + g_free (nodeSelBasedActions); + g_free (selBasedActions); + + [super dealloc]; +} + - (GtkWidget*) menubar { return gtk_ui_manager_get_widget (ui, "/MenuBar"); } +/* - (GtkWidget*) toolbar { return gtk_ui_manager_get_widget (ui, "/ToolBar"); } +*/ -- (MainWindow*) mainWindow { - return mainWindow; -} +@synthesize window; - (void) setUndoActionEnabled:(BOOL)enabled { gtk_action_set_sensitive (undoAction, enabled); @@ -846,13 +865,6 @@ create_recent_chooser_menu () } } -- (void) dealloc { - g_free (nodeSelBasedActions); - g_free (selBasedActions); - - [super dealloc]; -} - @end // }}} diff --git a/tikzit/src/gtk/MainWindow.h b/tikzit/src/gtk/Window.h index 3133d77..6aa710a 100644 --- a/tikzit/src/gtk/MainWindow.h +++ b/tikzit/src/gtk/Window.h @@ -1,5 +1,5 @@ /* - * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,7 +19,6 @@ #import <gtk/gtk.h> #import "WidgetSurface.h" -@class Configuration; @class GraphRenderer; @class GraphInputHandler; @class Menu; @@ -33,79 +32,69 @@ @class TikzDocument; /** - * Manages the main application window + * Manages a document window */ -@interface MainWindow: NSObject { - // the main application configuration - Configuration *configFile; - // maintains the known (user-defined) styles - StyleManager *styleManager; - // maintains the preambles used for previews - Preambles *preambles; - +@interface Window: NSObject { // GTK+ widgets - GtkWindow *mainWindow; + GtkWindow *window; GtkTextBuffer *tikzBuffer; GtkStatusbar *statusBar; GtkPaned *tikzPaneSplitter; GtkWidget *tikzPane; // Classes that manage parts of the window - // (or other windows) Menu *menu; GraphRenderer *renderer; GraphInputHandler *inputHandler; - PreambleEditor *preambleWindow; - PreviewWindow *previewWindow; - SettingsDialog *settingsDialog; WidgetSurface *surface; // state variables BOOL suppressTikzUpdates; BOOL hasParseError; - // the last-accessed folder (for open and save dialogs) - NSString *lastFolder; - // the open (active) document + + // the document displayed by the window TikzDocument *document; } /** - * Create and show the main window. + * The document displayed by the window + */ +@property (retain) TikzDocument *document; + +/** + * Create a window with an empty document */ - (id) init; ++ (id) window; + +/** + * Create a window with the given document + */ +- (id) initWithDocument:(TikzDocument*)doc; ++ (id) windowWithDocument:(TikzDocument*)doc; /** * Open a file, asking the user which file to open */ - (void) openFile; /** + * Open a file + */ +- (BOOL) openFileAtPath:(NSString*)path; +/** * Save the active document to the path it was opened from * or last saved to, or ask the user where to save it. */ -- (void) saveActiveDocument; +- (BOOL) saveActiveDocument; /** * Save the active document, asking the user where to save it. */ -- (void) saveActiveDocumentAs; +- (BOOL) saveActiveDocumentAs; /** * Save the active document as a shape, asking the user what to name it. */ - (void) saveActiveDocumentAsShape; -/** - * Quit the application, confirming with the user if there are - * changes to an open document. - */ -- (void) quit; -/** - * If there are changes to an open document, ask the user if they - * want to quit the application, discarding those changes. - * - * @result YES if there are no unsaved changes or the user is happy - * to discard any unsaved changes, NO if the application - * should not quit. - */ -- (BOOL) askCanQuit; /** * Cut the current selection to the clipboard. @@ -121,19 +110,6 @@ - (void) paste; /** - * Show the dialog for editing preambles. - */ -- (void) editPreambles; -/** - * Show or update the preview window. - */ -- (void) showPreview; -/** - * Show the settings dialog. - */ -- (void) showSettingsDialog; - -/** * The graph input handler */ - (GraphInputHandler*) graphInputHandler; @@ -142,43 +118,19 @@ */ - (GtkWindow*) gtkWindow; /** - * The main application configuration file - */ -- (Configuration*) mainConfiguration; -/** * The menu for the window. */ - (Menu*) menu; /** - * The document the user is currently editing - */ -- (TikzDocument*) activeDocument; - -/** - * Loads a new, empty document as the active document - */ -- (void) loadEmptyDocument; -/** - * Loads an existing document from a file as the active document - * - * @param path the path to the tikz file containing the document - */ -- (void) loadDocumentFromFile:(NSString*)path; - -/** * Present an error to the user * - * (currently just outputs it on the command line) - * * @param error the error to present */ - (void) presentError:(NSError*)error; /** * Present an error to the user * - * (currently just outputs it on the command line) - * * @param error the error to present * @param message a message to display with the error */ @@ -186,36 +138,21 @@ /** * Present an error to the user * - * (currently just outputs it on the command line) - * * @param error the error to present */ - (void) presentGError:(GError*)error; /** * Present an error to the user * - * (currently just outputs it on the command line) - * * @param error the error to present * @param message a message to display with the error */ - (void) presentGError:(GError*)error withMessage:(NSString*)message; -/** - * Save the application configuration to disk - * - * Should be called just before the application exits - */ -- (void) saveConfiguration; - - (void) zoomIn; - (void) zoomOut; - (void) zoomReset; -- (void) favourGraphControls; -- (void) favourNodeControls; -- (void) favourEdgeControls; - @end // vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/MainWindow.m b/tikzit/src/gtk/Window.m index 6764bcf..f8e4aef 100644 --- a/tikzit/src/gtk/MainWindow.m +++ b/tikzit/src/gtk/Window.m @@ -1,5 +1,5 @@ /* - * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -15,38 +15,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#import "MainWindow.h" +#import "Window.h" #import <gtk/gtk.h> #import "gtkhelpers.h" #import "clipboard.h" +#import "Application.h" #import "Configuration.h" #import "FileChooserDialog.h" #import "GraphInputHandler.h" #import "GraphRenderer.h" #import "Menu.h" -#import "PreambleEditor.h" -#ifdef HAVE_POPPLER -#import "Preambles.h" -#import "Preambles+Storage.h" -#import "PreviewWindow.h" -#endif -#import "PropertyPane.h" #import "RecentManager.h" -#ifdef HAVE_POPPLER -#import "SettingsDialog.h" -#endif #import "Shape.h" -#import "StyleManager.h" -#import "StyleManager+Storage.h" -#import "StylesPane.h" #import "SupportDir.h" #import "TikzDocument.h" #import "WidgetSurface.h" -#import "stat.h" - // {{{ Internal interfaces // {{{ Clipboard support @@ -67,16 +53,16 @@ static void clipboard_paste_contents (GtkClipboard *clipboard, // }}} // {{{ Signals -static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); -static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window); -static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window); -static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window); -static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window); +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window); +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window); +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window); +static void main_window_destroy_cb (GtkWidget *widget, Window *window); +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window); static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action); // }}} -@interface MainWindow (Notifications) +@interface Window (Notifications) - (void) tikzBufferChanged; - (void) windowSizeChangedWidth:(int)width height:(int)height; - (void) documentTikzChanged:(NSNotification*)notification; @@ -84,18 +70,16 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc - (void) undoStackChanged:(NSNotification*)notification; @end -@interface MainWindow (InitHelpers) -- (void) _loadConfig; -- (void) _loadStyles; -- (void) _loadPreambles; +@interface Window (InitHelpers) - (void) _loadUi; - (void) _restoreUiState; - (void) _connectSignals; @end -@interface MainWindow (Private) -- (BOOL) _confirmCloseDocumentTo:(NSString*)action; -- (void) _forceLoadDocumentFromFile:(NSString*)path; +@interface Window (Private) +- (BOOL) _askCanClose; +/** Open a document, dealing with errors as necessary */ +- (TikzDocument*) _openDocument:(NSString*)path; - (void) _placeGraphOnClipboard:(Graph*)graph; - (void) _setHasParseError:(BOOL)hasError; /** Update the window title. */ @@ -107,109 +91,149 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc /** Update the undo and redo actions to match the active document's * undo stack. */ - (void) _updateUndoActions; -/** Set the last-accessed folder */ -- (void) _setLastFolder:(NSString*)path; -/** Set the active document */ -- (void) _setActiveDocument:(TikzDocument*)newDoc; @end // }}} // {{{ API -@implementation MainWindow +@implementation Window - (id) init { + return [self initWithDocument:[TikzDocument documentWithStyleManager:[app styleManager]]]; +} ++ (id) window { + return [[[self alloc] init] autorelease]; +} +- (id) initWithDocument:(TikzDocument*)doc { self = [super init]; if (self) { - document = nil; - preambles = nil; - preambleWindow = nil; - previewWindow = nil; - settingsDialog = nil; - suppressTikzUpdates = NO; - hasParseError = NO; - - [self _loadConfig]; - [self _loadStyles]; - [self _loadPreambles]; - lastFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; [self _loadUi]; [self _restoreUiState]; [self _connectSignals]; - [self loadEmptyDocument]; + [self setDocument:doc]; - gtk_widget_show (GTK_WIDGET (mainWindow)); + gtk_widget_show (GTK_WIDGET (window)); } return self; } ++ (id) windowWithDocument:(TikzDocument*)doc { + return [[[self alloc] initWithDocument:doc] autorelease]; +} - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [configFile release]; - [styleManager release]; - [preambles release]; + [menu release]; [renderer release]; [inputHandler release]; - [preambleWindow release]; - [previewWindow release]; - [settingsDialog release]; [surface release]; - [lastFolder release]; [document release]; - g_object_unref (mainWindow); g_object_unref (tikzBuffer); - g_object_unref (statusBar); - g_object_unref (tikzPaneSplitter); g_object_unref (tikzPane); + g_object_unref (tikzPaneSplitter); + g_object_unref (statusBar); + g_object_unref (window); [super dealloc]; } -- (void) openFile { - if (![self _confirmCloseDocumentTo:@"open a new document"]) { - return; +- (TikzDocument*) document { + return document; +} +- (void) setDocument:(TikzDocument*)newDoc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + + [newDoc retain]; + [document release]; + document = newDoc; + + [renderer setDocument:document]; + [self _updateTikz]; + [self _updateTitle]; + [self _updateStatus]; + [self _updateUndoActions]; + [menu notifySelectionChanged:[document pickSupport]]; + [inputHandler resetState]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentTikzChanged:) + name:@"TikzChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(undoStackChanged:) + name:@"UndoStackChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"NodeSelectionChanged" object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"EdgeSelectionChanged" object:[document pickSupport]]; + + if ([document path] != nil) { + [[RecentManager defaultManager] addRecentFile:[document path]]; } - FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:mainWindow]; +} + +- (void) openFile { + FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:window]; [dialog addStandardFilters]; - if (lastFolder) { - [dialog setCurrentFolder:lastFolder]; + if ([document path]) { + [dialog setCurrentFolder:[document path]]; + } else if ([app lastOpenFolder]) { + [dialog setCurrentFolder:[app lastOpenFolder]]; } if ([dialog showDialog]) { - [self _forceLoadDocumentFromFile:[dialog filePath]]; - [self _setLastFolder:[dialog currentFolder]]; + if ([self openFileAtPath:[dialog filePath]]) { + [app setLastOpenFolder:[dialog currentFolder]]; + } } [dialog destroy]; } -- (void) saveActiveDocument { +- (BOOL) openFileAtPath:(NSString*)path { + TikzDocument *doc = [self _openDocument:path]; + if (doc != nil) { + if (![document hasUnsavedChanges] && [document path] == nil) { + // we just have a fresh, untitled document - replace it + [self setDocument:doc]; + } else { + [app newWindowWithDocument:doc]; + } + return YES; + } + return NO; +} + +- (BOOL) saveActiveDocument { if ([document path] == nil) { - [self saveActiveDocumentAs]; + return [self saveActiveDocumentAs]; } else { NSError *error = nil; if (![document save:&error]) { [self presentError:error]; + return NO; } else { [self _updateTitle]; + return YES; } } } -- (void) saveActiveDocumentAs { - FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:mainWindow]; +- (BOOL) saveActiveDocumentAs { + FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:window]; [dialog addStandardFilters]; if ([document path] != nil) { [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; - } else if (lastFolder != nil) { - [dialog setCurrentFolder:lastFolder]; + } else if ([app lastSaveAsFolder] != nil) { + [dialog setCurrentFolder:[app lastSaveAsFolder]]; } [dialog setSuggestedName:[document suggestedFileName]]; + BOOL saved = NO; if ([dialog showDialog]) { NSString *nfile = [dialog filePath]; @@ -219,16 +243,18 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } else { [self _updateTitle]; [[RecentManager defaultManager] addRecentFile:nfile]; - [self _setLastFolder:[dialog currentFolder]]; + [app setLastSaveAsFolder:[dialog currentFolder]]; + saved = YES; } } [dialog destroy]; + return saved; } - (void) saveActiveDocumentAsShape { GtkWidget *dialog = gtk_dialog_new_with_buttons ( "Save as shape", - mainWindow, + window, GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, @@ -253,7 +279,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc NSString *shapeName = [NSString stringWithUTF8String:dialogInput]; BOOL doSave = NO; if ([shapeName isEqual:@""]) { - GtkWidget *emptyStrDialog = gtk_message_dialog_new (mainWindow, + GtkWidget *emptyStrDialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -262,7 +288,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_destroy (emptyStrDialog); response = gtk_dialog_run (GTK_DIALOG (dialog)); } else if ([shapeDict objectForKey:shapeName] != nil) { - GtkWidget *overwriteDialog = gtk_message_dialog_new (mainWindow, + GtkWidget *overwriteDialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, @@ -298,27 +324,6 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_destroy (dialog); } -- (void) quit { - if ([self askCanQuit]) { - gtk_main_quit(); - } -} - -- (BOOL) askCanQuit { - if ([document hasUnsavedChanges]) { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Are you sure you want to quit without saving the current file?"); - gint result = gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - return (result == GTK_RESPONSE_YES) ? YES : NO; - } - - return YES; -} - - (void) cut { if ([[[document pickSupport] selectedNodes] count] > 0) { [self _placeGraphOnClipboard:[document cutSelection]]; @@ -338,73 +343,24 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc document); } -- (void) editPreambles { -#ifdef HAVE_POPPLER - if (preambleWindow == nil) { - preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles]; - [preambleWindow setParentWindow:mainWindow]; - } - [preambleWindow show]; -#endif -} - -- (void) showPreview { -#ifdef HAVE_POPPLER - if (previewWindow == nil) { - previewWindow = [[PreviewWindow alloc] initWithPreambles:preambles config:configFile]; - [previewWindow setParentWindow:mainWindow]; - [previewWindow setDocument:document]; - } - [previewWindow show]; -#endif -} - -- (void) showSettingsDialog { -#ifdef HAVE_POPPLER - if (settingsDialog == nil) { - settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile]; - [settingsDialog setParentWindow:mainWindow]; - } - [settingsDialog show]; -#endif -} - - (GraphInputHandler*) graphInputHandler { return inputHandler; } - (GtkWindow*) gtkWindow { - return mainWindow; + return window; } - (Configuration*) mainConfiguration { - return configFile; + return [app mainConfiguration]; } - (Menu*) menu { return menu; } -- (TikzDocument*) activeDocument { - return document; -} - -- (void) loadEmptyDocument { - if (![self _confirmCloseDocumentTo:@"start a new document"]) { - return; - } - [self _setActiveDocument:[TikzDocument documentWithStyleManager:styleManager]]; -} - -- (void) loadDocumentFromFile:(NSString*)path { - if (![self _confirmCloseDocumentTo:@"open a new document"]) { - return; - } - [self _forceLoadDocumentFromFile:path]; -} - - (void) presentError:(NSError*)error { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GtkWidget *dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -415,7 +371,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } - (void) presentError:(NSError*)error withMessage:(NSString*)message { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GtkWidget *dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -427,7 +383,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } - (void) presentGError:(GError*)error { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GtkWidget *dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -438,7 +394,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } - (void) presentGError:(GError*)error withMessage:(NSString*)message { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GtkWidget *dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, @@ -449,30 +405,6 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_destroy (dialog); } -- (void) saveConfiguration { - NSError *error = nil; - -#ifdef HAVE_POPPLER - if (preambles != nil) { - NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; - // NSFileManager is slightly dodgy on Windows - g_mkdir_with_parents ([preamblesDir UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); - [preambles storeToDirectory:preamblesDir]; - [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; - } -#endif - - [styleManager saveStylesUsingConfigurationName:@"styles"]; - - if (lastFolder != nil) { - [configFile setStringEntry:@"lastFolder" inGroup:@"Paths" value:lastFolder]; - } - - if (![configFile writeToStoreWithError:&error]) { - logError (error, @"Could not write config file"); - } -} - - (void) zoomIn { [surface zoomIn]; } @@ -485,23 +417,16 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc [surface zoomReset]; } -- (void) favourGraphControls { -} - -- (void) favourNodeControls { -} - -- (void) favourEdgeControls { -} - @end // }}} // {{{ Notifications -@implementation MainWindow (Notifications) +@implementation Window (Notifications) - (void) graphHeightChanged:(int)newHeight { - [configFile setIntegerEntry:@"graphHeight" inGroup:@"mainWindow" value:newHeight]; + [[app mainConfiguration] setIntegerEntry:@"graphHeight" + inGroup:@"window" + value:newHeight]; } - (void) tikzBufferChanged { @@ -528,7 +453,9 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc NSMutableArray *size = [NSMutableArray arrayWithCapacity:2]; [size addObject:w]; [size addObject:h]; - [configFile setIntegerListEntry:@"windowSize" inGroup:@"mainWindow" value:size]; + [[app mainConfiguration] setIntegerListEntry:@"windowSize" + inGroup:@"window" + value:size]; } } @@ -550,45 +477,19 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc // }}} // {{{ InitHelpers -@implementation MainWindow (InitHelpers) - -- (void) _loadConfig { - NSError *error = nil; - configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error]; - if (error != nil) { - logError (error, @"WARNING: Failed to load configuration"); - } -} - -- (void) _loadStyles { - styleManager = [[StyleManager alloc] init]; - [styleManager loadStylesUsingConfigurationName:@"styles"]; -} - -// must happen after _loadStyles -- (void) _loadPreambles { -#ifdef HAVE_POPPLER - NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; - preambles = [[Preambles alloc] initFromDirectory:preamblesDir]; - [preambles setStyleManager:styleManager]; - NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"]; - if (selectedPreamble != nil) { - [preambles setSelectedPreambleName:selectedPreamble]; - } -#endif -} +@implementation Window (InitHelpers) - (void) _loadUi { - mainWindow = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); - g_object_ref_sink (mainWindow); - gtk_window_set_title (mainWindow, "TikZiT"); - gtk_window_set_default_size (mainWindow, 700, 400); + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + g_object_ref_sink (window); + gtk_window_set_title (window, "TikZiT"); + gtk_window_set_default_size (window, 700, 400); GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0)); gtk_widget_show (GTK_WIDGET (mainLayout)); - gtk_container_add (GTK_CONTAINER (mainWindow), GTK_WIDGET (mainLayout)); + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (mainLayout)); - menu = [[Menu alloc] initForMainWindow:self]; + menu = [[Menu alloc] initForWindow:self]; GtkWidget *menubar = [menu menubar]; gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0); @@ -611,7 +512,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc [surface setGrabsFocusOnClick:YES]; renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; - inputHandler = [[GraphInputHandler alloc] initWithGraphRenderer:renderer window:self]; + inputHandler = [[GraphInputHandler alloc] initWithGraphRenderer:renderer]; [surface setInputDelegate:inputHandler]; tikzBuffer = gtk_text_buffer_new (NULL); @@ -631,6 +532,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_paned_pack2 (tikzPaneSplitter, tikzFrame, FALSE, TRUE); statusBar = GTK_STATUSBAR (gtk_statusbar_new ()); + g_object_ref_sink (statusBar); gtk_widget_show (GTK_WIDGET (statusBar)); gtk_box_pack_start (mainLayout, GTK_WIDGET (statusBar), FALSE, TRUE, 0); @@ -639,15 +541,18 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } - (void) _restoreUiState { - NSArray *windowSize = [configFile integerListEntry:@"windowSize" inGroup:@"mainWindow"]; + Configuration *config = [app mainConfiguration]; + NSArray *windowSize = [config integerListEntry:@"windowSize" + inGroup:@"window"]; if (windowSize && [windowSize count] == 2) { gint width = [[windowSize objectAtIndex:0] intValue]; gint height = [[windowSize objectAtIndex:1] intValue]; if (width > 0 && height > 0) { - gtk_window_set_default_size (mainWindow, width, height); + gtk_window_set_default_size (window, width, height); } } - int panePos = [configFile integerEntry:@"graphHeight" inGroup:@"mainWindow"]; + int panePos = [config integerEntry:@"graphHeight" + inGroup:@"window"]; if (panePos > 0) { gtk_paned_set_position (tikzPaneSplitter, panePos); } @@ -659,7 +564,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc "owner-change", G_CALLBACK (update_paste_action), [menu pasteAction]); - g_signal_connect (G_OBJECT (mainWindow), + g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (tz_hijack_key_press), NULL); @@ -671,15 +576,15 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc "changed", G_CALLBACK (tikz_buffer_changed_cb), self); - g_signal_connect (G_OBJECT (mainWindow), + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (main_window_delete_event_cb), self); - g_signal_connect (G_OBJECT (mainWindow), + g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (main_window_destroy_cb), self); - g_signal_connect (G_OBJECT (mainWindow), + g_signal_connect (G_OBJECT (window), "configure-event", G_CALLBACK (main_window_configure_event_cb), self); @@ -689,32 +594,40 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc // }}} // {{{ Private -@implementation MainWindow (Private) +@implementation Window (Private) -- (BOOL) _confirmCloseDocumentTo:(NSString*)action { - BOOL proceed = YES; +- (BOOL) _askCanClose { if ([document hasUnsavedChanges]) { - NSString *message = [NSString stringWithFormat:@"You have unsaved changes to the current document, which will be lost if you %@. Are you sure you want to continue?", action]; - GtkWidget *dialog = gtk_message_dialog_new (NULL, + GtkWidget *dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "%s", [message UTF8String]); - gtk_window_set_title(GTK_WINDOW(dialog), "Close current document?"); - proceed = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES; - gtk_widget_destroy (dialog); + GTK_BUTTONS_NONE, + "Save changes to the document \"%s\" before closing?", + [[document name] UTF8String]); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + "Save", GTK_RESPONSE_YES, + "Don't save", GTK_RESPONSE_NO, + "Cancel", GTK_RESPONSE_CANCEL, + NULL); + gint result = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + if (result == GTK_RESPONSE_YES) { + return [self saveActiveDocument]; + } else { + return result == GTK_RESPONSE_NO; + } + } else { + return YES; } - return proceed; } -- (void) _forceLoadDocumentFromFile:(NSString*)path { +- (TikzDocument*) _openDocument:(NSString*)path { NSError *error = nil; TikzDocument *d = [TikzDocument documentFromFile:path - styleManager:styleManager + styleManager:[app styleManager] error:&error]; if (d != nil) { - [self _setActiveDocument:d]; - [[RecentManager defaultManager] addRecentFile:path]; + return d; } else { if ([error code] == TZ_ERR_PARSE) { [self presentError:error withMessage:@"Invalid file"]; @@ -722,6 +635,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc [self presentError:error withMessage:@"Could not open file"]; } [[RecentManager defaultManager] removeRecentFile:path]; + return nil; } } @@ -763,10 +677,11 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s", [document name], ([document hasUnsavedChanges] ? "*" : "")]; - gtk_window_set_title(mainWindow, [title UTF8String]); + gtk_window_set_title(window, [title UTF8String]); } - (void) _updateStatus { + // FIXME: show tooltips or something instead GString *buffer = g_string_sized_new (30); gchar *nextNode = 0; @@ -819,50 +734,12 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc } } -- (void) _setLastFolder:(NSString*)path { - [path retain]; - [lastFolder release]; - lastFolder = path; -} - -- (void) _setActiveDocument:(TikzDocument*)newDoc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; - - [newDoc retain]; - [document release]; - document = newDoc; - - [renderer setDocument:document]; -#ifdef HAVE_POPPLER - [previewWindow setDocument:document]; -#endif - [self _updateTikz]; - [self _updateTitle]; - [self _updateStatus]; - [self _updateUndoActions]; - [menu notifySelectionChanged:[document pickSupport]]; - [inputHandler resetState]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(documentTikzChanged:) - name:@"TikzChanged" object:document]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(undoStackChanged:) - name:@"UndoStackChanged" object:document]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(documentSelectionChanged:) - name:@"NodeSelectionChanged" object:[document pickSupport]]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(documentSelectionChanged:) - name:@"EdgeSelectionChanged" object:[document pickSupport]]; -} - @end // }}} // {{{ GTK+ callbacks -static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) { +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; gint position; g_object_get (gobject, "position", &position, NULL); @@ -870,23 +747,27 @@ static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *psp [pool drain]; } -static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window) { +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window tikzBufferChanged]; [pool drain]; } -static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window) { +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - return ![window askCanQuit]; + BOOL canClose = ![window _askCanClose]; [pool drain]; + return canClose; } -static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window) { - gtk_main_quit(); +static void main_window_destroy_cb (GtkWidget *widget, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"WindowClosed" + object:window]; + [pool drain]; } -static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window) { +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [window windowSizeChangedWidth:event->width height:event->height]; [pool drain]; diff --git a/tikzit/src/gtk/main.m b/tikzit/src/gtk/main.m index 93aa9e4..9d179ab 100644 --- a/tikzit/src/gtk/main.m +++ b/tikzit/src/gtk/main.m @@ -26,9 +26,15 @@ #import "clipboard.h" #import "logo.h" -#import "MainWindow.h" +#import "Application.h" #import "TikzGraphAssembler.h" +static GOptionEntry entries[] = +{ + //{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL }, + { NULL } +}; + void onUncaughtException(NSException* exception) { NSLog(@"uncaught exception: %@", [exception description]); @@ -39,9 +45,20 @@ int main (int argc, char *argv[]) { [[NSAutoreleasePool alloc] init]; - gtk_init (&argc, &argv); - - NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + GError *error = NULL; + GOptionContext *context; + context = g_option_context_new ("[FILES] - PGF/TikZ-based graph editor"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + if (error->domain == G_OPTION_ERROR) { + g_print ("%s\nUse --help to see available options\n", error->message); + } else { + g_print ("Unexpected error parsing options: %s\n", error->message); + } + exit (1); + } #ifndef WINDOWS GList *icon_list = NULL; @@ -59,15 +76,29 @@ int main (int argc, char *argv[]) { } #endif + NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + clipboard_init(); [TikzGraphAssembler setup]; - MainWindow *window = [[MainWindow alloc] init]; + + Application *app = nil; + if (argc > 1) { + NSMutableArray *files = [NSMutableArray arrayWithCapacity:argc-1]; + for (int i = 1; i < argc; ++i) { + [files insertObject:[NSString stringWithGlibFilename:argv[i]] + atIndex:i-1]; + } + NSLog(@"Files: %@", files); + app = [[Application alloc] initWithFiles:files]; + } else { + app = [[Application alloc] init]; + } [initPool drain]; gtk_main (); - [window saveConfiguration]; + [app saveConfiguration]; return 0; } |