summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tikzit/src/Makefile.am22
-rw-r--r--tikzit/src/common/NSFileManager+Utils.h2
-rw-r--r--tikzit/src/common/NSFileManager+Utils.m2
-rw-r--r--tikzit/src/common/StyleManager.h4
-rw-r--r--tikzit/src/common/StyleManager.m60
-rw-r--r--tikzit/src/gtk/Application.h157
-rw-r--r--tikzit/src/gtk/Application.m390
-rw-r--r--tikzit/src/gtk/BoundingBoxTool.h45
-rw-r--r--tikzit/src/gtk/BoundingBoxTool.m353
-rw-r--r--tikzit/src/gtk/Configuration.h2
-rw-r--r--tikzit/src/gtk/Configuration.m2
-rw-r--r--tikzit/src/gtk/ContextWindow.h (renamed from tikzit/src/gtk/StylesPane.h)37
-rw-r--r--tikzit/src/gtk/ContextWindow.m170
-rw-r--r--tikzit/src/gtk/CreateEdgeTool.h45
-rw-r--r--tikzit/src/gtk/CreateEdgeTool.m203
-rw-r--r--tikzit/src/gtk/CreateNodeTool.h42
-rw-r--r--tikzit/src/gtk/CreateNodeTool.m146
-rw-r--r--tikzit/src/gtk/DocumentContext.h27
-rw-r--r--tikzit/src/gtk/EdgeStyleSelector.h38
-rw-r--r--tikzit/src/gtk/EdgeStyleSelector.m426
-rw-r--r--tikzit/src/gtk/EdgeStylesModel.h62
-rw-r--r--tikzit/src/gtk/EdgeStylesModel.m367
-rw-r--r--tikzit/src/gtk/EdgeStylesPalette.m7
-rw-r--r--tikzit/src/gtk/GraphEditorPanel.h48
-rw-r--r--tikzit/src/gtk/GraphEditorPanel.m186
-rw-r--r--tikzit/src/gtk/GraphInputHandler.m524
-rw-r--r--tikzit/src/gtk/GraphRenderer.h37
-rw-r--r--tikzit/src/gtk/GraphRenderer.m249
-rw-r--r--tikzit/src/gtk/HandTool.h33
-rw-r--r--tikzit/src/gtk/HandTool.m75
-rw-r--r--tikzit/src/gtk/InputDelegate.h3
-rw-r--r--tikzit/src/gtk/Menu.h43
-rw-r--r--tikzit/src/gtk/Menu.m760
-rw-r--r--tikzit/src/gtk/NodeStyleSelector.h34
-rw-r--r--tikzit/src/gtk/NodeStyleSelector.m425
-rw-r--r--tikzit/src/gtk/NodeStylesModel.h62
-rw-r--r--tikzit/src/gtk/NodeStylesModel.m381
-rw-r--r--tikzit/src/gtk/NodeStylesPalette.m7
-rw-r--r--tikzit/src/gtk/PreambleEditor.h1
-rw-r--r--tikzit/src/gtk/PreambleEditor.m6
-rw-r--r--tikzit/src/gtk/PreviewWindow.h1
-rw-r--r--tikzit/src/gtk/PreviewWindow.m8
-rw-r--r--tikzit/src/gtk/PropertiesPane.h (renamed from tikzit/src/gtk/PropertyPane.h)48
-rw-r--r--tikzit/src/gtk/PropertiesPane.m (renamed from tikzit/src/gtk/PropertyPane.m)382
-rw-r--r--tikzit/src/gtk/PropertyListEditor.m3
-rw-r--r--tikzit/src/gtk/SelectTool.h (renamed from tikzit/src/gtk/GraphInputHandler.h)66
-rw-r--r--tikzit/src/gtk/SelectTool.m509
-rw-r--r--tikzit/src/gtk/SelectionPane.h56
-rw-r--r--tikzit/src/gtk/SelectionPane.m432
-rw-r--r--tikzit/src/gtk/SettingsDialog.h1
-rw-r--r--tikzit/src/gtk/SettingsDialog.m6
-rw-r--r--tikzit/src/gtk/StyleManager+Storage.m1
-rw-r--r--tikzit/src/gtk/StylesPane.m162
-rw-r--r--tikzit/src/gtk/Surface.h4
-rw-r--r--tikzit/src/gtk/TikzDocument.h17
-rw-r--r--tikzit/src/gtk/TikzDocument.m14
-rw-r--r--tikzit/src/gtk/Tool.h41
-rw-r--r--tikzit/src/gtk/ToolBox.h45
-rw-r--r--tikzit/src/gtk/ToolBox.m278
-rw-r--r--tikzit/src/gtk/WidgetSurface.h22
-rw-r--r--tikzit/src/gtk/WidgetSurface.m86
-rw-r--r--tikzit/src/gtk/Window.h (renamed from tikzit/src/gtk/MainWindow.h)143
-rw-r--r--tikzit/src/gtk/Window.m (renamed from tikzit/src/gtk/MainWindow.m)630
-rw-r--r--tikzit/src/gtk/gtkhelpers.h26
-rw-r--r--tikzit/src/gtk/gtkhelpers.m275
-rw-r--r--tikzit/src/gtk/main.m45
-rw-r--r--tikzit/src/gtk/tzstockitems.h26
-rw-r--r--tikzit/src/gtk/tzstockitems.m64
-rw-r--r--tikzit/src/gtk/tztoolpalette.h56
-rw-r--r--tikzit/src/gtk/tztoolpalette.m158
70 files changed, 5774 insertions, 3314 deletions
diff --git a/tikzit/src/Makefile.am b/tikzit/src/Makefile.am
index 7b64bf8..b0e9e59 100644
--- a/tikzit/src/Makefile.am
+++ b/tikzit/src/Makefile.am
@@ -24,44 +24,56 @@ EDGEDECFILES = ../AH_*.png ../ED_*.png
bin_PROGRAMS = tikzit
BUILT_SOURCES = $(PARSERFILES)
-tikzit_SOURCES = gtk/CairoRenderContext.m \
+tikzit_SOURCES = gtk/Application.m \
+ gtk/BoundingBoxTool.m \
+ gtk/CairoRenderContext.m \
gtk/ColorRGB+IntegerListStorage.m \
gtk/ColorRGB+Gtk.m \
gtk/Configuration.m \
+ gtk/ContextWindow.m \
+ gtk/CreateEdgeTool.m \
+ gtk/CreateNodeTool.m \
gtk/Edge+Render.m \
gtk/EdgeStyle+Gtk.m \
gtk/EdgeStyle+Storage.m \
gtk/EdgeStyleEditor.m \
gtk/EdgeStyleSelector.m \
+ gtk/EdgeStylesModel.m \
gtk/EdgeStylesPalette.m \
gtk/FileChooserDialog.m \
- gtk/GraphInputHandler.m \
+ gtk/HandTool.m \
+ gtk/GraphEditorPanel.m \
gtk/GraphRenderer.m \
- gtk/MainWindow.m \
gtk/Menu.m \
gtk/Node+Render.m \
gtk/NodeStyle+Gtk.m \
gtk/NodeStyle+Storage.m \
gtk/NodeStyleEditor.m \
+ gtk/NodeStylesModel.m \
gtk/NodeStyleSelector.m \
gtk/NodeStylesPalette.m \
gtk/NSError+Glib.m \
gtk/NSFileManager+Glib.m \
gtk/NSString+Glib.m \
- gtk/PropertyPane.m \
+ gtk/PropertiesPane.m \
gtk/PropertyListEditor.m \
gtk/RecentManager.m \
+ gtk/SelectTool.m \
+ gtk/SelectionPane.m \
gtk/Shape+Render.m \
gtk/StyleManager+Storage.m \
- gtk/StylesPane.m \
gtk/TikzDocument.m \
+ gtk/ToolBox.m \
gtk/WidgetSurface.m \
+ gtk/Window.m \
gtk/cairo_helpers.m \
gtk/clipboard.m \
gtk/gtkhelpers.m \
gtk/logo.m \
gtk/mkdtemp.m \
gtk/main.m \
+ gtk/tzstockitems.m \
+ gtk/tztoolpalette.m \
common/CircleShape.m \
common/ColorRGB.m \
common/DiamondShape.m \
diff --git a/tikzit/src/common/NSFileManager+Utils.h b/tikzit/src/common/NSFileManager+Utils.h
index 75d8926..1349919 100644
--- a/tikzit/src/common/NSFileManager+Utils.h
+++ b/tikzit/src/common/NSFileManager+Utils.h
@@ -1,5 +1,5 @@
//
-// MainWindow.h
+// NSFileManager+Utils.h
// TikZiT
//
// Copyright 2010 Alex Merry
diff --git a/tikzit/src/common/NSFileManager+Utils.m b/tikzit/src/common/NSFileManager+Utils.m
index 2586eb6..87ede95 100644
--- a/tikzit/src/common/NSFileManager+Utils.m
+++ b/tikzit/src/common/NSFileManager+Utils.m
@@ -1,5 +1,5 @@
//
-// MainWindow.h
+// NSFileManager+Utils.h
// TikZiT
//
// Copyright 2010 Alex Merry
diff --git a/tikzit/src/common/StyleManager.h b/tikzit/src/common/StyleManager.h
index 406a86a..0fb2436 100644
--- a/tikzit/src/common/StyleManager.h
+++ b/tikzit/src/common/StyleManager.h
@@ -21,18 +21,14 @@
@interface StyleManager: NSObject {
NSMutableArray *nodeStyles;
- NodeStyle *activeNodeStyle;
NSMutableArray *edgeStyles;
- EdgeStyle *activeEdgeStyle;
}
+ (StyleManager*) manager;
- (id) init;
@property (readonly) NSArray *nodeStyles;
-@property (retain) NodeStyle *activeNodeStyle;
@property (readonly) NSArray *edgeStyles;
-@property (retain) EdgeStyle *activeEdgeStyle;
// only for use by loading code
- (void) _setNodeStyles:(NSMutableArray*)styles;
diff --git a/tikzit/src/common/StyleManager.m b/tikzit/src/common/StyleManager.m
index 1f895ea..837a094 100644
--- a/tikzit/src/common/StyleManager.m
+++ b/tikzit/src/common/StyleManager.m
@@ -99,9 +99,7 @@
if (self) {
// we lazily load the default styles, since they may not be needed
nodeStyles = nil;
- activeNodeStyle = nil;
edgeStyles = nil;
- activeEdgeStyle = nil;
}
return self;
@@ -198,14 +196,6 @@
[[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStylesReplaced" object:self];
}
-- (void) postActiveNodeStyleChanged {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"ActiveNodeStyleChanged" object:self];
-}
-
-- (void) postActiveEdgeStyleChanged {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"ActiveEdgeStyleChanged" object:self];
-}
-
- (NSArray*) nodeStyles {
if (nodeStyles == nil) {
[self loadDefaultNodeStyles];
@@ -225,15 +215,10 @@
[nodeStyles release];
[styles retain];
nodeStyles = styles;
- NodeStyle *oldActiveStyle = activeNodeStyle;
- activeNodeStyle = nil;
for (NodeStyle *style in styles) {
[self listenToNodeStyle:style];
}
[self postNodeStylesReplaced];
- if (oldActiveStyle != activeNodeStyle) {
- [self postActiveNodeStyleChanged];
- }
}
- (void) _setEdgeStyles:(NSMutableArray*)styles {
@@ -241,49 +226,10 @@
[edgeStyles release];
[styles retain];
edgeStyles = styles;
- EdgeStyle *oldActiveStyle = activeEdgeStyle;
- activeEdgeStyle = nil;
for (EdgeStyle *style in styles) {
[self listenToEdgeStyle:style];
}
[self postEdgeStylesReplaced];
- if (oldActiveStyle != activeEdgeStyle) {
- [self postActiveEdgeStyleChanged];
- }
-}
-
-- (NodeStyle*) activeNodeStyle {
- if (nodeStyles == nil) {
- [self loadDefaultNodeStyles];
- }
- return activeNodeStyle;
-}
-
-- (void) setActiveNodeStyle:(NodeStyle*)style {
- if (style == activeNodeStyle) {
- return;
- }
- if (style == nil || [nodeStyles containsObject:style]) {
- activeNodeStyle = style;
- [self postActiveNodeStyleChanged];
- }
-}
-
-- (EdgeStyle*) activeEdgeStyle {
- if (edgeStyles == nil) {
- [self loadDefaultEdgeStyles];
- }
- return activeEdgeStyle;
-}
-
-- (void) setActiveEdgeStyle:(EdgeStyle*)style {
- if (style == activeEdgeStyle) {
- return;
- }
- if (style == nil || [edgeStyles containsObject:style]) {
- activeEdgeStyle = style;
- [self postActiveEdgeStyleChanged];
- }
}
- (NodeStyle*) nodeStyleForName:(NSString*)name {
@@ -309,9 +255,6 @@
if (nodeStyles == nil) {
[self loadDefaultNodeStyles];
}
- if (activeNodeStyle == style) {
- [self setActiveNodeStyle:nil];
- }
[self ignoreNodeStyle:style];
[style retain];
@@ -343,9 +286,6 @@
if (edgeStyles == nil) {
[self loadDefaultEdgeStyles];
}
- if (activeEdgeStyle == style) {
- [self setActiveEdgeStyle:nil];
- }
[self ignoreEdgeStyle:style];
[style retain];
diff --git a/tikzit/src/gtk/Application.h b/tikzit/src/gtk/Application.h
new file mode 100644
index 0000000..b364c5e
--- /dev/null
+++ b/tikzit/src/gtk/Application.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "InputDelegate.h"
+
+@class Application;
+@class Configuration;
+@class ContextWindow;
+@class Preambles;
+@class PreambleEditor;
+@class PreviewWindow;
+@class SettingsDialog;
+@class StyleManager;
+@class TikzDocument;
+@class ToolBox;
+@class Window;
+@protocol Tool;
+
+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;
+
+ ToolBox *toolBox;
+ PreambleEditor *preambleWindow;
+ PreviewWindow *previewWindow;
+ ContextWindow *contextWindow;
+ SettingsDialog *settingsDialog;
+
+ // the open windows (array of Window*)
+ NSMutableArray *openWindows;
+
+ // tools
+ id<Tool> activeTool;
+ NSArray *tools;
+}
+
+/**
+ * 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 tools
+ */
+@property (readonly) NSArray *tools;
+
+/**
+ * The currently-selected tool
+ */
+@property (assign) id<Tool> activeTool;
+
+/**
+ * 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 the context-aware window
+ */
+- (void) showContextWindow;
+/**
+ * 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;
+
+- (void) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask;
+
+@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..61fb85d
--- /dev/null
+++ b/tikzit/src/gtk/Application.m
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "Application.h"
+
+#import "Configuration.h"
+#import "EdgeStylesModel.h"
+#import "NodeStylesModel.h"
+#import "PreambleEditor.h"
+#import "ContextWindow.h"
+#import "Shape.h"
+#import "StyleManager.h"
+#import "StyleManager+Storage.h"
+#import "SupportDir.h"
+#import "TikzDocument.h"
+#import "ToolBox.h"
+#import "Window.h"
+
+#ifdef HAVE_POPPLER
+#import "Preambles.h"
+#import "Preambles+Storage.h"
+#import "PreviewWindow.h"
+#import "SettingsDialog.h"
+#endif
+
+#import "BoundingBoxTool.h"
+#import "CreateNodeTool.h"
+#import "CreateEdgeTool.h"
+#import "HandTool.h"
+#import "SelectTool.h"
+
+// used for args to g_mkdir_with_parents
+#import "stat.h"
+
+Application* app = nil;
+
+@interface Application (Notifications)
+- (void) windowClosed:(NSNotification*)notification;
+- (void) windowGainedFocus:(NSNotification*)notification;
+- (void) selectedToolChanged:(NSNotification*)notification;
+- (void) windowDocumentChanged:(NSNotification*)n;
+@end
+
+@interface Application (Private)
+- (void) setActiveWindow:(Window*)window;
+@end
+
+@implementation Application
+
+@synthesize mainConfiguration=configFile;
+@synthesize styleManager, preambles;
+@synthesize lastOpenFolder, lastSaveAsFolder;
+@synthesize tools;
+
++ (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?
+ NodeStylesModel *nsm = [NodeStylesModel modelWithStyleManager:styleManager];
+ EdgeStylesModel *esm = [EdgeStylesModel modelWithStyleManager:styleManager];
+
+#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];
+
+ tools = [[NSArray alloc] initWithObjects:
+ [SelectTool tool],
+ [CreateNodeTool toolWithNodeStylesModel:nsm],
+ [CreateEdgeTool toolWithEdgeStylesModel:esm],
+ [BoundingBoxTool tool],
+ [HandTool tool],
+ nil];
+ activeTool = [tools objectAtIndex:0];
+ for (id<Tool> tool in tools) {
+ [tool loadConfiguration:configFile];
+ }
+
+ toolBox = [[ToolBox alloc] initWithTools:tools];
+ [toolBox loadConfiguration:configFile];
+ [toolBox setSelectedTool:activeTool];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(selectedToolChanged:)
+ name:@"ToolSelectionChanged"
+ object:toolBox];
+ [toolBox show];
+
+ contextWindow = [[ContextWindow alloc] initWithNodeStylesModel:nsm
+ andEdgeStylesModel:esm];
+ [contextWindow loadConfiguration:configFile];
+
+ 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];
+ [preambleWindow release];
+ [previewWindow release];
+ [settingsDialog release];
+ [openWindows release];
+ [tools release];
+ [activeTool release];
+ [toolBox release];
+ [contextWindow release];
+
+ [super dealloc];
+}
+
+- (id<Tool>) activeTool { return activeTool; }
+- (void) setActiveTool:(id<Tool>)tool {
+ if (activeTool == tool)
+ return;
+
+ activeTool = tool;
+ [toolBox setSelectedTool:tool];
+ for (Window* window in openWindows) {
+ [window setActiveTool:tool];
+ }
+}
+
+- (void) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask {
+ // FIXME: cache the accel info, rather than reparsing it every time?
+ for (id<Tool> tool in tools) {
+ guint toolKey = 0;
+ GdkModifierType toolMod = 0;
+ gtk_accelerator_parse ([[tool shortcut] UTF8String], &toolKey, &toolMod);
+ if (toolKey != 0 && toolKey == keyVal && (int)mask == (int)toolMod) {
+ [self setActiveTool:tool];
+ return;
+ }
+ }
+}
+
+- (void) _addWindow:(Window*)window {
+ [window setActiveTool:activeTool];
+ [openWindows addObject:window];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowClosed:)
+ name:@"WindowClosed"
+ object:window];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowGainedFocus:)
+ name:@"WindowGainedFocus"
+ object:window];
+ if ([window hasFocus]) {
+ [self setActiveWindow:window];
+ }
+}
+
+- (void) newWindow {
+ [self _addWindow:[Window window]];
+}
+
+- (void) newWindowWithDocument:(TikzDocument*)doc {
+ [self _addWindow:[Window windowWithDocument:doc]];
+}
+
+- (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 present];
+#endif
+}
+
+- (void) showContextWindow {
+ [contextWindow present];
+}
+
+- (void) showPreviewForDocument:(TikzDocument*)doc {
+#ifdef HAVE_POPPLER
+ if (previewWindow == nil) {
+ previewWindow = [[PreviewWindow alloc] initWithPreambles:preambles config:configFile];
+ //[previewWindow setParentWindow:mainWindow];
+ [previewWindow setDocument:doc];
+ }
+ [previewWindow present];
+#endif
+}
+
+- (void) showSettingsDialog {
+#ifdef HAVE_POPPLER
+ if (settingsDialog == nil) {
+ settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile];
+ //[settingsDialog setParentWindow:mainWindow];
+ }
+ [settingsDialog present];
+#endif
+}
+
+- (Configuration*) mainConfiguration {
+ return configFile;
+}
+
+- (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"];
+
+ for (id<Tool> tool in tools) {
+ [tool saveConfiguration:configFile];
+ }
+ [toolBox saveConfiguration:configFile];
+
+ [contextWindow saveConfiguration:configFile];
+
+ 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 {
+ [self setActiveWindow:[openWindows objectAtIndex:0]];
+ }
+}
+
+- (void) windowGainedFocus:(NSNotification*)notification {
+ Window *window = [notification object];
+ [self setActiveWindow:window];
+}
+
+- (void) selectedToolChanged:(NSNotification*)n {
+ id<Tool> tool = [[n userInfo] objectForKey:@"tool"];
+ if (tool != nil)
+ [self setActiveTool:tool];
+ else
+ NSLog(@"nil tool!");
+}
+
+- (void) windowDocumentChanged:(NSNotification*)n {
+ [contextWindow setDocument:[[n userInfo] objectForKey:@"document"]];
+}
+@end
+
+@implementation Application (Private)
+- (void) setActiveWindow:(Window*)window {
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"DocumentChanged"
+ object:nil];
+
+ [contextWindow setDocument:[window document]];
+
+ [contextWindow setTransientFor:window];
+ [toolBox setTransientFor:window];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(windowDocumentChanged:)
+ name:@"DocumentChanged"
+ object:window];
+}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/BoundingBoxTool.h b/tikzit/src/gtk/BoundingBoxTool.h
new file mode 100644
index 0000000..f6498b0
--- /dev/null
+++ b/tikzit/src/gtk/BoundingBoxTool.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "Tool.h"
+
+typedef enum {
+ NoHandle,
+ EastHandle,
+ SouthEastHandle,
+ SouthHandle,
+ SouthWestHandle,
+ WestHandle,
+ NorthWestHandle,
+ NorthHandle,
+ NorthEastHandle
+} ResizeHandle;
+
+@interface BoundingBoxTool : NSObject <Tool> {
+ GraphRenderer *renderer;
+ NSPoint dragOrigin;
+ ResizeHandle currentResizeHandle;
+ BOOL drawingNewBox;
+}
+
++ (id) tool;
+- (id) init;
+@end
+
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/BoundingBoxTool.m b/tikzit/src/gtk/BoundingBoxTool.m
new file mode 100644
index 0000000..483705e
--- /dev/null
+++ b/tikzit/src/gtk/BoundingBoxTool.m
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "BoundingBoxTool.h"
+
+#import "GraphRenderer.h"
+#import "TikzDocument.h"
+#import "tzstockitems.h"
+
+static const float handle_size = 8.0;
+float sideHandleTop(NSRect bbox) {
+ return (NSMinY(bbox) + NSMaxY(bbox) - handle_size)/2.0f;
+}
+float tbHandleLeft(NSRect bbox) {
+ return (NSMinX(bbox) + NSMaxX(bbox) - handle_size)/2.0f;
+}
+
+@interface BoundingBoxTool (Private)
+- (NSRect) screenBoundingBox;
+- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p;
+- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle;
+- (void) setResizeCursorForHandle:(ResizeHandle)handle;
+@end
+
+@implementation BoundingBoxTool
+- (NSString*) name { return @"Bounding Box"; }
+- (const gchar*) stockId { return TIKZIT_STOCK_BOUNDING_BOX; }
+- (NSString*) helpText { return @"Set the bounding box"; }
+- (NSString*) shortcut { return @"b"; }
+
++ (id) tool {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id) init {
+ self = [super init];
+
+ if (self) {
+ currentResizeHandle = NoHandle;
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [renderer release];
+
+ [super dealloc];
+}
+
+- (GraphRenderer*) activeRenderer { return renderer; }
+- (void) setActiveRenderer:(GraphRenderer*)r {
+ if (r == renderer)
+ return;
+
+ [[renderer surface] setCursor:NormalCursor];
+
+ [r retain];
+ [renderer release];
+ renderer = r;
+}
+
+- (GtkWidget*) configurationWidget { return NULL; }
+
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ dragOrigin = pos;
+ currentResizeHandle = [self boundingBoxResizeHandleAt:pos];
+ [[renderer document] startChangeBoundingBox];
+ if (currentResizeHandle == NoHandle) {
+ drawingNewBox = YES;
+ [[[renderer document] graph] setBoundingBox:NSZeroRect];
+ } else {
+ drawingNewBox = NO;
+ }
+ [renderer invalidateGraph];
+}
+
+- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
+ if (!(buttons & LeftButton)) {
+ ResizeHandle handle = [self boundingBoxResizeHandleAt:pos];
+ [self setResizeCursorForHandle:handle];
+ return;
+ }
+
+ Transformer *transformer = [renderer transformer];
+ Grid *grid = [renderer grid];
+ Graph *graph = [[renderer document] graph];
+
+ if (currentResizeHandle == NoHandle) {
+ NSRect bbox = NSRectAroundPoints(
+ [grid snapScreenPoint:dragOrigin],
+ [grid snapScreenPoint:pos]
+ );
+ [graph setBoundingBox:[transformer rectFromScreen:bbox]];
+ } else {
+ NSRect bbox = [transformer rectToScreen:[graph boundingBox]];
+ NSPoint p2 = [grid snapScreenPoint:pos];
+
+ if (currentResizeHandle == NorthWestHandle ||
+ currentResizeHandle == NorthHandle ||
+ currentResizeHandle == NorthEastHandle) {
+
+ float dy = p2.y - NSMinY(bbox);
+ if (dy < bbox.size.height) {
+ bbox.origin.y += dy;
+ bbox.size.height -= dy;
+ } else {
+ bbox.origin.y = NSMaxY(bbox);
+ bbox.size.height = 0;
+ }
+
+ } else if (currentResizeHandle == SouthWestHandle ||
+ currentResizeHandle == SouthHandle ||
+ currentResizeHandle == SouthEastHandle) {
+
+ float dy = p2.y - NSMaxY(bbox);
+ if (-dy < bbox.size.height) {
+ bbox.size.height += dy;
+ } else {
+ bbox.size.height = 0;
+ }
+ }
+
+ if (currentResizeHandle == NorthWestHandle ||
+ currentResizeHandle == WestHandle ||
+ currentResizeHandle == SouthWestHandle) {
+
+ float dx = p2.x - NSMinX(bbox);
+ if (dx < bbox.size.width) {
+ bbox.origin.x += dx;
+ bbox.size.width -= dx;
+ } else {
+ bbox.origin.x = NSMaxX(bbox);
+ bbox.size.width = 0;
+ }
+
+ } else if (currentResizeHandle == NorthEastHandle ||
+ currentResizeHandle == EastHandle ||
+ currentResizeHandle == SouthEastHandle) {
+
+ float dx = p2.x - NSMaxX(bbox);
+ if (-dx < bbox.size.width) {
+ bbox.size.width += dx;
+ } else {
+ bbox.size.width = 0;
+ }
+ }
+ [graph setBoundingBox:[transformer rectFromScreen:bbox]];
+ }
+ [[renderer document] changeBoundingBoxCheckPoint];
+ [renderer invalidateGraph];
+}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ [[renderer document] endChangeBoundingBox];
+ drawingNewBox = NO;
+ [renderer invalidateGraph];
+}
+
+- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {
+ if (!drawingNewBox && [[[renderer document] graph] hasBoundingBox]) {
+ [context saveState];
+
+ [context setAntialiasMode:AntialiasDisabled];
+ [context setLineWidth:1.0];
+
+ [context startPath];
+ [context rect:[self boundingBoxResizeHandleRect:EastHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:SouthEastHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:SouthHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:SouthWestHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:WestHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:NorthWestHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:NorthHandle]];
+ [context rect:[self boundingBoxResizeHandleRect:NorthEastHandle]];
+ [context strokePathWithColor:MakeSolidRColor (0.5, 0.5, 0.5)];
+
+ [context restoreState];
+ }
+}
+- (void) loadConfiguration:(Configuration*)config {}
+- (void) saveConfiguration:(Configuration*)config {}
+@end
+
+@implementation BoundingBoxTool (Private)
+- (NSRect) screenBoundingBox {
+ Transformer *transformer = [[renderer surface] transformer];
+ Graph *graph = [[renderer document] graph];
+ return [transformer rectToScreen:[graph boundingBox]];
+}
+
+- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p {
+ NSRect bbox = [self screenBoundingBox];
+ if (p.x >= NSMaxX(bbox)) {
+ if (p.x <= NSMaxX(bbox) + handle_size) {
+ if (p.y >= NSMaxY(bbox)) {
+ if (p.y <= NSMaxY(bbox) + handle_size) {
+ return SouthEastHandle;
+ }
+ } else if (p.y <= NSMinY(bbox)) {
+ if (p.y >= NSMinY(bbox) - handle_size) {
+ return NorthEastHandle;
+ }
+ } else {
+ float eastHandleTop = sideHandleTop(bbox);
+ if (p.y >= eastHandleTop && p.y <= (eastHandleTop + handle_size)) {
+ return EastHandle;
+ }
+ }
+ }
+ } else if (p.x <= NSMinX(bbox)) {
+ if (p.x >= NSMinX(bbox) - handle_size) {
+ if (p.y >= NSMaxY(bbox)) {
+ if (p.y <= NSMaxY(bbox) + handle_size) {
+ return SouthWestHandle;
+ }
+ } else if (p.y <= NSMinY(bbox)) {
+ if (p.y >= NSMinY(bbox) - handle_size) {
+ return NorthWestHandle;
+ }
+ } else {
+ float westHandleTop = sideHandleTop(bbox);
+ if (p.y >= westHandleTop && p.y <= (westHandleTop + handle_size)) {
+ return WestHandle;
+ }
+ }
+ }
+ } else if (p.y >= NSMaxY(bbox)) {
+ if (p.y <= NSMaxY(bbox) + handle_size) {
+ float southHandleLeft = tbHandleLeft(bbox);
+ if (p.x >= southHandleLeft && p.x <= (southHandleLeft + handle_size)) {
+ return SouthHandle;
+ }
+ }
+ } else if (p.y <= NSMinY(bbox)) {
+ if (p.y >= NSMinY(bbox) - handle_size) {
+ float northHandleLeft = tbHandleLeft(bbox);
+ if (p.x >= northHandleLeft && p.x <= (northHandleLeft + handle_size)) {
+ return NorthHandle;
+ }
+ }
+ }
+ return NoHandle;
+}
+
+- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle {
+ Graph *graph = [[renderer document] graph];
+ if (![graph hasBoundingBox]) {
+ return NSZeroRect;
+ }
+ NSRect bbox = [self screenBoundingBox];
+ float x;
+ float y;
+ switch (handle) {
+ case NorthEastHandle:
+ case EastHandle:
+ case SouthEastHandle:
+ x = NSMaxX(bbox);
+ break;
+ case NorthWestHandle:
+ case WestHandle:
+ case SouthWestHandle:
+ x = NSMinX(bbox) - handle_size;
+ break;
+ case SouthHandle:
+ case NorthHandle:
+ x = tbHandleLeft(bbox);
+ break;
+ default:
+ return NSZeroRect;
+ }
+ switch (handle) {
+ case EastHandle:
+ case WestHandle:
+ y = sideHandleTop(bbox);
+ break;
+ case SouthEastHandle:
+ case SouthHandle:
+ case SouthWestHandle:
+ y = NSMaxY(bbox);
+ break;
+ case NorthEastHandle:
+ case NorthHandle:
+ case NorthWestHandle:
+ y = NSMinY(bbox) - handle_size;
+ break;
+ default:
+ return NSZeroRect;
+ }
+ return NSMakeRect(x, y, handle_size, handle_size);
+}
+
+- (void) setResizeCursorForHandle:(ResizeHandle)handle {
+ if (handle != currentResizeHandle) {
+ currentResizeHandle = handle;
+ Cursor c = NormalCursor;
+ switch (handle) {
+ case EastHandle:
+ c = ResizeRightCursor;
+ break;
+ case SouthEastHandle:
+ c = ResizeBottomRightCursor;
+ break;
+ case SouthHandle:
+ c = ResizeBottomCursor;
+ break;
+ case SouthWestHandle:
+ c = ResizeBottomLeftCursor;
+ break;
+ case WestHandle:
+ c = ResizeLeftCursor;
+ break;
+ case NorthWestHandle:
+ c = ResizeTopLeftCursor;
+ break;
+ case NorthHandle:
+ c = ResizeTopCursor;
+ break;
+ case NorthEastHandle:
+ c = ResizeTopRightCursor;
+ break;
+ default:
+ c = NormalCursor;
+ break;
+ }
+ [[renderer surface] setCursor:c];
+ }
+}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/Configuration.h b/tikzit/src/gtk/Configuration.h
index 93a74fa..6c68681 100644
--- a/tikzit/src/gtk/Configuration.h
+++ b/tikzit/src/gtk/Configuration.h
@@ -1,5 +1,5 @@
//
-// MainWindow.h
+// Configuration.h
// TikZiT
//
// Copyright 2010 Alex Merry
diff --git a/tikzit/src/gtk/Configuration.m b/tikzit/src/gtk/Configuration.m
index 4904eed..7a0e65f 100644
--- a/tikzit/src/gtk/Configuration.m
+++ b/tikzit/src/gtk/Configuration.m
@@ -1,5 +1,5 @@
//
-// MainWindow.h
+// Configuration.h
// TikZiT
//
// Copyright 2010 Alex Merry
diff --git a/tikzit/src/gtk/StylesPane.h b/tikzit/src/gtk/ContextWindow.h
index 660e9cf..64ecd19 100644
--- a/tikzit/src/gtk/StylesPane.h
+++ b/tikzit/src/gtk/ContextWindow.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,34 +19,35 @@
#import <gtk/gtk.h>
@class Configuration;
-@class EdgeStylesPalette;
-@class NodeStylesPalette;
+@class EdgeStylesModel;
+@class NodeStylesModel;
+@class PropertiesPane;
+@class SelectionPane;
@class StyleManager;
@class TikzDocument;
+@class Window;
-@interface StylesPane: NSObject {
- NodeStylesPalette *nodeStyles;
- EdgeStylesPalette *edgeStyles;
+@interface ContextWindow: NSObject {
+ PropertiesPane *propsPane;
+ SelectionPane *selPane;
- GtkWidget *stylesPane;
-
- GtkExpander *nodeStylesExpander;
- GtkExpander *edgeStylesExpander;
+ GtkWidget *window;
+ GtkWidget *layout;
}
-@property (readonly) GtkWidget *widget;
@property (retain) TikzDocument *document;
-@property (retain) StyleManager *styleManager;
+@property (assign) BOOL visible;
-- (id) initWithManager:(StyleManager*)m;
+- (id) initWithStyleManager:(StyleManager*)mgr;
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm;
-- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group;
-- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group;
+- (void) present;
+- (void) setTransientFor:(Window*)parent;
-- (void) favourNodeStyles;
-- (void) favourEdgeStyles;
+- (void) loadConfiguration:(Configuration*)config;
+- (void) saveConfiguration:(Configuration*)config;
@end
// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
-
diff --git a/tikzit/src/gtk/ContextWindow.m b/tikzit/src/gtk/ContextWindow.m
new file mode 100644
index 0000000..a4d33ae
--- /dev/null
+++ b/tikzit/src/gtk/ContextWindow.m
@@ -0,0 +1,170 @@
+/*
+ * 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 "ContextWindow.h"
+
+#import "Configuration.h"
+#import "EdgeStylesModel.h"
+#import "NodeStylesModel.h"
+#import "PropertiesPane.h"
+#import "SelectionPane.h"
+#import "StyleManager.h"
+#import "Window.h"
+
+#import "gtkhelpers.h"
+
+static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window);
+
+@implementation ContextWindow
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)sm {
+ return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm]
+ andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]];
+}
+
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm {
+ self = [super init];
+
+ if (self) {
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_object_ref_sink (window);
+ gtk_window_set_title (GTK_WINDOW (window), "Context");
+ gtk_window_set_role (GTK_WINDOW (window), "context");
+ gtk_window_set_type_hint (GTK_WINDOW (window),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ gtk_window_set_default_size (GTK_WINDOW (window), 200, 500);
+ g_signal_connect (G_OBJECT (window),
+ "delete-event",
+ G_CALLBACK (props_window_delete_event_cb),
+ self);
+
+ layout = gtk_vbox_new (FALSE, 3);
+ g_object_ref_sink (layout);
+ gtk_widget_show (layout);
+ gtk_container_set_border_width (GTK_CONTAINER (layout), 6);
+
+ gtk_container_add (GTK_CONTAINER (window), layout);
+
+ propsPane = [[PropertiesPane alloc] initWithNodeStylesModel:nsm
+ andEdgeStylesModel:esm];
+ gtk_box_pack_start (GTK_BOX (layout), [propsPane gtkWidget],
+ TRUE, TRUE, 0);
+
+ GtkWidget *sep = gtk_hseparator_new ();
+ gtk_widget_show (sep);
+ gtk_box_pack_start (GTK_BOX (layout), sep,
+ FALSE, FALSE, 0);
+
+ selPane = [[SelectionPane alloc] initWithNodeStylesModel:nsm
+ andEdgeStylesModel:esm];
+ gtk_box_pack_start (GTK_BOX (layout), [selPane gtkWidget],
+ FALSE, FALSE, 0);
+
+ // hack to position the context window somewhere sensible
+ // (upper right)
+ gtk_window_parse_geometry (GTK_WINDOW (window), "-0+0");
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ g_object_unref (layout);
+ g_object_unref (window);
+
+ [propsPane release];
+
+ [super dealloc];
+}
+
+- (TikzDocument*) document {
+ return [propsPane document];
+}
+
+- (void) setDocument:(TikzDocument*)doc {
+ [propsPane setDocument:doc];
+ [selPane setDocument:doc];
+}
+
+- (BOOL) visible {
+ return gtk_widget_get_visible (window);
+}
+
+- (void) setVisible:(BOOL)visible {
+ gtk_widget_set_visible (window, visible);
+}
+
+- (void) present {
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+- (void) setTransientFor:(Window*)parent {
+ gtk_window_set_transient_for (GTK_WINDOW (window), [parent gtkWindow]);
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ [propsPane loadConfiguration:config];
+ [selPane loadConfiguration:config];
+
+ if ([config hasGroup:@"ContextWindow"]) {
+ tz_restore_window (GTK_WINDOW (window),
+ [config integerEntry:@"x" inGroup:@"ContextWindow"],
+ [config integerEntry:@"y" inGroup:@"ContextWindow"],
+ [config integerEntry:@"w" inGroup:@"ContextWindow"],
+ [config integerEntry:@"h" inGroup:@"ContextWindow"]);
+ }
+ [self setVisible:[config booleanEntry:@"visible"
+ inGroup:@"ContextWindow"
+ withDefault:YES]];
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ gint x, y, w, h;
+
+ gtk_window_get_position (GTK_WINDOW (window), &x, &y);
+ gtk_window_get_size (GTK_WINDOW (window), &w, &h);
+
+ [config setIntegerEntry:@"x" inGroup:@"ContextWindow" value:x];
+ [config setIntegerEntry:@"y" inGroup:@"ContextWindow" value:y];
+ [config setIntegerEntry:@"w" inGroup:@"ContextWindow" value:w];
+ [config setIntegerEntry:@"h" inGroup:@"ContextWindow" value:h];
+ [config setBooleanEntry:@"visible"
+ inGroup:@"ContextWindow"
+ value:[self visible]];
+
+ [propsPane saveConfiguration:config];
+ [selPane saveConfiguration:config];
+}
+
+@end
+
+static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [window setVisible:NO];
+ [pool drain];
+ return TRUE;
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/CreateEdgeTool.h b/tikzit/src/gtk/CreateEdgeTool.h
new file mode 100644
index 0000000..d33efce
--- /dev/null
+++ b/tikzit/src/gtk/CreateEdgeTool.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "Tool.h"
+
+@class EdgeStyle;
+@class EdgeStylesModel;
+@class EdgeStyleSelector;
+@class Node;
+@class StyleManager;
+
+@interface CreateEdgeTool : NSObject <Tool> {
+ GraphRenderer *renderer;
+ EdgeStyleSelector *stylePicker;
+ GtkWidget *configWidget;
+ Node *sourceNode;
+ NSPoint sourceNodeScreenPoint;
+ NSPoint halfEdgeEnd;
+}
+
+@property (retain) EdgeStyle *activeStyle;
+
++ (id) toolWithStyleManager:(StyleManager*)sm;
+- (id) initWithStyleManager:(StyleManager*)sm;
++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm;
+- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm;
+@end
+
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/CreateEdgeTool.m b/tikzit/src/gtk/CreateEdgeTool.m
new file mode 100644
index 0000000..e34e627
--- /dev/null
+++ b/tikzit/src/gtk/CreateEdgeTool.m
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "CreateEdgeTool.h"
+
+#import "Configuration.h"
+#import "EdgeStyleSelector.h"
+#import "EdgeStylesModel.h"
+#import "GraphRenderer.h"
+#import "TikzDocument.h"
+#import "tzstockitems.h"
+
+@implementation CreateEdgeTool
+- (NSString*) name { return @"Create Edge"; }
+- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_EDGE; }
+- (NSString*) helpText { return @"Create new edges"; }
+- (NSString*) shortcut { return @"e"; }
+@synthesize activeRenderer=renderer;
+@synthesize configurationWidget=configWidget;
+
++ (id) toolWithStyleManager:(StyleManager*)sm {
+ return [[[self alloc] initWithStyleManager:sm] autorelease];
+}
+
++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm {
+ return [[[self alloc] initWithEdgeStylesModel:esm] autorelease];
+}
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)sm {
+ return [self initWithEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]];
+}
+
+- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm {
+ self = [super init];
+
+ if (self) {
+ stylePicker = [[EdgeStyleSelector alloc] initWithModel:esm];
+
+ configWidget = gtk_vbox_new (FALSE, 0);
+ g_object_ref_sink (configWidget);
+
+ GtkWidget *label = gtk_label_new ("Edge style:");
+ gtk_widget_show (label);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ label,
+ FALSE,
+ FALSE,
+ 0);
+
+ GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_show (selWindow);
+ gtk_container_add (GTK_CONTAINER (selWindow),
+ [stylePicker widget]);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show ([stylePicker widget]);
+
+ GtkWidget *selectorFrame = gtk_frame_new (NULL);
+ gtk_widget_show (selectorFrame);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ selectorFrame,
+ TRUE,
+ TRUE,
+ 0);
+ gtk_container_add (GTK_CONTAINER (selectorFrame),
+ selWindow);
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [renderer release];
+ [stylePicker release];
+ [sourceNode release];
+
+ g_object_unref (G_OBJECT (configWidget));
+
+ [super dealloc];
+}
+
+- (EdgeStyle*) activeStyle {
+ return [stylePicker selectedStyle];
+}
+
+- (void) setActiveStyle:(EdgeStyle*)style {
+ return [stylePicker setSelectedStyle:style];
+}
+
+- (void) invalidateHalfEdge {
+ NSRect invRect = NSRectAroundPoints(sourceNodeScreenPoint, halfEdgeEnd);
+ [renderer invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)];
+}
+
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ sourceNode = [renderer anyNodeAt:pos];
+ if (sourceNode != nil) {
+ Transformer *transformer = [[renderer surface] transformer];
+ sourceNodeScreenPoint = [transformer toScreen:[sourceNode point]];
+ halfEdgeEnd = pos;
+ [renderer setNode:sourceNode highlighted:YES];
+ }
+}
+
+- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
+ if (!(buttons & LeftButton))
+ return;
+ if (sourceNode == nil)
+ return;
+
+ [self invalidateHalfEdge];
+
+ [renderer clearHighlightedNodes];
+ [renderer setNode:sourceNode highlighted:YES];
+ halfEdgeEnd = pos;
+ Node *targ = [renderer anyNodeAt:pos];
+ if (targ != nil) {
+ [renderer setNode:targ highlighted:YES];
+ }
+
+ [self invalidateHalfEdge];
+}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+ if (sourceNode == nil)
+ return;
+
+ [renderer clearHighlightedNodes];
+ [self invalidateHalfEdge];
+
+ Node *targ = [renderer anyNodeAt:pos];
+ if (targ != nil) {
+ Edge *edge = [Edge edgeWithSource:sourceNode andTarget:targ];
+ [edge setStyle:[self activeStyle]];
+ [[renderer document] addEdge:edge];
+ [renderer invalidateEdge:edge];
+ }
+
+ sourceNode = nil;
+}
+
+- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {
+ if (sourceNode == nil) {
+ return;
+ }
+ [context saveState];
+
+ [context setLineWidth:1.0];
+ [context startPath];
+ [context moveTo:sourceNodeScreenPoint];
+ [context lineTo:halfEdgeEnd];
+ [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)];
+
+ [context restoreState];
+}
+
+- (StyleManager*) styleManager {
+ return [[stylePicker model] styleManager];
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ NSString *styleName = [config stringEntry:@"ActiveStyle"
+ inGroup:@"CreateEdgeTool"
+ withDefault:nil];
+ [self setActiveStyle:[[self styleManager] edgeStyleForName:styleName]];
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ [config setStringEntry:@"ActiveStyle"
+ inGroup:@"CreateEdgeTool"
+ value:[[self activeStyle] name]];
+}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/CreateNodeTool.h b/tikzit/src/gtk/CreateNodeTool.h
new file mode 100644
index 0000000..94d6b31
--- /dev/null
+++ b/tikzit/src/gtk/CreateNodeTool.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import <gtk/gtk.h>
+#import "Tool.h"
+
+@class NodeStyle;
+@class NodeStyleSelector;
+@class NodeStylesModel;
+@class StyleManager;
+
+@interface CreateNodeTool : NSObject <Tool> {
+ GraphRenderer *renderer;
+ NodeStyleSelector *stylePicker;
+ GtkWidget *configWidget;
+}
+
+@property (retain) NodeStyle *activeStyle;
+
++ (id) toolWithStyleManager:(StyleManager*)sm;
+- (id) initWithStyleManager:(StyleManager*)sm;
++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm;
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm;
+@end
+
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/CreateNodeTool.m b/tikzit/src/gtk/CreateNodeTool.m
new file mode 100644
index 0000000..b6b8d1b
--- /dev/null
+++ b/tikzit/src/gtk/CreateNodeTool.m
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "CreateNodeTool.h"
+
+#import "Configuration.h"
+#import "GraphRenderer.h"
+#import "NodeStyleSelector.h"
+#import "NodeStylesModel.h"
+#import "TikzDocument.h"
+#import "tzstockitems.h"
+
+@implementation CreateNodeTool
+- (NSString*) name { return @"Create Node"; }
+- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_NODE; }
+- (NSString*) helpText { return @"Create new nodes"; }
+- (NSString*) shortcut { return @"n"; }
+@synthesize activeRenderer=renderer;
+@synthesize configurationWidget=configWidget;
+
++ (id) toolWithStyleManager:(StyleManager*)sm {
+ return [[[self alloc] initWithStyleManager:sm] autorelease];
+}
+
++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm {
+ return [[[self alloc] initWithNodeStylesModel:nsm] autorelease];
+}
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)sm {
+ return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm]];
+}
+
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm {
+ self = [super init];
+
+ if (self) {
+ stylePicker = [[NodeStyleSelector alloc] initWithModel:nsm];
+
+ configWidget = gtk_vbox_new (FALSE, 0);
+ g_object_ref_sink (configWidget);
+
+ GtkWidget *label = gtk_label_new ("Node style:");
+ gtk_widget_show (label);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ label,
+ FALSE,
+ FALSE,
+ 0);
+
+ GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_show (selWindow);
+ gtk_container_add (GTK_CONTAINER (selWindow),
+ [stylePicker widget]);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show ([stylePicker widget]);
+
+ GtkWidget *selectorFrame = gtk_frame_new (NULL);
+ gtk_widget_show (selectorFrame);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ selectorFrame,
+ TRUE,
+ TRUE,
+ 0);
+ gtk_container_add (GTK_CONTAINER (selectorFrame),
+ selWindow);
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [renderer release];
+ [stylePicker release];
+
+ g_object_unref (G_OBJECT (configWidget));
+
+ [super dealloc];
+}
+
+- (NodeStyle*) activeStyle {
+ return [stylePicker selectedStyle];
+}
+
+- (void) setActiveStyle:(NodeStyle*)style {
+ return [stylePicker setSelectedStyle:style];
+}
+
+// FIXME: create node on press, and drag it around?
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ Transformer *transformer = [renderer transformer];
+ NSPoint nodePoint = [transformer fromScreen:[[renderer grid] snapScreenPoint:pos]];
+ Node *node = [Node nodeWithPoint:nodePoint];
+ [node setStyle:[self activeStyle]];
+ [[renderer document] addNode:node];
+}
+
+- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {}
+
+- (StyleManager*) styleManager {
+ return [[stylePicker model] styleManager];
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ NSString *styleName = [config stringEntry:@"ActiveStyle"
+ inGroup:@"CreateNodeTool"
+ withDefault:nil];
+ [self setActiveStyle:[[self styleManager] nodeStyleForName:styleName]];
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ [config setStringEntry:@"ActiveStyle"
+ inGroup:@"CreateNodeTool"
+ value:[[self activeStyle] name]];
+}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/DocumentContext.h b/tikzit/src/gtk/DocumentContext.h
new file mode 100644
index 0000000..e4c1065
--- /dev/null
+++ b/tikzit/src/gtk/DocumentContext.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+
+@class TikzDocument;
+
+@interface DocumentContext {
+ TikzDocument *document;
+ GraphRenderer *renderSurface;
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/EdgeStyleSelector.h b/tikzit/src/gtk/EdgeStyleSelector.h
index 935c9b7..904bd93 100644
--- a/tikzit/src/gtk/EdgeStyleSelector.h
+++ b/tikzit/src/gtk/EdgeStyleSelector.h
@@ -17,52 +17,44 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
-#import "StyleManager.h"
+
+@class EdgeStyle;
+@class EdgeStylesModel;
+@class StyleManager;
@interface EdgeStyleSelector: NSObject {
- GtkListStore *store;
+ EdgeStylesModel *model;
GtkTreeView *view;
- StyleManager *styleManager;
- BOOL linkedToActiveStyle;
- BOOL suppressSetActiveStyle;
}
/*!
@property widget
@brief The GTK widget
*/
-@property (readonly) GtkWidget *widget;
-
-/*!
- @property manager
- @brief The StyleManager to use. Default is [StyleManager manager]
- */
-@property (retain) StyleManager *styleManager;
+@property (readonly) GtkWidget *widget;
/*!
- @property linkedToActiveStyles
- @brief Whether the current selection should be the same as the
- style manager's active style
+ @property model
+ @brief The model to use.
*/
-@property (getter=isLinkedToActiveStyle) BOOL linkedToActiveStyle;
+@property (retain) EdgeStylesModel *model;
/*!
@property selectedStyle
- @brief The selected style. If linkedToActiveStyle is YES, this
- will be the same as [manager activeStyle].
+ @brief The selected style.
When this changes, a SelectedStyleChanged notification will be posted
*/
-@property (assign) EdgeStyle *selectedStyle;
+@property (assign) EdgeStyle *selectedStyle;
/*!
- * Initialise with the default style manager
+ * Initialise with a new model for the given style manager
*/
-- (id) init;
+- (id) initWithStyleManager:(StyleManager*)m;
/*!
- * Initialise with the given style manager
+ * Initialise with the given model
*/
-- (id) initWithStyleManager:(StyleManager*)m;
+- (id) initWithModel:(EdgeStylesModel*)model;
@end
diff --git a/tikzit/src/gtk/EdgeStyleSelector.m b/tikzit/src/gtk/EdgeStyleSelector.m
index c9c9780..6a9db33 100644
--- a/tikzit/src/gtk/EdgeStyleSelector.m
+++ b/tikzit/src/gtk/EdgeStyleSelector.m
@@ -17,91 +17,43 @@
#import "EdgeStyleSelector.h"
-#import "CairoRenderContext.h"
-#import "Edge.h"
-#import "Edge+Render.h"
-#import "Node.h"
-#import "Shape.h"
-#import "Shape+Render.h"
-#import "ShapeNames.h"
-#import "StyleManager.h"
-
-#import <gdk-pixbuf/gdk-pixbuf.h>
+#import "EdgeStylesModel.h"
// {{{ Internal interfaces
-// {{{ Signals
static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr);
// }}}
-
-enum {
- STYLES_NAME_COL = 0,
- STYLES_ICON_COL,
- STYLES_PTR_COL,
- STYLES_N_COLS
-};
-
-@interface EdgeStyleSelector (Notifications)
-- (void) stylesReplaced:(NSNotification*)notification;
-- (void) styleAdded:(NSNotification*)notification;
-- (void) styleRemoved:(NSNotification*)notification;
-- (void) activeStyleChanged:(NSNotification*)notification;
-- (void) shapeDictionaryReplaced:(NSNotification*)n;
-- (void) selectionChanged;
-- (void) observeValueForKeyPath:(NSString*)keyPath
- ofObject:(id)object
- change:(NSDictionary*)change
- context:(void*)context;
-@end
-
-@interface EdgeStyleSelector (Private)
-- (void) clearModel;
-- (cairo_surface_t*) createEdgeIconSurface;
-- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style;
-- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface;
-- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface;
-- (void) addStyle:(EdgeStyle*)style;
-- (void) postSelectedStyleChanged;
-- (void) observeStyle:(EdgeStyle*)style;
-- (void) stopObservingStyle:(EdgeStyle*)style;
-- (void) reloadStyles;
-@end
-
-// }}}
// {{{ API
@implementation EdgeStyleSelector
- (id) init {
- self = [self initWithStyleManager:[StyleManager manager]];
- return self;
+ [self release];
+ return nil;
}
- (id) initWithStyleManager:(StyleManager*)m {
+ return [self initWithModel:[EdgeStylesModel modelWithStyleManager:m]];
+}
+- (id) initWithModel:(EdgeStylesModel*)m {
self = [super init];
if (self) {
- styleManager = nil;
- linkedToActiveStyle = YES;
+ model = [m retain];
- store = gtk_list_store_new (STYLES_N_COLS,
- G_TYPE_STRING,
- GDK_TYPE_PIXBUF,
- G_TYPE_POINTER);
- g_object_ref (store);
-
- view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)));
+ view = GTK_TREE_VIEW (gtk_tree_view_new_with_model ([m model]));
gtk_tree_view_set_headers_visible (view, FALSE);
g_object_ref (view);
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
renderer = gtk_cell_renderer_pixbuf_new ();
- column = gtk_tree_view_column_new_with_attributes ("Preview",
- renderer,
- "pixbuf", STYLES_ICON_COL,
- NULL);
+ column = gtk_tree_view_column_new_with_attributes (
+ "Preview",
+ renderer,
+ "pixbuf", EDGE_STYLES_ICON_COL,
+ NULL);
gtk_tree_view_append_column (view, column);
- gtk_tree_view_set_tooltip_column (view, STYLES_NAME_COL);
+ gtk_tree_view_set_tooltip_column (view, EDGE_STYLES_NAME_COL);
GtkTreeSelection *sel = gtk_tree_view_get_selection (view);
gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
@@ -110,13 +62,6 @@ enum {
"changed",
G_CALLBACK (selection_changed_cb),
self);
-
- [self setStyleManager:m];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(shapeDictionaryReplaced:)
- name:@"ShapeDictionaryReplaced"
- object:[Shape class]];
}
return self;
@@ -124,66 +69,31 @@ enum {
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+
g_object_unref (view);
- [self clearModel];
- g_object_unref (store);
- [styleManager release];
+ [model release];
[super dealloc];
}
-- (StyleManager*) styleManager {
- return styleManager;
+- (EdgeStylesModel*) model {
+ return model;
}
-- (void) setStyleManager:(StyleManager*)m {
- if (m == nil) {
- [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"];
- }
- [m retain];
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(stylesReplaced:)
- name:@"EdgeStylesReplaced"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(styleAdded:)
- name:@"EdgeStyleAdded"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(styleRemoved:)
- name:@"EdgeStyleRemoved"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(activeStyleChanged:)
- name:@"ActiveEdgeStyleChanged"
- object:m];
-
- [styleManager release];
- styleManager = m;
-
- [self reloadStyles];
+- (void) setModel:(EdgeStylesModel*)m {
+ if (m == model)
+ return;
+
+ EdgeStylesModel *oldModel = model;
+ model = [m retain];
+ gtk_tree_view_set_model (view, [model model]);
+ [oldModel release];
}
- (GtkWidget*) widget {
return GTK_WIDGET (view);
}
-- (BOOL) isLinkedToActiveStyle {
- return linkedToActiveStyle;
-}
-
-- (void) setLinkedToActiveStyle:(BOOL)linked {
- linkedToActiveStyle = linked;
- if (linkedToActiveStyle) {
- EdgeStyle *style = [self selectedStyle];
- if ([styleManager activeEdgeStyle] != style) {
- [self setSelectedStyle:[styleManager activeEdgeStyle]];
- }
- }
-}
-
- (EdgeStyle*) selectedStyle {
GtkTreeSelection *sel = gtk_tree_view_get_selection (view);
GtkTreeIter iter;
@@ -193,7 +103,7 @@ enum {
}
EdgeStyle *style = nil;
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, STYLES_PTR_COL, &style, -1);
+ gtk_tree_model_get ([model model], &iter, EDGE_STYLES_PTR_COL, &style, -1);
return style;
}
@@ -206,278 +116,12 @@ enum {
return;
}
- GtkTreeModel *m = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (m, &row)) {
- do {
- EdgeStyle *rowStyle;
- gtk_tree_model_get (m, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- gtk_tree_selection_unselect_all (sel);
- GtkTreePath *path = gtk_tree_model_get_path (m, &row);
- gtk_tree_selection_select_path (sel, path);
- gtk_tree_path_free (path);
- // styleManager.activeStyle will be updated by the GTK+ callback
- return;
- }
- } while (gtk_tree_model_iter_next (m, &row));
- }
-}
-
-@end
-
-// }}}
-// {{{ Notifications
-
-@implementation EdgeStyleSelector (Notifications)
-
-- (void) stylesReplaced:(NSNotification*)notification {
- [self reloadStyles];
-}
-
-- (void) styleAdded:(NSNotification*)notification {
- [self addStyle:[[notification userInfo] objectForKey:@"style"]];
-}
-
-- (void) styleRemoved:(NSNotification*)notification {
- EdgeStyle *style = [[notification userInfo] objectForKey:@"style"];
-
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- EdgeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- gtk_list_store_remove (store, &row);
- [self stopObservingStyle:rowStyle];
- [rowStyle release];
- return;
- }
- } while (gtk_tree_model_iter_next (model, &row));
- }
-}
-
-- (void) activeStyleChanged:(NSNotification*)notification {
- if (linkedToActiveStyle) {
- EdgeStyle *style = [self selectedStyle];
- if ([styleManager activeEdgeStyle] != style) {
- [self setSelectedStyle:[styleManager activeEdgeStyle]];
- }
- }
-}
-
-- (void) observeValueForKeyPath:(NSString*)keyPath
- ofObject:(id)object
- change:(NSDictionary*)change
- context:(void*)context
-{
- if ([object class] != [EdgeStyle class])
- return;
-
- EdgeStyle *style = object;
-
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- EdgeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- if ([@"name" isEqual:keyPath]) {
- gtk_list_store_set (store, &row, STYLES_NAME_COL, [[style name] UTF8String], -1);
- } else {
- GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style];
- gtk_list_store_set (store, &row, STYLES_ICON_COL, pixbuf, -1);
- g_object_unref (pixbuf);
- }
- }
- } while (gtk_tree_model_iter_next (model, &row));
- }
-}
-
-- (void) shapeDictionaryReplaced:(NSNotification*)n {
- [self reloadStyles];
-}
-
-- (void) selectionChanged {
- if (linkedToActiveStyle) {
- EdgeStyle *style = [self selectedStyle];
- if ([styleManager activeEdgeStyle] != style) {
- [styleManager setActiveEdgeStyle:style];
- }
- }
- [self postSelectedStyleChanged];
-}
-@end
-
-// }}}
-// {{{ Private
-
-@implementation EdgeStyleSelector (Private)
-- (void) clearModel {
- [self setSelectedStyle:nil];
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- EdgeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- [self stopObservingStyle:rowStyle];
- [rowStyle release];
- } while (gtk_tree_model_iter_next (model, &row));
- }
- gtk_list_store_clear (store);
-}
-
-- (cairo_surface_t*) createEdgeIconSurface {
- return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 48, 18);
-}
-
-- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style {
- cairo_surface_t *surface = [self createEdgeIconSurface];
- GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface];
- cairo_surface_destroy (surface);
- return pixbuf;
-}
-
-// Bring on GTK+3 and gdk_pixbuf_get_from_surface()
-- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface {
- cairo_surface_flush (surface);
-
- int width = cairo_image_surface_get_width (surface);
- int height = cairo_image_surface_get_height (surface);
- int stride = cairo_image_surface_get_stride (surface);
- unsigned char *data = cairo_image_surface_get_data (surface);
-
- GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
- TRUE,
- 8,
- width,
- height);
- unsigned char *pbdata = gdk_pixbuf_get_pixels (pixbuf);
- int pbstride = gdk_pixbuf_get_rowstride (pixbuf);
-
- for (int y = 0; y < height; ++y) {
- uint32_t *line = (uint32_t*)(data + y*stride);
- unsigned char *pbline = pbdata + (y*pbstride);
- for (int x = 0; x < width; ++x) {
- uint32_t pixel = *(line + x);
- unsigned char *pbpixel = pbline + (x*4);
- // NB: We should un-pre-mult the alpha here.
- // However, in our world, alpha is always
- // on or off, so it doesn't really matter
- pbpixel[3] = ((pixel & 0xff000000) >> 24);
- pbpixel[0] = ((pixel & 0x00ff0000) >> 16);
- pbpixel[1] = ((pixel & 0x0000ff00) >> 8);
- pbpixel[2] = (pixel & 0x000000ff);
- }
- }
-
- return pixbuf;
-}
-
-- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface {
- Transformer *transformer = [Transformer defaultTransformer];
- [transformer setFlippedAboutXAxis:YES];
-
- int width = cairo_image_surface_get_width (surface);
- int height = cairo_image_surface_get_height (surface);
- NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height);
- NSRect graphBounds = [transformer rectFromScreen:pixbufBounds];
-
- NSPoint start = NSMakePoint (NSMinX (graphBounds) + 0.1f, NSMidY (graphBounds));
- NSPoint end = NSMakePoint (NSMaxX (graphBounds) - 0.1f, NSMidY (graphBounds));
- Node *src = [Node nodeWithPoint:start];
- Node *tgt = [Node nodeWithPoint:end];
- Edge *e = [Edge edgeWithSource:src andTarget:tgt];
- [e setStyle:style];
-
- CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface];
- [context clearSurface];
- [e renderBasicEdgeInContext:context withTransformer:transformer selected:NO];
- [context release];
-
- return [self pixbufFromSurface:surface];
-}
-
-- (void) addStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface {
- GtkTreeIter iter;
- gtk_list_store_append (store, &iter);
-
- GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface];
- gtk_list_store_set (store, &iter,
- STYLES_NAME_COL, [[style name] UTF8String],
- STYLES_ICON_COL, pixbuf,
- STYLES_PTR_COL, (gpointer)[style retain],
- -1);
- g_object_unref (pixbuf);
- [self observeStyle:style];
-}
-
-- (void) addStyle:(EdgeStyle*)style {
- cairo_surface_t *surface = [self createEdgeIconSurface];
- [self addStyle:style usingSurface:surface];
- cairo_surface_destroy (surface);
-}
-
-- (void) postSelectedStyleChanged {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectedStyleChanged" object:self];
-}
-
-- (void) observeStyle:(EdgeStyle*)style {
- [style addObserver:self
- forKeyPath:@"name"
- options:NSKeyValueObservingOptionNew
- context:NULL];
- [style addObserver:self
- forKeyPath:@"thickness"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"headStyle"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"tailStyle"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"decorationStyle"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"colorRGB.red"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"colorRGB.green"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"colorRGB.blue"
- options:0
- context:NULL];
-}
-
-- (void) stopObservingStyle:(EdgeStyle*)style {
- [style removeObserver:self forKeyPath:@"name"];
- [style removeObserver:self forKeyPath:@"thickness"];
- [style removeObserver:self forKeyPath:@"headStyle"];
- [style removeObserver:self forKeyPath:@"tailStyle"];
- [style removeObserver:self forKeyPath:@"decorationStyle"];
- [style removeObserver:self forKeyPath:@"colorRGB.red"];
- [style removeObserver:self forKeyPath:@"colorRGB.green"];
- [style removeObserver:self forKeyPath:@"colorRGB.blue"];
-}
-
-- (void) reloadStyles {
- [self clearModel];
- cairo_surface_t *surface = [self createEdgeIconSurface];
- for (EdgeStyle *style in [styleManager edgeStyles]) {
- [self addStyle:style usingSurface:surface];
+ GtkTreePath *path = [model pathFromStyle:style];
+ if (path) {
+ gtk_tree_selection_unselect_all (sel);
+ gtk_tree_selection_select_path (sel, path);
+ gtk_tree_path_free (path);
}
- cairo_surface_destroy (surface);
}
@end
@@ -486,7 +130,11 @@ enum {
static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [mgr selectionChanged];
+
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"SelectedStyleChanged"
+ object:mgr];
+
[pool drain];
}
// }}}
diff --git a/tikzit/src/gtk/EdgeStylesModel.h b/tikzit/src/gtk/EdgeStylesModel.h
new file mode 100644
index 0000000..1166f92
--- /dev/null
+++ b/tikzit/src/gtk/EdgeStylesModel.h
@@ -0,0 +1,62 @@
+/*
+ * 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 "TZFoundation.h"
+#import <gtk/gtk.h>
+
+@class EdgeStyle;
+@class StyleManager;
+
+enum {
+ EDGE_STYLES_NAME_COL = 0,
+ EDGE_STYLES_ICON_COL,
+ EDGE_STYLES_PTR_COL,
+ EDGE_STYLES_N_COLS
+};
+
+@interface EdgeStylesModel: NSObject {
+ GtkListStore *store;
+ StyleManager *styleManager;
+}
+
+/*!
+ @property model
+ @brief The GTK+ tree model
+ */
+@property (readonly) GtkTreeModel *model;
+
+/*!
+ @property manager
+ @brief The StyleManager to use.
+ */
+@property (retain) StyleManager *styleManager;
+
+/*!
+ * Initialise with the given style manager
+ */
+- (id) initWithStyleManager:(StyleManager*)m;
+
++ (id) modelWithStyleManager:(StyleManager*)m;
+
+- (EdgeStyle*) styleFromPath:(GtkTreePath*)path;
+- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style;
+- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter;
+- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style;
+
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/EdgeStylesModel.m b/tikzit/src/gtk/EdgeStylesModel.m
new file mode 100644
index 0000000..2de57ed
--- /dev/null
+++ b/tikzit/src/gtk/EdgeStylesModel.m
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "EdgeStylesModel.h"
+
+#import "CairoRenderContext.h"
+#import "Edge.h"
+#import "Edge+Render.h"
+#import "EdgeStyle.h"
+#import "Node.h"
+#import "StyleManager.h"
+
+#import "gtkhelpers.h"
+
+#import <gdk-pixbuf/gdk-pixbuf.h>
+
+// {{{ Internal interfaces
+
+@interface EdgeStylesModel (Notifications)
+- (void) edgeStylesReplaced:(NSNotification*)notification;
+- (void) edgeStyleAdded:(NSNotification*)notification;
+- (void) edgeStyleRemoved:(NSNotification*)notification;
+- (void) observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context;
+@end
+
+@interface EdgeStylesModel (Private)
+- (cairo_surface_t*) createEdgeIconSurface;
+- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style;
+- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface;
+- (void) addEdgeStyle:(EdgeStyle*)style;
+- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface;
+- (void) observeEdgeStyle:(EdgeStyle*)style;
+- (void) stopObservingEdgeStyle:(EdgeStyle*)style;
+- (void) clearEdgeStylesModel;
+- (void) reloadEdgeStyles;
+@end
+
+// }}}
+// {{{ API
+
+@implementation EdgeStylesModel
+
++ (id) modelWithStyleManager:(StyleManager*)m {
+ return [[[self alloc] initWithStyleManager:m] autorelease];
+}
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)m {
+ self = [super init];
+
+ if (self) {
+ store = gtk_list_store_new (EDGE_STYLES_N_COLS,
+ G_TYPE_STRING,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_POINTER);
+ g_object_ref_sink (store);
+
+ [self setStyleManager:m];
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [self clearEdgeStylesModel];
+ g_object_unref (store);
+ [styleManager release];
+
+ [super dealloc];
+}
+
+- (GtkTreeModel*) model {
+ return GTK_TREE_MODEL (store);
+}
+
+- (StyleManager*) styleManager {
+ return styleManager;
+}
+
+- (void) setStyleManager:(StyleManager*)m {
+ if (m == nil) {
+ [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"];
+ }
+ [m retain];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(edgeStylesReplaced:)
+ name:@"EdgeStylesReplaced"
+ object:m];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(edgeStyleAdded:)
+ name:@"EdgeStyleAdded"
+ object:m];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(edgeStyleRemoved:)
+ name:@"EdgeStyleRemoved"
+ object:m];
+
+ [styleManager release];
+ styleManager = m;
+
+ [self reloadEdgeStyles];
+}
+
+- (EdgeStyle*) styleFromPath:(GtkTreePath*)path {
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+ EdgeStyle *style = nil;
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, EDGE_STYLES_PTR_COL, &style, -1);
+ return style;
+}
+
+- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style {
+ GtkTreeModel *m = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (m, &row)) {
+ do {
+ EdgeStyle *rowStyle;
+ gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ return gtk_tree_model_get_path (m, &row);
+ }
+ } while (gtk_tree_model_iter_next (m, &row));
+ }
+ return NULL;
+}
+
+- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter {
+ EdgeStyle *style = nil;
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EDGE_STYLES_PTR_COL, &style, -1);
+ return style;
+}
+
+- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style {
+ GtkTreeModel *m = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (m, &row)) {
+ do {
+ EdgeStyle *rowStyle;
+ gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ return gtk_tree_iter_copy (&row);
+ }
+ } while (gtk_tree_model_iter_next (m, &row));
+ }
+ return NULL;
+}
+@end
+
+// }}}
+// {{{ Notifications
+
+@implementation EdgeStylesModel (Notifications)
+
+- (void) edgeStylesReplaced:(NSNotification*)notification {
+ [self reloadEdgeStyles];
+}
+
+- (void) edgeStyleAdded:(NSNotification*)notification {
+ [self addEdgeStyle:[[notification userInfo] objectForKey:@"style"]];
+}
+
+- (void) edgeStyleRemoved:(NSNotification*)notification {
+ EdgeStyle *style = [[notification userInfo] objectForKey:@"style"];
+
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ EdgeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ gtk_list_store_remove (store, &row);
+ [self stopObservingEdgeStyle:rowStyle];
+ [rowStyle release];
+ return;
+ }
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+}
+
+- (void) observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context
+{
+ if ([object class] != [EdgeStyle class])
+ return;
+
+ EdgeStyle *style = object;
+
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ EdgeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ if ([@"name" isEqual:keyPath]) {
+ gtk_list_store_set (store, &row, EDGE_STYLES_NAME_COL, [[style name] UTF8String], -1);
+ } else {
+ GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style];
+ gtk_list_store_set (store, &row, EDGE_STYLES_ICON_COL, pixbuf, -1);
+ g_object_unref (pixbuf);
+ }
+ }
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+}
+@end
+
+// }}}
+// {{{ Private
+
+@implementation EdgeStylesModel (Private)
+- (cairo_surface_t*) createEdgeIconSurface {
+ return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 48, 18);
+}
+
+- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style {
+ cairo_surface_t *surface = [self createEdgeIconSurface];
+ GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface];
+ cairo_surface_destroy (surface);
+ return pixbuf;
+}
+
+- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface {
+ Transformer *transformer = [Transformer defaultTransformer];
+ [transformer setFlippedAboutXAxis:YES];
+
+ int width = cairo_image_surface_get_width (surface);
+ int height = cairo_image_surface_get_height (surface);
+ NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height);
+ NSRect graphBounds = [transformer rectFromScreen:pixbufBounds];
+
+ NSPoint start = NSMakePoint (NSMinX (graphBounds) + 0.1f, NSMidY (graphBounds));
+ NSPoint end = NSMakePoint (NSMaxX (graphBounds) - 0.1f, NSMidY (graphBounds));
+ Node *src = [Node nodeWithPoint:start];
+ Node *tgt = [Node nodeWithPoint:end];
+ Edge *e = [Edge edgeWithSource:src andTarget:tgt];
+ [e setStyle:style];
+
+ CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface];
+ [context clearSurface];
+ [e renderBasicEdgeInContext:context withTransformer:transformer selected:NO];
+ [context release];
+
+ return pixbuf_get_from_surface (surface);
+}
+
+- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface {
+ GtkTreeIter iter;
+ gtk_list_store_append (store, &iter);
+
+ GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface];
+ gtk_list_store_set (store, &iter,
+ EDGE_STYLES_NAME_COL, [[style name] UTF8String],
+ EDGE_STYLES_ICON_COL, pixbuf,
+ EDGE_STYLES_PTR_COL, (gpointer)[style retain],
+ -1);
+ g_object_unref (pixbuf);
+ [self observeEdgeStyle:style];
+}
+
+- (void) addEdgeStyle:(EdgeStyle*)style {
+ cairo_surface_t *surface = [self createEdgeIconSurface];
+ [self addEdgeStyle:style usingSurface:surface];
+ cairo_surface_destroy (surface);
+}
+
+- (void) observeEdgeStyle:(EdgeStyle*)style {
+ [style addObserver:self
+ forKeyPath:@"name"
+ options:NSKeyValueObservingOptionNew
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"thickness"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"headStyle"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"tailStyle"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"decorationStyle"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"colorRGB.red"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"colorRGB.green"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"colorRGB.blue"
+ options:0
+ context:NULL];
+}
+
+- (void) stopObservingEdgeStyle:(EdgeStyle*)style {
+ [style removeObserver:self forKeyPath:@"name"];
+ [style removeObserver:self forKeyPath:@"thickness"];
+ [style removeObserver:self forKeyPath:@"headStyle"];
+ [style removeObserver:self forKeyPath:@"tailStyle"];
+ [style removeObserver:self forKeyPath:@"decorationStyle"];
+ [style removeObserver:self forKeyPath:@"colorRGB.red"];
+ [style removeObserver:self forKeyPath:@"colorRGB.green"];
+ [style removeObserver:self forKeyPath:@"colorRGB.blue"];
+}
+
+- (void) clearEdgeStylesModel {
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ EdgeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1);
+ [self stopObservingEdgeStyle:rowStyle];
+ [rowStyle release];
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+ gtk_list_store_clear (store);
+}
+
+- (void) reloadEdgeStyles {
+ [self clearEdgeStylesModel];
+ cairo_surface_t *surface = [self createEdgeIconSurface];
+ for (EdgeStyle *style in [styleManager edgeStyles]) {
+ [self addEdgeStyle:style usingSurface:surface];
+ }
+ cairo_surface_destroy (surface);
+}
+@end
+
+// }}}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/EdgeStylesPalette.m b/tikzit/src/gtk/EdgeStylesPalette.m
index bcb631e..7e42552 100644
--- a/tikzit/src/gtk/EdgeStylesPalette.m
+++ b/tikzit/src/gtk/EdgeStylesPalette.m
@@ -144,11 +144,11 @@ static void clear_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette
}
- (StyleManager*) styleManager {
- return [selector styleManager];
+ return [[selector model] styleManager];
}
- (void) setStyleManager:(StyleManager*)m {
- [selector setStyleManager:m];
+ [[selector model] setStyleManager:m];
}
- (TikzDocument*) document {
@@ -221,7 +221,7 @@ static void clear_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette
- (void) removeSelectedStyle {
EdgeStyle *style = [selector selectedStyle];
if (style)
- [[selector styleManager] removeEdgeStyle:style];
+ [[[selector model] styleManager] removeEdgeStyle:style];
}
- (void) applySelectedStyle {
@@ -255,7 +255,6 @@ static void add_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette)
EdgeStyle *newStyle = [EdgeStyle defaultEdgeStyleWithName:@"newstyle"];
[[palette styleManager] addEdgeStyle:newStyle];
- [[palette styleManager] setActiveEdgeStyle:newStyle];
[pool drain];
}
diff --git a/tikzit/src/gtk/GraphEditorPanel.h b/tikzit/src/gtk/GraphEditorPanel.h
new file mode 100644
index 0000000..857b0ba
--- /dev/null
+++ b/tikzit/src/gtk/GraphEditorPanel.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "Tool.h"
+#import <gtk/gtk.h>
+
+@class GraphInputHandler;
+@class GraphRenderer;
+@class TikzDocument;
+@class WidgetSurface;
+
+@interface GraphEditorPanel : NSObject {
+ GraphRenderer *renderer;
+ WidgetSurface *surface;
+ GraphInputHandler *inputHandler;
+ id<Tool> tool;
+}
+@property (retain) TikzDocument *document;
+@property (readonly) GtkWidget *widget;
+@property (retain) id<Tool> activeTool;
+
+- (id) init;
+- (id) initWithDocument:(TikzDocument*)document;
+- (void) grabTool;
+- (void) zoomInAboutPoint:(NSPoint)pos;
+- (void) zoomOutAboutPoint:(NSPoint)pos;
+- (void) zoomIn;
+- (void) zoomOut;
+- (void) zoomReset;
+
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/GraphEditorPanel.m b/tikzit/src/gtk/GraphEditorPanel.m
new file mode 100644
index 0000000..4c7312a
--- /dev/null
+++ b/tikzit/src/gtk/GraphEditorPanel.m
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "GraphEditorPanel.h"
+
+#import "Application.h"
+#import "GraphRenderer.h"
+#import "HandTool.h"
+#import "InputDelegate.h"
+#import "TikzDocument.h"
+#import "WidgetSurface.h"
+
+@class GraphRenderer;
+@class WidgetSurface;
+
+/**
+ * Mostly just a multiplexer
+ */
+@interface GraphInputHandler : NSObject<InputDelegate> {
+ GraphEditorPanel *panel;
+}
+- (id) initForPanel:(GraphEditorPanel*)p;
+@end
+
+@implementation GraphEditorPanel
+- (id) init {
+ return [self initWithDocument:nil];
+}
+- (id) initWithDocument:(TikzDocument*)document {
+ self = [super init];
+ if (self) {
+ surface = [[WidgetSurface alloc] init];
+ [surface setDefaultScale:50.0f];
+ [surface setKeepCentered:YES];
+ [surface setCanFocus:YES];
+ renderer = [[GraphRenderer alloc] initWithSurface:surface document:document];
+
+ inputHandler = [[GraphInputHandler alloc] initForPanel:self];
+ [surface setInputDelegate:inputHandler];
+ }
+ return self;
+}
+
+- (void) dealloc {
+ [renderer release];
+ [surface release];
+ [inputHandler release];
+
+ [super dealloc];
+}
+
+- (TikzDocument*) document {
+ return [renderer document];
+}
+- (void) setDocument:(TikzDocument*)doc {
+ [renderer setDocument:doc];
+}
+- (GtkWidget*) widget {
+ return [surface widget];
+}
+- (id<Tool>) activeTool {
+ return tool;
+}
+- (void) setActiveTool:(id<Tool>)t {
+ if (t == tool)
+ return;
+
+ [[[renderer document] pickSupport] deselectAllNodes];
+ [[[renderer document] pickSupport] deselectAllEdges];
+
+ BOOL hadOldTool = ([tool activeRenderer] == renderer);
+
+ id oldTool = tool;
+ tool = [t retain];
+ [oldTool release];
+
+ if (hadOldTool) {
+ [self grabTool];
+ }
+}
+
+- (BOOL) hasTool {
+ return [tool activeRenderer] == renderer;
+}
+
+- (void) grabTool {
+ if ([tool activeRenderer] != renderer) {
+ [[tool activeRenderer] setPostRenderer:nil];
+ [tool setActiveRenderer:renderer];
+ }
+ [renderer setPostRenderer:tool];
+}
+
+- (void) zoomInAboutPoint:(NSPoint)pos { [surface zoomInAboutPoint:pos]; }
+- (void) zoomOutAboutPoint:(NSPoint)pos { [surface zoomOutAboutPoint:pos]; }
+- (void) zoomIn { [surface zoomIn]; }
+- (void) zoomOut { [surface zoomOut]; }
+- (void) zoomReset { [surface zoomReset]; }
+
+@end
+
+@implementation GraphInputHandler
+- (id) initForPanel:(GraphEditorPanel*)p {
+ self = [super init];
+ if (self) {
+ // NB: no retention!
+ panel = p;
+ }
+ return self;
+}
+- (id) init {
+ [self dealloc];
+ return nil;
+}
+
+// FIXME: use a local copy of HandTool to implement CTRL-dragging
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ [panel grabTool];
+ id<Tool> tool = [panel activeTool];
+ if ([tool respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) {
+ [tool mousePressAt:pos withButton:button andMask:mask];
+ }
+}
+
+- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ [panel grabTool];
+ id<Tool> tool = [panel activeTool];
+ if ([tool respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) {
+ [tool mouseDoubleClickAt:pos withButton:button andMask:mask];
+ }
+}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (![panel hasTool])
+ return;
+ id<Tool> tool = [panel activeTool];
+ if ([tool respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) {
+ [tool mouseReleaseAt:pos withButton:button andMask:mask];
+ }
+}
+
+- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
+ if (![panel hasTool])
+ return;
+ id<Tool> tool = [panel activeTool];
+ if ([tool respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) {
+ [tool mouseMoveTo:pos withButtons:buttons andMask:mask];
+ }
+}
+
+- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask {
+ id<Tool> tool = [panel activeTool];
+ if (mask == ControlMask) {
+ if (dir == ScrollUp) {
+ [panel zoomInAboutPoint:pos];
+ } else if (dir == ScrollDown) {
+ [panel zoomOutAboutPoint:pos];
+ }
+ } else if ([panel hasTool] && [tool respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) {
+ [tool mouseScrolledAt:pos inDirection:dir withMask:mask];
+ }
+}
+
+- (void) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask {
+ [app activateToolForKey:keyVal withMask:mask];
+}
+
+- (void) keyReleased:(unsigned int)keyVal withMask:(InputMask)mask {
+}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/GraphInputHandler.m b/tikzit/src/gtk/GraphInputHandler.m
deleted file mode 100644
index 02d39a1..0000000
--- a/tikzit/src/gtk/GraphInputHandler.m
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright 2011 Alex Merry <alex.merry@kdemail.net>
- * Copyright 2010 Chris Heunen
- *
- * 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 "GraphInputHandler.h"
-#import <gdk/gdkkeysyms.h>
-#import "MainWindow.h"
-#import "Edge+Render.h"
-
-static const InputMask unionSelectMask = ShiftMask;
-
-@interface GraphInputHandler (Notifications)
-- (void) nodeSelectionChanged:(NSNotification*)n;
-- (void) edgeSelectionChanged:(NSNotification*)n;
-@end
-
-@implementation GraphInputHandler
-- (id) initWithGraphRenderer:(GraphRenderer*)r {
- return [self initWithGraphRenderer:r window:nil];
-}
-- (id) initWithGraphRenderer:(GraphRenderer*)r window:(MainWindow*)w {
- self = [super init];
-
- if (self) {
- window = w;
- renderer = r;
- mode = SelectMode;
- state = QuietState;
- edgeFuzz = 3.0f;
- leaderNode = nil;
- modifyEdge = nil;
- selectionBoxContents = [[NSMutableSet alloc] initWithCapacity:10];
- 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;
-}
-
-- (void) dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [selectionBoxContents release];
-
- [super dealloc];
-}
-
-- (TikzDocument*) doc {
- return [renderer document];
-}
-
-- (void) deselectAllNodes {
- [[[self doc] pickSupport] deselectAllNodes];
-}
-
-- (void) deselectAllEdges {
- [[[self doc] pickSupport] deselectAllEdges];
-}
-
-- (void) deselectAll {
- [[[self doc] pickSupport] deselectAllNodes];
- [[[self doc] pickSupport] deselectAllEdges];
-}
-
-- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to {
- Transformer *transformer = [renderer transformer];
-
- NSPoint from = [transformer toScreen:[leader point]];
- //to = [[renderer grid] snapScreenPoint:to];
- float dx = to.x - from.x;
- float dy = to.y - from.y;
-
- for (Node *node in [[[self doc] pickSupport] selectedNodes]) {
- NSPoint p = [transformer toScreen:[node point]];
- p.x += dx;
- p.y += dy;
- p = [[renderer grid] snapScreenPoint:p];
- [node setPoint:[transformer fromScreen:p]];
- }
-}
-
-- (float) edgeFuzz {
- return edgeFuzz;
-}
-
-- (void) setEdgeFuzz:(float)fuzz {
- edgeFuzz = fuzz;
-}
-
-- (InputMode) mode {
- return mode;
-}
-
-- (void) resetState {
- state = QuietState;
-}
-
-- (void) setMode:(InputMode)m {
- if (mode != m) {
- if (mode == BoundingBoxMode) {
- [renderer setBoundingBoxHandlesShown:NO];
- [[renderer surface] setCursor:NormalCursor];
- }
- mode = m;
- [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];
- }
- }
-}
-
-- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p {
- return (NSDistanceBetweenPoints(c, p) <= r);
-}
-
-- (void) lookForControlPointAt:(NSPoint)pos {
- const float cpr = [Edge controlPointRadius];
- for (Edge *e in [[[self doc] pickSupport] selectedEdges]) {
- NSPoint cp1 = [[renderer transformer] toScreen:[e cp1]];
- if ([self circleWithCenter:cp1 andRadius:cpr containsPoint:pos]) {
- state = DragEdgeControlPoint1;
- modifyEdge = e;
- [[self doc] startModifyEdge:e];
- return;
- }
- NSPoint cp2 = [[renderer transformer] toScreen:[e cp2]];
- if ([self circleWithCenter:cp2 andRadius:cpr containsPoint:pos]) {
- state = DragEdgeControlPoint2;
- modifyEdge = e;
- [[self doc] startModifyEdge:e];
- return;
- }
- }
-}
-
-- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
- if (button != LeftButton)
- return;
-
- dragOrigin = pos;
-
- // we should already be in a quiet state, but no harm in making sure
- state = QuietState;
-
- if (mode == HandMode || mask == ControlMask) {
- state = CanvasDragState;
- oldOrigin = [[renderer transformer] origin];
- } else if (mode == DrawEdgeMode) {
- leaderNode = [renderer anyNodeAt:pos];
- if (leaderNode != nil) {
- state = EdgeDragState;
- }
- } else if (mode == BoundingBoxMode) {
- state = BoundingBoxState;
- currentResizeHandle = [renderer boundingBoxResizeHandleAt:pos];
- [[self doc] startChangeBoundingBox];
- if (currentResizeHandle == NoHandle) {
- [[[self doc] graph] setBoundingBox:NSZeroRect];
- [renderer setBoundingBoxHandlesShown:NO];
- }
- } else if (mode == SelectMode) {
- modifyEdge = nil;
- [self lookForControlPointAt:pos];
-
- if (modifyEdge == nil) {
- // we didn't find a control point
-
- BOOL unionSelect = (mask & unionSelectMask);
-
- leaderNode = [renderer anyNodeAt:pos];
- // if we hit a node, deselect other nodes (if Shift is up) and go to move mode
- if (leaderNode != nil) {
- BOOL alreadySelected = [[self doc] isNodeSelected:leaderNode];
- if (!unionSelect && !alreadySelected) {
- [self deselectAllEdges];
- [self deselectAllNodes];
- }
- if (unionSelect && alreadySelected) {
- state = ToggleSelectState;
- } else {
- [[[self doc] pickSupport] selectNode:leaderNode];
- state = MoveSelectedNodesState;
- oldLeaderPos = [leaderNode point];
- [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]];
- }
- }
-
- // if mouse did not hit a node, check if mouse hit an edge
- if (leaderNode == nil) {
- Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz];
- if (edge != nil) {
- BOOL alreadySelected = [[self doc] isEdgeSelected:edge];
- if (!unionSelect) {
- [self deselectAll];
- }
- if (unionSelect && alreadySelected) {
- [[[self doc] pickSupport] deselectEdge:edge];
- } else {
- [[[self doc] pickSupport] selectEdge:edge];
- }
- } else {
- // if mouse did not hit anything, put us in box mode
- if (!unionSelect) {
- [self deselectAll];
- }
- [selectionBoxContents removeAllObjects];
- state = SelectBoxState;
- }
- }
- }
- }
-}
-
-- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
- if (button != LeftButton)
- return;
-
- if (state == SelectBoxState) {
- BOOL shouldDeselect = !(mask & unionSelectMask);
- if (shouldDeselect) {
- [self deselectAllEdges];
- }
- [[[self doc] pickSupport] selectAllNodes:selectionBoxContents
- replacingSelection:shouldDeselect];
- [renderer clearSelectionBox];
- } else if (state == ToggleSelectState) {
- [[[self doc] pickSupport] deselectNode:leaderNode];
- leaderNode = nil;
- } else if (state == MoveSelectedNodesState) {
- if (NSEqualPoints (oldLeaderPos, [leaderNode point])) {
- [[self doc] cancelShiftNodes];
- } else {
- [[self doc] endShiftNodes];
- }
- leaderNode = nil;
- } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) {
- // FIXME: check if there was any real change
- [[self doc] endModifyEdge];
- } else if (state == EdgeDragState) {
- [renderer clearHalfEdge];
- Node *targ = [renderer anyNodeAt:pos];
- if (targ != nil) {
- [[self doc] addEdgeFrom:leaderNode to:targ];
- }
- } else if (state == QuietState && mode == CreateNodeMode) {
- Transformer *transformer = [renderer transformer];
- NSPoint nodePoint = [transformer fromScreen:[[renderer grid] snapScreenPoint:pos]];
- [[self doc] addNodeAt:nodePoint];
- } else if (state == BoundingBoxState) {
- [[self doc] endChangeBoundingBox];
- [renderer setBoundingBoxHandlesShown:YES];
- }
-
- state = QuietState;
-}
-
-- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
- if (button != LeftButton)
- return;
-
- if (mode != SelectMode) {
- return;
- }
- if (state != QuietState) {
- return;
- }
- // convert bend mode on edge under mouse cursor
- Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz];
- if (edge != nil) {
- [[self doc] startModifyEdge:edge];
- if ([edge bendMode]==EdgeBendModeBasic) {
- [edge convertBendToAngles];
- [edge setBendMode:EdgeBendModeInOut];
- } else {
- [edge setBendMode:EdgeBendModeBasic];
- }
- [[self doc] endModifyEdge];
-
- [self deselectAllEdges];
- [[[self doc] pickSupport] selectEdge:edge];
- }
-}
-
-- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
- if (!(buttons & LeftButton))
- return;
-
- Transformer *transformer = [renderer transformer];
-
- if (state == ToggleSelectState) {
- state = MoveSelectedNodesState;
- oldLeaderPos = [leaderNode point];
- [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]];
- }
-
- if (state == SelectBoxState) {
- NSRect selectionBox = NSRectAroundPoints(dragOrigin, pos);
- [renderer setSelectionBox:selectionBox];
-
- NSEnumerator *enumerator = [[self doc] nodeEnumerator];
- Node *node;
- while ((node = [enumerator nextObject]) != nil) {
- NSPoint nodePos = [transformer toScreen:[node point]];
- if (NSPointInRect(nodePos, selectionBox)) {
- if (![selectionBoxContents member:node]) {
- [selectionBoxContents addObject:node];
- [renderer invalidateNode:node];
- }
- } else {
- if ([selectionBoxContents member:node]) {
- [selectionBoxContents removeObject:node];
- [renderer invalidateNode:node];
- }
- }
- }
- } else if (state == MoveSelectedNodesState) {
- if (leaderNode != nil) {
- [self shiftNodesByMovingLeader:leaderNode to:pos];
- NSPoint shiftSoFar;
- shiftSoFar.x = [leaderNode point].x - oldLeaderPos.x;
- shiftSoFar.y = [leaderNode point].y - oldLeaderPos.y;
- [[self doc] shiftNodesUpdate:shiftSoFar];
- }
- } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) {
- // invalidate once before we start changing it: we may be shrinking
- // the control circles
- [[self doc] modifyEdgeCheckPoint];
- if (state == DragEdgeControlPoint1) {
- [modifyEdge moveCp1To:[transformer fromScreen:pos]
- withWeightCourseness:0.1f
- andBendCourseness:15
- forceLinkControlPoints:(mask & ControlMask)];
- } else {
- [modifyEdge moveCp2To:[transformer fromScreen:pos]
- withWeightCourseness:0.1f
- andBendCourseness:15
- forceLinkControlPoints:(mask & ControlMask)];
- }
- [[self doc] modifyEdgeCheckPoint];
- } else if (state == EdgeDragState) {
- [renderer setHalfEdgeFrom:leaderNode to:pos];
- } else if (state == BoundingBoxState) {
- Grid *grid = [renderer grid];
- Graph *graph = [[self doc] graph];
- if (currentResizeHandle == NoHandle) {
- NSRect bbox = NSRectAroundPoints(
- [grid snapScreenPoint:dragOrigin],
- [grid snapScreenPoint:pos]
- );
- [graph setBoundingBox:[transformer rectFromScreen:bbox]];
- } else {
- NSRect bbox = [transformer rectToScreen:[graph boundingBox]];
- NSPoint p2 = [grid snapScreenPoint:pos];
-
- if (currentResizeHandle == NorthWestHandle ||
- currentResizeHandle == NorthHandle ||
- currentResizeHandle == NorthEastHandle) {
-
- float dy = p2.y - NSMinY(bbox);
- if (dy < bbox.size.height) {
- bbox.origin.y += dy;
- bbox.size.height -= dy;
- } else {
- bbox.origin.y = NSMaxY(bbox);
- bbox.size.height = 0;
- }
-
- } else if (currentResizeHandle == SouthWestHandle ||
- currentResizeHandle == SouthHandle ||
- currentResizeHandle == SouthEastHandle) {
-
- float dy = p2.y - NSMaxY(bbox);
- if (-dy < bbox.size.height) {
- bbox.size.height += dy;
- } else {
- bbox.size.height = 0;
- }
- }
-
- if (currentResizeHandle == NorthWestHandle ||
- currentResizeHandle == WestHandle ||
- currentResizeHandle == SouthWestHandle) {
-
- float dx = p2.x - NSMinX(bbox);
- if (dx < bbox.size.width) {
- bbox.origin.x += dx;
- bbox.size.width -= dx;
- } else {
- bbox.origin.x = NSMaxX(bbox);
- bbox.size.width = 0;
- }
-
- } else if (currentResizeHandle == NorthEastHandle ||
- currentResizeHandle == EastHandle ||
- currentResizeHandle == SouthEastHandle) {
-
- float dx = p2.x - NSMaxX(bbox);
- if (-dx < bbox.size.width) {
- bbox.size.width += dx;
- } else {
- bbox.size.width = 0;
- }
- }
- [graph setBoundingBox:[transformer rectFromScreen:bbox]];
- }
- [[self doc] changeBoundingBoxCheckPoint];
- } else if (state == CanvasDragState) {
- NSPoint newOrigin = oldOrigin;
- newOrigin.x += pos.x - dragOrigin.x;
- newOrigin.y += pos.y - dragOrigin.y;
- [[renderer transformer] setOrigin:newOrigin];
- [renderer invalidateGraph];
- }
- if (mode == BoundingBoxMode && state != BoundingBoxState) {
- ResizeHandle handle = [renderer boundingBoxResizeHandleAt:pos];
- if (handle != currentResizeHandle) {
- currentResizeHandle = handle;
- Cursor c = NormalCursor;
- switch (handle) {
- case EastHandle:
- c = ResizeRightCursor;
- break;
- case SouthEastHandle:
- c = ResizeBottomRightCursor;
- break;
- case SouthHandle:
- c = ResizeBottomCursor;
- break;
- case SouthWestHandle:
- c = ResizeBottomLeftCursor;
- break;
- case WestHandle:
- c = ResizeLeftCursor;
- break;
- case NorthWestHandle:
- c = ResizeTopLeftCursor;
- break;
- case NorthHandle:
- c = ResizeTopCursor;
- break;
- case NorthEastHandle:
- c = ResizeTopRightCursor;
- break;
- default:
- c = NormalCursor;
- break;
- }
- [[renderer surface] setCursor:c];
- }
- }
-}
-
-- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask {
- if (mask == ControlMask) {
- if (dir == ScrollUp) {
- [[renderer surface] zoomInAboutPoint:pos];
- } else if (dir == ScrollDown) {
- [[renderer surface] zoomOutAboutPoint:pos];
- }
- }
-}
-
-@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/GraphRenderer.h b/tikzit/src/gtk/GraphRenderer.h
index 4609766..730d606 100644
--- a/tikzit/src/gtk/GraphRenderer.h
+++ b/tikzit/src/gtk/GraphRenderer.h
@@ -27,32 +27,20 @@
// protocols
#import "Surface.h"
-typedef enum {
- NoHandle,
- EastHandle,
- SouthEastHandle,
- SouthHandle,
- SouthWestHandle,
- WestHandle,
- NorthWestHandle,
- NorthHandle,
- NorthEastHandle
-} ResizeHandle;
-
@interface GraphRenderer: NSObject <RenderDelegate> {
TikzDocument *doc;
NSObject<Surface> *surface;
Grid *grid;
- NSRect selectionBox;
- Node *halfEdgeOrigin;
- NSPoint halfEdgeOriginPoint;
- NSPoint halfEdgeEnd;
- BOOL showBoundingBoxHandles;
+ NSMutableSet *highlightedNodes;
+ id<RenderDelegate> postRenderer;
}
+@property (retain) id<RenderDelegate> postRenderer;
+
- (id) initWithSurface:(NSObject <Surface> *)surface;
- (id) initWithSurface:(NSObject <Surface> *)surface document:(TikzDocument*)document;
- (void) renderWithContext:(id<RenderContext>)context;
+- (void) invalidateRect:(NSRect)rect;
- (void) invalidateGraph;
- (void) invalidateNode:(Node*)node;
- (void) invalidateEdge:(Edge*)edge;
@@ -87,18 +75,9 @@ typedef enum {
- (TikzDocument*) document;
- (void) setDocument:(TikzDocument*)document;
-- (NSRect) selectionBox;
-- (void) setSelectionBox:(NSRect)box;
-- (void) clearSelectionBox;
-
-- (void) setHalfEdgeFrom:(Node*)origin to:(NSPoint)end;
-- (void) clearHalfEdge;
-
-- (BOOL) boundingBoxHandlesShown;
-- (void) setBoundingBoxHandlesShown:(BOOL)shown;
-
-- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)point;
-- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle;
+- (BOOL) isNodeHighlighted:(Node*)node;
+- (void) setNode:(Node*)node highlighted:(BOOL)h;
+- (void) clearHighlightedNodes;
@end
diff --git a/tikzit/src/gtk/GraphRenderer.m b/tikzit/src/gtk/GraphRenderer.m
index 3fc14d6..76fe551 100644
--- a/tikzit/src/gtk/GraphRenderer.m
+++ b/tikzit/src/gtk/GraphRenderer.m
@@ -20,24 +20,11 @@
#import "Edge+Render.h"
#import "Node+Render.h"
-static const float size = 8.0;
-
-float sideHandleTop(NSRect bbox) {
- return (NSMinY(bbox) + NSMaxY(bbox) - size)/2.0f;
-}
-
-float tbHandleLeft(NSRect bbox) {
- return (NSMinX(bbox) + NSMaxX(bbox) - size)/2.0f;
-}
void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
@interface GraphRenderer (Private)
-- (BOOL) selectionBoxContainsNode:(Node*)node;
-- (BOOL) halfEdgeIncludesNode:(Node*)node;
- (enum NodeState) nodeState:(Node*)node;
- (void) renderBoundingBoxWithContext:(id<RenderContext>)context;
-- (void) renderSelectionBoxWithContext:(id<RenderContext>)context;
-- (void) renderImpendingEdgeWithContext:(id<RenderContext>)context;
- (void) nodeNeedsRefreshing:(NSNotification*)notification;
- (void) edgeNeedsRefreshing:(NSNotification*)notification;
- (void) graphNeedsRefreshing:(NSNotification*)notification;
@@ -53,9 +40,8 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
if (self) {
surface = [s retain];
- doc = nil;
grid = [[Grid alloc] initWithSpacing:1.0f subdivisions:4 transformer:[s transformer]];
- halfEdgeOrigin = nil;
+ highlightedNodes = [[NSMutableSet alloc] initWithCapacity:10];
[surface setRenderDelegate:self];
[[NSNotificationCenter defaultCenter] addObserver:self
@@ -85,11 +71,26 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[doc release];
[grid release];
+ [highlightedNodes release];
[surface release];
[super dealloc];
}
+- (id<RenderDelegate>) postRenderer {
+ return postRenderer;
+}
+- (void) setPostRenderer:(id<RenderDelegate>)r {
+ if (r == postRenderer)
+ return;
+
+ [r retain];
+ [postRenderer release];
+ postRenderer = r;
+
+ [self invalidateGraph];
+}
+
- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {
[self renderWithContext:context];
}
@@ -116,14 +117,17 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
}
[self renderBoundingBoxWithContext:context];
- [self renderSelectionBoxWithContext:context];
- [self renderImpendingEdgeWithContext:context];
+ [postRenderer renderWithContext:context onSurface:surface];
}
- (void) invalidateGraph {
[surface invalidate];
}
+- (void) invalidateRect:(NSRect)rect {
+ [surface invalidateRect:rect];
+}
+
- (void) invalidateNodes:(NSSet*)nodes {
for (Node *node in nodes) {
[self invalidateNode:node];
@@ -285,171 +289,34 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
[surface invalidate];
}
-- (NSRect) selectionBox {
- return selectionBox;
-}
-
-- (void) setSelectionBox:(NSRect)box {
- NSRect invRect = NSUnionRect (selectionBox, box);
- selectionBox = box;
- [surface invalidateRect:NSInsetRect (invRect, -2, -2)];
-}
-
-- (void) clearSelectionBox {
- NSRect oldRect = selectionBox;
-
- NSRect emptyRect;
- selectionBox = emptyRect;
-
- [surface invalidateRect:NSInsetRect (oldRect, -2, -2)];
+- (BOOL) isNodeHighlighted:(Node*)node {
+ return [highlightedNodes containsObject:node];
}
-
-- (void) invalidateHalfEdge {
- if (halfEdgeOrigin != nil) {
- NSRect invRect = NSRectAroundPoints(halfEdgeEnd, halfEdgeOriginPoint);
- invRect = NSUnionRect(invRect, [halfEdgeOrigin renderBoundsWithLabelForSurface:surface]);
-
- NSEnumerator *enumerator = [doc nodeEnumerator];
- Node *node;
- while ((node = [enumerator nextObject]) != nil) {
- if ([self point:halfEdgeEnd fuzzyHitsNode:node]) {
- invRect = NSUnionRect(invRect, [node renderBoundsWithLabelForSurface:surface]);
- }
- }
- [surface invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)];
- }
-}
-
-- (void) setHalfEdgeFrom:(Node*)origin to:(NSPoint)end {
- [self invalidateHalfEdge];
-
- if (halfEdgeOrigin != origin) {
- [self invalidateNode:halfEdgeOrigin];
- halfEdgeOrigin = origin;
- halfEdgeOriginPoint = [[surface transformer] toScreen:[origin point]];
- [self invalidateNode:origin];
- }
-
- if (origin != nil) {
- halfEdgeEnd = end;
- [self invalidateHalfEdge];
- }
-}
-
-- (void) clearHalfEdge {
- [self invalidateHalfEdge];
- halfEdgeOrigin = nil;
-}
-
-- (BOOL) boundingBoxHandlesShown {
- return showBoundingBoxHandles;
-}
-
-- (void) setBoundingBoxHandlesShown:(BOOL)shown {
- if (showBoundingBoxHandles != shown) {
- showBoundingBoxHandles = shown;
- [self invalidateGraph];
- }
-}
-
-- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p {
- NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]];
- if (p.x >= NSMaxX(bbox)) {
- if (p.x <= NSMaxX(bbox) + size) {
- if (p.y >= NSMaxY(bbox)) {
- if (p.y <= NSMaxY(bbox) + size) {
- return SouthEastHandle;
- }
- } else if (p.y <= NSMinY(bbox)) {
- if (p.y >= NSMinY(bbox) - size) {
- return NorthEastHandle;
- }
- } else {
- float eastHandleTop = sideHandleTop(bbox);
- if (p.y >= eastHandleTop && p.y <= (eastHandleTop + size)) {
- return EastHandle;
- }
- }
- }
- } else if (p.x <= NSMinX(bbox)) {
- if (p.x >= NSMinX(bbox) - size) {
- if (p.y >= NSMaxY(bbox)) {
- if (p.y <= NSMaxY(bbox) + size) {
- return SouthWestHandle;
- }
- } else if (p.y <= NSMinY(bbox)) {
- if (p.y >= NSMinY(bbox) - size) {
- return NorthWestHandle;
- }
- } else {
- float westHandleTop = sideHandleTop(bbox);
- if (p.y >= westHandleTop && p.y <= (westHandleTop + size)) {
- return WestHandle;
- }
- }
- }
- } else if (p.y >= NSMaxY(bbox)) {
- if (p.y <= NSMaxY(bbox) + size) {
- float southHandleLeft = tbHandleLeft(bbox);
- if (p.x >= southHandleLeft && p.x <= (southHandleLeft + size)) {
- return SouthHandle;
- }
+- (void) setNode:(Node*)node highlighted:(BOOL)h {
+ if (h) {
+ if (![highlightedNodes containsObject:node]) {
+ [highlightedNodes addObject:node];
+ [self invalidateNode:node];
}
- } else if (p.y <= NSMinY(bbox)) {
- if (p.y >= NSMinY(bbox) - size) {
- float northHandleLeft = tbHandleLeft(bbox);
- if (p.x >= northHandleLeft && p.x <= (northHandleLeft + size)) {
- return NorthHandle;
- }
+ } else {
+ if ([highlightedNodes containsObject:node]) {
+ [highlightedNodes removeObject:node];
+ [self invalidateNode:node];
}
}
- return NoHandle;
}
-
-- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle {
- if (![[self graph] hasBoundingBox]) {
- return NSZeroRect;
- }
- NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]];
- switch (handle) {
- case EastHandle:
- return NSMakeRect(NSMaxX(bbox), sideHandleTop(bbox), size, size);
- case SouthEastHandle:
- return NSMakeRect(NSMaxX(bbox), NSMaxY(bbox), size, size);
- case SouthHandle:
- return NSMakeRect(tbHandleLeft(bbox), NSMaxY(bbox), size, size);
- case SouthWestHandle:
- return NSMakeRect(NSMaxX(bbox), NSMinY(bbox) - size, size, size);
- case WestHandle:
- return NSMakeRect(NSMinX(bbox) - size, sideHandleTop(bbox), size, size);
- case NorthWestHandle:
- return NSMakeRect(NSMinX(bbox) - size, NSMinY(bbox) - size, size, size);
- case NorthHandle:
- return NSMakeRect(tbHandleLeft(bbox), NSMinY(bbox) - size, size, size);
- case NorthEastHandle:
- return NSMakeRect(NSMinX(bbox) - size, NSMaxY(bbox), size, size);
- default:
- return NSZeroRect;
- }
+- (void) clearHighlightedNodes {
+ [self invalidateNodes:highlightedNodes];
+ [highlightedNodes removeAllObjects];
}
@end
@implementation GraphRenderer (Private)
-- (BOOL) selectionBoxContainsNode:(Node*)node {
- return !NSIsEmptyRect (selectionBox)
- && NSPointInRect([[surface transformer] toScreen:[node point]], selectionBox);
-}
-- (BOOL) halfEdgeIncludesNode:(Node*)node {
- if (halfEdgeOrigin == nil) {
- return FALSE;
- }
- return halfEdgeOrigin == node || [self point:halfEdgeEnd hitsNode:node];
-}
- (enum NodeState) nodeState:(Node*)node {
if ([doc isNodeSelected:node]) {
return NodeSelected;
- } else if ([self selectionBoxContainsNode:node] || [self halfEdgeIncludesNode:node]) {
+ } else if ([self isNodeHighlighted:node]) {
return NodeHighlighted;
} else {
return NodeNormal;
@@ -468,54 +335,10 @@ void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event);
[context rect:bbox];
[context strokePathWithColor:MakeSolidRColor (1.0, 0.7, 0.5)];
- if ([self boundingBoxHandlesShown]) {
- [context startPath];
- [context rect:[self boundingBoxResizeHandleRect:EastHandle]];
- [context rect:[self boundingBoxResizeHandleRect:SouthEastHandle]];
- [context rect:[self boundingBoxResizeHandleRect:SouthHandle]];
- [context rect:[self boundingBoxResizeHandleRect:SouthWestHandle]];
- [context rect:[self boundingBoxResizeHandleRect:WestHandle]];
- [context rect:[self boundingBoxResizeHandleRect:NorthWestHandle]];
- [context rect:[self boundingBoxResizeHandleRect:NorthHandle]];
- [context rect:[self boundingBoxResizeHandleRect:NorthEastHandle]];
- [context strokePathWithColor:MakeSolidRColor (0.5, 0.5, 0.5)];
- }
-
- [context restoreState];
- }
-}
-
-- (void) renderSelectionBoxWithContext:(id<RenderContext>)context {
- if (!NSIsEmptyRect (selectionBox)) {
- [context saveState];
-
- [context setAntialiasMode:AntialiasDisabled];
- [context setLineWidth:1.0];
- [context startPath];
- [context rect:selectionBox];
- RColor fColor = MakeRColor (0.8, 0.8, 0.8, 0.2);
- RColor sColor = MakeSolidRColor (0.6, 0.6, 0.6);
- [context strokePathWithColor:sColor andFillWithColor:fColor];
-
[context restoreState];
}
}
-- (void) renderImpendingEdgeWithContext:(id<RenderContext>)context {
- if (halfEdgeOrigin == nil) {
- return;
- }
- [context saveState];
-
- [context setLineWidth:1.0];
- [context startPath];
- [context moveTo:halfEdgeOriginPoint];
- [context lineTo:halfEdgeEnd];
- [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)];
-
- [context restoreState];
-}
-
- (void) nodeNeedsRefreshing:(NSNotification*)notification {
[self invalidateNode:[[notification userInfo] objectForKey:@"node"]];
}
diff --git a/tikzit/src/gtk/HandTool.h b/tikzit/src/gtk/HandTool.h
new file mode 100644
index 0000000..c96de36
--- /dev/null
+++ b/tikzit/src/gtk/HandTool.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "Tool.h"
+
+@interface HandTool : NSObject <Tool> {
+ GraphRenderer *renderer;
+ NSPoint dragOrigin;
+ NSPoint oldGraphOrigin;
+}
+
+
++ (id) tool;
+- (id) init;
+@end
+
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/HandTool.m b/tikzit/src/gtk/HandTool.m
new file mode 100644
index 0000000..c3a0fb4
--- /dev/null
+++ b/tikzit/src/gtk/HandTool.m
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "HandTool.h"
+
+#import "GraphRenderer.h"
+#import "TikzDocument.h"
+#import "tzstockitems.h"
+
+@implementation HandTool
+- (NSString*) name { return @"Drag"; }
+- (const gchar*) stockId { return TIKZIT_STOCK_DRAG; }
+- (NSString*) helpText { return @"Move the diagram to view different parts"; }
+- (NSString*) shortcut { return @"m"; }
+@synthesize activeRenderer=renderer;
+
++ (id) tool {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id) init {
+ return [super init];
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [renderer release];
+
+ [super dealloc];
+}
+
+- (GtkWidget*) configurationWidget { return NULL; }
+
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ dragOrigin = pos;
+ oldGraphOrigin = [[renderer transformer] origin];
+}
+
+- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
+ if (!(buttons & LeftButton))
+ return;
+
+ NSPoint newGraphOrigin = oldGraphOrigin;
+ newGraphOrigin.x += pos.x - dragOrigin.x;
+ newGraphOrigin.y += pos.y - dragOrigin.y;
+ [[renderer transformer] setOrigin:newGraphOrigin];
+ [renderer invalidateGraph];
+}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {}
+
+- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {}
+- (void) loadConfiguration:(Configuration*)config {}
+- (void) saveConfiguration:(Configuration*)config {}
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/InputDelegate.h b/tikzit/src/gtk/InputDelegate.h
index 1e35223..9f9b426 100644
--- a/tikzit/src/gtk/InputDelegate.h
+++ b/tikzit/src/gtk/InputDelegate.h
@@ -38,7 +38,8 @@ typedef enum {
ScrollRight = 4,
} ScrollDirection;
-@interface NSObject (InputDelegate)
+@protocol InputDelegate <NSObject>
+@optional
/**
* A mouse button was pressed.
*/
diff --git a/tikzit/src/gtk/Menu.h b/tikzit/src/gtk/Menu.h
index 5a364e4..e0f78d4 100644
--- a/tikzit/src/gtk/Menu.h
+++ b/tikzit/src/gtk/Menu.h
@@ -1,10 +1,6 @@
/*
* Copyright 2011 Alex Merry <alex.merry@kdemail.net>
*
- * Stuff stolen from glade-window.c in Glade:
- * Copyright (C) 2001 Ximian, Inc.
- * Copyright (C) 2007 Vincent Geddes.
- *
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
@@ -22,21 +18,19 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
-@class MainWindow;
+@class Window;
@class PickSupport;
/**
- * Manages the menu and toolbar for the main window.
+ * Manages the menu
*/
@interface Menu: NSObject {
- MainWindow *mainWindow;
- GtkUIManager *ui;
- GtkActionGroup *staticActions;
- GtkActionGroup *documentActions;
-// GtkActionGroup *documents_list_menu_actions;
- GtkAction *undoAction;
- GtkAction *redoAction;
- GtkAction *pasteAction;
+ GtkWidget *menubar;
+ GtkActionGroup *appActions;
+ GtkActionGroup *windowActions;
+ GtkAction *undoAction; // no ref
+ GtkAction *redoAction; // no ref
+ GtkAction *pasteAction; // no ref
GtkAction **nodeSelBasedActions;
guint nodeSelBasedActionCount;
GtkAction **edgeSelBasedActions;
@@ -46,25 +40,16 @@
}
/**
- * 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
- */
-- (GtkWidget*) menubar;
-/**
- * The toolbar widget, to be inserted into the main window
- */
-- (GtkWidget*) toolbar;
-/**
- * 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..a741520 100644
--- a/tikzit/src/gtk/Menu.m
+++ b/tikzit/src/gtk/Menu.m
@@ -21,11 +21,12 @@
#import "Menu.h"
-#import "MainWindow.h"
-#import "GraphInputHandler.h"
+#import "Application.h"
+#import "Window.h"
#import "Configuration.h"
#import "PickSupport.h"
#import "Shape.h"
+#import "Tool.h"
#import "TikzDocument.h"
#import <glib.h>
@@ -37,63 +38,48 @@
#import "gtkhelpers.h"
-#define ACTION_GROUP_STATIC "TZStatic"
-#define ACTION_GROUP_DOCUMENT "TZDocument"
-#define ACTION_GROUP_DOCUMENTS_LIST_MENU "TZDocumentsList"
-
#import "logo.h"
-#include <gdk-pixbuf/gdk-pixdata.h>
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wpointer-sign"
-#import "icondata.m"
-#pragma GCC diagnostic pop
-
-// {{{ Callbacks
-
-static void new_cb (GtkAction *action, MainWindow *window) {
+// {{{ Application actions
+static void new_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window loadEmptyDocument];
+ [appl newWindow];
[pool drain];
}
-static void open_cb (GtkAction *action, MainWindow *window) {
+static void refresh_shapes_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window openFile];
- [pool drain];
-}
-
-static void save_cb (GtkAction *action, MainWindow *window) {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window saveActiveDocument];
+ [Shape refreshShapeDictionary];
[pool drain];
}
-static void save_as_cb (GtkAction *action, MainWindow *window) {
+#ifdef HAVE_POPPLER
+static void show_preferences_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window saveActiveDocumentAs];
+ [appl showSettingsDialog];
[pool drain];
}
-static void save_as_shape_cb (GtkAction *action, MainWindow *window) {
+static void show_preamble_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window saveActiveDocumentAsShape];
+ [appl showPreamblesEditor];
[pool drain];
}
+#endif
-static void refresh_shapes_cb (GtkAction *action, MainWindow *window) {
+static void show_context_window_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [Shape refreshShapeDictionary];
+ [appl showContextWindow];
[pool drain];
}
-static void quit_cb (GtkAction *action, MainWindow *window) {
+static void quit_cb (GtkAction *action, Application *appl) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window quit];
+ [appl quit];
[pool drain];
}
-static void help_cb (GtkAction *action, MainWindow *window) {
+static void help_cb (GtkAction *action, Application *appl) {
GError *gerror = NULL;
gtk_show_uri (NULL, "http://tikzit.sourceforge.net/manual.html", GDK_CURRENT_TIME, &gerror);
if (gerror != NULL) {
@@ -103,7 +89,7 @@ static void help_cb (GtkAction *action, MainWindow *window) {
}
}
-static void about_cb (GtkAction *action, MainWindow *window) {
+static void about_cb (GtkAction *action, Application *appl) {
static const gchar * const authors[] =
{ "Aleks Kissinger <aleks0@gmail.com>",
"Chris Heunen <chrisheunen@gmail.com>",
@@ -130,7 +116,7 @@ static void about_cb (GtkAction *action, MainWindow *window) {
"Copyright \xc2\xa9 2010-2011 Aleks Kissinger, Chris Heunen and Alex Merry.";
GdkPixbuf *logo = get_logo (LOGO_SIZE_128);
- gtk_show_about_dialog (GTK_WINDOW ([window gtkWindow]),
+ gtk_show_about_dialog (NULL,
"program-name", PACKAGE_NAME,
"logo", logo,
"authors", authors,
@@ -145,10 +131,83 @@ static void about_cb (GtkAction *action, MainWindow *window) {
g_object_unref (logo);
}
-static void undo_cb (GtkAction *action, MainWindow *window) {
+static GtkActionEntry app_action_entries[] = {
+ /*
+ Fields:
+ * action name
+ * stock id or name of icon for action
+ * label for action (mark for translation with N_)
+ * accelerator (as understood by gtk_accelerator_parse())
+ * tooltip (mark for translation with N_)
+ * callback
+ */
+ { "New", GTK_STOCK_NEW, NULL, "<control>N",
+ N_("Create a new graph"), G_CALLBACK (new_cb) },
+
+ { "RefreshShapes", NULL, N_("_Refresh shapes"), NULL,
+ N_(""), G_CALLBACK (refresh_shapes_cb) },
+
+ { "Quit", GTK_STOCK_QUIT, NULL, "<control>Q",
+ N_("Quit the program"), G_CALLBACK (quit_cb) },
+
+ { "Tool", NULL, N_("_Tool") },
+
+#ifdef HAVE_POPPLER
+ { "ShowPreferences", GTK_STOCK_PREFERENCES, NULL, NULL,
+ N_("Edit the TikZiT preferences"), G_CALLBACK (show_preferences_cb) },
+
+ { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL,
+ N_("Edit the preambles used to generate the preview"), G_CALLBACK (show_preamble_cb) },
+#endif
+
+ { "ShowContextWindow", NULL, N_("_Context Window"), NULL,
+ N_("Show the contextual tools window"), G_CALLBACK (show_context_window_cb) },
+
+ /* HelpMenu */
+ { "HelpManual", GTK_STOCK_HELP, N_("_Online manual"), "F1",
+ N_("TikZiT manual (online)"), G_CALLBACK (help_cb) },
+
+ { "About", GTK_STOCK_ABOUT, NULL, NULL,
+ N_("About this application"), G_CALLBACK (about_cb) },
+};
+static guint n_app_action_entries = G_N_ELEMENTS (app_action_entries);
+// }}}
+// {{{ Window actions
+
+static void open_cb (GtkAction *action, Window *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [window openFile];
+ [pool drain];
+}
+
+static void close_cb (GtkAction *action, Window *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [window close];
+ [pool drain];
+}
+
+static void save_cb (GtkAction *action, Window *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [window saveActiveDocument];
+ [pool drain];
+}
+
+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, Window *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [window saveActiveDocumentAsShape];
+ [pool drain];
+}
+
+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 +218,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,143 +232,114 @@ 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_preview_cb (GtkAction *action, Window *window) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window showSettingsDialog];
- [pool drain];
-}
-
-static void show_preamble_cb (GtkAction *action, MainWindow *window) {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [window editPreambles];
- [pool drain];
-}
-
-static void show_preview_cb (GtkAction *action, MainWindow *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) {
- 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];
-
- gint value = gtk_radio_action_get_current_value (action);
- gtk_toolbar_set_style (GTK_TOOLBAR ([menu toolbar]), (GtkToolbarStyle)value);
- [[[menu mainWindow] mainConfiguration] setIntegerEntry:@"toolbarStyle" inGroup:@"UI" value:value];
-
- [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 +354,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);
@@ -339,117 +363,7 @@ static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, MainWin
}
-
-// }}}
-// {{{ UI XML
-
-static const gchar ui_info[] =
-"<ui>"
-" <menubar name='MenuBar'>"
-" <menu action='FileMenu'>"
-" <menuitem action='New'/>"
-" <menuitem action='Open'/>"
-" <menuitem action='OpenRecent'/>"
-" <separator/>"
-" <menuitem action='Save'/>"
-" <menuitem action='SaveAs'/>"
-" <separator/>"
-" <menuitem action='SaveAsShape'/>"
-" <menuitem action='RefreshShapes'/>"
-" <separator/>"
-//" <menuitem action='Close'/>"
-" <menuitem action='Quit'/>"
-" </menu>"
-" <menu action='EditMenu'>"
-" <menu action='Tool'>"
-" <menuitem action='SelectMode'/>"
-" <menuitem action='CreateNodeMode'/>"
-" <menuitem action='DrawEdgeMode'/>"
-" <menuitem action='BoundingBoxMode'/>"
-" <menuitem action='HandMode'/>"
-" </menu>"
-" <separator/>"
-" <menuitem action='Undo'/>"
-" <menuitem action='Redo'/>"
-" <separator/>"
-" <menuitem action='Cut'/>"
-" <menuitem action='Copy'/>"
-" <menuitem action='Paste'/>"
-" <menuitem action='Delete'/>"
-" <separator/>"
-" <menuitem action='SelectAll'/>"
-" <menuitem action='DeselectAll'/>"
-" <separator/>"
-" <menuitem action='FlipVert'/>"
-" <menuitem action='FlipHoriz'/>"
-" <menuitem action='ReverseEdges'/>"
-" <separator/>"
-" <menu action='Arrange'>"
-" <menuitem action='SendToBack'/>"
-" <menuitem action='SendBackward'/>"
-" <menuitem action='BringForward'/>"
-" <menuitem action='BringToFront'/>"
-" </menu>"
-#ifdef HAVE_POPPLER
-" <separator/>"
-" <menuitem action='ShowPreferences'/>"
-#endif
-" </menu>"
-" <menu action='ViewMenu'>"
-" <menu action='ToolbarStyle'>"
-" <menuitem action='ToolbarIconsOnly'/>"
-" <menuitem action='ToolbarTextOnly'/>"
-" <menuitem action='ToolbarTextIcons'/>"
-" <menuitem action='ToolbarTextIconsHoriz'/>"
-" </menu>"
-/*
-" <menuitem action='ToolbarVisible'/>"
-" <menuitem action='StatusbarVisible'/>"
-*/
-#ifdef HAVE_POPPLER
-" <menuitem action='ShowPreamble'/>"
-" <menuitem action='ShowPreview'/>"
-#endif
-" <menu action='Zoom'>"
-" <menuitem action='ZoomIn'/>"
-" <menuitem action='ZoomOut'/>"
-" <menuitem action='ZoomReset'/>"
-" </menu>"
-" </menu>"
-/*
-" <menu action='Window'>"
-" <placeholder name='DocumentsListPlaceholder'/>"
-" </menu>"
-*/
-" <menu action='HelpMenu'>"
-" <menuitem action='HelpManual'/>"
-" <separator/>"
-" <menuitem action='About'/>"
-" </menu>"
-" </menubar>"
-" <toolbar name='ToolBar'>"
-" <toolitem action='New'/>"
-" <toolitem action='Open'/>"
-" <toolitem action='Save'/>"
-" <separator/>"
-" <toolitem action='Cut'/>"
-" <toolitem action='Copy'/>"
-" <toolitem action='Paste'/>"
-" <separator/>"
-" <toolitem action='SelectMode'/>"
-" <toolitem action='CreateNodeMode'/>"
-" <toolitem action='DrawEdgeMode'/>"
-" <toolitem action='BoundingBoxMode'/>"
-" <toolitem action='HandMode'/>"
-" </toolbar>"
-"</ui>";
-
-
-
-// }}}
-// {{{ Actions
-
-static GtkActionEntry static_entries[] = {
+static GtkActionEntry window_action_entries[] = {
/*
Fields:
* action name
@@ -462,45 +376,16 @@ static GtkActionEntry static_entries[] = {
{ "FileMenu", NULL, N_("_File") },
{ "EditMenu", NULL, N_("_Edit") },
{ "ViewMenu", NULL, N_("_View") },
- //{ "ProjectMenu", NULL, N_("_Projects") },
{ "HelpMenu", NULL, N_("_Help") },
- //{ "UndoMenu", NULL, NULL },
- //{ "RedoMenu", NULL, NULL },
- /* FileMenu */
- { "New", GTK_STOCK_NEW, NULL, "<control>N",
- N_("Create a new graph"), G_CALLBACK (new_cb) },
+ { "Arrange", NULL, N_("_Arrange") },
+ { "Zoom", NULL, N_("_Zoom") },
{ "Open", GTK_STOCK_OPEN, N_("_Open\342\200\246") ,"<control>O",
N_("Open a graph"), G_CALLBACK (open_cb) },
- { "OpenRecent", NULL, N_("Open _Recent") },
-
- { "RefreshShapes", NULL, N_("_Refresh shapes"), NULL,
- N_(""), G_CALLBACK (refresh_shapes_cb) },
-
- { "Quit", GTK_STOCK_QUIT, NULL, "<control>Q",
- N_("Quit the program"), G_CALLBACK (quit_cb) },
-
- /* EditMenu */
- { "Tool", NULL, N_("_Tool") },
-
- { "Arrange", NULL, N_("_Arrange") },
-
-#ifdef HAVE_POPPLER
- { "ShowPreferences", GTK_STOCK_PREFERENCES, NULL, NULL,
- N_("Edit the TikZiT preferences"), G_CALLBACK (show_preferences_cb) },
-#endif
-
- /* ViewMenu */
- { "ToolbarStyle", NULL, N_("_Toolbar style") },
-
-#ifdef HAVE_POPPLER
- { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL,
- N_("Edit the preambles used to generate the preview"), G_CALLBACK (show_preamble_cb) },
-#endif
-
- { "Zoom", NULL, N_("_Zoom") },
+ { "Close", GTK_STOCK_CLOSE, NULL, "<control>W",
+ N_("Close the current graph"), G_CALLBACK (close_cb) },
{ "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, "<control>plus",
NULL, G_CALLBACK (zoom_in_cb) },
@@ -511,19 +396,6 @@ static GtkActionEntry static_entries[] = {
{ "ZoomReset", GTK_STOCK_ZOOM_100, N_("_Reset zoom"), "<control>0",
NULL, G_CALLBACK (zoom_reset_cb) },
- /* HelpMenu */
- { "HelpManual", GTK_STOCK_HELP, N_("_Online manual"), "F1",
- N_("TikZiT manual (online)"), G_CALLBACK (help_cb) },
-
- { "About", GTK_STOCK_ABOUT, NULL, NULL,
- N_("About this application"), G_CALLBACK (about_cb) },
-};
-
-static guint n_static_entries = G_N_ELEMENTS (static_entries);
-
-static GtkActionEntry document_entries[] = {
-
- /* FileMenu */
{ "Save", GTK_STOCK_SAVE, NULL, "<control>S",
N_("Save the current graph"), G_CALLBACK (save_cb) },
@@ -533,12 +405,6 @@ static GtkActionEntry document_entries[] = {
{ "SaveAsShape", NULL, N_("Save As S_hape\342\200\246"), NULL,
N_("Save the current graph as a shape for use in styles"), G_CALLBACK (save_as_shape_cb) },
-/*
- { "Close", GTK_STOCK_CLOSE, NULL, "<control>W",
- N_("Close the current graph"), G_CALLBACK (close_cb) },
-*/
-
- /* EditMenu */
{ "Undo", GTK_STOCK_UNDO, NULL, "<control>Z",
N_("Undo the last action"), G_CALLBACK (undo_cb) },
@@ -590,101 +456,115 @@ static GtkActionEntry document_entries[] = {
N_("See the graph as it will look when rendered in LaTeX"), G_CALLBACK (show_preview_cb) },
#endif
};
-static guint n_document_entries = G_N_ELEMENTS (document_entries);
-
-static GtkRadioActionEntry mode_entries[] = {
- /*
- Fields:
- * action name
- * stock id or name of icon for action
- * label for action (mark for translation with N_)
- * accelerator (as understood by gtk_accelerator_parse())
- * tooltip (mark for translation with N_)
- * value (see gtk_radio_action_get_current_value())
- */
-
- { "SelectMode", NULL, N_("_Select"), "<control><shift>s",
- N_("Select, move and edit nodes and edges"), (gint)SelectMode },
-
- { "CreateNodeMode", NULL, N_("_Create nodes"), "<control><shift>c",
- N_("Create new nodes"), (gint)CreateNodeMode },
-
- { "DrawEdgeMode", NULL, N_("_Draw edges"), "<control><shift>e",
- N_("Draw new edges"), (gint)DrawEdgeMode },
-
- { "BoundingBoxMode", NULL, N_("_Bounding box"), "<control><shift>x",
- N_("Set the bounding box"), (gint)BoundingBoxMode },
-
- { "HandMode", NULL, N_("_Pan"), "<control><shift>f",
- N_("Move the diagram to view different parts"), (gint)HandMode },
-};
-static guint n_mode_entries = G_N_ELEMENTS (mode_entries);
+static guint n_window_action_entries = G_N_ELEMENTS (window_action_entries);
-static GtkRadioActionEntry toolbar_style_entries[] = {
- /*
- Fields:
- * action name
- * stock id or name of icon for action
- * label for action (mark for translation with N_)
- * accelerator (as understood by gtk_accelerator_parse())
- * tooltip (mark for translation with N_)
- * value (see gtk_radio_action_get_current_value())
- */
-
- { "ToolbarIconsOnly", NULL, N_("_Icons only"), NULL,
- N_("Show only icons on the toolbar"), (gint)GTK_TOOLBAR_ICONS },
+// }}}
+// {{{ UI XML
- { "ToolbarTextOnly", NULL, N_("_Text only"), NULL,
- N_("Show only text on the toolbar"), (gint)GTK_TOOLBAR_TEXT },
+static const gchar ui_info[] =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu action='FileMenu'>"
+" <menuitem action='New'/>"
+" <menuitem action='Open'/>"
+" <menuitem action='OpenRecent'/>"
+" <separator/>"
+" <menuitem action='Save'/>"
+" <menuitem action='SaveAs'/>"
+" <separator/>"
+" <menuitem action='SaveAsShape'/>"
+" <menuitem action='RefreshShapes'/>"
+" <separator/>"
+" <menuitem action='Close'/>"
+" <menuitem action='Quit'/>"
+" </menu>"
+" <menu action='EditMenu'>"
+" <menu action='Tool'>"
+" </menu>"
+" <separator/>"
+" <menuitem action='Undo'/>"
+" <menuitem action='Redo'/>"
+" <separator/>"
+" <menuitem action='Cut'/>"
+" <menuitem action='Copy'/>"
+" <menuitem action='Paste'/>"
+" <menuitem action='Delete'/>"
+" <separator/>"
+" <menuitem action='SelectAll'/>"
+" <menuitem action='DeselectAll'/>"
+" <separator/>"
+" <menuitem action='FlipVert'/>"
+" <menuitem action='FlipHoriz'/>"
+" <menuitem action='ReverseEdges'/>"
+" <separator/>"
+" <menu action='Arrange'>"
+" <menuitem action='SendToBack'/>"
+" <menuitem action='SendBackward'/>"
+" <menuitem action='BringForward'/>"
+" <menuitem action='BringToFront'/>"
+" </menu>"
+#ifdef HAVE_POPPLER
+" <separator/>"
+" <menuitem action='ShowPreferences'/>"
+#endif
+" </menu>"
+" <menu action='ViewMenu'>"
+" <menuitem action='ShowContextWindow'/>"
+#ifdef HAVE_POPPLER
+" <menuitem action='ShowPreamble'/>"
+" <menuitem action='ShowPreview'/>"
+#endif
+" <menu action='Zoom'>"
+" <menuitem action='ZoomIn'/>"
+" <menuitem action='ZoomOut'/>"
+" <menuitem action='ZoomReset'/>"
+" </menu>"
+" </menu>"
+" <menu action='HelpMenu'>"
+" <menuitem action='HelpManual'/>"
+" <separator/>"
+" <menuitem action='About'/>"
+" </menu>"
+" </menubar>"
+/*
+" <toolbar name='ToolBar'>"
+" <toolitem action='New'/>"
+" <toolitem action='Open'/>"
+" <toolitem action='Save'/>"
+" <separator/>"
+" <toolitem action='Cut'/>"
+" <toolitem action='Copy'/>"
+" <toolitem action='Paste'/>"
+" <separator/>"
+" <toolitem action='SelectMode'/>"
+" <toolitem action='CreateNodeMode'/>"
+" <toolitem action='DrawEdgeMode'/>"
+" <toolitem action='BoundingBoxMode'/>"
+" <toolitem action='HandMode'/>"
+" </toolbar>"
+*/
+"</ui>";
- { "ToolbarTextIcons", NULL, N_("Text _below icons"), NULL,
- N_("Show icons on the toolbar with text below"), (gint)GTK_TOOLBAR_BOTH },
- { "ToolbarTextIconsHoriz", NULL, N_("Text be_side icons"), NULL,
- N_("Show icons on the toolbar with text beside"), (gint)GTK_TOOLBAR_BOTH_HORIZ },
-};
-static guint n_toolbar_style_entries = G_N_ELEMENTS (toolbar_style_entries);
// }}}
// {{{ Helper methods
-
-static void
-set_tool_button_image (GtkToolButton *button, const GdkPixdata *image_data)
-{
- GtkWidget *image = NULL;
-
- if (image_data) {
- GdkPixbuf *buf = gdk_pixbuf_from_pixdata (image_data, FALSE, NULL);
- image = gtk_image_new_from_pixbuf (buf);
- g_object_unref (buf);
- }
-
- gtk_tool_button_set_icon_widget (button, image);
-
- if (image) {
- gtk_widget_show (image);
- }
-}
-
-GtkWidget *
-create_recent_chooser_menu ()
+static void configure_recent_chooser (GtkRecentChooser *chooser)
{
- GtkWidget *recent_menu;
- GtkRecentFilter *filter;
+ gtk_recent_chooser_set_local_only (chooser, TRUE);
+ gtk_recent_chooser_set_show_icons (chooser, FALSE);
+ gtk_recent_chooser_set_sort_type (chooser, GTK_RECENT_SORT_MRU);
- recent_menu = gtk_recent_chooser_menu_new_for_manager (gtk_recent_manager_get_default ());
-
- gtk_recent_chooser_set_local_only (GTK_RECENT_CHOOSER (recent_menu), TRUE);
- gtk_recent_chooser_set_show_icons (GTK_RECENT_CHOOSER (recent_menu), FALSE);
- gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (recent_menu), GTK_RECENT_SORT_MRU);
- gtk_recent_chooser_menu_set_show_numbers (GTK_RECENT_CHOOSER_MENU (recent_menu), TRUE);
-
- filter = gtk_recent_filter_new ();
+ GtkRecentFilter *filter = gtk_recent_filter_new ();
gtk_recent_filter_add_application (filter, g_get_application_name());
- gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (recent_menu), filter);
+ gtk_recent_chooser_set_filter (chooser, filter);
+}
- return recent_menu;
+static void tool_cb (GtkAction *action, id<Tool> tool) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [app setActiveTool:tool];
+ [pool drain];
}
@@ -694,117 +574,122 @@ create_recent_chooser_menu ()
@implementation Menu
-- (id) initForMainWindow:(MainWindow*)window
-{
+- (id) init {
+ [self release];
+ self = nil;
+ return nil;
+}
+
+- (id) initForWindow:(Window*)window {
self = [super init];
if (!self) {
return nil;
}
- mainWindow = window;
-
GError *error = NULL;
- staticActions = gtk_action_group_new (ACTION_GROUP_STATIC);
- //gtk_action_group_set_translation_domain (staticActions, GETTEXT_PACKAGE);
+ appActions = gtk_action_group_new ("TZApp");
+ //gtk_action_group_set_translation_domain (actions, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (appActions,
+ app_action_entries,
+ n_app_action_entries,
+ app);
+ for (id<Tool> tool in [app tools]) {
+ NSString *tooltip = [NSString stringWithFormat:
+ @"%@: %@ (%@)", [tool name], [tool helpText], [tool shortcut]];
+ GtkAction *action = gtk_action_new (
+ [[tool name] UTF8String],
+ [[tool name] UTF8String],
+ [tooltip UTF8String],
+ [tool stockId]);
+ gtk_action_group_add_action_with_accel (
+ appActions,
+ action,
+ NULL);
+ g_signal_connect (
+ G_OBJECT (action),
+ "activate",
+ G_CALLBACK (tool_cb),
+ tool);
+ g_object_unref (action);
+ }
- gtk_action_group_add_actions (staticActions,
- static_entries,
- n_static_entries,
- window);
- 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);
-
- gtk_action_group_add_actions (documentActions,
- document_entries,
- n_document_entries,
+ windowActions = gtk_action_group_new ("TZWindow");
+ //gtk_action_group_set_translation_domain (windowActions, GETTEXT_PACKAGE);
+
+ gtk_action_group_add_actions (windowActions,
+ window_action_entries,
+ n_window_action_entries,
window);
- /*
- documents_list_menu_actions =
- gtk_action_group_new (ACTION_GROUP_DOCUMENTS_LIST_MENU);
- gtk_action_group_set_translation_domain (documents_list_menu_actions,
- GETTEXT_PACKAGE);
- */
+ GtkAction *action = gtk_recent_action_new ("OpenRecent", N_("Open _Recent"), NULL, NULL);
+ g_signal_connect (G_OBJECT (action),
+ "item-activated",
+ G_CALLBACK (recent_chooser_item_activated_cb),
+ window);
+ configure_recent_chooser (GTK_RECENT_CHOOSER (action));
+ gtk_action_group_add_action_with_accel (windowActions, action, NULL);
+ g_object_unref (action);
- ui = gtk_ui_manager_new ();
+ /* Save refs to actions that will need to be updated */
+ undoAction = gtk_action_group_get_action (windowActions, "Undo");
+ redoAction = gtk_action_group_get_action (windowActions, "Redo");
+ pasteAction = gtk_action_group_get_action (windowActions, "Paste");
- gtk_ui_manager_insert_action_group (ui, staticActions, 0);
- gtk_ui_manager_insert_action_group (ui, documentActions, 1);
- //gtk_ui_manager_insert_action_group (ui, documents_list_menu_actions, 3);
+ nodeSelBasedActionCount = 4;
+ nodeSelBasedActions = g_new (GtkAction*, nodeSelBasedActionCount);
+ nodeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "Cut");
+ nodeSelBasedActions[1] = gtk_action_group_get_action (windowActions, "Copy");
+ nodeSelBasedActions[2] = gtk_action_group_get_action (windowActions, "FlipHoriz");
+ nodeSelBasedActions[3] = gtk_action_group_get_action (windowActions, "FlipVert");
+ edgeSelBasedActionCount = 1;
+ edgeSelBasedActions = g_new (GtkAction*, edgeSelBasedActionCount);
+ edgeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "ReverseEdges");
+ selBasedActionCount = 2;
+ selBasedActions = g_new (GtkAction*, selBasedActionCount);
+ selBasedActions[0] = gtk_action_group_get_action (windowActions, "Delete");
+ selBasedActions[1] = gtk_action_group_get_action (windowActions, "DeselectAll");
- gtk_window_add_accel_group ([window gtkWindow], gtk_ui_manager_get_accel_group (ui));
+ GtkUIManager *ui = gtk_ui_manager_new ();
+ gtk_ui_manager_insert_action_group (ui, windowActions, 0);
+ gtk_ui_manager_insert_action_group (ui, appActions, 1);
+ gtk_window_add_accel_group ([window gtkWindow], gtk_ui_manager_get_accel_group (ui));
if (!gtk_ui_manager_add_ui_from_string (ui, ui_info, -1, &error))
{
g_message ("Building menus failed: %s", error->message);
g_error_free (error);
- return NULL;
+ g_object_unref (ui);
+ [self release];
+ return nil;
}
-
- /* 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");
- redoAction = gtk_action_group_get_action (documentActions, "Redo");
- pasteAction = gtk_action_group_get_action (documentActions, "Paste");
-
- /* Recent items */
- GtkWidget *recentMenu = create_recent_chooser_menu();
- GtkMenuItem *recentMenuItem = GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/OpenRecent"));
- gtk_menu_item_set_submenu (recentMenuItem, recentMenu);
- g_signal_connect (recentMenu, "item-activated", G_CALLBACK (recent_chooser_item_activated_cb), window);
-
- nodeSelBasedActionCount = 4;
- nodeSelBasedActions = g_new (GtkAction*, nodeSelBasedActionCount);
- nodeSelBasedActions[0] = gtk_action_group_get_action (documentActions, "Cut");
- nodeSelBasedActions[1] = gtk_action_group_get_action (documentActions, "Copy");
- nodeSelBasedActions[2] = gtk_action_group_get_action (documentActions, "FlipHoriz");
- nodeSelBasedActions[3] = gtk_action_group_get_action (documentActions, "FlipVert");
- edgeSelBasedActionCount = 1;
- edgeSelBasedActions = g_new (GtkAction*, edgeSelBasedActionCount);
- edgeSelBasedActions[0] = gtk_action_group_get_action (documentActions, "ReverseEdges");
- selBasedActionCount = 2;
- selBasedActions = g_new (GtkAction*, selBasedActionCount);
- selBasedActions[0] = gtk_action_group_get_action (documentActions, "Delete");
- selBasedActions[1] = gtk_action_group_get_action (documentActions, "DeselectAll");
-
- Configuration *configFile = [window 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);
+ guint tool_merge_id = gtk_ui_manager_new_merge_id (ui);
+ for (id<Tool> tool in [app tools]) {
+ gtk_ui_manager_add_ui (ui,
+ tool_merge_id,
+ "/ui/MenuBar/EditMenu/Tool",
+ [[tool name] UTF8String],
+ [[tool name] UTF8String],
+ GTK_UI_MANAGER_AUTO,
+ FALSE);
}
+ menubar = gtk_ui_manager_get_widget (ui, "/MenuBar");
+ g_object_ref_sink (menubar);
+ g_object_unref (ui);
return self;
}
-- (GtkWidget*) menubar {
- return gtk_ui_manager_get_widget (ui, "/MenuBar");
-}
+- (void) dealloc {
+ g_free (nodeSelBasedActions);
+ g_free (selBasedActions);
+ g_object_unref (menubar);
+ g_object_unref (windowActions);
-- (GtkWidget*) toolbar {
- return gtk_ui_manager_get_widget (ui, "/ToolBar");
+ [super dealloc];
}
-- (MainWindow*) mainWindow {
- return mainWindow;
-}
+@synthesize menubar;
- (void) setUndoActionEnabled:(BOOL)enabled {
gtk_action_set_sensitive (undoAction, enabled);
@@ -846,13 +731,6 @@ create_recent_chooser_menu ()
}
}
-- (void) dealloc {
- g_free (nodeSelBasedActions);
- g_free (selBasedActions);
-
- [super dealloc];
-}
-
@end
// }}}
diff --git a/tikzit/src/gtk/NodeStyleSelector.h b/tikzit/src/gtk/NodeStyleSelector.h
index 894d71e..a699dc8 100644
--- a/tikzit/src/gtk/NodeStyleSelector.h
+++ b/tikzit/src/gtk/NodeStyleSelector.h
@@ -17,14 +17,14 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
-#import "StyleManager.h"
+
+@class NodeStyle;
+@class NodeStylesModel;
+@class StyleManager;
@interface NodeStyleSelector: NSObject {
- GtkListStore *store;
+ NodeStylesModel *model;
GtkIconView *view;
- StyleManager *styleManager;
- BOOL linkedToActiveStyle;
- BOOL suppressSetActiveStyle;
}
/*!
@@ -34,35 +34,27 @@
@property (readonly) GtkWidget *widget;
/*!
- @property manager
- @brief The StyleManager to use. Default is [StyleManager manager]
- */
-@property (retain) StyleManager *styleManager;
-
-/*!
- @property linkedToActiveStyles
- @brief Whether the current selection should be the same as the
- style manager's active style
+ @property model
+ @brief The model to use.
*/
-@property (getter=isLinkedToActiveStyle) BOOL linkedToActiveStyle;
+@property (retain) NodeStylesModel *model;
/*!
@property selectedStyle
- @brief The selected style. If linkedToActiveStyle is YES, this
- will be the same as [manager activeStyle].
+ @brief The selected style.
When this changes, a SelectedStyleChanged notification will be posted
*/
@property (assign) NodeStyle *selectedStyle;
/*!
- * Initialise with the default style manager
+ * Initialise with a new model for the given style manager
*/
-- (id) init;
+- (id) initWithStyleManager:(StyleManager*)manager;
/*!
- * Initialise with the given style manager
+ * Initialise with the given model
*/
-- (id) initWithStyleManager:(StyleManager*)m;
+- (id) initWithModel:(NodeStylesModel*)model;
@end
diff --git a/tikzit/src/gtk/NodeStyleSelector.m b/tikzit/src/gtk/NodeStyleSelector.m
index a175be4..14cdc75 100644
--- a/tikzit/src/gtk/NodeStyleSelector.m
+++ b/tikzit/src/gtk/NodeStyleSelector.m
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -17,94 +17,41 @@
#import "NodeStyleSelector.h"
-#import "CairoRenderContext.h"
-#import "Shape.h"
-#import "Shape+Render.h"
-#import "ShapeNames.h"
-#import "StyleManager.h"
-
-#import <gdk-pixbuf/gdk-pixbuf.h>
+#import "NodeStylesModel.h"
// {{{ Internal interfaces
-// {{{ Signals
static void selection_changed_cb (GtkIconView *widget, NodeStyleSelector *mgr);
// }}}
-
-enum {
- STYLES_NAME_COL = 0,
- STYLES_ICON_COL,
- STYLES_PTR_COL,
- STYLES_N_COLS
-};
-
-@interface NodeStyleSelector (Notifications)
-- (void) stylesReplaced:(NSNotification*)notification;
-- (void) styleAdded:(NSNotification*)notification;
-- (void) styleRemoved:(NSNotification*)notification;
-- (void) activeStyleChanged:(NSNotification*)notification;
-- (void) shapeDictionaryReplaced:(NSNotification*)n;
-- (void) selectionChanged;
-- (void) observeValueForKeyPath:(NSString*)keyPath
- ofObject:(id)object
- change:(NSDictionary*)change
- context:(void*)context;
-@end
-
-@interface NodeStyleSelector (Private)
-- (cairo_surface_t*) createNodeIconSurface;
-- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style;
-- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface;
-- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface;
-- (void) addStyle:(NodeStyle*)style;
-- (void) postSelectedStyleChanged;
-- (void) observeStyle:(NodeStyle*)style;
-- (void) stopObservingStyle:(NodeStyle*)style;
-- (void) clearModel;
-- (void) reloadStyles;
-@end
-
-// }}}
// {{{ API
@implementation NodeStyleSelector
- (id) init {
- self = [self initWithStyleManager:[StyleManager manager]];
- return self;
+ [self release];
+ return nil;
}
- (id) initWithStyleManager:(StyleManager*)m {
+ return [self initWithModel:[NodeStylesModel modelWithStyleManager:m]];
+}
+- (id) initWithModel:(NodeStylesModel*)m {
self = [super init];
if (self) {
- styleManager = nil;
- linkedToActiveStyle = YES;
-
- store = gtk_list_store_new (STYLES_N_COLS,
- G_TYPE_STRING,
- GDK_TYPE_PIXBUF,
- G_TYPE_POINTER);
- g_object_ref (store);
+ model = [m retain];
view = GTK_ICON_VIEW (gtk_icon_view_new ());
- g_object_ref (view);
+ g_object_ref_sink (view);
- gtk_icon_view_set_model (view, GTK_TREE_MODEL (store));
- gtk_icon_view_set_pixbuf_column (view, STYLES_ICON_COL);
- gtk_icon_view_set_tooltip_column (view, STYLES_NAME_COL);
+ gtk_icon_view_set_model (view, [model model]);
+ gtk_icon_view_set_pixbuf_column (view, NODE_STYLES_ICON_COL);
+ gtk_icon_view_set_tooltip_column (view, NODE_STYLES_NAME_COL);
gtk_icon_view_set_selection_mode (view, GTK_SELECTION_SINGLE);
g_signal_connect (G_OBJECT (view),
"selection-changed",
G_CALLBACK (selection_changed_cb),
self);
-
- [self setStyleManager:m];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(shapeDictionaryReplaced:)
- name:@"ShapeDictionaryReplaced"
- object:[Shape class]];
}
return self;
@@ -112,66 +59,31 @@ enum {
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+
g_object_unref (view);
- [self clearModel];
- g_object_unref (store);
- [styleManager release];
+ [model release];
[super dealloc];
}
-- (StyleManager*) styleManager {
- return styleManager;
+- (NodeStylesModel*) model {
+ return model;
}
-- (void) setStyleManager:(StyleManager*)m {
- if (m == nil) {
- [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"];
- }
- [m retain];
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(stylesReplaced:)
- name:@"NodeStylesReplaced"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(styleAdded:)
- name:@"NodeStyleAdded"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(styleRemoved:)
- name:@"NodeStyleRemoved"
- object:m];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(activeStyleChanged:)
- name:@"ActiveNodeStyleChanged"
- object:m];
-
- [styleManager release];
- styleManager = m;
+- (void) setModel:(NodeStylesModel*)m {
+ if (m == model)
+ return;
- [self reloadStyles];
+ NodeStylesModel *oldModel = model;
+ model = [m retain];
+ gtk_icon_view_set_model (view, [model model]);
+ [oldModel release];
}
- (GtkWidget*) widget {
return GTK_WIDGET (view);
}
-- (BOOL) isLinkedToActiveStyle {
- return linkedToActiveStyle;
-}
-
-- (void) setLinkedToActiveStyle:(BOOL)linked {
- linkedToActiveStyle = linked;
- if (linkedToActiveStyle) {
- NodeStyle *style = [self selectedStyle];
- if ([styleManager activeNodeStyle] != style) {
- [self setSelectedStyle:[styleManager activeNodeStyle]];
- }
- }
-}
-
- (NodeStyle*) selectedStyle {
GList *list = gtk_icon_view_get_selected_items (view);
if (!list) {
@@ -182,10 +94,7 @@ enum {
}
GtkTreePath *path = (GtkTreePath*) list->data;
- GtkTreeIter iter;
- gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
- NodeStyle *style = nil;
- gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, STYLES_PTR_COL, &style, -1);
+ NodeStyle *style = [model styleFromPath:path];
g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
g_list_free (list);
@@ -199,284 +108,14 @@ enum {
return;
}
- GtkTreeModel *m = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (m, &row)) {
- do {
- NodeStyle *rowStyle;
- gtk_tree_model_get (m, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- gtk_icon_view_unselect_all (view);
- GtkTreePath *path = gtk_tree_model_get_path (m, &row);
- gtk_icon_view_select_path (view, path);
- gtk_tree_path_free (path);
- // styleManager.activeStyle will be updated by the GTK+ callback
- return;
- }
- } while (gtk_tree_model_iter_next (m, &row));
- }
-}
-
-@end
-
-// }}}
-// {{{ Notifications
-
-@implementation NodeStyleSelector (Notifications)
-
-- (void) stylesReplaced:(NSNotification*)notification {
- [self reloadStyles];
-}
-
-- (void) styleAdded:(NSNotification*)notification {
- [self addStyle:[[notification userInfo] objectForKey:@"style"]];
-}
-
-- (void) styleRemoved:(NSNotification*)notification {
- NodeStyle *style = [[notification userInfo] objectForKey:@"style"];
-
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- NodeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- gtk_list_store_remove (store, &row);
- [self stopObservingStyle:rowStyle];
- [rowStyle release];
- return;
- }
- } while (gtk_tree_model_iter_next (model, &row));
- }
-}
-
-- (void) activeStyleChanged:(NSNotification*)notification {
- if (linkedToActiveStyle) {
- NodeStyle *style = [self selectedStyle];
- if ([styleManager activeNodeStyle] != style) {
- [self setSelectedStyle:[styleManager activeNodeStyle]];
- }
- }
-}
-
-- (void) observeValueForKeyPath:(NSString*)keyPath
- ofObject:(id)object
- change:(NSDictionary*)change
- context:(void*)context
-{
- if ([object class] != [NodeStyle class])
- return;
-
- NodeStyle *style = object;
-
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- NodeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- if (style == rowStyle) {
- if ([@"name" isEqual:keyPath]) {
- gtk_list_store_set (store, &row, STYLES_NAME_COL, [[style name] UTF8String], -1);
- } else {
- GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style];
- gtk_list_store_set (store, &row, STYLES_ICON_COL, pixbuf, -1);
- g_object_unref (pixbuf);
- }
- }
- } while (gtk_tree_model_iter_next (model, &row));
- }
-}
-
-- (void) shapeDictionaryReplaced:(NSNotification*)n {
- [self reloadStyles];
-}
-
-- (void) selectionChanged {
- if (linkedToActiveStyle) {
- NodeStyle *style = [self selectedStyle];
- if ([styleManager activeNodeStyle] != style) {
- [styleManager setActiveNodeStyle:style];
- }
- }
- [self postSelectedStyleChanged];
-}
-@end
-
-// }}}
-// {{{ Private
-
-@implementation NodeStyleSelector (Private)
-- (cairo_surface_t*) createNodeIconSurface {
- return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 24, 24);
-}
-
-- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style {
- cairo_surface_t *surface = [self createNodeIconSurface];
- GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface];
- cairo_surface_destroy (surface);
- return pixbuf;
-}
-
-// Bring on GTK+3 and gdk_pixbuf_get_from_surface()
-- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface {
- cairo_surface_flush (surface);
-
- int width = cairo_image_surface_get_width (surface);
- int height = cairo_image_surface_get_height (surface);
- int stride = cairo_image_surface_get_stride (surface);
- unsigned char *data = cairo_image_surface_get_data (surface);
-
- GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
- TRUE,
- 8,
- width,
- height);
- unsigned char *pbdata = gdk_pixbuf_get_pixels (pixbuf);
- int pbstride = gdk_pixbuf_get_rowstride (pixbuf);
-
- for (int y = 0; y < height; ++y) {
- uint32_t *line = (uint32_t*)(data + y*stride);
- unsigned char *pbline = pbdata + (y*pbstride);
- for (int x = 0; x < width; ++x) {
- uint32_t pixel = *(line + x);
- unsigned char *pbpixel = pbline + (x*4);
- // NB: We should un-pre-mult the alpha here.
- // However, in our world, alpha is always
- // on or off, so it doesn't really matter
- pbpixel[3] = ((pixel & 0xff000000) >> 24);
- pbpixel[0] = ((pixel & 0x00ff0000) >> 16);
- pbpixel[1] = ((pixel & 0x0000ff00) >> 8);
- pbpixel[2] = (pixel & 0x000000ff);
- }
- }
-
- return pixbuf;
-}
-
-- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface {
- Shape *shape = [Shape shapeForName:[style shapeName]];
-
- int width = cairo_image_surface_get_width (surface);
- int height = cairo_image_surface_get_height (surface);
- NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height);
- const CGFloat lineWidth = [style strokeThickness];
- Transformer *shapeTrans = [Transformer transformerToFit:[shape boundingRect]
- intoScreenRect:NSInsetRect(pixbufBounds, lineWidth, lineWidth)
- flippedAboutXAxis:YES];
- if ([style scale] < 1.0)
- [shapeTrans setScale:[style scale] * [shapeTrans scale]];
-
- CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface];
- [context clearSurface];
- [shape drawPathWithTransform:shapeTrans andContext:context];
- [context setLineWidth:lineWidth];
- [context strokePathWithColor:[[style strokeColorRGB] rColor]
- andFillWithColor:[[style fillColorRGB] rColor]];
- [context release];
-
- return [self pixbufFromSurface:surface];
-}
-
-- (void) addStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface {
- GtkTreeIter iter;
- gtk_list_store_append (store, &iter);
-
- GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface];
- gtk_list_store_set (store, &iter,
- STYLES_NAME_COL, [[style name] UTF8String],
- STYLES_ICON_COL, pixbuf,
- STYLES_PTR_COL, (gpointer)[style retain],
- -1);
- g_object_unref (pixbuf);
- [self observeStyle:style];
-}
-
-- (void) addStyle:(NodeStyle*)style {
- cairo_surface_t *surface = [self createNodeIconSurface];
- [self addStyle:style usingSurface:surface];
- cairo_surface_destroy (surface);
-}
-
-- (void) postSelectedStyleChanged {
- [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectedStyleChanged" object:self];
-}
-
-- (void) observeStyle:(NodeStyle*)style {
- [style addObserver:self
- forKeyPath:@"name"
- options:NSKeyValueObservingOptionNew
- context:NULL];
- [style addObserver:self
- forKeyPath:@"strokeThickness"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"strokeColorRGB.red"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"strokeColorRGB.green"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"strokeColorRGB.blue"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"fillColorRGB.red"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"fillColorRGB.green"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"fillColorRGB.blue"
- options:0
- context:NULL];
- [style addObserver:self
- forKeyPath:@"shapeName"
- options:0
- context:NULL];
-}
-
-- (void) stopObservingStyle:(NodeStyle*)style {
- [style removeObserver:self forKeyPath:@"name"];
- [style removeObserver:self forKeyPath:@"strokeThickness"];
- [style removeObserver:self forKeyPath:@"strokeColorRGB.red"];
- [style removeObserver:self forKeyPath:@"strokeColorRGB.green"];
- [style removeObserver:self forKeyPath:@"strokeColorRGB.blue"];
- [style removeObserver:self forKeyPath:@"fillColorRGB.red"];
- [style removeObserver:self forKeyPath:@"fillColorRGB.green"];
- [style removeObserver:self forKeyPath:@"fillColorRGB.blue"];
- [style removeObserver:self forKeyPath:@"shapeName"];
-}
-
-- (void) clearModel {
- [self setSelectedStyle:nil];
- GtkTreeModel *model = GTK_TREE_MODEL (store);
- GtkTreeIter row;
- if (gtk_tree_model_get_iter_first (model, &row)) {
- do {
- NodeStyle *rowStyle;
- gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1);
- [self stopObservingStyle:rowStyle];
- [rowStyle release];
- } while (gtk_tree_model_iter_next (model, &row));
+ GtkTreePath *path = [model pathFromStyle:style];
+ if (path) {
+ gtk_icon_view_unselect_all (view);
+ gtk_icon_view_select_path (view, path);
+ gtk_tree_path_free (path);
}
- gtk_list_store_clear (store);
}
-- (void) reloadStyles {
- [self clearModel];
- cairo_surface_t *surface = [self createNodeIconSurface];
- for (NodeStyle *style in [styleManager nodeStyles]) {
- [self addStyle:style usingSurface:surface];
- }
- cairo_surface_destroy (surface);
-}
@end
// }}}
@@ -484,7 +123,11 @@ enum {
static void selection_changed_cb (GtkIconView *view, NodeStyleSelector *mgr) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [mgr selectionChanged];
+
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"SelectedStyleChanged"
+ object:mgr];
+
[pool drain];
}
// }}}
diff --git a/tikzit/src/gtk/NodeStylesModel.h b/tikzit/src/gtk/NodeStylesModel.h
new file mode 100644
index 0000000..a048560
--- /dev/null
+++ b/tikzit/src/gtk/NodeStylesModel.h
@@ -0,0 +1,62 @@
+/*
+ * 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 "TZFoundation.h"
+#import <gtk/gtk.h>
+
+@class NodeStyle;
+@class StyleManager;
+
+enum {
+ NODE_STYLES_NAME_COL = 0,
+ NODE_STYLES_ICON_COL,
+ NODE_STYLES_PTR_COL,
+ NODE_STYLES_N_COLS
+};
+
+@interface NodeStylesModel: NSObject {
+ GtkListStore *store;
+ StyleManager *styleManager;
+}
+
+/*!
+ @property model
+ @brief The GTK+ tree model
+ */
+@property (readonly) GtkTreeModel *model;
+
+/*!
+ @property manager
+ @brief The StyleManager to use.
+ */
+@property (retain) StyleManager *styleManager;
+
+/*!
+ * Initialise with the given style manager
+ */
+- (id) initWithStyleManager:(StyleManager*)m;
+
++ (id) modelWithStyleManager:(StyleManager*)m;
+
+- (NodeStyle*) styleFromPath:(GtkTreePath*)path;
+- (GtkTreePath*) pathFromStyle:(NodeStyle*)style;
+- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter;
+- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style;
+
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/NodeStylesModel.m b/tikzit/src/gtk/NodeStylesModel.m
new file mode 100644
index 0000000..3cc5771
--- /dev/null
+++ b/tikzit/src/gtk/NodeStylesModel.m
@@ -0,0 +1,381 @@
+/*
+ * 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 "NodeStylesModel.h"
+
+#import "CairoRenderContext.h"
+#import "NodeStyle.h"
+#import "Shape.h"
+#import "Shape+Render.h"
+#import "ShapeNames.h"
+#import "StyleManager.h"
+
+#import "gtkhelpers.h"
+
+#import <gdk-pixbuf/gdk-pixbuf.h>
+
+// {{{ Internal interfaces
+
+@interface NodeStylesModel (Notifications)
+- (void) nodeStylesReplaced:(NSNotification*)notification;
+- (void) nodeStyleAdded:(NSNotification*)notification;
+- (void) nodeStyleRemoved:(NSNotification*)notification;
+- (void) shapeDictionaryReplaced:(NSNotification*)n;
+- (void) observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context;
+@end
+
+@interface NodeStylesModel (Private)
+- (cairo_surface_t*) createNodeIconSurface;
+- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style;
+- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface;
+- (void) addNodeStyle:(NodeStyle*)style;
+- (void) addNodeStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface;
+- (void) observeNodeStyle:(NodeStyle*)style;
+- (void) stopObservingNodeStyle:(NodeStyle*)style;
+- (void) clearNodeStylesModel;
+- (void) reloadNodeStyles;
+@end
+
+// }}}
+// {{{ API
+
+@implementation NodeStylesModel
+
++ (id) modelWithStyleManager:(StyleManager*)m {
+ return [[[self alloc] initWithStyleManager:m] autorelease];
+}
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)m {
+ self = [super init];
+
+ if (self) {
+ store = gtk_list_store_new (NODE_STYLES_N_COLS,
+ G_TYPE_STRING,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_POINTER);
+ g_object_ref_sink (store);
+
+ [self setStyleManager:m];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(shapeDictionaryReplaced:)
+ name:@"ShapeDictionaryReplaced"
+ object:[Shape class]];
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [self clearNodeStylesModel];
+ g_object_unref (store);
+ [styleManager release];
+
+ [super dealloc];
+}
+
+- (StyleManager*) styleManager {
+ return styleManager;
+}
+
+- (void) setStyleManager:(StyleManager*)m {
+ if (m == nil) {
+ [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"];
+ }
+ [m retain];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(nodeStylesReplaced:)
+ name:@"NodeStylesReplaced"
+ object:m];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(nodeStyleAdded:)
+ name:@"NodeStyleAdded"
+ object:m];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(nodeStyleRemoved:)
+ name:@"NodeStyleRemoved"
+ object:m];
+
+ [styleManager release];
+ styleManager = m;
+
+ [self reloadNodeStyles];
+}
+
+- (GtkTreeModel*) model {
+ return GTK_TREE_MODEL (store);
+}
+
+- (NodeStyle*) styleFromPath:(GtkTreePath*)path {
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+ NodeStyle *style = nil;
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, NODE_STYLES_PTR_COL, &style, -1);
+ return style;
+}
+
+- (GtkTreePath*) pathFromStyle:(NodeStyle*)style {
+ GtkTreeModel *m = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (m, &row)) {
+ do {
+ NodeStyle *rowStyle;
+ gtk_tree_model_get (m, &row, NODE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ return gtk_tree_model_get_path (m, &row);
+ }
+ } while (gtk_tree_model_iter_next (m, &row));
+ }
+ return NULL;
+}
+
+- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter {
+ NodeStyle *style = nil;
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter, NODE_STYLES_PTR_COL, &style, -1);
+ return style;
+}
+
+- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style {
+ GtkTreeModel *m = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (m, &row)) {
+ do {
+ NodeStyle *rowStyle;
+ gtk_tree_model_get (m, &row, NODE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ return gtk_tree_iter_copy (&row);
+ }
+ } while (gtk_tree_model_iter_next (m, &row));
+ }
+ return NULL;
+}
+@end
+
+// }}}
+// {{{ Notifications
+
+@implementation NodeStylesModel (Notifications)
+
+- (void) nodeStylesReplaced:(NSNotification*)notification {
+ [self reloadNodeStyles];
+}
+
+- (void) nodeStyleAdded:(NSNotification*)notification {
+ [self addNodeStyle:[[notification userInfo] objectForKey:@"style"]];
+}
+
+- (void) nodeStyleRemoved:(NSNotification*)notification {
+ NodeStyle *style = [[notification userInfo] objectForKey:@"style"];
+
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ NodeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ gtk_list_store_remove (store, &row);
+ [self stopObservingNodeStyle:rowStyle];
+ [rowStyle release];
+ return;
+ }
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+}
+
+- (void) observeValueForKeyPath:(NSString*)keyPath
+ ofObject:(id)object
+ change:(NSDictionary*)change
+ context:(void*)context
+{
+ if ([object class] == [NodeStyle class]) {
+ NodeStyle *style = object;
+
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ NodeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1);
+ if (style == rowStyle) {
+ if ([@"name" isEqual:keyPath]) {
+ gtk_list_store_set (store, &row, NODE_STYLES_NAME_COL, [[style name] UTF8String], -1);
+ } else {
+ GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style];
+ gtk_list_store_set (store, &row, NODE_STYLES_ICON_COL, pixbuf, -1);
+ g_object_unref (pixbuf);
+ }
+ }
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+ }
+}
+
+- (void) shapeDictionaryReplaced:(NSNotification*)n {
+ [self reloadNodeStyles];
+}
+@end
+
+// }}}
+// {{{ Private
+
+@implementation NodeStylesModel (Private)
+- (cairo_surface_t*) createNodeIconSurface {
+ return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 24, 24);
+}
+
+- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style {
+ cairo_surface_t *surface = [self createNodeIconSurface];
+ GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface];
+ cairo_surface_destroy (surface);
+ return pixbuf;
+}
+
+- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface {
+ Shape *shape = [Shape shapeForName:[style shapeName]];
+
+ int width = cairo_image_surface_get_width (surface);
+ int height = cairo_image_surface_get_height (surface);
+ NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height);
+ const CGFloat lineWidth = [style strokeThickness];
+ Transformer *shapeTrans = [Transformer transformerToFit:[shape boundingRect]
+ intoScreenRect:NSInsetRect(pixbufBounds, lineWidth, lineWidth)
+ flippedAboutXAxis:YES];
+ if ([style scale] < 1.0)
+ [shapeTrans setScale:[style scale] * [shapeTrans scale]];
+
+ CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface];
+ [context clearSurface];
+ [shape drawPathWithTransform:shapeTrans andContext:context];
+ [context setLineWidth:lineWidth];
+ [context strokePathWithColor:[[style strokeColorRGB] rColor]
+ andFillWithColor:[[style fillColorRGB] rColor]];
+ [context release];
+
+ return pixbuf_get_from_surface (surface);
+}
+
+- (void) addNodeStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface {
+ GtkTreeIter iter;
+ gtk_list_store_append (store, &iter);
+
+ GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface];
+ gtk_list_store_set (store, &iter,
+ NODE_STYLES_NAME_COL, [[style name] UTF8String],
+ NODE_STYLES_ICON_COL, pixbuf,
+ NODE_STYLES_PTR_COL, (gpointer)[style retain],
+ -1);
+ g_object_unref (pixbuf);
+ [self observeNodeStyle:style];
+}
+
+- (void) addNodeStyle:(NodeStyle*)style {
+ cairo_surface_t *surface = [self createNodeIconSurface];
+ [self addNodeStyle:style usingSurface:surface];
+ cairo_surface_destroy (surface);
+}
+
+- (void) observeNodeStyle:(NodeStyle*)style {
+ [style addObserver:self
+ forKeyPath:@"name"
+ options:NSKeyValueObservingOptionNew
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"strokeThickness"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"strokeColorRGB.red"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"strokeColorRGB.green"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"strokeColorRGB.blue"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"fillColorRGB.red"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"fillColorRGB.green"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"fillColorRGB.blue"
+ options:0
+ context:NULL];
+ [style addObserver:self
+ forKeyPath:@"shapeName"
+ options:0
+ context:NULL];
+}
+
+- (void) stopObservingNodeStyle:(NodeStyle*)style {
+ [style removeObserver:self forKeyPath:@"name"];
+ [style removeObserver:self forKeyPath:@"strokeThickness"];
+ [style removeObserver:self forKeyPath:@"strokeColorRGB.red"];
+ [style removeObserver:self forKeyPath:@"strokeColorRGB.green"];
+ [style removeObserver:self forKeyPath:@"strokeColorRGB.blue"];
+ [style removeObserver:self forKeyPath:@"fillColorRGB.red"];
+ [style removeObserver:self forKeyPath:@"fillColorRGB.green"];
+ [style removeObserver:self forKeyPath:@"fillColorRGB.blue"];
+ [style removeObserver:self forKeyPath:@"shapeName"];
+}
+
+- (void) clearNodeStylesModel {
+ GtkTreeModel *model = GTK_TREE_MODEL (store);
+ GtkTreeIter row;
+ if (gtk_tree_model_get_iter_first (model, &row)) {
+ do {
+ NodeStyle *rowStyle;
+ gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1);
+ [self stopObservingNodeStyle:rowStyle];
+ [rowStyle release];
+ } while (gtk_tree_model_iter_next (model, &row));
+ }
+ gtk_list_store_clear (store);
+}
+
+- (void) reloadNodeStyles {
+ [self clearNodeStylesModel];
+ cairo_surface_t *surface = [self createNodeIconSurface];
+ for (NodeStyle *style in [styleManager nodeStyles]) {
+ [self addNodeStyle:style usingSurface:surface];
+ }
+ cairo_surface_destroy (surface);
+}
+@end
+
+// }}}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/NodeStylesPalette.m b/tikzit/src/gtk/NodeStylesPalette.m
index d5ac2e1..23ca7ea 100644
--- a/tikzit/src/gtk/NodeStylesPalette.m
+++ b/tikzit/src/gtk/NodeStylesPalette.m
@@ -142,11 +142,11 @@ static void clear_style_button_cb (GtkButton *widget, NodeStylesPalette *palette
}
- (StyleManager*) styleManager {
- return [selector styleManager];
+ return [[selector model] styleManager];
}
- (void) setStyleManager:(StyleManager*)m {
- [selector setStyleManager:m];
+ [[selector model] setStyleManager:m];
}
- (TikzDocument*) document {
@@ -217,7 +217,7 @@ static void clear_style_button_cb (GtkButton *widget, NodeStylesPalette *palette
- (void) removeSelectedStyle {
NodeStyle *style = [selector selectedStyle];
if (style)
- [[selector styleManager] removeNodeStyle:style];
+ [[[selector model] styleManager] removeNodeStyle:style];
}
- (void) applySelectedStyle {
@@ -251,7 +251,6 @@ static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette)
NodeStyle *newStyle = [NodeStyle defaultNodeStyleWithName:@"newstyle"];
[[palette styleManager] addNodeStyle:newStyle];
- [[palette styleManager] setActiveNodeStyle:newStyle];
[pool drain];
}
diff --git a/tikzit/src/gtk/PreambleEditor.h b/tikzit/src/gtk/PreambleEditor.h
index b081e00..f181446 100644
--- a/tikzit/src/gtk/PreambleEditor.h
+++ b/tikzit/src/gtk/PreambleEditor.h
@@ -40,6 +40,7 @@
- (Preambles*) preambles;
+- (void) present;
- (void) show;
- (void) hide;
- (BOOL) isVisible;
diff --git a/tikzit/src/gtk/PreambleEditor.m b/tikzit/src/gtk/PreambleEditor.m
index 2c292f5..feae6a5 100644
--- a/tikzit/src/gtk/PreambleEditor.m
+++ b/tikzit/src/gtk/PreambleEditor.m
@@ -116,6 +116,12 @@ static void preamble_selection_changed_cb (GtkTreeSelection *treeselection,
}
}
+- (void) present {
+ [self loadUi];
+ gtk_window_present (GTK_WINDOW (window));
+ [self revert];
+}
+
- (void) show {
[self loadUi];
gtk_widget_show (GTK_WIDGET (window));
diff --git a/tikzit/src/gtk/PreviewWindow.h b/tikzit/src/gtk/PreviewWindow.h
index ca6b30c..8bcd3e5 100644
--- a/tikzit/src/gtk/PreviewWindow.h
+++ b/tikzit/src/gtk/PreviewWindow.h
@@ -40,6 +40,7 @@
- (BOOL) update;
+- (void) present;
- (void) show;
- (void) hide;
- (BOOL) isVisible;
diff --git a/tikzit/src/gtk/PreviewWindow.m b/tikzit/src/gtk/PreviewWindow.m
index 5443e8d..c5f138a 100644
--- a/tikzit/src/gtk/PreviewWindow.m
+++ b/tikzit/src/gtk/PreviewWindow.m
@@ -83,6 +83,14 @@
[previewer setDocument:doc];
}
+- (void) present {
+ if ([self updateOrShowError]) {
+ [self updateDefaultSize];
+ gtk_window_present (GTK_WINDOW (window));
+ [surface invalidate];
+ }
+}
+
- (BOOL) update {
if ([self updateOrShowError]) {
[self updateDefaultSize];
diff --git a/tikzit/src/gtk/PropertyPane.h b/tikzit/src/gtk/PropertiesPane.h
index b4a44f7..696fd0b 100644
--- a/tikzit/src/gtk/PropertyPane.h
+++ b/tikzit/src/gtk/PropertiesPane.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -17,16 +17,19 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
-#import "Configuration.h"
-#import "TikzDocument.h"
-@class PropertyListEditor;
+@class Configuration;
+@class EdgeNodePropertyDelegate;
+@class EdgePropertyDelegate;
+@class EdgeStylesModel;
@class GraphPropertyDelegate;
@class NodePropertyDelegate;
-@class EdgePropertyDelegate;
-@class EdgeNodePropertyDelegate;
+@class NodeStylesModel;
+@class PropertyListEditor;
+@class StyleManager;
+@class TikzDocument;
-@interface PropertyPane: NSObject {
+@interface PropertiesPane: NSObject {
TikzDocument *document;
BOOL blockUpdates;
@@ -35,16 +38,18 @@
PropertyListEditor *edgeProps;
PropertyListEditor *edgeNodeProps;
- GraphPropertyDelegate *graphPropDelegate;
- NodePropertyDelegate *nodePropDelegate;
- EdgePropertyDelegate *edgePropDelegate;
+ GraphPropertyDelegate *graphPropDelegate;
+ NodePropertyDelegate *nodePropDelegate;
+ EdgePropertyDelegate *edgePropDelegate;
EdgeNodePropertyDelegate *edgeNodePropDelegate;
- GtkWidget *propertiesPane;
+ GtkWidget *layout;
- GtkExpander *graphPropsExpander;
- GtkExpander *nodePropsExpander;
- GtkExpander *edgePropsExpander;
+ GtkWidget *currentPropsWidget; // no ref!
+
+ GtkWidget *graphPropsWidget;
+ GtkWidget *nodePropsWidget;
+ GtkWidget *edgePropsWidget;
GtkEntry *nodeLabelEntry;
GtkToggleButton *edgeNodeToggle;
@@ -52,17 +57,16 @@
GtkEntry *edgeNodeLabelEntry;
}
-@property (readonly) GtkWidget *widget;
@property (retain) TikzDocument *document;
+@property (assign) BOOL visible;
+@property (readonly) GtkWidget *gtkWidget;
-- (id) init;
-
-- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group;
-- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group;
+- (id) initWithStyleManager:(StyleManager*)mgr;
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm;
-- (void) favourGraphProperties;
-- (void) favourNodeProperties;
-- (void) favourEdgeProperties;
+- (void) loadConfiguration:(Configuration*)config;
+- (void) saveConfiguration:(Configuration*)config;
@end
diff --git a/tikzit/src/gtk/PropertyPane.m b/tikzit/src/gtk/PropertiesPane.m
index c4bd73e..990304e 100644
--- a/tikzit/src/gtk/PropertyPane.m
+++ b/tikzit/src/gtk/PropertiesPane.m
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -15,23 +15,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#import "PropertyPane.h"
-#import "PropertyListEditor.h"
+#import "PropertiesPane.h"
+
#import "GraphElementProperty.h"
+#import "PropertyListEditor.h"
+#import "TikzDocument.h"
+
#import "gtkhelpers.h"
// {{{ Internal interfaces
// {{{ GTK+ helpers
static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry);
static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry);
+static GtkWidget *createBoldLabel (const gchar *text);
// }}}
// {{{ GTK+ callbacks
-static void node_label_changed_cb (GtkEditable *widget, PropertyPane *pane);
-static void edge_node_label_changed_cb (GtkEditable *widget, PropertyPane *pane);
-static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
+static void node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane);
+static void edge_node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane);
+static void edge_node_toggled_cb (GtkToggleButton *widget, PropertiesPane *pane);
// }}}
-@interface PropertyPane (Notifications)
+@interface PropertiesPane (Notifications)
- (void) nodeSelectionChanged:(NSNotification*)n;
- (void) edgeSelectionChanged:(NSNotification*)n;
- (void) graphChanged:(NSNotification*)n;
@@ -40,12 +44,9 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
- (void) edgeNodeToggled:(BOOL)newValue;
@end
-@interface PropertyPane (Private)
-- (void) updateGraphPane;
-- (void) updateNodePane;
-- (void) updateEdgePane;
-- (void) _addSplitter;
-- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents;
+@interface PropertiesPane (Private)
+- (void) _updatePane;
+- (void) _setDisplayedWidget:(GtkWidget*)widget;
@end
// {{{ Delegates
@@ -77,9 +78,18 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
// }}}
// {{{ API
-@implementation PropertyPane
+@implementation PropertiesPane
-@synthesize widget=propertiesPane;
+// we don't currently use the styles models
+- (id) initWithStyleManager:(StyleManager*)sm {
+ return [self init];
+}
+
+// we don't currently use the styles models
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm {
+ return [self init];
+}
- (id) init {
self = [super init];
@@ -103,71 +113,117 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
edgeNodeProps = [[PropertyListEditor alloc] init];
[edgeNodeProps setDelegate:edgePropDelegate];
- propertiesPane = gtk_vbox_new (FALSE, 0);
- g_object_ref_sink (propertiesPane);
+ layout = gtk_vbox_new (FALSE, 0);
+ g_object_ref_sink (layout);
+ gtk_widget_show (layout);
/*
* Graph properties
*/
- graphPropsExpander = [self _addExpanderWithName:"Graph properties"
- contents:[graphProps widget]];
- g_object_ref_sink (graphPropsExpander);
+ graphPropsWidget = gtk_vbox_new (FALSE, 6);
+ g_object_ref_sink (graphPropsWidget);
+ gtk_widget_show (graphPropsWidget);
+
+ GtkWidget *label = createBoldLabel ("Graph properties");
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (graphPropsWidget),
+ label,
+ FALSE, FALSE, 0);
+ gtk_widget_show ([graphProps widget]);
+ gtk_box_pack_start (GTK_BOX (graphPropsWidget),
+ [graphProps widget],
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (layout),
+ graphPropsWidget,
+ TRUE, TRUE, 0);
- [self _addSplitter];
/*
* Node properties
*/
- GtkWidget *nodePropsWidget = createPropsPaneWithLabelEntry(nodeProps, &nodeLabelEntry);
- g_object_ref (nodeLabelEntry);
- nodePropsExpander = [self _addExpanderWithName:"Node properties"
- contents:nodePropsWidget];
- g_object_ref (nodePropsExpander);
+ nodePropsWidget = gtk_vbox_new (FALSE, 6);
+ g_object_ref_sink (nodePropsWidget);
+ gtk_box_pack_start (GTK_BOX (layout),
+ nodePropsWidget,
+ TRUE, TRUE, 0);
+
+ label = createBoldLabel ("Node properties");
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (nodePropsWidget),
+ label,
+ FALSE, FALSE, 0);
+
+ GtkWidget *labelWidget = createLabelledEntry ("Label", &nodeLabelEntry);
+ gtk_widget_show (labelWidget);
+ gtk_box_pack_start (GTK_BOX (nodePropsWidget),
+ labelWidget,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show ([nodeProps widget]);
+ gtk_box_pack_start (GTK_BOX (nodePropsWidget),
+ [nodeProps widget],
+ TRUE, TRUE, 0);
+
g_signal_connect (G_OBJECT (nodeLabelEntry),
"changed",
G_CALLBACK (node_label_changed_cb),
self);
-
- [self _addSplitter];
-
/*
* Edge properties
*/
- GtkBox *edgePropsBox = GTK_BOX (gtk_vbox_new (FALSE, 0));
- gtk_box_set_spacing (edgePropsBox, 6);
- edgePropsExpander = [self _addExpanderWithName:"Edge properties"
- contents:GTK_WIDGET (edgePropsBox)];
- g_object_ref (edgePropsExpander);
+ edgePropsWidget = gtk_vbox_new (FALSE, 6);
+ g_object_ref_sink (edgePropsWidget);
+ gtk_box_pack_start (GTK_BOX (layout),
+ edgePropsWidget,
+ TRUE, TRUE, 0);
+
+ label = createBoldLabel ("Edge properties");
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (edgePropsWidget),
+ label,
+ FALSE, FALSE, 0);
gtk_widget_show ([edgeProps widget]);
- gtk_box_pack_start (edgePropsBox, [edgeProps widget], FALSE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (edgePropsWidget),
+ [edgeProps widget],
+ TRUE, TRUE, 0);
GtkWidget *split = gtk_hseparator_new ();
- gtk_box_pack_start (edgePropsBox, split, FALSE, FALSE, 0);
gtk_widget_show (split);
+ gtk_box_pack_start (GTK_BOX (edgePropsWidget),
+ split,
+ FALSE, FALSE, 0);
edgeNodeToggle = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_label ("Child node"));
- g_object_ref (edgeNodeToggle);
+ g_object_ref_sink (edgeNodeToggle);
gtk_widget_show (GTK_WIDGET (edgeNodeToggle));
- gtk_box_pack_start (edgePropsBox, GTK_WIDGET (edgeNodeToggle), FALSE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (edgePropsWidget),
+ GTK_WIDGET (edgeNodeToggle),
+ FALSE, FALSE, 0);
g_signal_connect (G_OBJECT (GTK_WIDGET (edgeNodeToggle)),
"toggled",
G_CALLBACK (edge_node_toggled_cb),
self);
edgeNodePropsWidget = createPropsPaneWithLabelEntry(edgeNodeProps, &edgeNodeLabelEntry);
- g_object_ref (edgeNodePropsWidget);
- g_object_ref (edgeNodeLabelEntry);
- gtk_box_pack_start (edgePropsBox, edgeNodePropsWidget, FALSE, TRUE, 0);
+ g_object_ref_sink (edgeNodePropsWidget);
+ g_object_ref_sink (edgeNodeLabelEntry);
+ gtk_box_pack_start (GTK_BOX (edgePropsWidget),
+ edgeNodePropsWidget,
+ TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (edgeNodeLabelEntry),
"changed",
G_CALLBACK (edge_node_label_changed_cb),
self);
+ /*
+ * Misc setup
+ */
- [self _addSplitter];
+ [self _setDisplayedWidget:graphPropsWidget];
}
return self;
@@ -175,24 +231,29 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
- [document release];
- g_object_unref (propertiesPane);
- g_object_unref (graphPropsExpander);
- g_object_unref (nodePropsExpander);
- g_object_unref (edgePropsExpander);
+ g_object_unref (graphPropsWidget);
+ g_object_unref (nodePropsWidget);
+ g_object_unref (edgePropsWidget);
+
g_object_unref (nodeLabelEntry);
g_object_unref (edgeNodeToggle);
g_object_unref (edgeNodePropsWidget);
g_object_unref (edgeNodeLabelEntry);
+ g_object_unref (layout);
+
[graphProps release];
[nodeProps release];
[edgeProps release];
[edgeNodeProps release];
+
[graphPropDelegate release];
[nodePropDelegate release];
[edgePropDelegate release];
+ [edgeNodePropDelegate release];
+
+ [document release];
[super dealloc];
}
@@ -207,6 +268,10 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]];
}
+ [doc retain];
+ [document release];
+ document = doc;
+
[graphPropDelegate setDocument:doc];
[nodePropDelegate setDocument:doc];
[edgePropDelegate setDocument:doc];
@@ -223,101 +288,43 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
name:@"TikzChanged" object:doc];
}
- [self updateGraphPane];
- [self updateNodePane];
- [self updateEdgePane];
+ [self _updatePane];
+}
- [doc retain];
- [document release];
- document = doc;
+- (BOOL) visible {
+ return gtk_widget_get_visible (layout);
}
-- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group {
- gtk_expander_set_expanded (graphPropsExpander,
- [file booleanEntry:@"graph-props-expanded"
- inGroup:group
- withDefault:NO]);
- gtk_expander_set_expanded (nodePropsExpander,
- [file booleanEntry:@"node-props-expanded"
- inGroup:group
- withDefault:YES]);
- gtk_expander_set_expanded (edgePropsExpander,
- [file booleanEntry:@"edge-props-expanded"
- inGroup:group
- withDefault:NO]);
-}
-
-- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group {
- [file setBooleanEntry:@"graph-props-expanded"
- inGroup:group
- value:gtk_expander_get_expanded (graphPropsExpander)];
- [file setBooleanEntry:@"node-props-expanded"
- inGroup:group
- value:gtk_expander_get_expanded (nodePropsExpander)];
- [file setBooleanEntry:@"edge-props-expanded"
- inGroup:group
- value:gtk_expander_get_expanded (edgePropsExpander)];
-}
-
-- (int) expandedPaneCount {
- int eps = 0;
- if (gtk_expander_get_expanded (graphPropsExpander))
- eps++;
- if (gtk_expander_get_expanded (nodePropsExpander))
- eps++;
- if (gtk_expander_get_expanded (edgePropsExpander))
- eps++;
- return eps;
-}
-
-- (void) favourGraphProperties {
- if (!gtk_expander_get_expanded (graphPropsExpander)) {
- if ([self expandedPaneCount] == 1) {
- gtk_expander_set_expanded (nodePropsExpander, FALSE);
- gtk_expander_set_expanded (edgePropsExpander, FALSE);
- gtk_expander_set_expanded (graphPropsExpander, TRUE);
- }
- }
+- (void) setVisible:(BOOL)visible {
+ gtk_widget_set_visible (layout, visible);
}
-- (void) favourNodeProperties {
- if (!gtk_expander_get_expanded (nodePropsExpander)) {
- if ([self expandedPaneCount] == 1) {
- gtk_expander_set_expanded (graphPropsExpander, FALSE);
- gtk_expander_set_expanded (edgePropsExpander, FALSE);
- gtk_expander_set_expanded (nodePropsExpander, TRUE);
- }
- }
+- (GtkWidget*) gtkWidget {
+ return layout;
}
-- (void) favourEdgeProperties {
- if (!gtk_expander_get_expanded (edgePropsExpander)) {
- if ([self expandedPaneCount] == 1) {
- gtk_expander_set_expanded (graphPropsExpander, FALSE);
- gtk_expander_set_expanded (nodePropsExpander, FALSE);
- gtk_expander_set_expanded (edgePropsExpander, TRUE);
- }
- }
+- (void) loadConfiguration:(Configuration*)config {
+}
+
+- (void) saveConfiguration:(Configuration*)config {
}
@end
// }}}
// {{{ Notifications
-@implementation PropertyPane (Notifications)
+@implementation PropertiesPane (Notifications)
- (void) nodeSelectionChanged:(NSNotification*)n {
- [self updateNodePane];
+ [self _updatePane];
}
- (void) edgeSelectionChanged:(NSNotification*)n {
- [self updateEdgePane];
+ [self _updatePane];
}
- (void) graphChanged:(NSNotification*)n {
- [self updateGraphPane];
- [self updateNodePane];
- [self updateEdgePane];
+ [self _updatePane];
}
- (void) nodeLabelEdited:(NSString*)newValue {
@@ -378,93 +385,71 @@ static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane);
// }}}
// {{{ Private
-@implementation PropertyPane (Private)
+@implementation PropertiesPane (Private)
-- (void) updateGraphPane {
+- (void) _setDisplayedWidget:(GtkWidget*)widget {
+ if (currentPropsWidget != widget) {
+ if (currentPropsWidget)
+ gtk_widget_hide (currentPropsWidget);
+ currentPropsWidget = widget;
+ if (widget)
+ gtk_widget_show (widget);
+ }
+}
+
+- (void) _updatePane {
blockUpdates = YES;
+ BOOL editGraphProps = YES;
GraphElementData *data = [[document graph] data];
[graphProps setData:data];
- gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (graphPropsExpander)), data != nil);
-
- blockUpdates = NO;
-}
-
-- (void) updateNodePane {
- blockUpdates = YES;
- NSSet *sel = [[document pickSupport] selectedNodes];
- if ([sel count] == 1) {
- Node *n = [sel anyObject];
+ NSSet *nodeSel = [[document pickSupport] selectedNodes];
+ if ([nodeSel count] == 1) {
+ Node *n = [nodeSel anyObject];
[nodePropDelegate setNode:n];
[nodeProps setData:[n data]];
gtk_entry_set_text (nodeLabelEntry, [[n label] UTF8String]);
- gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (nodePropsExpander)), TRUE);
+ [self _setDisplayedWidget:nodePropsWidget];
+ editGraphProps = NO;
} else {
[nodePropDelegate setNode:nil];
[nodeProps setData:nil];
gtk_entry_set_text (nodeLabelEntry, "");
- gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (nodePropsExpander)), FALSE);
- }
-
- blockUpdates = NO;
-}
-- (void) updateEdgePane {
- blockUpdates = YES;
-
- NSSet *sel = [[document pickSupport] selectedEdges];
- if ([sel count] == 1) {
- Edge *e = [sel anyObject];
- [edgePropDelegate setEdge:e];
- [edgeProps setData:[e data]];
- gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (edgePropsExpander)), TRUE);
- if ([e hasEdgeNode]) {
- gtk_toggle_button_set_active (edgeNodeToggle, TRUE);
- gtk_widget_show (edgeNodePropsWidget);
- gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), [[[e edgeNode] label] UTF8String]);
- [edgeNodeProps setData:[[e edgeNode] data]];
- gtk_widget_set_sensitive (edgeNodePropsWidget, TRUE);
+ NSSet *edgeSel = [[document pickSupport] selectedEdges];
+ if ([edgeSel count] == 1) {
+ Edge *e = [edgeSel anyObject];
+ [edgePropDelegate setEdge:e];
+ [edgeProps setData:[e data]];
+ if ([e hasEdgeNode]) {
+ gtk_toggle_button_set_active (edgeNodeToggle, TRUE);
+ gtk_widget_show (edgeNodePropsWidget);
+ gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), [[[e edgeNode] label] UTF8String]);
+ [edgeNodeProps setData:[[e edgeNode] data]];
+ gtk_widget_set_sensitive (edgeNodePropsWidget, TRUE);
+ } else {
+ gtk_toggle_button_set_active (edgeNodeToggle, FALSE);
+ gtk_widget_hide (edgeNodePropsWidget);
+ gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), "");
+ [edgeNodeProps setData:nil];
+ gtk_widget_set_sensitive (edgeNodePropsWidget, FALSE);
+ }
+ [self _setDisplayedWidget:edgePropsWidget];
+ editGraphProps = NO;
} else {
- gtk_toggle_button_set_active (edgeNodeToggle, FALSE);
- gtk_widget_hide (edgeNodePropsWidget);
- gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), "");
+ [edgePropDelegate setEdge:nil];
+ [edgeProps setData:nil];
[edgeNodeProps setData:nil];
- gtk_widget_set_sensitive (edgeNodePropsWidget, FALSE);
+ gtk_entry_set_text (edgeNodeLabelEntry, "");
}
- } else {
- [edgePropDelegate setEdge:nil];
- [edgeProps setData:nil];
- [edgeNodeProps setData:nil];
- gtk_entry_set_text (edgeNodeLabelEntry, "");
- gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (edgePropsExpander)), FALSE);
}
- blockUpdates = NO;
-}
-
-- (void) _addSplitter {
- GtkWidget *split = gtk_hseparator_new ();
- gtk_box_pack_start (GTK_BOX (propertiesPane),
- split,
- FALSE, // expand
- FALSE, // fill
- 0); // padding
- gtk_widget_show (split);
-}
+ if (editGraphProps) {
+ [self _setDisplayedWidget:graphPropsWidget];
+ }
-- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents {
- GtkWidget *exp = gtk_expander_new (name);
- gtk_box_pack_start (GTK_BOX (propertiesPane),
- exp,
- FALSE, // expand
- TRUE, // fill
- 0); // padding
- gtk_widget_show (exp);
- gtk_container_set_border_width (GTK_CONTAINER (contents), 6);
- gtk_container_add (GTK_CONTAINER (exp), contents);
- gtk_widget_show (contents);
- return GTK_EXPANDER (exp);
+ blockUpdates = NO;
}
@end
@@ -578,22 +563,27 @@ static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry)
}
static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry) {
- GtkBox *box = GTK_BOX (gtk_vbox_new (FALSE, 0));
- gtk_box_set_spacing (box, 6);
+ GtkBox *box = GTK_BOX (gtk_vbox_new (FALSE, 6));
GtkWidget *labelWidget = createLabelledEntry ("Label", labelEntry);
gtk_widget_show (labelWidget);
- // box widget expand fill pad
- gtk_box_pack_start (box, labelWidget, FALSE, TRUE, 0);
- gtk_box_pack_start (box, [props widget], FALSE, TRUE, 0);
+ // box widget expand fill pad
+ gtk_box_pack_start (box, labelWidget, FALSE, FALSE, 0);
+ gtk_box_pack_start (box, [props widget], TRUE, TRUE, 0);
gtk_widget_show ([props widget]);
return GTK_WIDGET (box);
}
+static GtkWidget *createBoldLabel (const gchar *text) {
+ GtkWidget *label = gtk_label_new (text);
+ label_set_bold (GTK_LABEL (label));
+ return label;
+}
+
// }}}
// {{{ GTK+ callbacks
-static void node_label_changed_cb (GtkEditable *editable, PropertyPane *pane) {
+static void node_label_changed_cb (GtkEditable *editable, PropertiesPane *pane) {
if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) {
// clearly wasn't the user editing
return;
@@ -607,7 +597,7 @@ static void node_label_changed_cb (GtkEditable *editable, PropertyPane *pane) {
[pool drain];
}
-static void edge_node_label_changed_cb (GtkEditable *editable, PropertyPane *pane) {
+static void edge_node_label_changed_cb (GtkEditable *editable, PropertiesPane *pane) {
if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) {
// clearly wasn't the user editing
return;
@@ -621,7 +611,7 @@ static void edge_node_label_changed_cb (GtkEditable *editable, PropertyPane *pan
[pool drain];
}
-static void edge_node_toggled_cb (GtkToggleButton *toggle, PropertyPane *pane) {
+static void edge_node_toggled_cb (GtkToggleButton *toggle, PropertiesPane *pane) {
if (!gtk_widget_is_sensitive (GTK_WIDGET (toggle))) {
// clearly wasn't the user editing
return;
diff --git a/tikzit/src/gtk/PropertyListEditor.m b/tikzit/src/gtk/PropertyListEditor.m
index 51f3cbf..ba909da 100644
--- a/tikzit/src/gtk/PropertyListEditor.m
+++ b/tikzit/src/gtk/PropertyListEditor.m
@@ -165,6 +165,8 @@ static void remove_clicked_cb (GtkButton *button,
gtk_widget_show_all (GTK_WIDGET (buttonBox));
gtk_widget_show_all (scrolledview);
+
+ gtk_widget_set_sensitive (widget, FALSE);
}
return self;
@@ -207,6 +209,7 @@ static void remove_clicked_cb (GtkButton *button,
[data release];
data = d;
[self reloadProperties];
+ gtk_widget_set_sensitive (widget, data != nil);
}
- (NSObject<PropertyChangeDelegate>*) delegate {
diff --git a/tikzit/src/gtk/GraphInputHandler.h b/tikzit/src/gtk/SelectTool.h
index 7277e82..65f511a 100644
--- a/tikzit/src/gtk/GraphInputHandler.h
+++ b/tikzit/src/gtk/SelectTool.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <alex.merry@kdemail.net>
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -16,54 +16,48 @@
*/
#import "TZFoundation.h"
-#import "GraphRenderer.h"
-#import "InputDelegate.h"
-#import "StyleManager.h"
+#import "Tool.h"
-@class MainWindow;
-
-typedef enum {
- SelectMode,
- CreateNodeMode,
- DrawEdgeMode,
- BoundingBoxMode,
- HandMode
-} InputMode;
+@class Edge;
+@class Node;
+// FIXME: replace this with delegates
typedef enum {
QuietState,
SelectBoxState,
ToggleSelectState,
MoveSelectedNodesState,
DragEdgeControlPoint1,
- DragEdgeControlPoint2,
- EdgeDragState,
- BoundingBoxState,
- CanvasDragState
-} MouseState;
+ DragEdgeControlPoint2
+} SelectToolState;
-@interface GraphInputHandler: NSObject {
- MainWindow *window;
- GraphRenderer *renderer;
- InputMode mode;
- MouseState state;
- float edgeFuzz;
- NSPoint dragOrigin;
- Node *leaderNode;
- NSPoint oldLeaderPos;
- Edge *modifyEdge;
- NSMutableSet *selectionBoxContents;
- ResizeHandle currentResizeHandle;
- NSPoint oldOrigin;
+typedef enum {
+ DragSelectsNodes = 1,
+ DragSelectsEdges = 2,
+ DragSelectsBoth = DragSelectsNodes | DragSelectsEdges
+} DragSelectMode;
+
+@interface SelectTool : NSObject <Tool> {
+ GraphRenderer *renderer;
+ SelectToolState state;
+ float edgeFuzz;
+ DragSelectMode dragSelectMode;
+ NSPoint dragOrigin;
+ Node *leaderNode;
+ NSPoint oldLeaderPos;
+ Edge *modifyEdge;
+ NSRect selectionBox;
+ NSMutableSet *selectionBoxContents;
+
+ GtkWidget *configWidget;
+ GSList *dragSelectModeButtons;
}
@property (assign) float edgeFuzz;
-@property (assign) InputMode mode;
-
-- (id) initWithGraphRenderer:(GraphRenderer*)r window:(MainWindow*)w;
-
-- (void) resetState;
+@property (assign) DragSelectMode dragSelectMode;
+- (id) init;
++ (id) tool;
@end
// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/SelectTool.m b/tikzit/src/gtk/SelectTool.m
new file mode 100644
index 0000000..6f3ceeb
--- /dev/null
+++ b/tikzit/src/gtk/SelectTool.m
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 "SelectTool.h"
+
+#import "Configuration.h"
+#import "Edge+Render.h"
+#import "GraphRenderer.h"
+#import "TikzDocument.h"
+#import "tzstockitems.h"
+
+#define DRAG_SELECT_MODE_KEY "tikzit-drag-select-mode"
+
+static const InputMask unionSelectMask = ShiftMask;
+
+static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool);
+
+@interface SelectTool (Private)
+- (TikzDocument*) doc;
+- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to;
+- (void) deselectAllNodes;
+- (void) deselectAllEdges;
+- (void) deselectAll;
+- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p;
+- (void) lookForControlPointAt:(NSPoint)pos;
+- (void) setSelectionBox:(NSRect)box;
+- (void) clearSelectionBox;
+- (BOOL) selectionBoxContainsNode:(Node*)node;
+@end
+
+@implementation SelectTool
+- (NSString*) name { return @"Select"; }
+- (const gchar*) stockId { return TIKZIT_STOCK_SELECT; }
+- (NSString*) helpText { return @"Select, move and edit nodes and edges"; }
+- (NSString*) shortcut { return @"s"; }
+@synthesize configurationWidget=configWidget;
+@synthesize edgeFuzz;
+
++ (id) tool {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id) init {
+ self = [super init];
+
+ if (self) {
+ state = QuietState;
+ edgeFuzz = 3.0f;
+ dragSelectMode = DragSelectsNodes;
+
+ configWidget = gtk_vbox_new (FALSE, 0);
+ g_object_ref_sink (configWidget);
+
+ GtkWidget *label = gtk_label_new ("Drag selects:");
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ label,
+ FALSE,
+ FALSE,
+ 0);
+
+ GtkWidget *nodeOpt = gtk_radio_button_new_with_label (NULL, "nodes");
+ g_object_set_data (G_OBJECT (nodeOpt),
+ DRAG_SELECT_MODE_KEY,
+ (gpointer)DragSelectsNodes);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ nodeOpt,
+ FALSE,
+ FALSE,
+ 0);
+ g_signal_connect (G_OBJECT (nodeOpt),
+ "toggled",
+ G_CALLBACK (drag_select_mode_cb),
+ self);
+
+ GtkWidget *edgeOpt = gtk_radio_button_new_with_label (
+ gtk_radio_button_get_group (GTK_RADIO_BUTTON (nodeOpt)),
+ "edges");
+ g_object_set_data (G_OBJECT (edgeOpt),
+ DRAG_SELECT_MODE_KEY,
+ (gpointer)DragSelectsEdges);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ edgeOpt,
+ FALSE,
+ FALSE,
+ 0);
+ g_signal_connect (G_OBJECT (edgeOpt),
+ "toggled",
+ G_CALLBACK (drag_select_mode_cb),
+ self);
+
+ GtkWidget *bothOpt = gtk_radio_button_new_with_label (
+ gtk_radio_button_get_group (GTK_RADIO_BUTTON (edgeOpt)),
+ "both");
+ g_object_set_data (G_OBJECT (bothOpt),
+ DRAG_SELECT_MODE_KEY,
+ (gpointer)DragSelectsBoth);
+ gtk_box_pack_start (GTK_BOX (configWidget),
+ bothOpt,
+ FALSE,
+ FALSE,
+ 0);
+ g_signal_connect (G_OBJECT (bothOpt),
+ "toggled",
+ G_CALLBACK (drag_select_mode_cb),
+ self);
+ dragSelectModeButtons = gtk_radio_button_get_group (GTK_RADIO_BUTTON (bothOpt));
+
+ gtk_widget_show_all (configWidget);
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [renderer release];
+ [leaderNode release];
+ [modifyEdge release];
+
+ g_object_unref (G_OBJECT (configWidget));
+
+ [super dealloc];
+}
+
+- (DragSelectMode) dragSelectMode {
+ return dragSelectMode;
+}
+
+- (void) setDragSelectMode:(DragSelectMode)mode {
+ if (dragSelectMode == mode)
+ return;
+
+ dragSelectMode = mode;
+
+ GSList *entry = dragSelectModeButtons;
+ while (entry) {
+ GtkToggleButton *button = GTK_TOGGLE_BUTTON (entry->data);
+ DragSelectMode buttonMode =
+ (DragSelectMode) g_object_get_data (
+ G_OBJECT (button),
+ DRAG_SELECT_MODE_KEY);
+ if (buttonMode == dragSelectMode) {
+ gtk_toggle_button_set_active (button, TRUE);
+ break;
+ }
+
+ entry = g_slist_next (entry);
+ }
+}
+
+- (GraphRenderer*) activeRenderer { return renderer; }
+- (void) setActiveRenderer:(GraphRenderer*)r {
+ if (r == renderer)
+ return;
+
+ [r retain];
+ [renderer release];
+ renderer = r;
+
+ state = QuietState;
+}
+
+- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ dragOrigin = pos;
+
+ // we should already be in a quiet state, but no harm in making sure
+ state = QuietState;
+
+ modifyEdge = nil;
+ [self lookForControlPointAt:pos];
+
+ if (modifyEdge == nil) {
+ // we didn't find a control point
+
+ BOOL unionSelect = (mask & unionSelectMask);
+
+ leaderNode = [renderer anyNodeAt:pos];
+ // if we hit a node, deselect other nodes (if Shift is up) and go to move mode
+ if (leaderNode != nil) {
+ BOOL alreadySelected = [[self doc] isNodeSelected:leaderNode];
+ if (!unionSelect && !alreadySelected) {
+ [self deselectAll];
+ }
+ if (unionSelect && alreadySelected) {
+ state = ToggleSelectState;
+ } else {
+ [[[self doc] pickSupport] selectNode:leaderNode];
+ state = MoveSelectedNodesState;
+ oldLeaderPos = [leaderNode point];
+ [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]];
+ }
+ }
+
+ // if mouse did not hit a node, check if mouse hit an edge
+ if (leaderNode == nil) {
+ Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz];
+ if (edge != nil) {
+ BOOL alreadySelected = [[self doc] isEdgeSelected:edge];
+ if (!unionSelect) {
+ [self deselectAll];
+ }
+ if (unionSelect && alreadySelected) {
+ [[[self doc] pickSupport] deselectEdge:edge];
+ } else {
+ [[[self doc] pickSupport] selectEdge:edge];
+ }
+ } else {
+ // if mouse did not hit anything, put us in box mode
+ if (!unionSelect) {
+ [self deselectAll];
+ }
+ [renderer clearHighlightedNodes];
+ state = SelectBoxState;
+ }
+ }
+ }
+}
+
+- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask {
+ if (!(buttons & LeftButton))
+ return;
+
+ Transformer *transformer = [renderer transformer];
+
+ if (state == ToggleSelectState) {
+ state = MoveSelectedNodesState;
+ oldLeaderPos = [leaderNode point];
+ [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]];
+ }
+
+ if (state == SelectBoxState) {
+ [self setSelectionBox:NSRectAroundPoints(dragOrigin, pos)];
+
+ NSEnumerator *enumerator = [[self doc] nodeEnumerator];
+ Node *node;
+ while ((node = [enumerator nextObject]) != nil) {
+ NSPoint nodePos = [transformer toScreen:[node point]];
+ if (NSPointInRect(nodePos, selectionBox)) {
+ [renderer setNode:node highlighted:YES];
+ } else {
+ [renderer setNode:node highlighted:NO];
+ }
+ }
+ } else if (state == MoveSelectedNodesState) {
+ if (leaderNode != nil) {
+ [self shiftNodesByMovingLeader:leaderNode to:pos];
+ NSPoint shiftSoFar;
+ shiftSoFar.x = [leaderNode point].x - oldLeaderPos.x;
+ shiftSoFar.y = [leaderNode point].y - oldLeaderPos.y;
+ [[self doc] shiftNodesUpdate:shiftSoFar];
+ }
+ } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) {
+ // invalidate once before we start changing it: we may be shrinking
+ // the control circles
+ [[self doc] modifyEdgeCheckPoint];
+ if (state == DragEdgeControlPoint1) {
+ [modifyEdge moveCp1To:[transformer fromScreen:pos]
+ withWeightCourseness:0.1f
+ andBendCourseness:15
+ forceLinkControlPoints:(mask & ControlMask)];
+ } else {
+ [modifyEdge moveCp2To:[transformer fromScreen:pos]
+ withWeightCourseness:0.1f
+ andBendCourseness:15
+ forceLinkControlPoints:(mask & ControlMask)];
+ }
+ [[self doc] modifyEdgeCheckPoint];
+ }
+}
+
+- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ if (state == SelectBoxState) {
+ PickSupport *ps = [[self doc] pickSupport];
+ Transformer *transformer = [renderer transformer];
+
+ if (!(mask & unionSelectMask)) {
+ [ps deselectAllNodes];
+ [ps deselectAllEdges];
+ }
+
+ Graph *graph = [[self doc] graph];
+ if (dragSelectMode & DragSelectsNodes) {
+ for (Node *node in [graph nodes]) {
+ NSPoint nodePos = [transformer toScreen:[node point]];
+ if (NSPointInRect(nodePos, selectionBox)) {
+ [ps selectNode:node];
+ }
+ }
+ }
+ if (dragSelectMode & DragSelectsEdges) {
+ for (Edge *edge in [graph edges]) {
+ NSPoint edgePos = [transformer toScreen:[edge mid]];
+ if (NSPointInRect(edgePos, selectionBox)) {
+ [ps selectEdge:edge];
+ }
+ }
+ }
+
+ [self clearSelectionBox];
+ } else if (state == ToggleSelectState) {
+ [[[self doc] pickSupport] deselectNode:leaderNode];
+ leaderNode = nil;
+ } else if (state == MoveSelectedNodesState) {
+ if (NSEqualPoints (oldLeaderPos, [leaderNode point])) {
+ [[self doc] cancelShiftNodes];
+ } else {
+ [[self doc] endShiftNodes];
+ }
+ leaderNode = nil;
+ } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) {
+ // FIXME: check if there was any real change
+ [[self doc] endModifyEdge];
+ }
+}
+
+- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {
+ if (button != LeftButton)
+ return;
+
+ if (state != QuietState) {
+ return;
+ }
+ // convert bend mode on edge under mouse cursor
+ Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz];
+ if (edge != nil) {
+ [[self doc] startModifyEdge:edge];
+ if ([edge bendMode]==EdgeBendModeBasic) {
+ [edge convertBendToAngles];
+ [edge setBendMode:EdgeBendModeInOut];
+ } else {
+ [edge setBendMode:EdgeBendModeBasic];
+ }
+ [[self doc] endModifyEdge];
+
+ [self deselectAllEdges];
+ [[[self doc] pickSupport] selectEdge:edge];
+ }
+}
+
+- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {
+ if (!NSIsEmptyRect (selectionBox)) {
+ [context saveState];
+
+ [context setAntialiasMode:AntialiasDisabled];
+ [context setLineWidth:1.0];
+ [context startPath];
+ [context rect:selectionBox];
+ RColor fColor = MakeRColor (0.8, 0.8, 0.8, 0.2);
+ RColor sColor = MakeSolidRColor (0.6, 0.6, 0.6);
+ [context strokePathWithColor:sColor andFillWithColor:fColor];
+
+ [context restoreState];
+ }
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ NSString *mode = [config stringEntry:@"Drag select mode"
+ inGroup:@"SelectTool"];
+ if ([mode isEqualToString:@"nodes"]) {
+ [self setDragSelectMode:DragSelectsNodes];
+ } else if ([mode isEqualToString:@"edges"]) {
+ [self setDragSelectMode:DragSelectsEdges];
+ } else if ([mode isEqualToString:@"both"]) {
+ [self setDragSelectMode:DragSelectsBoth];
+ }
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ switch (dragSelectMode) {
+ case DragSelectsNodes:
+ [config setStringEntry:@"Drag select mode"
+ inGroup:@"SelectTool"
+ value:@"nodes"];
+ break;
+ case DragSelectsEdges:
+ [config setStringEntry:@"Drag select mode"
+ inGroup:@"SelectTool"
+ value:@"edges"];
+ break;
+ case DragSelectsBoth:
+ [config setStringEntry:@"Drag select mode"
+ inGroup:@"SelectTool"
+ value:@"both"];
+ break;
+ }
+}
+
+@end
+
+@implementation SelectTool (Private)
+- (TikzDocument*) doc {
+ return [renderer document];
+}
+
+- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to {
+ Transformer *transformer = [renderer transformer];
+
+ NSPoint from = [transformer toScreen:[leader point]];
+ //to = [[renderer grid] snapScreenPoint:to];
+ float dx = to.x - from.x;
+ float dy = to.y - from.y;
+
+ for (Node *node in [[[self doc] pickSupport] selectedNodes]) {
+ NSPoint p = [transformer toScreen:[node point]];
+ p.x += dx;
+ p.y += dy;
+ p = [[renderer grid] snapScreenPoint:p];
+ [node setPoint:[transformer fromScreen:p]];
+ }
+}
+
+- (void) deselectAllNodes {
+ [[[self doc] pickSupport] deselectAllNodes];
+}
+
+- (void) deselectAllEdges {
+ [[[self doc] pickSupport] deselectAllEdges];
+}
+
+- (void) deselectAll {
+ [[[self doc] pickSupport] deselectAllNodes];
+ [[[self doc] pickSupport] deselectAllEdges];
+}
+
+- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p {
+ return (NSDistanceBetweenPoints(c, p) <= r);
+}
+
+- (void) lookForControlPointAt:(NSPoint)pos {
+ const float cpr = [Edge controlPointRadius];
+ for (Edge *e in [[[self doc] pickSupport] selectedEdges]) {
+ NSPoint cp1 = [[renderer transformer] toScreen:[e cp1]];
+ if ([self circleWithCenter:cp1 andRadius:cpr containsPoint:pos]) {
+ state = DragEdgeControlPoint1;
+ modifyEdge = e;
+ [[self doc] startModifyEdge:e];
+ return;
+ }
+ NSPoint cp2 = [[renderer transformer] toScreen:[e cp2]];
+ if ([self circleWithCenter:cp2 andRadius:cpr containsPoint:pos]) {
+ state = DragEdgeControlPoint2;
+ modifyEdge = e;
+ [[self doc] startModifyEdge:e];
+ return;
+ }
+ }
+}
+
+- (void) setSelectionBox:(NSRect)box {
+ NSRect invRect = NSUnionRect (selectionBox, box);
+ selectionBox = box;
+ [renderer invalidateRect:NSInsetRect (invRect, -2, -2)];
+}
+
+- (void) clearSelectionBox {
+ NSRect oldRect = selectionBox;
+
+ NSRect emptyRect;
+ selectionBox = emptyRect;
+
+ [renderer invalidateRect:NSInsetRect (oldRect, -2, -2)];
+ [renderer clearHighlightedNodes];
+}
+
+- (BOOL) selectionBoxContainsNode:(Node*)node {
+ if (!NSIsEmptyRect (selectionBox))
+ return NO;
+
+ Transformer *transf = [[renderer surface] transformer];
+ NSPoint screenPt = [transf toScreen:[node point]];
+ return NSPointInRect(screenPt, selectionBox);
+}
+@end
+
+static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool) {
+ if (gtk_toggle_button_get_active (button)) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ DragSelectMode buttonMode =
+ (DragSelectMode) g_object_get_data (
+ G_OBJECT (button),
+ DRAG_SELECT_MODE_KEY);
+ [tool setDragSelectMode:buttonMode];
+ [pool drain];
+ }
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/SelectionPane.h b/tikzit/src/gtk/SelectionPane.h
new file mode 100644
index 0000000..57a766a
--- /dev/null
+++ b/tikzit/src/gtk/SelectionPane.h
@@ -0,0 +1,56 @@
+/*
+ * 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 "TZFoundation.h"
+#import <gtk/gtk.h>
+
+@class Configuration;
+@class EdgeStylesModel;
+@class NodeStylesModel;
+@class StyleManager;
+@class TikzDocument;
+
+@interface SelectionPane: NSObject {
+ TikzDocument *document;
+
+ NodeStylesModel *nodeStylesModel;
+ EdgeStylesModel *edgeStylesModel;
+
+ GtkWidget *layout;
+
+ GtkWidget *nodeStyleCombo;
+ GtkWidget *applyNodeStyleButton;
+ GtkWidget *clearNodeStyleButton;
+ GtkWidget *edgeStyleCombo;
+ GtkWidget *applyEdgeStyleButton;
+ GtkWidget *clearEdgeStyleButton;
+}
+
+@property (retain) TikzDocument *document;
+@property (assign) BOOL visible;
+@property (readonly) GtkWidget *gtkWidget;
+
+- (id) initWithStyleManager:(StyleManager*)mgr;
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm;
+
+- (void) loadConfiguration:(Configuration*)config;
+- (void) saveConfiguration:(Configuration*)config;
+
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/SelectionPane.m b/tikzit/src/gtk/SelectionPane.m
new file mode 100644
index 0000000..2931258
--- /dev/null
+++ b/tikzit/src/gtk/SelectionPane.m
@@ -0,0 +1,432 @@
+/*
+ * 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 "SelectionPane.h"
+
+#import "Configuration.h"
+#import "EdgeStylesModel.h"
+#import "NodeStylesModel.h"
+#import "TikzDocument.h"
+
+#import "gtkhelpers.h"
+
+// {{{ Internal interfaces
+
+static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane);
+static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane);
+static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane);
+static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane);
+static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane);
+static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane);
+
+static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col);
+
+@interface SelectionPane (Notifications)
+- (void) nodeSelectionChanged:(NSNotification*)n;
+- (void) edgeSelectionChanged:(NSNotification*)n;
+@end
+
+@interface SelectionPane (Private)
+- (void) _updateNodeStyleButtons;
+- (void) _updateEdgeStyleButtons;
+- (NodeStyle*) _selectedNodeStyle;
+- (EdgeStyle*) _selectedEdgeStyle;
+- (void) _applyNodeStyle;
+- (void) _clearNodeStyle;
+- (void) _applyEdgeStyle;
+- (void) _clearEdgeStyle;
+@end
+
+// }}}
+// {{{ API
+
+@implementation SelectionPane
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithStyleManager:(StyleManager*)sm {
+ return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm]
+ andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]];
+}
+
+- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm
+ andEdgeStylesModel:(EdgeStylesModel*)esm {
+ self = [super init];
+
+ if (self) {
+ nodeStylesModel = [nsm retain];
+ edgeStylesModel = [esm retain];
+
+ layout = gtk_vbox_new (FALSE, 0);
+ g_object_ref_sink (layout);
+ gtk_widget_show (layout);
+
+ GtkWidget *label = gtk_label_new ("Selection");
+ label_set_bold (GTK_LABEL (label));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (layout), label,
+ FALSE, FALSE, 0);
+
+ GtkWidget *lvl1_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (layout), lvl1_box,
+ FALSE, FALSE, 3);
+
+ nodeStyleCombo = gtk_combo_box_new_with_model ([nodeStylesModel model]);
+ g_object_ref_sink (nodeStyleCombo);
+ setup_style_cell_layout (GTK_CELL_LAYOUT (nodeStyleCombo),
+ NODE_STYLES_ICON_COL,
+ NODE_STYLES_NAME_COL);
+ g_signal_connect (G_OBJECT (nodeStyleCombo),
+ "changed",
+ G_CALLBACK (node_style_changed_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl1_box), nodeStyleCombo,
+ FALSE, FALSE, 0);
+
+ GtkWidget *lvl2_box = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box,
+ FALSE, FALSE, 0);
+
+ applyNodeStyleButton = gtk_button_new_with_label ("Apply");
+ g_object_ref_sink (applyNodeStyleButton);
+ gtk_widget_set_tooltip_text (applyNodeStyleButton, "Apply style to selected nodes");
+ gtk_widget_set_sensitive (applyNodeStyleButton, FALSE);
+ g_signal_connect (G_OBJECT (applyNodeStyleButton),
+ "clicked",
+ G_CALLBACK (apply_node_style_button_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl2_box), applyNodeStyleButton,
+ FALSE, FALSE, 0);
+
+ clearNodeStyleButton = gtk_button_new_with_label ("Clear");
+ g_object_ref_sink (clearNodeStyleButton);
+ gtk_widget_set_tooltip_text (clearNodeStyleButton, "Clear style from selected nodes");
+ gtk_widget_set_sensitive (clearNodeStyleButton, FALSE);
+ g_signal_connect (G_OBJECT (clearNodeStyleButton),
+ "clicked",
+ G_CALLBACK (clear_node_style_button_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl2_box), clearNodeStyleButton,
+ FALSE, FALSE, 0);
+
+ lvl1_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (layout), lvl1_box,
+ FALSE, FALSE, 3);
+
+ edgeStyleCombo = gtk_combo_box_new_with_model ([edgeStylesModel model]);
+ g_object_ref_sink (edgeStyleCombo);
+ setup_style_cell_layout (GTK_CELL_LAYOUT (edgeStyleCombo),
+ EDGE_STYLES_ICON_COL,
+ EDGE_STYLES_NAME_COL);
+ g_signal_connect (G_OBJECT (edgeStyleCombo),
+ "changed",
+ G_CALLBACK (edge_style_changed_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl1_box), edgeStyleCombo,
+ FALSE, FALSE, 0);
+
+ lvl2_box = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box,
+ FALSE, FALSE, 0);
+
+ applyEdgeStyleButton = gtk_button_new_with_label ("Apply");
+ g_object_ref_sink (applyEdgeStyleButton);
+ gtk_widget_set_tooltip_text (applyEdgeStyleButton, "Apply style to selected edges");
+ gtk_widget_set_sensitive (applyEdgeStyleButton, FALSE);
+ g_signal_connect (G_OBJECT (applyEdgeStyleButton),
+ "clicked",
+ G_CALLBACK (apply_edge_style_button_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl2_box), applyEdgeStyleButton,
+ FALSE, FALSE, 0);
+
+ clearEdgeStyleButton = gtk_button_new_with_label ("Clear");
+ g_object_ref_sink (clearEdgeStyleButton);
+ gtk_widget_set_tooltip_text (clearEdgeStyleButton, "Clear style from selected edges");
+ gtk_widget_set_sensitive (clearEdgeStyleButton, FALSE);
+ g_signal_connect (G_OBJECT (clearEdgeStyleButton),
+ "clicked",
+ G_CALLBACK (clear_edge_style_button_cb),
+ self);
+ gtk_box_pack_start (GTK_BOX (lvl2_box), clearEdgeStyleButton,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (layout);
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ g_object_unref (nodeStyleCombo);
+ g_object_unref (applyNodeStyleButton);
+ g_object_unref (clearNodeStyleButton);
+ g_object_unref (edgeStyleCombo);
+ g_object_unref (applyEdgeStyleButton);
+ g_object_unref (clearEdgeStyleButton);
+
+ g_object_unref (layout);
+
+ [nodeStylesModel release];
+ [edgeStylesModel release];
+
+ [document release];
+
+ [super dealloc];
+}
+
+- (TikzDocument*) document {
+ return document;
+}
+
+- (void) setDocument:(TikzDocument*)doc {
+ if (document != nil) {
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:nil
+ object:[document pickSupport]];
+ }
+
+ [doc retain];
+ [document release];
+ document = doc;
+
+ if (doc != nil) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(nodeSelectionChanged:)
+ name:@"NodeSelectionChanged" object:[doc pickSupport]];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(edgeSelectionChanged:)
+ name:@"EdgeSelectionChanged" object:[doc pickSupport]];
+ }
+
+ [self _updateNodeStyleButtons];
+ [self _updateEdgeStyleButtons];
+}
+
+- (BOOL) visible {
+ return gtk_widget_get_visible (layout);
+}
+
+- (void) setVisible:(BOOL)visible {
+ gtk_widget_set_visible (layout, visible);
+}
+
+- (GtkWidget*) gtkWidget {
+ return layout;
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ NSString *nodeStyleName = [config stringEntry:@"SelectedNodeStyle"
+ inGroup:@"SelectionPane"
+ withDefault:nil];
+ NodeStyle *nodeStyle = [[nodeStylesModel styleManager] nodeStyleForName:nodeStyleName];
+ if (nodeStyle == nil) {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (nodeStyleCombo), -1);
+ } else {
+ GtkTreeIter *iter = [nodeStylesModel iterFromStyle:nodeStyle];
+ if (iter) {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (nodeStyleCombo), iter);
+ gtk_tree_iter_free (iter);
+ }
+ }
+
+ NSString *edgeStyleName = [config stringEntry:@"SelectedEdgeStyle"
+ inGroup:@"SelectionPane"
+ withDefault:nil];
+ EdgeStyle *edgeStyle = [[edgeStylesModel styleManager] edgeStyleForName:edgeStyleName];
+ if (edgeStyle == nil) {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (edgeStyleCombo), -1);
+ } else {
+ GtkTreeIter *iter = [edgeStylesModel iterFromStyle:edgeStyle];
+ if (iter) {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (edgeStyleCombo), iter);
+ gtk_tree_iter_free (iter);
+ }
+ }
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ [config setStringEntry:@"SelectedNodeStyle"
+ inGroup:@"SelectionPane"
+ value:[[self _selectedNodeStyle] name]];
+ [config setStringEntry:@"SelectedEdgeStyle"
+ inGroup:@"SelectionPane"
+ value:[[self _selectedEdgeStyle] name]];
+}
+
+@end
+
+// }}}
+// {{{ Notifications
+
+@implementation SelectionPane (Notifications)
+- (void) nodeSelectionChanged:(NSNotification*)n {
+ [self _updateNodeStyleButtons];
+}
+
+- (void) edgeSelectionChanged:(NSNotification*)n {
+ [self _updateEdgeStyleButtons];
+}
+@end
+
+// }}}
+// {{{ Private
+
+@implementation SelectionPane (Private)
+- (void) _updateNodeStyleButtons {
+ gboolean hasNodeSelection = [[[document pickSupport] selectedNodes] count] > 0;
+
+ gtk_widget_set_sensitive (applyNodeStyleButton,
+ hasNodeSelection && [self _selectedNodeStyle] != nil);
+ gtk_widget_set_sensitive (clearNodeStyleButton, hasNodeSelection);
+}
+
+- (void) _updateEdgeStyleButtons {
+ gboolean hasEdgeSelection = [[[document pickSupport] selectedEdges] count] > 0;
+
+ gtk_widget_set_sensitive (applyEdgeStyleButton,
+ hasEdgeSelection && [self _selectedEdgeStyle] != nil);
+ gtk_widget_set_sensitive (clearEdgeStyleButton, hasEdgeSelection);
+}
+
+- (NodeStyle*) _selectedNodeStyle {
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (nodeStyleCombo), &iter)) {
+ return [nodeStylesModel styleFromIter:&iter];
+ } else {
+ return nil;
+ }
+}
+
+- (EdgeStyle*) _selectedEdgeStyle {
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (edgeStyleCombo), &iter)) {
+ return [edgeStylesModel styleFromIter:&iter];
+ } else {
+ return nil;
+ }
+}
+
+- (void) _applyNodeStyle {
+ [document startModifyNodes:[[document pickSupport] selectedNodes]];
+
+ NodeStyle *style = [self _selectedNodeStyle];
+ for (Node *node in [[document pickSupport] selectedNodes]) {
+ [node setStyle:style];
+ }
+
+ [document endModifyNodes];
+}
+
+- (void) _clearNodeStyle {
+ [document startModifyNodes:[[document pickSupport] selectedNodes]];
+
+ for (Node *node in [[document pickSupport] selectedNodes]) {
+ [node setStyle:nil];
+ }
+
+ [document endModifyNodes];
+}
+
+- (void) _applyEdgeStyle {
+ [document startModifyEdges:[[document pickSupport] selectedEdges]];
+
+ EdgeStyle *style = [self _selectedEdgeStyle];
+ for (Edge *edge in [[document pickSupport] selectedEdges]) {
+ [edge setStyle:style];
+ }
+
+ [document endModifyEdges];
+}
+
+- (void) _clearEdgeStyle {
+ [document startModifyEdges:[[document pickSupport] selectedEdges]];
+
+ for (Edge *edge in [[document pickSupport] selectedEdges]) {
+ [edge setStyle:nil];
+ }
+
+ [document endModifyEdges];
+}
+@end
+
+// }}}
+// {{{ GTK+ callbacks
+
+static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _updateNodeStyleButtons];
+ [pool drain];
+}
+
+static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _applyNodeStyle];
+ [pool drain];
+}
+
+static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _clearNodeStyle];
+ [pool drain];
+}
+
+static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _updateEdgeStyleButtons];
+ [pool drain];
+}
+
+static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _applyEdgeStyle];
+ [pool drain];
+}
+
+static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [pane _clearEdgeStyle];
+ [pool drain];
+}
+
+// }}}
+//
+static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col) {
+ gtk_cell_layout_clear (cell_layout);
+ GtkCellRenderer *pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (cell_layout, pixbuf_renderer, FALSE);
+ gtk_cell_layout_set_attributes (
+ cell_layout,
+ pixbuf_renderer,
+ "pixbuf", pixbuf_col,
+ NULL);
+ GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (cell_layout, text_renderer, FALSE);
+ gtk_cell_layout_set_attributes (
+ cell_layout,
+ text_renderer,
+ "text", name_col,
+ NULL);
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/SettingsDialog.h b/tikzit/src/gtk/SettingsDialog.h
index a159afb..d937774 100644
--- a/tikzit/src/gtk/SettingsDialog.h
+++ b/tikzit/src/gtk/SettingsDialog.h
@@ -36,6 +36,7 @@
- (void) setParentWindow:(GtkWindow*)parent;
+- (void) present;
- (void) show;
- (void) hide;
- (BOOL) isVisible;
diff --git a/tikzit/src/gtk/SettingsDialog.m b/tikzit/src/gtk/SettingsDialog.m
index aa5517f..11a0d0d 100644
--- a/tikzit/src/gtk/SettingsDialog.m
+++ b/tikzit/src/gtk/SettingsDialog.m
@@ -82,6 +82,12 @@ static void cancel_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog)
}
}
+- (void) present {
+ [self loadUi];
+ [self revert];
+ gtk_window_present (GTK_WINDOW (window));
+}
+
- (void) show {
[self loadUi];
[self revert];
diff --git a/tikzit/src/gtk/StyleManager+Storage.m b/tikzit/src/gtk/StyleManager+Storage.m
index 112b885..f4c8232 100644
--- a/tikzit/src/gtk/StyleManager+Storage.m
+++ b/tikzit/src/gtk/StyleManager+Storage.m
@@ -27,6 +27,7 @@ static NSString *edgeStyleGroupPrefix = @"EdgeStyle ";
- (void) loadStylesUsingConfigurationName:(NSString*)name {
if (![Configuration configurationExistsWithName:name]) {
+ NSLog(@"No styles config found");
return;
}
NSError *error = nil;
diff --git a/tikzit/src/gtk/StylesPane.m b/tikzit/src/gtk/StylesPane.m
deleted file mode 100644
index 455878a..0000000
--- a/tikzit/src/gtk/StylesPane.m
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#import "StylesPane.h"
-
-#import "Configuration.h"
-#import "NodeStylesPalette.h"
-#import "EdgeStylesPalette.h"
-
-@interface StylesPane (Private)
-- (void) _addSplitter;
-- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents;
-@end
-
-// {{{ API
-@implementation StylesPane
-
-@synthesize widget=stylesPane;
-
-- (id) init {
- [self dealloc];
- self = nil;
- return nil;
-}
-
-- (id) initWithManager:(StyleManager*)m {
- self = [super init];
-
- if (self) {
- nodeStyles = [[NodeStylesPalette alloc] initWithManager:m];
- edgeStyles = [[EdgeStylesPalette alloc] initWithManager:m];
-
- stylesPane = gtk_vbox_new (FALSE, 0);
- g_object_ref_sink (stylesPane);
-
- nodeStylesExpander = [self _addExpanderWithName:"Node styles"
- contents:[nodeStyles widget]];
- g_object_ref_sink (nodeStylesExpander);
- [self _addSplitter];
-
- edgeStylesExpander = [self _addExpanderWithName:"Edge styles"
- contents:[edgeStyles widget]];
- g_object_ref_sink (edgeStylesExpander);
- [self _addSplitter];
- }
-
- return self;
-}
-
-- (void) dealloc {
- g_object_unref (stylesPane);
-
- [nodeStyles release];
- [edgeStyles release];
-
- [super dealloc];
-}
-
-- (TikzDocument*) document {
- return [nodeStyles document];
-}
-
-- (void) setDocument:(TikzDocument*)doc {
- [nodeStyles setDocument:doc];
- [edgeStyles setDocument:doc];
-}
-
-- (StyleManager*) styleManager {
- return [nodeStyles styleManager];
-}
-
-- (void) setStyleManager:(StyleManager*)m {
- [nodeStyles setStyleManager:m];
- [edgeStyles setStyleManager:m];
-}
-
-- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group {
- gtk_expander_set_expanded (nodeStylesExpander,
- [file booleanEntry:@"node-styles-expanded"
- inGroup:group
- withDefault:YES]);
- gtk_expander_set_expanded (edgeStylesExpander,
- [file booleanEntry:@"edge-styles-expanded"
- inGroup:group
- withDefault:NO]);
-}
-
-- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group {
- [file setBooleanEntry:@"node-styles-expanded"
- inGroup:group
- value:gtk_expander_get_expanded (nodeStylesExpander)];
- [file setBooleanEntry:@"edge-styles-expanded"
- inGroup:group
- value:gtk_expander_get_expanded (edgeStylesExpander)];
-}
-
-- (void) favourNodeStyles {
- if (!gtk_expander_get_expanded (nodeStylesExpander)) {
- if (gtk_expander_get_expanded (edgeStylesExpander)) {
- gtk_expander_set_expanded (edgeStylesExpander, FALSE);
- gtk_expander_set_expanded (nodeStylesExpander, TRUE);
- }
- }
-}
-
-- (void) favourEdgeStyles {
- if (!gtk_expander_get_expanded (edgeStylesExpander)) {
- if (gtk_expander_get_expanded (nodeStylesExpander)) {
- gtk_expander_set_expanded (nodeStylesExpander, FALSE);
- gtk_expander_set_expanded (edgeStylesExpander, TRUE);
- }
- }
-}
-
-@end
-
-// }}}
-// {{{ Private
-
-@implementation StylesPane (Private)
-- (void) _addSplitter {
- GtkWidget *split = gtk_hseparator_new ();
- gtk_box_pack_start (GTK_BOX (stylesPane),
- split,
- FALSE, // expand
- FALSE, // fill
- 0); // padding
- gtk_widget_show (split);
-}
-
-- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents {
- GtkWidget *exp = gtk_expander_new (name);
- gtk_box_pack_start (GTK_BOX (stylesPane),
- exp,
- FALSE, // expand
- TRUE, // fill
- 0); // padding
- gtk_widget_show (exp);
- gtk_container_set_border_width (GTK_CONTAINER (contents), 6);
- gtk_container_add (GTK_CONTAINER (exp), contents);
- gtk_widget_show (contents);
- return GTK_EXPANDER (exp);
-}
-@end
-
-// }}}
-
-// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/Surface.h b/tikzit/src/gtk/Surface.h
index 449721f..b6d8d2e 100644
--- a/tikzit/src/gtk/Surface.h
+++ b/tikzit/src/gtk/Surface.h
@@ -33,7 +33,7 @@ typedef enum {
@protocol Surface;
-@protocol RenderDelegate
+@protocol RenderDelegate <NSObject>
- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface;
@end
@@ -47,7 +47,7 @@ typedef enum {
* The surface should send a "SurfaceSizeChanged" notification
* when the width or height changes.
*/
-@protocol Surface
+@protocol Surface <NSObject>
/**
* The width of the surface, in surface units
diff --git a/tikzit/src/gtk/TikzDocument.h b/tikzit/src/gtk/TikzDocument.h
index 79a9b17..da73fac 100644
--- a/tikzit/src/gtk/TikzDocument.h
+++ b/tikzit/src/gtk/TikzDocument.h
@@ -128,26 +128,9 @@
- (void) removeSelected;
- (void) addNode:(Node*)node;
-/*!
- * Convenience function to add a node in the active style
- * at the given point.
- *
- * @param pos the position (in graph co-ordinates) of the new node
- * @return the added node
- */
-- (Node*) addNodeAt:(NSPoint)pos;
- (void) removeNode:(Node*)node;
- (void) addEdge:(Edge*)edge;
- (void) removeEdge:(Edge*)edge;
-/*!
- * Convenience function to add an edge in the active style
- * between the given nodes.
- *
- * @param source the source node
- * @param target the target node
- * @return the added edge
- */
-- (Edge*) addEdgeFrom:(Node*)source to:(Node*)target;
- (void) shiftSelectedNodesByPoint:(NSPoint)offset;
- (void) insertGraph:(Graph*)g;
- (void) flipSelectedNodesHorizontally;
diff --git a/tikzit/src/gtk/TikzDocument.m b/tikzit/src/gtk/TikzDocument.m
index 2016d2a..30d604a 100644
--- a/tikzit/src/gtk/TikzDocument.m
+++ b/tikzit/src/gtk/TikzDocument.m
@@ -669,13 +669,6 @@
[self completedGraphChange:change withName:@"Add node"];
}
-- (Node*) addNodeAt:(NSPoint)pos {
- Node *node = [Node nodeWithPoint:pos];
- [node setStyle:[styleManager activeNodeStyle]];
- [self addNode:node];
- return node;
-}
-
- (void) removeNode:(Node*)node {
[pickSupport deselectNode:node];
GraphChange *change = [graph removeNode:node];
@@ -693,13 +686,6 @@
[self completedGraphChange:change withName:@"Remove edge"];
}
-- (Edge*) addEdgeFrom:(Node*)source to:(Node*)target {
- Edge *edge = [Edge edgeWithSource:source andTarget:target];
- [edge setStyle:[styleManager activeEdgeStyle]];
- [self addEdge:edge];
- return edge;
-}
-
- (void) shiftSelectedNodesByPoint:(NSPoint)offset {
if ([[pickSupport selectedNodes] count] > 0) {
GraphChange *change = [graph shiftNodes:[pickSupport selectedNodes] byPoint:offset];
diff --git a/tikzit/src/gtk/Tool.h b/tikzit/src/gtk/Tool.h
new file mode 100644
index 0000000..22c983e
--- /dev/null
+++ b/tikzit/src/gtk/Tool.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import "InputDelegate.h"
+#import "Surface.h"
+
+#import <gtk/gtk.h>
+#import <gdk-pixbuf/gdk-pixdata.h>
+
+@class Configuration;
+@class GraphRenderer;
+@protocol InputDelegate;
+@protocol RenderDelegate;
+
+@protocol Tool <RenderDelegate,InputDelegate>
+@property (readonly) NSString *name;
+@property (readonly) const gchar *stockId;
+@property (readonly) NSString *helpText;
+@property (readonly) NSString *shortcut;
+@property (retain) GraphRenderer *activeRenderer;
+@property (readonly) GtkWidget *configurationWidget;
+- (void) loadConfiguration:(Configuration*)config;
+- (void) saveConfiguration:(Configuration*)config;
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/ToolBox.h b/tikzit/src/gtk/ToolBox.h
new file mode 100644
index 0000000..7076417
--- /dev/null
+++ b/tikzit/src/gtk/ToolBox.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "TZFoundation.h"
+#import <gtk/gtk.h>
+
+@class Configuration;
+@class Window;
+@protocol Tool;
+
+@interface ToolBox : NSObject {
+ GtkWidget *window;
+ GtkToolItemGroup *toolGroup;
+ GtkWidget *titleLabel;
+ GtkWidget *configWidgetContainer;
+ GtkWidget *configWidget;
+}
+
+@property (assign) id<Tool> selectedTool;
+
+- (id) initWithTools:(NSArray*)tools;
+
+- (void) show;
+- (void) present;
+- (void) setTransientFor:(Window*)w;
+
+- (void) loadConfiguration:(Configuration*)config;
+- (void) saveConfiguration:(Configuration*)config;
+@end
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/ToolBox.m b/tikzit/src/gtk/ToolBox.m
new file mode 100644
index 0000000..5d8f936
--- /dev/null
+++ b/tikzit/src/gtk/ToolBox.m
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "ToolBox.h"
+
+#import "Application.h"
+#import "Configuration.h"
+#import "Tool.h"
+#import "Window.h"
+
+#import "gtkhelpers.h"
+#import "tztoolpalette.h"
+
+static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox);
+
+#define TOOL_DATA_KEY "tikzit-tool"
+
+@implementation ToolBox
+
+- (id) init {
+ [self release];
+ return nil;
+}
+
+- (id) initWithTools:(NSArray*)tools {
+ self = [super init];
+
+ if (self) {
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_object_ref_sink (window);
+ gtk_window_set_title (GTK_WINDOW (window), "Toolbox");
+ gtk_window_set_role (GTK_WINDOW (window), "toolbox");
+ gtk_window_set_type_hint (GTK_WINDOW (window),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ gtk_window_set_deletable (GTK_WINDOW (window), FALSE);
+
+ GtkWidget *mainLayout = gtk_vbox_new (FALSE, 5);
+ gtk_widget_show (mainLayout);
+ gtk_container_add (GTK_CONTAINER (window), mainLayout);
+
+ GtkWidget *toolPalette = tz_tool_palette_new ();
+ gtk_widget_show (toolPalette);
+ gtk_box_pack_start (GTK_BOX (mainLayout),
+ toolPalette,
+ FALSE,
+ FALSE,
+ 0);
+
+ toolGroup = GTK_TOOL_ITEM_GROUP (gtk_tool_item_group_new ("Tools"));
+ g_object_ref_sink (G_OBJECT (toolGroup));
+ gtk_tool_item_group_set_label_widget (
+ toolGroup,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (toolPalette), GTK_WIDGET (toolGroup));
+ gtk_widget_show (GTK_WIDGET (toolGroup));
+
+ GSList *item_group = NULL;
+ for (id<Tool> tool in tools) {
+ NSString *tooltip = [NSString stringWithFormat:
+ @"%@: %@ (%@)",
+ [tool name], [tool helpText], [tool shortcut]];
+ GtkToolItem *item = gtk_radio_tool_button_new_from_stock (
+ item_group,
+ [tool stockId]);
+ gtk_tool_item_set_tooltip_text (item, [tooltip UTF8String]);
+ item_group = gtk_radio_tool_button_get_group (
+ GTK_RADIO_TOOL_BUTTON (item));
+ gtk_tool_item_group_insert (
+ toolGroup,
+ item,
+ -1);
+ gtk_widget_show (GTK_WIDGET (item));
+ g_object_set_data_full (
+ G_OBJECT(item),
+ TOOL_DATA_KEY,
+ [tool retain],
+ release_obj);
+
+ g_signal_connect (item, "toggled",
+ G_CALLBACK (tool_button_toggled_cb),
+ self);
+ }
+
+ GtkWidget *sep = gtk_hseparator_new ();
+ gtk_widget_show (sep);
+ gtk_box_pack_start (GTK_BOX (mainLayout),
+ sep,
+ FALSE,
+ FALSE,
+ 0);
+
+ titleLabel = gtk_label_new ("");
+ g_object_ref_sink (titleLabel);
+ gtk_widget_show (titleLabel);
+
+ PangoAttrList *attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs,
+ pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD));
+ gtk_label_set_attributes (GTK_LABEL (titleLabel), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_box_pack_start (GTK_BOX (mainLayout),
+ titleLabel,
+ FALSE,
+ FALSE,
+ 0);
+
+ configWidgetContainer = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+ g_object_ref_sink (configWidgetContainer);
+ gtk_widget_show (configWidgetContainer);
+ gtk_box_pack_start (GTK_BOX (mainLayout),
+ configWidgetContainer,
+ TRUE,
+ TRUE,
+ 0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (configWidgetContainer),
+ 5, 5, 5, 5);
+
+ gint button_width;
+ gint button_height;
+
+ if (tz_tool_palette_get_button_size (TZ_TOOL_PALETTE (toolPalette),
+ &button_width, &button_height))
+ {
+ GdkGeometry geometry;
+
+ geometry.min_width = 2 * button_width;
+ geometry.min_height = -1;
+ geometry.base_width = button_width;
+ geometry.base_height = 0;
+ geometry.width_inc = button_width;
+ geometry.height_inc = 1;
+
+ gtk_window_set_geometry_hints (GTK_WINDOW (window),
+ NULL,
+ &geometry,
+ GDK_HINT_MIN_SIZE |
+ GDK_HINT_BASE_SIZE |
+ GDK_HINT_RESIZE_INC |
+ GDK_HINT_USER_POS);
+ }
+ gtk_window_set_default_size (GTK_WINDOW (window), button_width * 5, 500);
+
+ // hack to position the toolbox window somewhere sensible
+ // (upper left)
+ gtk_window_parse_geometry (GTK_WINDOW (window), "+0+0");
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ if (window) {
+ g_object_unref (G_OBJECT (toolGroup));
+ g_object_unref (G_OBJECT (titleLabel));
+ g_object_unref (G_OBJECT (configWidgetContainer));
+ if (configWidget)
+ g_object_unref (G_OBJECT (configWidget));
+ gtk_widget_destroy (window);
+ g_object_unref (G_OBJECT (window));
+ }
+
+ [super dealloc];
+}
+
+- (id<Tool>) selectedTool {
+ guint count = gtk_tool_item_group_get_n_items (toolGroup);
+ for (guint i = 0; i < count; ++i) {
+ GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i);
+ if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (item))) {
+ return (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY);
+ }
+ }
+ return nil;
+}
+
+- (void) _setToolWidget:(GtkWidget*)widget {
+ if (configWidget) {
+ gtk_widget_hide (configWidget);
+ gtk_container_remove (GTK_CONTAINER (configWidgetContainer),
+ configWidget);
+ g_object_unref (configWidget);
+ }
+ configWidget = widget;
+ if (configWidget) {
+ g_object_ref (configWidget);
+ gtk_container_add (GTK_CONTAINER (configWidgetContainer),
+ configWidget);
+ gtk_widget_show (configWidget);
+ }
+}
+
+- (void) setSelectedTool:(id<Tool>)tool {
+ guint count = gtk_tool_item_group_get_n_items (toolGroup);
+ for (guint i = 0; i < count; ++i) {
+ GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i);
+ id<Tool> data = (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY);
+ if (data == tool) {
+ gtk_toggle_tool_button_set_active (
+ GTK_TOGGLE_TOOL_BUTTON (item),
+ TRUE);
+ break;
+ }
+ }
+ gtk_label_set_label (GTK_LABEL (titleLabel),
+ [[tool name] UTF8String]);
+ [self _setToolWidget:[tool configurationWidget]];
+}
+
+- (void) show {
+ gtk_widget_show (window);
+}
+
+- (void) present {
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+- (void) setTransientFor:(Window*)parent {
+ gtk_window_set_transient_for (GTK_WINDOW (window), [parent gtkWindow]);
+}
+
+- (void) loadConfiguration:(Configuration*)config {
+ if ([config hasGroup:@"ToolBox"]) {
+ tz_restore_window (GTK_WINDOW (window),
+ [config integerEntry:@"x" inGroup:@"ToolBox"],
+ [config integerEntry:@"y" inGroup:@"ToolBox"],
+ [config integerEntry:@"w" inGroup:@"ToolBox"],
+ [config integerEntry:@"h" inGroup:@"ToolBox"]);
+ }
+}
+
+- (void) saveConfiguration:(Configuration*)config {
+ gint x, y, w, h;
+
+ gtk_window_get_position (GTK_WINDOW (window), &x, &y);
+ gtk_window_get_size (GTK_WINDOW (window), &w, &h);
+
+ [config setIntegerEntry:@"x" inGroup:@"ToolBox" value:x];
+ [config setIntegerEntry:@"y" inGroup:@"ToolBox" value:y];
+ [config setIntegerEntry:@"w" inGroup:@"ToolBox" value:w];
+ [config setIntegerEntry:@"h" inGroup:@"ToolBox" value:h];
+}
+
+@end
+
+static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox) {
+ if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (widget))) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ id<Tool> tool = (id)g_object_get_data (G_OBJECT(widget), TOOL_DATA_KEY);
+ [app setActiveTool:tool];
+ NSDictionary *userInfo = [NSDictionary
+ dictionaryWithObject:tool
+ forKey:@"tool"];
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"ToolSelectionChanged"
+ object:toolBox
+ userInfo:userInfo];
+
+ [pool drain];
+ }
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/WidgetSurface.h b/tikzit/src/gtk/WidgetSurface.h
index 32c8222..667749f 100644
--- a/tikzit/src/gtk/WidgetSurface.h
+++ b/tikzit/src/gtk/WidgetSurface.h
@@ -17,6 +17,7 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
+#import <InputDelegate.h>
#import <Surface.h>
/**
@@ -26,27 +27,22 @@
GtkWidget *widget;
Transformer *transformer;
id <RenderDelegate> renderDelegate;
- id inputDelegate;
+ id <InputDelegate> inputDelegate;
BOOL keepCentered;
- BOOL grabsFocusOnClick;
+ BOOL buttonPressesRequired;
CGFloat defaultScale;
NSSize lastKnownSize;
}
+@property (assign) BOOL canFocus;
+@property (assign) BOOL keepCentered;
+@property (assign) CGFloat defaultScale;
+
- (id) initWithWidget:(GtkWidget*)widget;
- (GtkWidget*) widget;
-- (id) inputDelegate;
-- (void) setInputDelegate:(id)delegate;
-
-- (BOOL) keepCentered;
-- (void) setKeepCentered:(BOOL)centered;
-
-- (BOOL) grabsFocusOnClick;
-- (void) setGrabsFocusOnClick:(BOOL)focusOnClick;
-
-- (CGFloat) defaultScale;
-- (void) setDefaultScale:(CGFloat)scale;
+- (id<InputDelegate>) inputDelegate;
+- (void) setInputDelegate:(id<InputDelegate>)delegate;
/**
* Set the minimum size that this widget wants
diff --git a/tikzit/src/gtk/WidgetSurface.m b/tikzit/src/gtk/WidgetSurface.m
index 14d799b..64adc25 100644
--- a/tikzit/src/gtk/WidgetSurface.m
+++ b/tikzit/src/gtk/WidgetSurface.m
@@ -40,6 +40,8 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
- (void) updateLastKnownSize;
- (void) zoomTo:(CGFloat)scale aboutPoint:(NSPoint)p;
- (void) zoomTo:(CGFloat)scale;
+- (void) addToEventMask:(GdkEventMask)values;
+- (void) removeFromEventMask:(GdkEventMask)values;
@end
// }}}
// {{{ API
@@ -55,10 +57,6 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
if (self) {
widget = w;
g_object_ref_sink (G_OBJECT (widget));
- renderDelegate = nil;
- inputDelegate = nil;
- keepCentered = NO;
- grabsFocusOnClick = NO;
defaultScale = 1.0f;
transformer = [[Transformer alloc] init];
[transformer setFlippedAboutXAxis:YES];
@@ -77,6 +75,11 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
selector:@selector(widgetSizeChanged:)
name:@"SurfaceSizeChanged"
object:self];
+ if ([self canFocus]) {
+ [self addToEventMask:GDK_BUTTON_PRESS_MASK];
+ } else {
+ [self removeFromEventMask:GDK_BUTTON_PRESS_MASK];
+ }
}
return self;
@@ -143,23 +146,6 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
return widget;
}
-- (void) addToEventMask:(GdkEventMask)value {
- GdkEventMask mask;
- g_object_get (G_OBJECT (widget), "events", &mask, NULL);
- mask |= value;
- g_object_set (G_OBJECT (widget), "events", mask, NULL);
-}
-
-- (void) removeFromEventMask:(GdkEventMask)value {
- GdkEventMask mask;
- g_object_get (G_OBJECT (widget), "events", &mask, NULL);
- mask ^= value;
- if (grabsFocusOnClick) {
- mask |= GDK_BUTTON_PRESS_MASK;
- }
- g_object_set (G_OBJECT (widget), "events", mask, NULL);
-}
-
- (void) setRenderDelegate:(id <RenderDelegate>)delegate {
// NB: no retention!
renderDelegate = delegate;
@@ -170,14 +156,15 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
}
}
-- (id) inputDelegate {
+- (id<InputDelegate>) inputDelegate {
return inputDelegate;
}
-- (void) setInputDelegate:(id)delegate {
+- (void) setInputDelegate:(id<InputDelegate>)delegate {
if (delegate == inputDelegate) {
return;
}
+ buttonPressesRequired = NO;
if (inputDelegate != nil) {
[self removeFromEventMask:GDK_POINTER_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
@@ -189,12 +176,14 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
if (delegate != nil) {
GdkEventMask mask = 0;
if ([delegate respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) {
+ buttonPressesRequired = YES;
mask |= GDK_BUTTON_PRESS_MASK;
}
if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) {
mask |= GDK_BUTTON_RELEASE_MASK;
}
if ([delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) {
+ buttonPressesRequired = YES;
mask |= GDK_BUTTON_PRESS_MASK;
}
if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) {
@@ -223,18 +212,16 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
return keepCentered;
}
-- (BOOL) grabsFocusOnClick {
- return grabsFocusOnClick;
+- (BOOL) canFocus {
+ return gtk_widget_get_can_focus (widget);
}
-- (void) setGrabsFocusOnClick:(BOOL)focus {
- if (grabsFocusOnClick != focus) {
- grabsFocusOnClick = focus;
- if (grabsFocusOnClick) {
- [self addToEventMask:GDK_BUTTON_PRESS_MASK];
- } else {
- [self removeFromEventMask:GDK_BUTTON_PRESS_MASK];
- }
+- (void) setCanFocus:(BOOL)focus {
+ gtk_widget_set_can_focus (widget, focus);
+ if (focus) {
+ [self addToEventMask:GDK_BUTTON_PRESS_MASK];
+ } else if (!buttonPressesRequired) {
+ [self removeFromEventMask:GDK_BUTTON_PRESS_MASK];
}
}
@@ -395,6 +382,23 @@ static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, Widge
[self zoomTo:scale aboutPoint:centre];
}
+- (void) addToEventMask:(GdkEventMask)values {
+ GdkEventMask mask;
+ g_object_get (G_OBJECT (widget), "events", &mask, NULL);
+ mask |= values;
+ g_object_set (G_OBJECT (widget), "events", mask, NULL);
+}
+
+- (void) removeFromEventMask:(GdkEventMask)values {
+ GdkEventMask mask;
+ g_object_get (G_OBJECT (widget), "events", &mask, NULL);
+ mask ^= values;
+ if (buttonPressesRequired || [self canFocus]) {
+ mask |= GDK_BUTTON_PRESS_MASK;
+ }
+ g_object_set (G_OBJECT (widget), "events", mask, NULL);
+}
+
@end
// }}}
// {{{ GTK+ callbacks
@@ -465,13 +469,13 @@ MouseButton buttons_from_gdk_modifier_state (GdkModifierType state) {
static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- if ([surface grabsFocusOnClick]) {
- if (!GTK_WIDGET_HAS_FOCUS (widget)) {
+ if ([surface canFocus]) {
+ if (!gtk_widget_has_focus (widget)) {
gtk_widget_grab_focus (widget);
}
}
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
NSPoint pos = NSMakePoint (event->x, event->y);
MouseButton button = (MouseButton)event->button;
@@ -491,7 +495,7 @@ static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event,
static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) {
NSPoint pos = NSMakePoint (event->x, event->y);
@@ -508,7 +512,7 @@ static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event
static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) {
NSPoint pos = NSMakePoint (event->x, event->y);
@@ -525,7 +529,7 @@ static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event,
static gboolean key_press_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
if ([delegate respondsToSelector:@selector(keyPressed:withMask:)]) {
InputMask mask = mask_from_gdk_modifier_state (event->state);
@@ -540,7 +544,7 @@ static gboolean key_press_event_cb(GtkWidget *widget, GdkEventKey *event, Widget
static gboolean key_release_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
if ([delegate respondsToSelector:@selector(keyReleased:withMask:)]) {
InputMask mask = mask_from_gdk_modifier_state (event->state);
@@ -555,7 +559,7 @@ static gboolean key_release_event_cb(GtkWidget *widget, GdkEventKey *event, Widg
static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, WidgetSurface *surface) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- id delegate = [surface inputDelegate];
+ id<InputDelegate> delegate = [surface inputDelegate];
if (delegate != nil) {
if ([delegate respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) {
NSPoint pos = NSMakePoint (event->x, event->y);
diff --git a/tikzit/src/gtk/MainWindow.h b/tikzit/src/gtk/Window.h
index 8314296..e1f64ac 100644
--- a/tikzit/src/gtk/MainWindow.h
+++ b/tikzit/src/gtk/Window.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -17,11 +17,8 @@
#import "TZFoundation.h"
#import <gtk/gtk.h>
-#import "WidgetSurface.h"
-@class Configuration;
-@class GraphRenderer;
-@class GraphInputHandler;
+@class GraphEditorPanel;
@class Menu;
@class PropertyPane;
@class Preambles;
@@ -31,85 +28,87 @@
@class StyleManager;
@class StylesPane;
@class TikzDocument;
+@protocol Tool;
/**
- * Manages the main application window
+ * Manages a document window
*/
-@interface MainWindow: NSObject {
- // the main application configuration
- Configuration *configFile;
- // maintains the known (user-defined) styles
- StyleManager *styleManager;
- // maintains the preambles used for previews
- Preambles *preambles;
-
+@interface Window: NSObject {
// GTK+ widgets
- GtkWindow *mainWindow;
+ GtkWindow *window;
GtkTextBuffer *tikzBuffer;
GtkStatusbar *statusBar;
- GtkPaned *propertyPaneSplitter;
- GtkPaned *stylesPaneSplitter;
GtkPaned *tikzPaneSplitter;
GtkWidget *tikzPane;
+ gulong clipboard_handler_id;
+
// Classes that manage parts of the window
- // (or other windows)
Menu *menu;
- GraphRenderer *renderer;
- GraphInputHandler *inputHandler;
- StylesPane *stylesPane;
- PropertyPane *propertyPane;
- PreambleEditor *preambleWindow;
- PreviewWindow *previewWindow;
- SettingsDialog *settingsDialog;
-
- WidgetSurface *surface;
+ GraphEditorPanel *graphPanel;
// state variables
BOOL suppressTikzUpdates;
BOOL hasParseError;
- // the last-accessed folder (for open and save dialogs)
- NSString *lastFolder;
- // the open (active) document
+
+ // the document displayed by the window
TikzDocument *document;
}
/**
- * Create and show the main window.
+ * The document displayed by the window
+ */
+@property (retain) TikzDocument *document;
+@property (readonly) BOOL hasFocus;
+@property (readonly) GtkWindow *gtkWindow;
+
+/**
+ * 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;
+
+/**
+ * Present the window to the user
+ */
+- (void) present;
/**
* Open a file, asking the user which file to open
*/
- (void) openFile;
/**
+ * Open a file
+ */
+- (BOOL) openFileAtPath:(NSString*)path;
+/**
* Save the active document to the path it was opened from
* or last saved to, or ask the user where to save it.
*/
-- (void) saveActiveDocument;
+- (BOOL) saveActiveDocument;
/**
* Save the active document, asking the user where to save it.
*/
-- (void) saveActiveDocumentAs;
+- (BOOL) saveActiveDocumentAs;
/**
* Save the active document as a shape, asking the user what to name it.
*/
- (void) saveActiveDocumentAsShape;
+
/**
- * Quit the application, confirming with the user if there are
- * changes to an open document.
- */
-- (void) quit;
-/**
- * If there are changes to an open document, ask the user if they
- * want to quit the application, discarding those changes.
+ * Close the window.
*
- * @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.
+ * May terminate the application if this is the last window.
+ *
+ * Will ask for user confirmation if the document is not saved.
*/
-- (BOOL) askCanQuit;
+- (void) close;
/**
* Cut the current selection to the clipboard.
@@ -125,64 +124,23 @@
- (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
*/
@@ -190,36 +148,23 @@
/**
* 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) setActiveTool:(id<Tool>)tool;
- (void) zoomIn;
- (void) zoomOut;
- (void) zoomReset;
-- (void) favourGraphControls;
-- (void) favourNodeControls;
-- (void) favourEdgeControls;
-
@end
// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/MainWindow.m b/tikzit/src/gtk/Window.m
index 9e406b7..15049ea 100644
--- a/tikzit/src/gtk/MainWindow.m
+++ b/tikzit/src/gtk/Window.m
@@ -1,5 +1,5 @@
/*
- * Copyright 2011 Alex Merry <dev@randomguy3.me.uk>
+ * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -15,37 +15,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#import "MainWindow.h"
+#import "Window.h"
#import <gtk/gtk.h>
#import "gtkhelpers.h"
#import "clipboard.h"
+#import "Application.h"
#import "Configuration.h"
#import "FileChooserDialog.h"
-#import "GraphInputHandler.h"
-#import "GraphRenderer.h"
+#import "GraphEditorPanel.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
@@ -67,20 +51,17 @@ static void clipboard_paste_contents (GtkClipboard *clipboard,
// }}}
// {{{ 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 window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window);
+static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window);
+static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window);
+static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window);
+static void main_window_destroy_cb (GtkWidget *widget, Window *window);
+static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window);
static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action);
// }}}
-@interface MainWindow (Notifications)
-- (void) toolboxWidthChanged:(int)newWidth;
-- (void) styleboxWidthChanged:(int)newWidth;
+@interface Window (Notifications)
- (void) tikzBufferChanged;
- (void) windowSizeChangedWidth:(int)width height:(int)height;
- (void) documentTikzChanged:(NSNotification*)notification;
@@ -88,18 +69,16 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
- (void) undoStackChanged:(NSNotification*)notification;
@end
-@interface MainWindow (InitHelpers)
-- (void) _loadConfig;
-- (void) _loadStyles;
-- (void) _loadPreambles;
+@interface Window (InitHelpers)
- (void) _loadUi;
- (void) _restoreUiState;
- (void) _connectSignals;
@end
-@interface MainWindow (Private)
-- (BOOL) _confirmCloseDocumentTo:(NSString*)action;
-- (void) _forceLoadDocumentFromFile:(NSString*)path;
+@interface Window (Private)
+- (BOOL) _askCanClose;
+/** Open a document, dealing with errors as necessary */
+- (TikzDocument*) _openDocument:(NSString*)path;
- (void) _placeGraphOnClipboard:(Graph*)graph;
- (void) _setHasParseError:(BOOL)hasError;
/** Update the window title. */
@@ -111,113 +90,169 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
/** Update the undo and redo actions to match the active document's
* undo stack. */
- (void) _updateUndoActions;
-/** Set the last-accessed folder */
-- (void) _setLastFolder:(NSString*)path;
-/** Set the active document */
-- (void) _setActiveDocument:(TikzDocument*)newDoc;
@end
// }}}
// {{{ API
-@implementation MainWindow
+@implementation Window
+
+@synthesize gtkWindow=window;
- (id) init {
+ return [self initWithDocument:[TikzDocument documentWithStyleManager:[app styleManager]]];
+}
++ (id) window {
+ return [[[self alloc] init] autorelease];
+}
+- (id) initWithDocument:(TikzDocument*)doc {
self = [super init];
if (self) {
- document = nil;
- preambles = nil;
- preambleWindow = nil;
- previewWindow = nil;
- settingsDialog = nil;
- suppressTikzUpdates = NO;
- hasParseError = NO;
-
- [self _loadConfig];
- [self _loadStyles];
- [self _loadPreambles];
- lastFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain];
[self _loadUi];
[self _restoreUiState];
[self _connectSignals];
- [self loadEmptyDocument];
+ [self setDocument:doc];
- gtk_widget_show (GTK_WIDGET (mainWindow));
+ gtk_widget_show (GTK_WIDGET (window));
}
return self;
}
++ (id) windowWithDocument:(TikzDocument*)doc {
+ return [[[self alloc] initWithDocument:doc] autorelease];
+}
- (void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
- [configFile release];
- [styleManager release];
- [preambles release];
+ g_signal_handler_disconnect (
+ gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+ clipboard_handler_id);
+
[menu release];
- [renderer release];
- [inputHandler release];
- [stylesPane release];
- [propertyPane release];
- [preambleWindow release];
- [previewWindow release];
- [settingsDialog release];
- [surface release];
- [lastFolder release];
+ [graphPanel 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);
+ g_object_unref (tikzPaneSplitter);
+ g_object_unref (statusBar);
+ g_object_unref (window);
[super dealloc];
}
-- (void) openFile {
- if (![self _confirmCloseDocumentTo:@"open a new document"]) {
- return;
+- (TikzDocument*) document {
+ return document;
+}
+- (void) setDocument:(TikzDocument*)newDoc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document];
+
+ TikzDocument *oldDoc = document;
+ document = [newDoc retain];
+
+ [graphPanel setDocument:document];
+ [self _updateTikz];
+ [self _updateTitle];
+ [self _updateStatus];
+ [self _updateUndoActions];
+ [menu notifySelectionChanged:[document pickSupport]];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(documentTikzChanged:)
+ name:@"TikzChanged" object:document];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(undoStackChanged:)
+ name:@"UndoStackChanged" object:document];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(documentSelectionChanged:)
+ name:@"NodeSelectionChanged" object:[document pickSupport]];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(documentSelectionChanged:)
+ name:@"EdgeSelectionChanged" object:[document pickSupport]];
+
+ if ([document path] != nil) {
+ [[RecentManager defaultManager] addRecentFile:[document path]];
}
- FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:mainWindow];
+
+ NSDictionary *userInfo;
+ userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+ document, @"document",
+ oldDoc, @"oldDocument",
+ nil];
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"DocumentChanged"
+ object:self
+ userInfo:userInfo];
+ [oldDoc release];
+}
+
+- (BOOL) hasFocus {
+ return gtk_window_has_toplevel_focus (GTK_WINDOW (window));
+}
+
+- (void) present {
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+- (void) openFile {
+ FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:window];
[dialog addStandardFilters];
- if (lastFolder) {
- [dialog setCurrentFolder:lastFolder];
+ if ([document path]) {
+ [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]];
+ } else if ([app lastOpenFolder]) {
+ [dialog setCurrentFolder:[app lastOpenFolder]];
}
if ([dialog showDialog]) {
- [self _forceLoadDocumentFromFile:[dialog filePath]];
- [self _setLastFolder:[dialog currentFolder]];
+ if ([self openFileAtPath:[dialog filePath]]) {
+ [app setLastOpenFolder:[dialog currentFolder]];
+ }
}
[dialog destroy];
}
-- (void) saveActiveDocument {
+- (BOOL) openFileAtPath:(NSString*)path {
+ TikzDocument *doc = [self _openDocument:path];
+ if (doc != nil) {
+ if (![document hasUnsavedChanges] && [document path] == nil) {
+ // we just have a fresh, untitled document - replace it
+ [self setDocument:doc];
+ } else {
+ [app newWindowWithDocument:doc];
+ }
+ return YES;
+ }
+ return NO;
+}
+
+- (BOOL) saveActiveDocument {
if ([document path] == nil) {
- [self saveActiveDocumentAs];
+ return [self saveActiveDocumentAs];
} else {
NSError *error = nil;
if (![document save:&error]) {
[self presentError:error];
+ return NO;
} else {
[self _updateTitle];
+ return YES;
}
}
}
-- (void) saveActiveDocumentAs {
- FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:mainWindow];
+- (BOOL) saveActiveDocumentAs {
+ FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:window];
[dialog addStandardFilters];
if ([document path] != nil) {
[dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]];
- } else if (lastFolder != nil) {
- [dialog setCurrentFolder:lastFolder];
+ } else if ([app lastSaveAsFolder] != nil) {
+ [dialog setCurrentFolder:[app lastSaveAsFolder]];
}
[dialog setSuggestedName:[document suggestedFileName]];
+ BOOL saved = NO;
if ([dialog showDialog]) {
NSString *nfile = [dialog filePath];
@@ -227,16 +262,18 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
} else {
[self _updateTitle];
[[RecentManager defaultManager] addRecentFile:nfile];
- [self _setLastFolder:[dialog currentFolder]];
+ [app setLastSaveAsFolder:[dialog currentFolder]];
+ saved = YES;
}
}
[dialog destroy];
+ return saved;
}
- (void) saveActiveDocumentAsShape {
GtkWidget *dialog = gtk_dialog_new_with_buttons (
"Save as shape",
- mainWindow,
+ window,
GTK_DIALOG_MODAL,
GTK_STOCK_OK,
GTK_RESPONSE_ACCEPT,
@@ -261,7 +298,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
NSString *shapeName = [NSString stringWithUTF8String:dialogInput];
BOOL doSave = NO;
if ([shapeName isEqual:@""]) {
- GtkWidget *emptyStrDialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *emptyStrDialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
@@ -270,7 +307,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
gtk_widget_destroy (emptyStrDialog);
response = gtk_dialog_run (GTK_DIALOG (dialog));
} else if ([shapeDict objectForKey:shapeName] != nil) {
- GtkWidget *overwriteDialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *overwriteDialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_YES_NO,
@@ -306,27 +343,12 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
gtk_widget_destroy (dialog);
}
-- (void) quit {
- if ([self askCanQuit]) {
- gtk_main_quit();
+- (void) close {
+ if ([self _askCanClose]) {
+ gtk_widget_destroy (GTK_WIDGET (window));
}
}
-- (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]];
@@ -346,73 +368,20 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
document);
}
-- (void) editPreambles {
-#ifdef HAVE_POPPLER
- if (preambleWindow == nil) {
- preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles];
- [preambleWindow setParentWindow:mainWindow];
- }
- [preambleWindow show];
-#endif
-}
-
-- (void) showPreview {
-#ifdef HAVE_POPPLER
- if (previewWindow == nil) {
- previewWindow = [[PreviewWindow alloc] initWithPreambles:preambles config:configFile];
- [previewWindow setParentWindow:mainWindow];
- [previewWindow setDocument:document];
- }
- [previewWindow show];
-#endif
-}
-
-- (void) showSettingsDialog {
-#ifdef HAVE_POPPLER
- if (settingsDialog == nil) {
- settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile];
- [settingsDialog setParentWindow:mainWindow];
- }
- [settingsDialog show];
-#endif
-}
-
-- (GraphInputHandler*) graphInputHandler {
- return inputHandler;
-}
-
- (GtkWindow*) gtkWindow {
- return mainWindow;
+ return window;
}
- (Configuration*) mainConfiguration {
- return configFile;
+ return [app mainConfiguration];
}
- (Menu*) menu {
return menu;
}
-- (TikzDocument*) activeDocument {
- return document;
-}
-
-- (void) loadEmptyDocument {
- if (![self _confirmCloseDocumentTo:@"start a new document"]) {
- return;
- }
- [self _setActiveDocument:[TikzDocument documentWithStyleManager:styleManager]];
-}
-
-- (void) loadDocumentFromFile:(NSString*)path {
- if (![self _confirmCloseDocumentTo:@"open a new document"]) {
- return;
- }
- [self _forceLoadDocumentFromFile:path];
-}
-
- (void) presentError:(NSError*)error {
- GtkWidget *dialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
@@ -423,7 +392,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
}
- (void) presentError:(NSError*)error withMessage:(NSString*)message {
- GtkWidget *dialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
@@ -435,7 +404,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
}
- (void) presentGError:(GError*)error {
- GtkWidget *dialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
@@ -446,7 +415,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
}
- (void) presentGError:(GError*)error withMessage:(NSString*)message {
- GtkWidget *dialog = gtk_message_dialog_new (mainWindow,
+ GtkWidget *dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
@@ -457,56 +426,25 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
gtk_widget_destroy (dialog);
}
-- (void) saveConfiguration {
- NSError *error = nil;
-
-#ifdef HAVE_POPPLER
- if (preambles != nil) {
- NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"];
- // NSFileManager is slightly dodgy on Windows
- g_mkdir_with_parents ([preamblesDir UTF8String], S_IRUSR | S_IWUSR | S_IXUSR);
- [preambles storeToDirectory:preamblesDir];
- [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]];
- }
-#endif
-
- [styleManager saveStylesUsingConfigurationName:@"styles"];
- [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) setActiveTool:(id<Tool>)tool {
+ [graphPanel setActiveTool:tool];
+ gboolean hasfocus;
+ g_object_get (G_OBJECT (window), "has-toplevel-focus", &hasfocus, NULL);
+ if (hasfocus) {
+ [graphPanel grabTool];
}
}
- (void) zoomIn {
- [surface zoomIn];
+ [graphPanel zoomIn];
}
- (void) zoomOut {
- [surface zoomOut];
+ [graphPanel zoomOut];
}
- (void) zoomReset {
- [surface zoomReset];
-}
-
-- (void) favourGraphControls {
- [propertyPane favourGraphProperties];
-}
-
-- (void) favourNodeControls {
- [stylesPane favourNodeStyles];
- [propertyPane favourNodeProperties];
-}
-
-- (void) favourEdgeControls {
- [stylesPane favourEdgeStyles];
- [propertyPane favourEdgeProperties];
+ [graphPanel zoomReset];
}
@end
@@ -514,17 +452,11 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
// }}}
// {{{ 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];
-}
-
+@implementation Window (Notifications)
- (void) graphHeightChanged:(int)newHeight {
- [configFile setIntegerEntry:@"graphHeight" inGroup:@"mainWindow" value:newHeight];
+ [[app mainConfiguration] setIntegerEntry:@"graphHeight"
+ inGroup:@"window"
+ value:newHeight];
}
- (void) tikzBufferChanged {
@@ -551,7 +483,9 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
NSMutableArray *size = [NSMutableArray arrayWithCapacity:2];
[size addObject:w];
[size addObject:h];
- [configFile setIntegerListEntry:@"windowSize" inGroup:@"mainWindow" value:size];
+ [[app mainConfiguration] setIntegerListEntry:@"windowSize"
+ inGroup:@"window"
+ value:size];
}
}
@@ -573,99 +507,37 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
// }}}
// {{{ InitHelpers
-@implementation MainWindow (InitHelpers)
-
-- (void) _loadConfig {
- NSError *error = nil;
- configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error];
- if (error != nil) {
- logError (error, @"WARNING: Failed to load configuration");
- }
-}
-
-- (void) _loadStyles {
- styleManager = [[StyleManager alloc] init];
- [styleManager loadStylesUsingConfigurationName:@"styles"];
-}
-
-// must happen after _loadStyles
-- (void) _loadPreambles {
-#ifdef HAVE_POPPLER
- NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"];
- preambles = [[Preambles alloc] initFromDirectory:preamblesDir];
- [preambles setStyleManager:styleManager];
- NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"];
- if (selectedPreamble != nil) {
- [preambles setSelectedPreambleName:selectedPreamble];
- }
-#endif
-}
+@implementation Window (InitHelpers)
- (void) _loadUi {
- mainWindow = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
- g_object_ref_sink (mainWindow);
- gtk_window_set_title (mainWindow, "TikZiT");
- gtk_window_set_default_size (mainWindow, 700, 400);
+ window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+ g_object_ref_sink (window);
+ gtk_window_set_title (window, "TikZiT");
+ gtk_window_set_default_size (window, 700, 400);
GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0));
gtk_widget_show (GTK_WIDGET (mainLayout));
- gtk_container_add (GTK_CONTAINER (mainWindow), GTK_WIDGET (mainLayout));
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (mainLayout));
- menu = [[Menu alloc] initForMainWindow:self];
+ menu = [[Menu alloc] initForWindow:self];
GtkWidget *menubar = [menu menubar];
gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0);
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);
+ gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0);
- surface = [[WidgetSurface alloc] init];
- gtk_widget_show ([surface widget]);
+ graphPanel = [[GraphEditorPanel alloc] initWithDocument:document];
+ GtkWidget *graphEditorWidget = [graphPanel widget];
+ gtk_widget_show (graphEditorWidget);
GtkWidget *graphFrame = gtk_frame_new (NULL);
- gtk_container_add (GTK_CONTAINER (graphFrame), [surface widget]);
+ gtk_container_add (GTK_CONTAINER (graphFrame), graphEditorWidget);
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);
@@ -684,6 +556,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
gtk_paned_pack2 (tikzPaneSplitter, tikzFrame, FALSE, TRUE);
statusBar = GTK_STATUSBAR (gtk_statusbar_new ());
+ g_object_ref_sink (statusBar);
gtk_widget_show (GTK_WIDGET (statusBar));
gtk_box_pack_start (mainLayout, GTK_WIDGET (statusBar), FALSE, TRUE, 0);
@@ -692,47 +565,37 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
}
- (void) _restoreUiState {
- NSArray *windowSize = [configFile integerListEntry:@"windowSize" inGroup:@"mainWindow"];
+ Configuration *config = [app mainConfiguration];
+ NSArray *windowSize = [config integerListEntry:@"windowSize"
+ inGroup:@"window"];
if (windowSize && [windowSize count] == 2) {
gint width = [[windowSize objectAtIndex:0] intValue];
gint height = [[windowSize objectAtIndex:1] intValue];
if (width > 0 && height > 0) {
- gtk_window_set_default_size (mainWindow, width, height);
+ gtk_window_set_default_size (window, width, height);
}
}
- int panePos = [configFile integerEntry:@"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"];
+ int panePos = [config integerEntry:@"graphHeight"
+ inGroup:@"window"];
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),
+ clipboard_handler_id =
+ 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 (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),
+ g_signal_connect (G_OBJECT (window),
+ "notify::has-toplevel-focus",
+ G_CALLBACK (window_toplevel_focus_changed_cb),
self);
g_signal_connect (G_OBJECT (tikzPaneSplitter),
"notify::position",
@@ -742,15 +605,15 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
"changed",
G_CALLBACK (tikz_buffer_changed_cb),
self);
- g_signal_connect (G_OBJECT (mainWindow),
+ g_signal_connect (G_OBJECT (window),
"delete-event",
G_CALLBACK (main_window_delete_event_cb),
self);
- g_signal_connect (G_OBJECT (mainWindow),
+ g_signal_connect (G_OBJECT (window),
"destroy",
G_CALLBACK (main_window_destroy_cb),
self);
- g_signal_connect (G_OBJECT (mainWindow),
+ g_signal_connect (G_OBJECT (window),
"configure-event",
G_CALLBACK (main_window_configure_event_cb),
self);
@@ -760,32 +623,40 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
// }}}
// {{{ Private
-@implementation MainWindow (Private)
+@implementation Window (Private)
-- (BOOL) _confirmCloseDocumentTo:(NSString*)action {
- BOOL proceed = YES;
+- (BOOL) _askCanClose {
if ([document hasUnsavedChanges]) {
- NSString *message = [NSString stringWithFormat:@"You have unsaved changes to the current document, which will be lost if you %@. Are you sure you want to continue?", action];
- GtkWidget *dialog = gtk_message_dialog_new (NULL,
+ GtkWidget *dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
- GTK_BUTTONS_YES_NO,
- "%s", [message UTF8String]);
- gtk_window_set_title(GTK_WINDOW(dialog), "Close current document?");
- proceed = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES;
- gtk_widget_destroy (dialog);
+ GTK_BUTTONS_NONE,
+ "Save changes to the document \"%s\" before closing?",
+ [[document name] UTF8String]);
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ "Save", GTK_RESPONSE_YES,
+ "Don't save", GTK_RESPONSE_NO,
+ "Cancel", GTK_RESPONSE_CANCEL,
+ NULL);
+ gint result = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ if (result == GTK_RESPONSE_YES) {
+ return [self saveActiveDocument];
+ } else {
+ return result == GTK_RESPONSE_NO;
+ }
+ } else {
+ return YES;
}
- return proceed;
}
-- (void) _forceLoadDocumentFromFile:(NSString*)path {
+- (TikzDocument*) _openDocument:(NSString*)path {
NSError *error = nil;
TikzDocument *d = [TikzDocument documentFromFile:path
- styleManager:styleManager
+ styleManager:[app styleManager]
error:&error];
if (d != nil) {
- [self _setActiveDocument:d];
- [[RecentManager defaultManager] addRecentFile:path];
+ return d;
} else {
if ([error code] == TZ_ERR_PARSE) {
[self presentError:error withMessage:@"Invalid file"];
@@ -793,6 +664,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
[self presentError:error withMessage:@"Could not open file"];
}
[[RecentManager defaultManager] removeRecentFile:path];
+ return nil;
}
}
@@ -834,10 +706,11 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s",
[document name],
([document hasUnsavedChanges] ? "*" : "")];
- gtk_window_set_title(mainWindow, [title UTF8String]);
+ gtk_window_set_title(window, [title UTF8String]);
}
- (void) _updateStatus {
+ // FIXME: show tooltips or something instead
GString *buffer = g_string_sized_new (30);
gchar *nextNode = 0;
@@ -890,44 +763,8 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
}
}
-- (void) _setLastFolder:(NSString*)path {
- [path retain];
- [lastFolder release];
- lastFolder = path;
-}
-
-- (void) _setActiveDocument:(TikzDocument*)newDoc {
- [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document];
-
- [newDoc retain];
- [document release];
- document = newDoc;
-
- [renderer setDocument:document];
- [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]];
+- (GraphEditorPanel*) _graphPanel {
+ return graphPanel;
}
@end
@@ -935,23 +772,24 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc
// }}}
// {{{ GTK+ callbacks
-static void toolbox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) {
+static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *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];
+ gboolean hasfocus;
+ g_object_get (gobject, "has-toplevel-focus", &hasfocus, NULL);
+ if (hasfocus) {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"WindowGainedFocus"
+ object:window];
+ [[window _graphPanel] grabTool];
+ } else {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"WindowLostFocus"
+ object:window];
+ }
[pool drain];
}
-static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) {
+static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
gint position;
g_object_get (gobject, "position", &position, NULL);
@@ -959,23 +797,27 @@ static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *psp
[pool drain];
}
-static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window) {
+static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[window tikzBufferChanged];
[pool drain];
}
-static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window) {
+static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- return ![window askCanQuit];
+ [window close];
[pool drain];
+ return TRUE;
}
-static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window) {
- gtk_main_quit();
+static void main_window_destroy_cb (GtkWidget *widget, Window *window) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"WindowClosed"
+ object:window];
+ [pool drain];
}
-static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window) {
+static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[window windowSizeChangedWidth:event->width height:event->height];
[pool drain];
diff --git a/tikzit/src/gtk/gtkhelpers.h b/tikzit/src/gtk/gtkhelpers.h
index 418d234..a28b127 100644
--- a/tikzit/src/gtk/gtkhelpers.h
+++ b/tikzit/src/gtk/gtkhelpers.h
@@ -22,21 +22,18 @@
//
#import "TZFoundation.h"
#include <gtk/gtk.h>
+#import <gdk-pixbuf/gdk-pixbuf.h>
-void gtk_table_adjust_attach (GtkTable *table,
- GtkWidget *widget,
- gint left_adjust,
- gint right_adjust,
- gint top_adjust,
- gint bottom_adjust);
-void gtk_table_delete_row (GtkTable *table, guint row);
-void gtk_table_delete_rows (GtkTable *table, guint firstRow, guint count);
+/**
+ * Releases the Objective-C object pointed to by data
+ *
+ * Intended for use as a cleanup function in Glib/GObject-based
+ * code.
+ */
+void release_obj (gpointer data);
NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end);
-void gtk_entry_set_string (GtkEntry *entry, NSString *string);
-NSString * gtk_entry_get_string (GtkEntry *entry);
-
GdkRectangle gdk_rectangle_from_ns_rect (NSRect rect);
NSRect gdk_rectangle_to_ns_rect (GdkRectangle rect);
@@ -46,4 +43,11 @@ gint tz_hijack_key_press (GtkWindow *win,
GdkEventKey *event,
gpointer user_data);
+// Equivalent of GTK+3's gdk_pixbuf_get_from_surface()
+GdkPixbuf * pixbuf_get_from_surface(cairo_surface_t *surface);
+
+void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h);
+
+void label_set_bold (GtkLabel *label);
+
// vim:ft=objc:sts=2:sw=2:et
diff --git a/tikzit/src/gtk/gtkhelpers.m b/tikzit/src/gtk/gtkhelpers.m
index 164228c..c37077b 100644
--- a/tikzit/src/gtk/gtkhelpers.m
+++ b/tikzit/src/gtk/gtkhelpers.m
@@ -25,135 +25,9 @@
#import "gtkhelpers.h"
#import <gdk/gdkkeysyms.h>
-void gtk_table_adjust_attach (GtkTable *table,
- GtkWidget *widget,
- gint left_adjust,
- gint right_adjust,
- gint top_adjust,
- gint bottom_adjust) {
- guint top_attach;
- guint bottom_attach;
- guint left_attach;
- guint right_attach;
- GtkAttachOptions xoptions;
- GtkAttachOptions yoptions;
- guint xpadding;
- guint ypadding;
-
- gtk_container_child_get (GTK_CONTAINER (table), widget,
- "top-attach", &top_attach,
- "bottom-attach", &bottom_attach,
- "left-attach", &left_attach,
- "right-attach", &right_attach,
- "x-options", &xoptions,
- "y-options", &yoptions,
- "x-padding", &xpadding,
- "y-padding", &ypadding,
- NULL);
-
- g_object_ref (G_OBJECT (widget));
- gtk_container_remove (GTK_CONTAINER (table), widget);
- gtk_table_attach (table, widget,
- left_attach + left_adjust,
- right_attach + right_adjust,
- top_attach + top_adjust,
- bottom_attach + bottom_adjust,
- xoptions,
- yoptions,
- xpadding,
- ypadding);
- g_object_unref (G_OBJECT (widget));
-}
-
-/*
- * Delete multiple table rows
- */
-void gtk_table_delete_rows (GtkTable *table, guint firstRow, guint count) {
- if (count == 0) {
- return;
- }
- GtkContainer *tableC = GTK_CONTAINER (table);
-
- guint n_columns;
- guint n_rows;
- g_object_get (G_OBJECT (table),
- "n-columns", &n_columns,
- "n-rows", &n_rows,
- NULL);
- guint topBound = firstRow;
- guint bottomBound = firstRow + count;
- if (bottomBound > n_rows) {
- bottomBound = n_rows;
- count = bottomBound - topBound;
- }
-
- GList *toBeDeleted = NULL;
- GList *toBeShrunk = NULL;
- /* indexed by top-attach */
- GPtrArray *toBeMoved = g_ptr_array_sized_new (n_rows - topBound);
- g_ptr_array_set_size (toBeMoved, n_rows - topBound);
-
- GList *childIt = gtk_container_get_children (tableC);
-
- while (childIt) {
- GtkWidget *widget = GTK_WIDGET (childIt->data);
- guint top_attach;
- guint bottom_attach;
- gtk_container_child_get (tableC, widget,
- "top-attach", &top_attach,
- "bottom-attach", &bottom_attach,
- NULL);
- if (top_attach >= topBound && bottom_attach <= bottomBound) {
- toBeDeleted = g_list_prepend (toBeDeleted, widget);
- } else if (top_attach <= topBound && bottom_attach > topBound) {
- toBeShrunk = g_list_prepend (toBeShrunk, widget);
- } else if (top_attach > topBound) {
- GList *rowList = (GList*)g_ptr_array_index (toBeMoved, top_attach - topBound);
- rowList = g_list_prepend (rowList, widget);
- g_ptr_array_index (toBeMoved, top_attach - topBound) = rowList;
- }
- childIt = childIt->next;
- }
- g_list_free (childIt);
-
- /* remove anything that is completely within the segment being deleted */
- while (toBeDeleted) {
- gtk_container_remove (tableC, GTK_WIDGET (toBeDeleted->data));
- toBeDeleted = toBeDeleted->next;
- }
- g_list_free (toBeDeleted);
-
- /* shrink anything that spans the segment */
- while (toBeShrunk) {
- GtkWidget *widget = GTK_WIDGET (toBeShrunk->data);
- gtk_table_adjust_attach (table, widget, 0, 0, 0, -count);
- toBeShrunk = toBeShrunk->next;
- }
- g_list_free (toBeShrunk);
-
- /* move everything below the segment being deleted up, in order */
- /* note that "n-rows" is not a valid "top-attach" */
- for (int offset = 0; offset < (n_rows - 1) - topBound; ++offset) {
- GList *rowList = (GList *)g_ptr_array_index (toBeMoved, offset);
- guint top_attach = offset + topBound;
- guint overlap = bottomBound - top_attach;
- while (rowList) {
- GtkWidget *widget = GTK_WIDGET (rowList->data);
- gtk_table_adjust_attach (table, widget, 0, 0, -offset, -(offset + overlap));
- rowList = rowList->next;
- }
- g_list_free (rowList);
- g_ptr_array_index (toBeMoved, offset) = NULL;
- }
-
- gtk_table_resize (table, n_rows - 1, n_columns);
-}
-
-/*
- * Delete a table row
- */
-void gtk_table_delete_row (GtkTable *table, guint row) {
- gtk_table_delete_rows (table, row, 1);
+void release_obj (gpointer data) {
+ id obj = (id)data;
+ [obj release];
}
NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end)
@@ -164,16 +38,6 @@ NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end)
return string;
}
-void gtk_entry_set_string (GtkEntry *entry, NSString *string)
-{
- gtk_entry_set_text (entry, string == nil ? "" : [string UTF8String]);
-}
-
-NSString * gtk_entry_get_string (GtkEntry *entry)
-{
- return [NSString stringWithUTF8String:gtk_entry_get_text (entry)];
-}
-
GdkRectangle gdk_rectangle_from_ns_rect (NSRect box) {
GdkRectangle rect;
rect.x = box.origin.x;
@@ -213,8 +77,6 @@ void gtk_action_set_detailed_label (GtkAction *action, const gchar *baseLabel, c
* it simply allows the window contents to treat key events /before/
* accelerator keys come into play (this way widgets dont get deleted
* when cutting text in an entry etc.).
- * Creates a liststore suitable for comboboxes and such to
- * chose from a variety of types.
*
* Returns: whether the event was handled
*/
@@ -241,4 +103,135 @@ tz_hijack_key_press (GtkWindow *win,
return FALSE;
}
+GdkPixbuf * pixbuf_get_from_surface(cairo_surface_t *surface) {
+ cairo_surface_flush (surface);
+
+ int width = cairo_image_surface_get_width (surface);
+ int height = cairo_image_surface_get_height (surface);
+ int stride = cairo_image_surface_get_stride (surface);
+ unsigned char *data = cairo_image_surface_get_data (surface);
+
+ GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ width,
+ height);
+ unsigned char *pbdata = gdk_pixbuf_get_pixels (pixbuf);
+ int pbstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ for (int y = 0; y < height; ++y) {
+ uint32_t *line = (uint32_t*)(data + y*stride);
+ unsigned char *pbline = pbdata + (y*pbstride);
+ for (int x = 0; x < width; ++x) {
+ uint32_t pixel = *(line + x);
+ unsigned char *pbpixel = pbline + (x*4);
+ // NB: We should un-pre-mult the alpha here.
+ // However, in our world, alpha is always
+ // on or off, so it doesn't really matter
+ pbpixel[3] = ((pixel & 0xff000000) >> 24);
+ pbpixel[0] = ((pixel & 0x00ff0000) >> 16);
+ pbpixel[1] = ((pixel & 0x0000ff00) >> 8);
+ pbpixel[2] = (pixel & 0x000000ff);
+ }
+ }
+
+ return pixbuf;
+}
+
+/* This function mostly lifted from
+ * gtk+/gdk/gdkscreen.c:gdk_screen_get_monitor_at_window()
+ */
+static gint
+get_appropriate_monitor (GdkScreen *screen,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GdkRectangle rect;
+ gint area = 0;
+ gint monitor = -1;
+ gint num_monitors;
+ gint i;
+
+ rect.x = x;
+ rect.y = y;
+ rect.width = w;
+ rect.height = h;
+
+ num_monitors = gdk_screen_get_n_monitors (screen);
+
+ for (i = 0; i < num_monitors; i++)
+ {
+ GdkRectangle geometry;
+
+ gdk_screen_get_monitor_geometry (screen, i, &geometry);
+
+ if (gdk_rectangle_intersect (&rect, &geometry, &geometry) &&
+ geometry.width * geometry.height > area)
+ {
+ area = geometry.width * geometry.height;
+ monitor = i;
+ }
+ }
+
+ if (monitor >= 0)
+ return monitor;
+ else
+ return gdk_screen_get_monitor_at_point (screen,
+ rect.x + rect.width / 2,
+ rect.y + rect.height / 2);
+}
+
+/* This function mostly lifted from gimp_session_info_apply_geometry
+ * in gimp-2.6/app/widgets/gimpsessioninfo.c
+ */
+void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h)
+{
+ gint forced_w = w;
+ gint forced_h = h;
+ if (w <= 0 || h <= 0) {
+ gtk_window_get_default_size (window, &w, &h);
+ }
+ if (w <= 0 || h <= 0) {
+ gtk_window_get_size (window, &w, &h);
+ }
+
+ GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (window));
+
+ gint monitor = 0;
+ if (w > 0 && h > 0) {
+ monitor = get_appropriate_monitor (screen, x, y, w, h);
+ } else {
+ monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+ }
+
+ GdkRectangle rect;
+ gdk_screen_get_monitor_geometry (screen, monitor, &rect);
+
+ x = CLAMP (x,
+ rect.x,
+ rect.x + rect.width - (w > 0 ? w : 128));
+ y = CLAMP (y,
+ rect.y,
+ rect.y + rect.height - (h > 0 ? h : 128));
+
+ gchar geom[32];
+ g_snprintf (geom, sizeof (geom), "%+d%+d", x, y);
+
+ gtk_window_parse_geometry (window, geom);
+
+ if (forced_w > 0 && forced_h > 0) {
+ gtk_window_set_default_size (window, forced_w, forced_h);
+ }
+}
+
+void label_set_bold (GtkLabel *label) {
+ PangoAttrList *attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs,
+ pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD));
+ gtk_label_set_attributes (label, attrs);
+ pango_attr_list_unref (attrs);
+}
+
// vim:ft=objc:ts=8:et:sts=4:sw=4
diff --git a/tikzit/src/gtk/main.m b/tikzit/src/gtk/main.m
index 93aa9e4..eb06449 100644
--- a/tikzit/src/gtk/main.m
+++ b/tikzit/src/gtk/main.m
@@ -25,10 +25,17 @@
#import <gtk/gtk.h>
#import "clipboard.h"
#import "logo.h"
+#import "tzstockitems.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 +46,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 +77,30 @@ int main (int argc, char *argv[]) {
}
#endif
+ NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init];
+
+ tz_register_stock_items();
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;
}
diff --git a/tikzit/src/gtk/tzstockitems.h b/tikzit/src/gtk/tzstockitems.h
new file mode 100644
index 0000000..5ad0da9
--- /dev/null
+++ b/tikzit/src/gtk/tzstockitems.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define TIKZIT_STOCK_SELECT "tikzit-select"
+#define TIKZIT_STOCK_CREATE_NODE "tikzit-create-node"
+#define TIKZIT_STOCK_CREATE_EDGE "tikzit-create-edge"
+#define TIKZIT_STOCK_BOUNDING_BOX "tikzit-bounding-box"
+#define TIKZIT_STOCK_DRAG "tikzit-drag"
+
+void tz_register_stock_items();
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/tzstockitems.m b/tikzit/src/gtk/tzstockitems.m
new file mode 100644
index 0000000..5eba912
--- /dev/null
+++ b/tikzit/src/gtk/tzstockitems.m
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 Alex Merry <dev@randomguy3.me.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tzstockitems.h"
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpointer-sign"
+#import "icondata.m"
+#pragma GCC diagnostic pop
+
+static GtkStockItem stock_items[] = {
+ // gchar *stock_id;
+ // gchar *label;
+ // GdkModifierType modifier;
+ // guint keyval;
+ // gchar *translation_domain;
+ { TIKZIT_STOCK_SELECT, "Select", 0, 0, NULL },
+ { TIKZIT_STOCK_CREATE_NODE, "Create Node", 0, 0, NULL },
+ { TIKZIT_STOCK_CREATE_EDGE, "Create Edge", 0, 0, NULL },
+ { TIKZIT_STOCK_BOUNDING_BOX, "Bounding Box", 0, 0, NULL },
+ { TIKZIT_STOCK_DRAG, "Drag", 0, 0, NULL },
+};
+static guint n_stock_items = G_N_ELEMENTS (stock_items);
+
+static void icon_factory_add_pixdata (GtkIconFactory *factory,
+ const gchar *stock_id,
+ const GdkPixdata *image_data) {
+
+ GdkPixbuf *buf = gdk_pixbuf_from_pixdata (image_data, FALSE, NULL);
+ GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf (buf);
+ gtk_icon_factory_add (factory, stock_id, icon_set);
+ gtk_icon_set_unref (icon_set);
+ g_object_unref (G_OBJECT (buf));
+}
+
+void tz_register_stock_items() {
+ gtk_stock_add_static (stock_items, n_stock_items);
+
+ GtkIconFactory *factory = gtk_icon_factory_new ();
+ icon_factory_add_pixdata (factory, TIKZIT_STOCK_SELECT, &select_rectangular);
+ icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_NODE, &draw_ellipse);
+ icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_EDGE, &draw_path);
+ icon_factory_add_pixdata (factory, TIKZIT_STOCK_BOUNDING_BOX, &transform_crop_and_resize);
+ icon_factory_add_pixdata (factory, TIKZIT_STOCK_DRAG, &transform_move);
+ gtk_icon_factory_add_default (factory);
+}
+
+// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker
diff --git a/tikzit/src/gtk/tztoolpalette.h b/tikzit/src/gtk/tztoolpalette.h
new file mode 100644
index 0000000..45ec2ac
--- /dev/null
+++ b/tikzit/src/gtk/tztoolpalette.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * tztoolpalette.h, based on gimptoolpalette.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 3 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/>.
+ */
+
+#ifndef __TZ_TOOL_PALETTE_H__
+#define __TZ_TOOL_PALETTE_H__
+
+
+#define TZ_TYPE_TOOL_PALETTE (tz_tool_palette_get_type ())
+#define TZ_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPalette))
+#define TZ_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass))
+#define TZ_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TZ_TYPE_TOOL_PALETTE))
+#define TZ_IS_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TZ_TYPE_TOOL_PALETTE))
+#define TZ_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass))
+
+
+typedef struct _TzToolPaletteClass TzToolPaletteClass;
+typedef struct _TzToolPalette TzToolPalette;
+
+struct _TzToolPalette
+{
+ GtkToolPalette parent_instance;
+};
+
+struct _TzToolPaletteClass
+{
+ GtkToolPaletteClass parent_class;
+};
+
+
+GType tz_tool_palette_get_type (void) G_GNUC_CONST;
+
+GtkWidget * tz_tool_palette_new (void);
+
+gboolean tz_tool_palette_get_button_size (TzToolPalette *widget,
+ gint *width,
+ gint *height);
+
+
+#endif /* __TZ_TOOL_PALETTE_H__ */
diff --git a/tikzit/src/gtk/tztoolpalette.m b/tikzit/src/gtk/tztoolpalette.m
new file mode 100644
index 0000000..a948127
--- /dev/null
+++ b/tikzit/src/gtk/tztoolpalette.m
@@ -0,0 +1,158 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * tztoolpalette.c, based on gimptoolpalette.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ * Copyright (C) 2012 Alex Merry <alex.merry@kdemail.net>
+ *
+ * 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 3 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/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "tztoolpalette.h"
+
+
+#define DEFAULT_TOOL_ICON_SIZE GTK_ICON_SIZE_BUTTON
+#define DEFAULT_BUTTON_RELIEF GTK_RELIEF_NONE
+
+#define TOOL_BUTTON_DATA_KEY "tz-tool-palette-item"
+#define TOOL_INFO_DATA_KEY "tz-tool-info"
+
+
+typedef struct _TzToolPalettePrivate TzToolPalettePrivate;
+
+struct _TzToolPalettePrivate
+{
+ gint tool_rows;
+ gint tool_columns;
+};
+
+#define GET_PRIVATE(p) G_TYPE_INSTANCE_GET_PRIVATE (p, \
+ TZ_TYPE_TOOL_PALETTE, \
+ TzToolPalettePrivate)
+
+
+static void tz_tool_palette_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+
+G_DEFINE_TYPE (TzToolPalette, tz_tool_palette, GTK_TYPE_TOOL_PALETTE)
+
+#define parent_class tz_tool_palette_parent_class
+
+
+static void
+tz_tool_palette_class_init (TzToolPaletteClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->size_allocate = tz_tool_palette_size_allocate;
+
+ g_type_class_add_private (klass, sizeof (TzToolPalettePrivate));
+}
+
+static void
+tz_tool_palette_init (TzToolPalette *palette)
+{
+}
+
+static GtkToolItemGroup *
+tz_tool_palette_tool_group (TzToolPalette *palette)
+{
+ GList *children;
+ GtkToolItemGroup *group;
+
+ children = gtk_container_get_children (GTK_CONTAINER (palette));
+ g_return_val_if_fail (children, NULL);
+ group = GTK_TOOL_ITEM_GROUP (children->data);
+ g_list_free (children);
+
+ return group;
+}
+
+gboolean
+tz_tool_palette_get_button_size (TzToolPalette *palette,
+ gint *width,
+ gint *height)
+{
+ g_return_val_if_fail (width || height, FALSE);
+
+ GtkToolItemGroup *group = tz_tool_palette_tool_group (palette);
+ g_return_val_if_fail (group, FALSE);
+
+ guint tool_count = gtk_tool_item_group_get_n_items (group);
+ if (tool_count > 0)
+ {
+ GtkWidget *tool_button;
+ GtkRequisition button_requisition;
+
+ tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0));
+ gtk_widget_size_request (tool_button, &button_requisition);
+ if (width)
+ *width = button_requisition.width;
+ if (height)
+ *height = button_requisition.height;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+tz_tool_palette_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ TzToolPalettePrivate *private = GET_PRIVATE (widget);
+ GtkToolItemGroup *group = tz_tool_palette_tool_group (TZ_TOOL_PALETTE (widget));
+
+ g_return_if_fail (group);
+
+ GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+
+ guint tool_count = gtk_tool_item_group_get_n_items (group);
+ if (tool_count > 0)
+ {
+ GtkWidget *tool_button;
+ GtkRequisition button_requisition;
+ gint tool_rows;
+ gint tool_columns;
+
+ tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0));
+ gtk_widget_size_request (tool_button, &button_requisition);
+
+ tool_columns = MAX (1, (allocation->width / button_requisition.width));
+ tool_rows = tool_count / tool_columns;
+
+ if (tool_count % tool_columns)
+ tool_rows++;
+
+ if (private->tool_rows != tool_rows ||
+ private->tool_columns != tool_columns)
+ {
+ private->tool_rows = tool_rows;
+ private->tool_columns = tool_columns;
+
+ gtk_widget_set_size_request (widget, -1,
+ tool_rows * button_requisition.height);
+ }
+ }
+}
+
+GtkWidget *
+tz_tool_palette_new (void)
+{
+ return g_object_new (TZ_TYPE_TOOL_PALETTE, NULL);
+}
+
+// vim:ft=objc:ts=8:et:sts=2:sw=2:foldmethod=marker