diff options
Diffstat (limited to 'tikzit/src/gtk/MainWindow.m')
-rw-r--r-- | tikzit/src/gtk/MainWindow.m | 1035 |
1 files changed, 1035 insertions, 0 deletions
diff --git a/tikzit/src/gtk/MainWindow.m b/tikzit/src/gtk/MainWindow.m new file mode 100644 index 0000000..c391ea5 --- /dev/null +++ b/tikzit/src/gtk/MainWindow.m @@ -0,0 +1,1035 @@ +/* + * Copyright 2011 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 "MainWindow.h" + +#import <gtk/gtk.h> +#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" + + +// {{{ 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 toolbox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); +static void stylebox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); +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) toolboxWidthChanged:(int)newWidth; +- (void) styleboxWidthChanged:(int)newWidth; +- (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]; + [stylesPane release]; + [propertyPane 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 (propertyPaneSplitter); + g_object_unref (stylesPaneSplitter); + 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], 700); + [preambles storeToDirectory:preamblesDir]; + [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; + } +#endif + + [styleManager saveStylesUsingConfigurationName:@"styles"]; + [propertyPane saveUiStateToConfig:configFile group:@"PropertyPane"]; + [stylesPane saveUiStateToConfig:configFile group:@"StylesPane"]; + + 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]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation MainWindow (Notifications) +- (void) toolboxWidthChanged:(int)newWidth { + [configFile setIntegerEntry:@"toolboxWidth" inGroup:@"mainWindow" value:newWidth]; +} + +- (void) styleboxWidthChanged:(int)newWidth { + [configFile setIntegerEntry:@"styleboxWidth" inGroup:@"mainWindow" value:newWidth]; +} + +- (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 setTikz:[NSString stringWithUTF8String:text]]; + [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); + + GtkWidget *toolbarBox = gtk_handle_box_new (); + gtk_box_pack_start (mainLayout, toolbarBox, FALSE, TRUE, 0); + gtk_widget_show (toolbarBox); + gtk_container_add (GTK_CONTAINER (toolbarBox), [menu toolbar]); + gtk_widget_show ([menu toolbar]); + + propertyPaneSplitter = GTK_PANED (gtk_hpaned_new ()); + g_object_ref_sink (propertyPaneSplitter); + gtk_widget_show (GTK_WIDGET (propertyPaneSplitter)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (propertyPaneSplitter), TRUE, TRUE, 0); + + propertyPane = [[PropertyPane alloc] init]; + GtkWidget *propFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (propFrame), [propertyPane widget]); + gtk_paned_pack1 (propertyPaneSplitter, propFrame, FALSE, TRUE); + gtk_widget_show (propFrame); + gtk_widget_show ([propertyPane widget]); + + stylesPaneSplitter = GTK_PANED (gtk_hpaned_new ()); + g_object_ref_sink (stylesPaneSplitter); + gtk_widget_show (GTK_WIDGET (stylesPaneSplitter)); + gtk_paned_pack2 (propertyPaneSplitter, GTK_WIDGET (stylesPaneSplitter), TRUE, TRUE); + + stylesPane = [[StylesPane alloc] initWithManager:styleManager]; + GtkWidget *stylesFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (stylesFrame), [stylesPane widget]); + gtk_paned_pack2 (stylesPaneSplitter, stylesFrame, FALSE, TRUE); + gtk_widget_show (stylesFrame); + gtk_widget_show ([stylesPane widget]); + + tikzPaneSplitter = GTK_PANED (gtk_vpaned_new ()); + g_object_ref_sink (tikzPaneSplitter); + gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); + gtk_paned_pack1 (stylesPaneSplitter, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE); + + 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 ()); + 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:@"toolboxWidth" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (propertyPaneSplitter, panePos); + } + panePos = [configFile integerEntry:@"styleboxWidth" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (stylesPaneSplitter, panePos); + } + panePos = [configFile integerEntry:@"graphHeight" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (tikzPaneSplitter, panePos); + } + [propertyPane restoreUiStateFromConfig:configFile group:@"PropertyPane"]; + [stylesPane restoreUiStateFromConfig:configFile group:@"StylesPane"]; +} + +- (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 (propertyPaneSplitter), + "notify::position", + G_CALLBACK (toolbox_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (stylesPaneSplitter), + "notify::position", + G_CALLBACK (stylebox_divider_position_changed_cb), + self); + 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, + [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 { + [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]; + [stylesPane setDocument:document]; + [propertyPane 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 toolbox_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 toolboxWidthChanged:position]; + [pool drain]; +} + +static void stylebox_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 styleboxWidthChanged:position]; + [pool drain]; +} + +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 |