summaryrefslogtreecommitdiff
path: root/tikzit/src/gtk/Window.m
diff options
context:
space:
mode:
authorAlex Merry <alex.merry@cs.ox.ac.uk>2012-12-03 19:02:53 +0000
committerAlex Merry <alex.merry@cs.ox.ac.uk>2012-12-04 11:12:28 +0000
commit94ff07fc9d728d97dde159e0c3e6ab80e29e0855 (patch)
tree2e74ff3d709db87e2e2b27aa501ed46727c054d7 /tikzit/src/gtk/Window.m
parent0d715a7f17b8e5570b42d7802c502e4aa234a3c1 (diff)
Refactor MainWindow into Application and Window
Basic multiple-window support, but no tools.
Diffstat (limited to 'tikzit/src/gtk/Window.m')
-rw-r--r--tikzit/src/gtk/Window.m849
1 files changed, 849 insertions, 0 deletions
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 <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "Window.h"
+
+#import <gtk/gtk.h>
+#import "gtkhelpers.h"
+#import "clipboard.h"
+
+#import "Application.h"
+#import "Configuration.h"
+#import "FileChooserDialog.h"
+#import "GraphInputHandler.h"
+#import "GraphRenderer.h"
+#import "Menu.h"
+#import "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