diff options
Diffstat (limited to 'tikzit')
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 |