From 94ff07fc9d728d97dde159e0c3e6ab80e29e0855 Mon Sep 17 00:00:00 2001 From: Alex Merry Date: Mon, 3 Dec 2012 19:02:53 +0000 Subject: Refactor MainWindow into Application and Window Basic multiple-window support, but no tools. --- tikzit/src/Makefile.am | 5 +- tikzit/src/gtk/Application.h | 139 ++++++ tikzit/src/gtk/Application.m | 268 ++++++++++ tikzit/src/gtk/GraphInputHandler.h | 5 +- tikzit/src/gtk/GraphInputHandler.m | 55 --- tikzit/src/gtk/MainWindow.h | 221 --------- tikzit/src/gtk/MainWindow.m | 968 ------------------------------------- tikzit/src/gtk/Menu.h | 33 +- tikzit/src/gtk/Menu.m | 156 +++--- tikzit/src/gtk/Window.h | 158 ++++++ tikzit/src/gtk/Window.m | 849 ++++++++++++++++++++++++++++++++ tikzit/src/gtk/main.m | 43 +- 12 files changed, 1551 insertions(+), 1349 deletions(-) create mode 100644 tikzit/src/gtk/Application.h create mode 100644 tikzit/src/gtk/Application.m delete mode 100644 tikzit/src/gtk/MainWindow.h delete mode 100644 tikzit/src/gtk/MainWindow.m create mode 100644 tikzit/src/gtk/Window.h create mode 100644 tikzit/src/gtk/Window.m diff --git a/tikzit/src/Makefile.am b/tikzit/src/Makefile.am index 7b64bf8..30d9815 100644 --- a/tikzit/src/Makefile.am +++ b/tikzit/src/Makefile.am @@ -24,7 +24,8 @@ EDGEDECFILES = ../AH_*.png ../ED_*.png bin_PROGRAMS = tikzit BUILT_SOURCES = $(PARSERFILES) -tikzit_SOURCES = gtk/CairoRenderContext.m \ +tikzit_SOURCES = gtk/Application.m \ + gtk/CairoRenderContext.m \ gtk/ColorRGB+IntegerListStorage.m \ gtk/ColorRGB+Gtk.m \ gtk/Configuration.m \ @@ -37,7 +38,6 @@ tikzit_SOURCES = gtk/CairoRenderContext.m \ gtk/FileChooserDialog.m \ gtk/GraphInputHandler.m \ gtk/GraphRenderer.m \ - gtk/MainWindow.m \ gtk/Menu.m \ gtk/Node+Render.m \ gtk/NodeStyle+Gtk.m \ @@ -56,6 +56,7 @@ tikzit_SOURCES = gtk/CairoRenderContext.m \ gtk/StylesPane.m \ gtk/TikzDocument.m \ gtk/WidgetSurface.m \ + gtk/Window.m \ gtk/cairo_helpers.m \ gtk/clipboard.m \ gtk/gtkhelpers.m \ 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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#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 -#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/MainWindow.h b/tikzit/src/gtk/MainWindow.h deleted file mode 100644 index 3133d77..0000000 --- a/tikzit/src/gtk/MainWindow.h +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2011 Alex Merry - * - * 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 . - */ - -#import "TZFoundation.h" -#import -#import "WidgetSurface.h" - -@class Configuration; -@class GraphRenderer; -@class GraphInputHandler; -@class Menu; -@class PropertyPane; -@class Preambles; -@class PreambleEditor; -@class PreviewWindow; -@class SettingsDialog; -@class StyleManager; -@class StylesPane; -@class TikzDocument; - -/** - * Manages the main application 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; - - // GTK+ widgets - GtkWindow *mainWindow; - 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 - TikzDocument *document; -} - -/** - * Create and show the main window. - */ -- (id) init; - -/** - * Open a file, asking the user which file to open - */ -- (void) openFile; -/** - * 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; -/** - * Save the active document, asking the user where to save it. - */ -- (void) 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. - */ -- (void) cut; -/** - * Copy the current selection to the clipboard. - */ -- (void) copy; -/** - * Paste from the clipboard to the appropriate place. - */ -- (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; -/** - * The GTK+ window that this class manages. - */ -- (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 - */ -- (void) presentError:(NSError*)error withMessage:(NSString*)message; -/** - * 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/MainWindow.m deleted file mode 100644 index 6764bcf..0000000 --- a/tikzit/src/gtk/MainWindow.m +++ /dev/null @@ -1,968 +0,0 @@ -/* - * Copyright 2011 Alex Merry - * - * 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 . - */ - -#import "MainWindow.h" - -#import -#import "gtkhelpers.h" -#import "clipboard.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 - -static void clipboard_provide_data (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, - gpointer clipboard_graph_data); -static void clipboard_release_data (GtkClipboard *clipboard, gpointer clipboard_graph_data); -static void clipboard_check_targets (GtkClipboard *clipboard, - GdkAtom *atoms, - gint n_atoms, - gpointer action); -static void clipboard_paste_contents (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - gpointer document); - -// }}} -// {{{ 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 update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action); - -// }}} - -@interface MainWindow (Notifications) -- (void) tikzBufferChanged; -- (void) windowSizeChangedWidth:(int)width height:(int)height; -- (void) documentTikzChanged:(NSNotification*)notification; -- (void) documentSelectionChanged:(NSNotification*)notification; -- (void) undoStackChanged:(NSNotification*)notification; -@end - -@interface MainWindow (InitHelpers) -- (void) _loadConfig; -- (void) _loadStyles; -- (void) _loadPreambles; -- (void) _loadUi; -- (void) _restoreUiState; -- (void) _connectSignals; -@end - -@interface MainWindow (Private) -- (BOOL) _confirmCloseDocumentTo:(NSString*)action; -- (void) _forceLoadDocumentFromFile:(NSString*)path; -- (void) _placeGraphOnClipboard:(Graph*)graph; -- (void) _setHasParseError:(BOOL)hasError; -/** Update the window title. */ -- (void) _updateTitle; -/** Update the window status bar default text. */ -- (void) _updateStatus; -/** Update the displayed tikz code to match the active document. */ -- (void) _updateTikz; -/** 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 - -- (id) init { - 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]; - - gtk_widget_show (GTK_WIDGET (mainWindow)); - } - - return self; -} - -- (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); - - [super dealloc]; -} - -- (void) openFile { - if (![self _confirmCloseDocumentTo:@"open a new document"]) { - return; - } - FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:mainWindow]; - [dialog addStandardFilters]; - if (lastFolder) { - [dialog setCurrentFolder:lastFolder]; - } - - if ([dialog showDialog]) { - [self _forceLoadDocumentFromFile:[dialog filePath]]; - [self _setLastFolder:[dialog currentFolder]]; - } - [dialog destroy]; -} - -- (void) saveActiveDocument { - if ([document path] == nil) { - [self saveActiveDocumentAs]; - } else { - NSError *error = nil; - if (![document save:&error]) { - [self presentError:error]; - } else { - [self _updateTitle]; - } - } -} - -- (void) saveActiveDocumentAs { - FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:mainWindow]; - [dialog addStandardFilters]; - if ([document path] != nil) { - [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; - } else if (lastFolder != nil) { - [dialog setCurrentFolder:lastFolder]; - } - [dialog setSuggestedName:[document suggestedFileName]]; - - if ([dialog showDialog]) { - NSString *nfile = [dialog filePath]; - - NSError *error = nil; - if (![document saveToPath:nfile error:&error]) { - [self presentError:error]; - } else { - [self _updateTitle]; - [[RecentManager defaultManager] addRecentFile:nfile]; - [self _setLastFolder:[dialog currentFolder]]; - } - } - [dialog destroy]; -} - -- (void) saveActiveDocumentAsShape { - GtkWidget *dialog = gtk_dialog_new_with_buttons ( - "Save as shape", - mainWindow, - GTK_DIALOG_MODAL, - GTK_STOCK_OK, - GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, - GTK_RESPONSE_REJECT, - NULL); - GtkBox *content = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))); - GtkWidget *label1 = gtk_label_new ("Please choose a name for the shape"); - GtkWidget *label2 = gtk_label_new ("Name:"); - GtkWidget *input = gtk_entry_new (); - GtkBox *hbox = GTK_BOX (gtk_hbox_new (FALSE, 5)); - gtk_box_pack_start (hbox, label2, FALSE, TRUE, 0); - gtk_box_pack_start (hbox, input, TRUE, TRUE, 0); - gtk_box_pack_start (content, label1, TRUE, TRUE, 5); - gtk_box_pack_start (content, GTK_WIDGET (hbox), TRUE, TRUE, 5); - gtk_widget_show_all (GTK_WIDGET (content)); - gint response = gtk_dialog_run (GTK_DIALOG (dialog)); - while (response == GTK_RESPONSE_ACCEPT) { - response = GTK_RESPONSE_NONE; - NSDictionary *shapeDict = [Shape shapeDictionary]; - const gchar *dialogInput = gtk_entry_get_text (GTK_ENTRY (input)); - NSString *shapeName = [NSString stringWithUTF8String:dialogInput]; - BOOL doSave = NO; - if ([shapeName isEqual:@""]) { - GtkWidget *emptyStrDialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "You must specify a shape name"); - gtk_dialog_run (GTK_DIALOG (emptyStrDialog)); - gtk_widget_destroy (emptyStrDialog); - response = gtk_dialog_run (GTK_DIALOG (dialog)); - } else if ([shapeDict objectForKey:shapeName] != nil) { - GtkWidget *overwriteDialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_YES_NO, - "Do you want to replace the existing shape named '%s'?", - dialogInput); - gint overwriteResp = gtk_dialog_run (GTK_DIALOG (overwriteDialog)); - gtk_widget_destroy (overwriteDialog); - - if (overwriteResp == GTK_RESPONSE_YES) { - doSave = YES; - } else { - response = gtk_dialog_run (GTK_DIALOG (dialog)); - } - } else { - doSave = YES; - } - if (doSave) { - NSError *error = nil; - NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; - NSString *file = [NSString stringWithFormat:@"%@/%@.tikz", userShapeDir, shapeName]; - if (![[NSFileManager defaultManager] ensureDirectoryExists:userShapeDir error:&error]) { - [self presentError:error withMessage:@"Could not create user shape directory"]; - } else { - NSLog (@"Saving shape to %@", file); - if (![document saveCopyToPath:file error:&error]) { - [self presentError:error withMessage:@"Could not save shape file"]; - } else { - [Shape refreshShapeDictionary]; - } - } - } - } - 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]]; - } -} - -- (void) copy { - if ([[[document pickSupport] selectedNodes] count] > 0) { - [self _placeGraphOnClipboard:[document copySelection]]; - } -} - -- (void) paste { - gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), - tikzit_picture_atom, - clipboard_paste_contents, - 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; -} - -- (Configuration*) mainConfiguration { - return configFile; -} - -- (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, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s", - [[error localizedDescription] UTF8String]); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -- (void) presentError:(NSError*)error withMessage:(NSString*)message { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s: %s", - [message UTF8String], - [[error localizedDescription] UTF8String]); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -- (void) presentGError:(GError*)error { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s", - error->message); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -- (void) presentGError:(GError*)error withMessage:(NSString*)message { - GtkWidget *dialog = gtk_message_dialog_new (mainWindow, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s: %s", - [message UTF8String], - error->message); - gtk_dialog_run (GTK_DIALOG (dialog)); - 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]; -} - -- (void) zoomOut { - [surface zoomOut]; -} - -- (void) zoomReset { - [surface zoomReset]; -} - -- (void) favourGraphControls { -} - -- (void) favourNodeControls { -} - -- (void) favourEdgeControls { -} - -@end - -// }}} -// {{{ Notifications - -@implementation MainWindow (Notifications) -- (void) graphHeightChanged:(int)newHeight { - [configFile setIntegerEntry:@"graphHeight" inGroup:@"mainWindow" value:newHeight]; -} - -- (void) tikzBufferChanged { - if (!suppressTikzUpdates) { - suppressTikzUpdates = TRUE; - - GtkTextIter start, end; - gtk_text_buffer_get_bounds (tikzBuffer, &start, &end); - gchar *text = gtk_text_buffer_get_text (tikzBuffer, &start, &end, FALSE); - - BOOL success = [document updateTikz:[NSString stringWithUTF8String:text] error:NULL]; - [self _setHasParseError:!success]; - - g_free (text); - - suppressTikzUpdates = FALSE; - } -} - -- (void) windowSizeChangedWidth:(int)width height:(int)height { - if (width > 0 && height > 0) { - NSNumber *w = [NSNumber numberWithInt:width]; - NSNumber *h = [NSNumber numberWithInt:height]; - NSMutableArray *size = [NSMutableArray arrayWithCapacity:2]; - [size addObject:w]; - [size addObject:h]; - [configFile setIntegerListEntry:@"windowSize" inGroup:@"mainWindow" value:size]; - } -} - -- (void) documentTikzChanged:(NSNotification*)notification { - [self _updateTitle]; - [self _updateTikz]; -} - -- (void) documentSelectionChanged:(NSNotification*)notification { - [self _updateStatus]; - [menu notifySelectionChanged:[document pickSupport]]; -} - -- (void) undoStackChanged:(NSNotification*)notification { - [self _updateUndoActions]; -} -@end - -// }}} -// {{{ 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 -} - -- (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); - - GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0)); - gtk_widget_show (GTK_WIDGET (mainLayout)); - gtk_container_add (GTK_CONTAINER (mainWindow), GTK_WIDGET (mainLayout)); - - menu = [[Menu alloc] initForMainWindow:self]; - - GtkWidget *menubar = [menu menubar]; - gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0); - gtk_box_reorder_child (mainLayout, menubar, 0); - gtk_widget_show (menubar); - - tikzPaneSplitter = GTK_PANED (gtk_vpaned_new ()); - g_object_ref_sink (tikzPaneSplitter); - gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); - gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0); - - surface = [[WidgetSurface alloc] init]; - gtk_widget_show ([surface widget]); - GtkWidget *graphFrame = gtk_frame_new (NULL); - gtk_container_add (GTK_CONTAINER (graphFrame), [surface widget]); - gtk_widget_show (graphFrame); - gtk_paned_pack1 (tikzPaneSplitter, graphFrame, TRUE, TRUE); - [surface setDefaultScale:50.0f]; - [surface setKeepCentered:YES]; - [surface setGrabsFocusOnClick:YES]; - renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; - - inputHandler = [[GraphInputHandler alloc] initWithGraphRenderer:renderer window:self]; - [surface setInputDelegate:inputHandler]; - - tikzBuffer = gtk_text_buffer_new (NULL); - g_object_ref_sink (tikzBuffer); - GtkWidget *tikzScroller = gtk_scrolled_window_new (NULL, NULL); - gtk_widget_show (tikzScroller); - - tikzPane = gtk_text_view_new_with_buffer (tikzBuffer); - gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tikzPane), 3); - gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tikzPane), 3); - g_object_ref_sink (tikzPane); - gtk_widget_show (tikzPane); - gtk_container_add (GTK_CONTAINER (tikzScroller), tikzPane); - GtkWidget *tikzFrame = gtk_frame_new (NULL); - gtk_container_add (GTK_CONTAINER (tikzFrame), tikzScroller); - gtk_widget_show (tikzFrame); - gtk_paned_pack2 (tikzPaneSplitter, tikzFrame, FALSE, TRUE); - - statusBar = GTK_STATUSBAR (gtk_statusbar_new ()); - gtk_widget_show (GTK_WIDGET (statusBar)); - gtk_box_pack_start (mainLayout, GTK_WIDGET (statusBar), FALSE, TRUE, 0); - - GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - update_paste_action (clipboard, NULL, [menu pasteAction]); -} - -- (void) _restoreUiState { - NSArray *windowSize = [configFile integerListEntry:@"windowSize" inGroup:@"mainWindow"]; - 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); - } - } - int panePos = [configFile integerEntry:@"graphHeight" inGroup:@"mainWindow"]; - if (panePos > 0) { - gtk_paned_set_position (tikzPaneSplitter, panePos); - } -} - -- (void) _connectSignals { - GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - g_signal_connect (G_OBJECT (clipboard), - "owner-change", - G_CALLBACK (update_paste_action), - [menu pasteAction]); - g_signal_connect (G_OBJECT (mainWindow), - "key-press-event", - G_CALLBACK (tz_hijack_key_press), - NULL); - g_signal_connect (G_OBJECT (tikzPaneSplitter), - "notify::position", - G_CALLBACK (graph_divider_position_changed_cb), - self); - g_signal_connect (G_OBJECT (tikzBuffer), - "changed", - G_CALLBACK (tikz_buffer_changed_cb), - self); - g_signal_connect (G_OBJECT (mainWindow), - "delete-event", - G_CALLBACK (main_window_delete_event_cb), - self); - g_signal_connect (G_OBJECT (mainWindow), - "destroy", - G_CALLBACK (main_window_destroy_cb), - self); - g_signal_connect (G_OBJECT (mainWindow), - "configure-event", - G_CALLBACK (main_window_configure_event_cb), - self); -} -@end - -// }}} -// {{{ Private - -@implementation MainWindow (Private) - -- (BOOL) _confirmCloseDocumentTo:(NSString*)action { - BOOL proceed = YES; - 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, - 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); - } - return proceed; -} - -- (void) _forceLoadDocumentFromFile:(NSString*)path { - NSError *error = nil; - TikzDocument *d = [TikzDocument documentFromFile:path - styleManager:styleManager - error:&error]; - if (d != nil) { - [self _setActiveDocument:d]; - [[RecentManager defaultManager] addRecentFile:path]; - } else { - if ([error code] == TZ_ERR_PARSE) { - [self presentError:error withMessage:@"Invalid file"]; - } else { - [self presentError:error withMessage:@"Could not open file"]; - } - [[RecentManager defaultManager] removeRecentFile:path]; - } -} - -- (void) _placeGraphOnClipboard:(Graph*)graph { - GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - - static const GtkTargetEntry targets[] = { - { "TIKZITPICTURE", 0, TARGET_TIKZIT_PICTURE }, - { "UTF8_STRING", 0, TARGET_UTF8_STRING } }; - - gtk_clipboard_set_with_data (clipboard, - targets, G_N_ELEMENTS (targets), - clipboard_provide_data, - clipboard_release_data, - clipboard_graph_data_new (graph)); -} - -- (void) _setHasParseError:(BOOL)hasError { - if (hasError && !hasParseError) { - gtk_statusbar_push (statusBar, 1, "Parse error"); - GdkColor color = {0, 65535, 61184, 61184}; - gtk_widget_modify_base (tikzPane, GTK_STATE_NORMAL, &color); - } else if (!hasError && hasParseError) { - gtk_statusbar_pop (statusBar, 1); - gtk_widget_modify_base (tikzPane, GTK_STATE_NORMAL, NULL); - } - hasParseError = hasError; -} - -- (void) _updateUndoActions { - [menu setUndoActionEnabled:[document canUndo]]; - [menu setUndoActionDetail:[document undoName]]; - - [menu setRedoActionEnabled:[document canRedo]]; - [menu setRedoActionDetail:[document redoName]]; -} - -- (void) _updateTitle { - NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s", - [document name], - ([document hasUnsavedChanges] ? "*" : "")]; - gtk_window_set_title(mainWindow, [title UTF8String]); -} - -- (void) _updateStatus { - GString *buffer = g_string_sized_new (30); - gchar *nextNode = 0; - - for (Node *n in [[document pickSupport] selectedNodes]) { - if (nextNode) { - if (buffer->len == 0) { - g_string_printf(buffer, "Nodes %s", nextNode); - } else { - g_string_append_printf(buffer, ", %s", nextNode); - } - } - nextNode = (gchar *)[[n name] UTF8String]; - } - if (nextNode) { - if (buffer->len == 0) { - g_string_printf(buffer, "Node %s is selected", nextNode); - } else { - g_string_append_printf(buffer, " and %s are selected", nextNode); - } - } - - if (buffer->len == 0) { - int nrNodes = [[[document graph] nodes] count]; - int nrEdges = [[[document graph] edges] count]; - g_string_printf(buffer, "Graph has %d node%s and %d edge%s", - nrNodes, - nrNodes!=1 ? "s" : "", - nrEdges, - nrEdges!=1 ? "s" : ""); - } - gtk_statusbar_pop(statusBar, 0); - gtk_statusbar_push(statusBar, 0, buffer->str); - - g_string_free (buffer, TRUE); -} - -- (void) _updateTikz { - if (document != nil && !suppressTikzUpdates) { - suppressTikzUpdates = TRUE; - - if (document != nil) { - const char *tikzString = [[document tikz] UTF8String]; - gtk_text_buffer_set_text (tikzBuffer, tikzString, -1); - } else { - gtk_text_buffer_set_text (tikzBuffer, "", -1); - } - [self _setHasParseError:NO]; - - suppressTikzUpdates = FALSE; - } -} - -- (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) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - gint position; - g_object_get (gobject, "position", &position, NULL); - [window graphHeightChanged:position]; - [pool drain]; -} - -static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window tikzBufferChanged]; - [pool drain]; -} - -static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - return ![window askCanQuit]; - [pool drain]; -} - -static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window) { - gtk_main_quit(); -} - -static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window windowSizeChangedWidth:event->width height:event->height]; - [pool drain]; - return FALSE; -} - -static void clipboard_provide_data (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, - gpointer clipboard_graph_data) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - ClipboardGraphData *data = (ClipboardGraphData*)clipboard_graph_data; - if (info == TARGET_UTF8_STRING || info == TARGET_TIKZIT_PICTURE) { - clipboard_graph_data_convert (data); - GdkAtom target = (info == TARGET_UTF8_STRING) ? utf8_atom : tikzit_picture_atom; - gtk_selection_data_set (selection_data, - target, - 8*sizeof(gchar), - (guchar*)data->tikz, - data->tikz_length); - } - - [pool drain]; -} - -static void clipboard_release_data (GtkClipboard *clipboard, gpointer data) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - clipboard_graph_data_free ((ClipboardGraphData*)data); - [pool drain]; -} - -static void clipboard_check_targets (GtkClipboard *clipboard, - GdkAtom *atoms, - gint n_atoms, - gpointer action) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - gboolean found = FALSE; - for (gint i = 0; i < n_atoms; ++i) { - if (atoms[i] == tikzit_picture_atom) { - found = TRUE; - break; - } - } - gtk_action_set_sensitive (GTK_ACTION (action), found); - - [pool drain]; -} - -static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action) { - gtk_action_set_sensitive (action, FALSE); - gtk_clipboard_request_targets (clipboard, clipboard_check_targets, action); -} - -static void clipboard_paste_contents (GtkClipboard *clipboard, - GtkSelectionData *selection_data, - gpointer document) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - TikzDocument *doc = (TikzDocument*)document; - gint length = gtk_selection_data_get_length (selection_data); - if (length >= 0) { - const guchar *raw_data = gtk_selection_data_get_data (selection_data); - gchar *data = g_new (gchar, length+1); - g_strlcpy (data, (const gchar *)raw_data, length+1); - NSString *tikz = [NSString stringWithUTF8String:data]; - if (tikz != nil) { - [doc pasteFromTikz:tikz]; - } - g_free (data); - } - - [pool drain]; -} - -// }}} - -// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker 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 * - * 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 -@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 ", "Chris Heunen ", @@ -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 " " " " +/* " " " " " " " " " " " " +*/ /* " " " " @@ -427,6 +426,7 @@ static const gchar ui_info[] = " " " " " " +/* " " " " " " @@ -442,6 +442,7 @@ static const gchar ui_info[] = " " " " " " +*/ ""; @@ -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/Window.h b/tikzit/src/gtk/Window.h new file mode 100644 index 0000000..6aa710a --- /dev/null +++ b/tikzit/src/gtk/Window.h @@ -0,0 +1,158 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * 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 . + */ + +#import "TZFoundation.h" +#import +#import "WidgetSurface.h" + +@class GraphRenderer; +@class GraphInputHandler; +@class Menu; +@class PropertyPane; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class StylesPane; +@class TikzDocument; + +/** + * Manages a document window + */ +@interface Window: NSObject { + // GTK+ widgets + GtkWindow *window; + GtkTextBuffer *tikzBuffer; + GtkStatusbar *statusBar; + GtkPaned *tikzPaneSplitter; + GtkWidget *tikzPane; + + // Classes that manage parts of the window + Menu *menu; + GraphRenderer *renderer; + GraphInputHandler *inputHandler; + + WidgetSurface *surface; + + // state variables + BOOL suppressTikzUpdates; + BOOL hasParseError; + + // the document displayed by the window + TikzDocument *document; +} + +/** + * 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. + */ +- (BOOL) saveActiveDocument; +/** + * Save the active document, asking the user where to save it. + */ +- (BOOL) saveActiveDocumentAs; +/** + * Save the active document as a shape, asking the user what to name it. + */ +- (void) saveActiveDocumentAsShape; + +/** + * Cut the current selection to the clipboard. + */ +- (void) cut; +/** + * Copy the current selection to the clipboard. + */ +- (void) copy; +/** + * Paste from the clipboard to the appropriate place. + */ +- (void) paste; + +/** + * The graph input handler + */ +- (GraphInputHandler*) graphInputHandler; +/** + * The GTK+ window that this class manages. + */ +- (GtkWindow*) gtkWindow; +/** + * The menu for the window. + */ +- (Menu*) menu; + +/** + * Present an error to the user + * + * @param error the error to present + */ +- (void) presentError:(NSError*)error; +/** + * Present an error to the user + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentError:(NSError*)error withMessage:(NSString*)message; +/** + * Present an error to the user + * + * @param error the error to present + */ +- (void) presentGError:(GError*)error; +/** + * Present an error to the user + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentGError:(GError*)error withMessage:(NSString*)message; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/Window.m b/tikzit/src/gtk/Window.m new file mode 100644 index 0000000..f8e4aef --- /dev/null +++ b/tikzit/src/gtk/Window.m @@ -0,0 +1,849 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * 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 . + */ + +#import "Window.h" + +#import +#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 "RecentManager.h" +#import "Shape.h" +#import "SupportDir.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + + +// {{{ Internal interfaces +// {{{ Clipboard support + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data); +static void clipboard_release_data (GtkClipboard *clipboard, gpointer clipboard_graph_data); +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action); +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document); + +// }}} +// {{{ Signals + +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 Window (Notifications) +- (void) tikzBufferChanged; +- (void) windowSizeChangedWidth:(int)width height:(int)height; +- (void) documentTikzChanged:(NSNotification*)notification; +- (void) documentSelectionChanged:(NSNotification*)notification; +- (void) undoStackChanged:(NSNotification*)notification; +@end + +@interface Window (InitHelpers) +- (void) _loadUi; +- (void) _restoreUiState; +- (void) _connectSignals; +@end + +@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. */ +- (void) _updateTitle; +/** Update the window status bar default text. */ +- (void) _updateStatus; +/** Update the displayed tikz code to match the active document. */ +- (void) _updateTikz; +/** Update the undo and redo actions to match the active document's + * undo stack. */ +- (void) _updateUndoActions; +@end + +// }}} +// {{{ API + +@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) { + [self _loadUi]; + [self _restoreUiState]; + [self _connectSignals]; + + [self setDocument:doc]; + + gtk_widget_show (GTK_WIDGET (window)); + } + + return self; +} ++ (id) windowWithDocument:(TikzDocument*)doc { + return [[[self alloc] initWithDocument:doc] autorelease]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [menu release]; + [renderer release]; + [inputHandler release]; + [surface release]; + [document release]; + + g_object_unref (tikzBuffer); + g_object_unref (tikzPane); + g_object_unref (tikzPaneSplitter); + g_object_unref (statusBar); + g_object_unref (window); + + [super dealloc]; +} + +- (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]]; + } +} + +- (void) openFile { + FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path]) { + [dialog setCurrentFolder:[document path]]; + } else if ([app lastOpenFolder]) { + [dialog setCurrentFolder:[app lastOpenFolder]]; + } + + if ([dialog showDialog]) { + if ([self openFileAtPath:[dialog filePath]]) { + [app setLastOpenFolder:[dialog currentFolder]]; + } + } + [dialog destroy]; +} + +- (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) { + return [self saveActiveDocumentAs]; + } else { + NSError *error = nil; + if (![document save:&error]) { + [self presentError:error]; + return NO; + } else { + [self _updateTitle]; + return YES; + } + } +} + +- (BOOL) saveActiveDocumentAs { + FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path] != nil) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if ([app lastSaveAsFolder] != nil) { + [dialog setCurrentFolder:[app lastSaveAsFolder]]; + } + [dialog setSuggestedName:[document suggestedFileName]]; + + BOOL saved = NO; + if ([dialog showDialog]) { + NSString *nfile = [dialog filePath]; + + NSError *error = nil; + if (![document saveToPath:nfile error:&error]) { + [self presentError:error]; + } else { + [self _updateTitle]; + [[RecentManager defaultManager] addRecentFile:nfile]; + [app setLastSaveAsFolder:[dialog currentFolder]]; + saved = YES; + } + } + [dialog destroy]; + return saved; +} + +- (void) saveActiveDocumentAsShape { + GtkWidget *dialog = gtk_dialog_new_with_buttons ( + "Save as shape", + window, + GTK_DIALOG_MODAL, + GTK_STOCK_OK, + GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, + GTK_RESPONSE_REJECT, + NULL); + GtkBox *content = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))); + GtkWidget *label1 = gtk_label_new ("Please choose a name for the shape"); + GtkWidget *label2 = gtk_label_new ("Name:"); + GtkWidget *input = gtk_entry_new (); + GtkBox *hbox = GTK_BOX (gtk_hbox_new (FALSE, 5)); + gtk_box_pack_start (hbox, label2, FALSE, TRUE, 0); + gtk_box_pack_start (hbox, input, TRUE, TRUE, 0); + gtk_box_pack_start (content, label1, TRUE, TRUE, 5); + gtk_box_pack_start (content, GTK_WIDGET (hbox), TRUE, TRUE, 5); + gtk_widget_show_all (GTK_WIDGET (content)); + gint response = gtk_dialog_run (GTK_DIALOG (dialog)); + while (response == GTK_RESPONSE_ACCEPT) { + response = GTK_RESPONSE_NONE; + NSDictionary *shapeDict = [Shape shapeDictionary]; + const gchar *dialogInput = gtk_entry_get_text (GTK_ENTRY (input)); + NSString *shapeName = [NSString stringWithUTF8String:dialogInput]; + BOOL doSave = NO; + if ([shapeName isEqual:@""]) { + GtkWidget *emptyStrDialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "You must specify a shape name"); + gtk_dialog_run (GTK_DIALOG (emptyStrDialog)); + gtk_widget_destroy (emptyStrDialog); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } else if ([shapeDict objectForKey:shapeName] != nil) { + GtkWidget *overwriteDialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Do you want to replace the existing shape named '%s'?", + dialogInput); + gint overwriteResp = gtk_dialog_run (GTK_DIALOG (overwriteDialog)); + gtk_widget_destroy (overwriteDialog); + + if (overwriteResp == GTK_RESPONSE_YES) { + doSave = YES; + } else { + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } + } else { + doSave = YES; + } + if (doSave) { + NSError *error = nil; + NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; + NSString *file = [NSString stringWithFormat:@"%@/%@.tikz", userShapeDir, shapeName]; + if (![[NSFileManager defaultManager] ensureDirectoryExists:userShapeDir error:&error]) { + [self presentError:error withMessage:@"Could not create user shape directory"]; + } else { + NSLog (@"Saving shape to %@", file); + if (![document saveCopyToPath:file error:&error]) { + [self presentError:error withMessage:@"Could not save shape file"]; + } else { + [Shape refreshShapeDictionary]; + } + } + } + } + gtk_widget_destroy (dialog); +} + +- (void) cut { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document cutSelection]]; + } +} + +- (void) copy { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document copySelection]]; + } +} + +- (void) paste { + gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + tikzit_picture_atom, + clipboard_paste_contents, + document); +} + +- (GraphInputHandler*) graphInputHandler { + return inputHandler; +} + +- (GtkWindow*) gtkWindow { + return window; +} + +- (Configuration*) mainConfiguration { + return [app mainConfiguration]; +} + +- (Menu*) menu { + return menu; +} + +- (void) presentError:(NSError*)error { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + [[error localizedDescription] UTF8String]); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentError:(NSError*)error withMessage:(NSString*)message { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + [[error localizedDescription] UTF8String]); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error withMessage:(NSString*)message { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) zoomIn { + [surface zoomIn]; +} + +- (void) zoomOut { + [surface zoomOut]; +} + +- (void) zoomReset { + [surface zoomReset]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation Window (Notifications) +- (void) graphHeightChanged:(int)newHeight { + [[app mainConfiguration] setIntegerEntry:@"graphHeight" + inGroup:@"window" + value:newHeight]; +} + +- (void) tikzBufferChanged { + if (!suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + GtkTextIter start, end; + gtk_text_buffer_get_bounds (tikzBuffer, &start, &end); + gchar *text = gtk_text_buffer_get_text (tikzBuffer, &start, &end, FALSE); + + BOOL success = [document updateTikz:[NSString stringWithUTF8String:text] error:NULL]; + [self _setHasParseError:!success]; + + g_free (text); + + suppressTikzUpdates = FALSE; + } +} + +- (void) windowSizeChangedWidth:(int)width height:(int)height { + if (width > 0 && height > 0) { + NSNumber *w = [NSNumber numberWithInt:width]; + NSNumber *h = [NSNumber numberWithInt:height]; + NSMutableArray *size = [NSMutableArray arrayWithCapacity:2]; + [size addObject:w]; + [size addObject:h]; + [[app mainConfiguration] setIntegerListEntry:@"windowSize" + inGroup:@"window" + value:size]; + } +} + +- (void) documentTikzChanged:(NSNotification*)notification { + [self _updateTitle]; + [self _updateTikz]; +} + +- (void) documentSelectionChanged:(NSNotification*)notification { + [self _updateStatus]; + [menu notifySelectionChanged:[document pickSupport]]; +} + +- (void) undoStackChanged:(NSNotification*)notification { + [self _updateUndoActions]; +} +@end + +// }}} +// {{{ InitHelpers + +@implementation Window (InitHelpers) + +- (void) _loadUi { + 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 (window), GTK_WIDGET (mainLayout)); + + menu = [[Menu alloc] initForWindow:self]; + + GtkWidget *menubar = [menu menubar]; + gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0); + gtk_box_reorder_child (mainLayout, menubar, 0); + gtk_widget_show (menubar); + + tikzPaneSplitter = GTK_PANED (gtk_vpaned_new ()); + g_object_ref_sink (tikzPaneSplitter); + gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0); + + surface = [[WidgetSurface alloc] init]; + gtk_widget_show ([surface widget]); + GtkWidget *graphFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (graphFrame), [surface widget]); + gtk_widget_show (graphFrame); + gtk_paned_pack1 (tikzPaneSplitter, graphFrame, TRUE, TRUE); + [surface setDefaultScale:50.0f]; + [surface setKeepCentered:YES]; + [surface setGrabsFocusOnClick:YES]; + renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; + + inputHandler = [[GraphInputHandler alloc] initWithGraphRenderer:renderer]; + [surface setInputDelegate:inputHandler]; + + tikzBuffer = gtk_text_buffer_new (NULL); + g_object_ref_sink (tikzBuffer); + GtkWidget *tikzScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (tikzScroller); + + tikzPane = gtk_text_view_new_with_buffer (tikzBuffer); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tikzPane), 3); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tikzPane), 3); + g_object_ref_sink (tikzPane); + gtk_widget_show (tikzPane); + gtk_container_add (GTK_CONTAINER (tikzScroller), tikzPane); + GtkWidget *tikzFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (tikzFrame), tikzScroller); + gtk_widget_show (tikzFrame); + 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); + + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + update_paste_action (clipboard, NULL, [menu pasteAction]); +} + +- (void) _restoreUiState { + 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 (window, width, height); + } + } + int panePos = [config integerEntry:@"graphHeight" + inGroup:@"window"]; + if (panePos > 0) { + gtk_paned_set_position (tikzPaneSplitter, panePos); + } +} + +- (void) _connectSignals { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + g_signal_connect (G_OBJECT (clipboard), + "owner-change", + G_CALLBACK (update_paste_action), + [menu pasteAction]); + g_signal_connect (G_OBJECT (window), + "key-press-event", + G_CALLBACK (tz_hijack_key_press), + NULL); + g_signal_connect (G_OBJECT (tikzPaneSplitter), + "notify::position", + G_CALLBACK (graph_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (tikzBuffer), + "changed", + G_CALLBACK (tikz_buffer_changed_cb), + self); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (main_window_delete_event_cb), + self); + g_signal_connect (G_OBJECT (window), + "destroy", + G_CALLBACK (main_window_destroy_cb), + self); + g_signal_connect (G_OBJECT (window), + "configure-event", + G_CALLBACK (main_window_configure_event_cb), + self); +} +@end + +// }}} +// {{{ Private + +@implementation Window (Private) + +- (BOOL) _askCanClose { + if ([document hasUnsavedChanges]) { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + 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; + } +} + +- (TikzDocument*) _openDocument:(NSString*)path { + NSError *error = nil; + TikzDocument *d = [TikzDocument documentFromFile:path + styleManager:[app styleManager] + error:&error]; + if (d != nil) { + return d; + } else { + if ([error code] == TZ_ERR_PARSE) { + [self presentError:error withMessage:@"Invalid file"]; + } else { + [self presentError:error withMessage:@"Could not open file"]; + } + [[RecentManager defaultManager] removeRecentFile:path]; + return nil; + } +} + +- (void) _placeGraphOnClipboard:(Graph*)graph { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + + static const GtkTargetEntry targets[] = { + { "TIKZITPICTURE", 0, TARGET_TIKZIT_PICTURE }, + { "UTF8_STRING", 0, TARGET_UTF8_STRING } }; + + gtk_clipboard_set_with_data (clipboard, + targets, G_N_ELEMENTS (targets), + clipboard_provide_data, + clipboard_release_data, + clipboard_graph_data_new (graph)); +} + +- (void) _setHasParseError:(BOOL)hasError { + if (hasError && !hasParseError) { + gtk_statusbar_push (statusBar, 1, "Parse error"); + GdkColor color = {0, 65535, 61184, 61184}; + gtk_widget_modify_base (tikzPane, GTK_STATE_NORMAL, &color); + } else if (!hasError && hasParseError) { + gtk_statusbar_pop (statusBar, 1); + gtk_widget_modify_base (tikzPane, GTK_STATE_NORMAL, NULL); + } + hasParseError = hasError; +} + +- (void) _updateUndoActions { + [menu setUndoActionEnabled:[document canUndo]]; + [menu setUndoActionDetail:[document undoName]]; + + [menu setRedoActionEnabled:[document canRedo]]; + [menu setRedoActionDetail:[document redoName]]; +} + +- (void) _updateTitle { + NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s", + [document name], + ([document hasUnsavedChanges] ? "*" : "")]; + 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; + + for (Node *n in [[document pickSupport] selectedNodes]) { + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Nodes %s", nextNode); + } else { + g_string_append_printf(buffer, ", %s", nextNode); + } + } + nextNode = (gchar *)[[n name] UTF8String]; + } + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Node %s is selected", nextNode); + } else { + g_string_append_printf(buffer, " and %s are selected", nextNode); + } + } + + if (buffer->len == 0) { + int nrNodes = [[[document graph] nodes] count]; + int nrEdges = [[[document graph] edges] count]; + g_string_printf(buffer, "Graph has %d node%s and %d edge%s", + nrNodes, + nrNodes!=1 ? "s" : "", + nrEdges, + nrEdges!=1 ? "s" : ""); + } + gtk_statusbar_pop(statusBar, 0); + gtk_statusbar_push(statusBar, 0, buffer->str); + + g_string_free (buffer, TRUE); +} + +- (void) _updateTikz { + if (document != nil && !suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + if (document != nil) { + const char *tikzString = [[document tikz] UTF8String]; + gtk_text_buffer_set_text (tikzBuffer, tikzString, -1); + } else { + gtk_text_buffer_set_text (tikzBuffer, "", -1); + } + [self _setHasParseError:NO]; + + suppressTikzUpdates = FALSE; + } +} + +@end + +// }}} +// {{{ GTK+ callbacks + +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); + [window graphHeightChanged:position]; + [pool drain]; +} + +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, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + BOOL canClose = ![window _askCanClose]; + [pool drain]; + return canClose; +} + +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, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window windowSizeChangedWidth:event->width height:event->height]; + [pool drain]; + return FALSE; +} + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + ClipboardGraphData *data = (ClipboardGraphData*)clipboard_graph_data; + if (info == TARGET_UTF8_STRING || info == TARGET_TIKZIT_PICTURE) { + clipboard_graph_data_convert (data); + GdkAtom target = (info == TARGET_UTF8_STRING) ? utf8_atom : tikzit_picture_atom; + gtk_selection_data_set (selection_data, + target, + 8*sizeof(gchar), + (guchar*)data->tikz, + data->tikz_length); + } + + [pool drain]; +} + +static void clipboard_release_data (GtkClipboard *clipboard, gpointer data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + clipboard_graph_data_free ((ClipboardGraphData*)data); + [pool drain]; +} + +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gboolean found = FALSE; + for (gint i = 0; i < n_atoms; ++i) { + if (atoms[i] == tikzit_picture_atom) { + found = TRUE; + break; + } + } + gtk_action_set_sensitive (GTK_ACTION (action), found); + + [pool drain]; +} + +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action) { + gtk_action_set_sensitive (action, FALSE); + gtk_clipboard_request_targets (clipboard, clipboard_check_targets, action); +} + +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *doc = (TikzDocument*)document; + gint length = gtk_selection_data_get_length (selection_data); + if (length >= 0) { + const guchar *raw_data = gtk_selection_data_get_data (selection_data); + gchar *data = g_new (gchar, length+1); + g_strlcpy (data, (const gchar *)raw_data, length+1); + NSString *tikz = [NSString stringWithUTF8String:data]; + if (tikz != nil) { + [doc pasteFromTikz:tikz]; + } + g_free (data); + } + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker 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; } -- cgit v1.2.3