From f23a2d9daa1eb62b2afac8997f1d76eb881628f7 Mon Sep 17 00:00:00 2001 From: Alex Merry Date: Tue, 4 Dec 2012 18:55:25 +0000 Subject: Refactor GraphInputHandler into Tools --- tikzit/src/Makefile.am | 8 +- tikzit/src/gtk/Application.h | 17 +- tikzit/src/gtk/Application.m | 48 ++- tikzit/src/gtk/BoundingBoxTool.h | 45 +++ tikzit/src/gtk/BoundingBoxTool.m | 353 +++++++++++++++++++ tikzit/src/gtk/CreateEdgeTool.h | 43 +++ tikzit/src/gtk/CreateEdgeTool.m | 166 +++++++++ tikzit/src/gtk/CreateNodeTool.h | 39 +++ tikzit/src/gtk/CreateNodeTool.m | 109 ++++++ tikzit/src/gtk/DocumentContext.h | 27 ++ tikzit/src/gtk/GraphEditorPanel.h | 48 +++ tikzit/src/gtk/GraphEditorPanel.m | 170 ++++++++++ tikzit/src/gtk/GraphInputHandler.h | 66 ---- tikzit/src/gtk/GraphInputHandler.m | 469 ------------------------- tikzit/src/gtk/GraphRenderer.h | 37 +- tikzit/src/gtk/GraphRenderer.m | 249 ++------------ tikzit/src/gtk/HandTool.h | 33 ++ tikzit/src/gtk/HandTool.m | 75 ++++ tikzit/src/gtk/Menu.h | 18 +- tikzit/src/gtk/Menu.m | 678 +++++++++++++++---------------------- tikzit/src/gtk/SelectTool.h | 54 +++ tikzit/src/gtk/SelectTool.m | 366 ++++++++++++++++++++ tikzit/src/gtk/TikzDocument.h | 17 - tikzit/src/gtk/TikzDocument.m | 14 - tikzit/src/gtk/Tool.h | 42 +++ tikzit/src/gtk/Window.h | 25 +- tikzit/src/gtk/Window.m | 69 ++-- tikzit/src/gtk/main.m | 2 + tikzit/src/gtk/tzstockitems.h | 26 ++ tikzit/src/gtk/tzstockitems.m | 64 ++++ 30 files changed, 2088 insertions(+), 1289 deletions(-) create mode 100644 tikzit/src/gtk/BoundingBoxTool.h create mode 100644 tikzit/src/gtk/BoundingBoxTool.m create mode 100644 tikzit/src/gtk/CreateEdgeTool.h create mode 100644 tikzit/src/gtk/CreateEdgeTool.m create mode 100644 tikzit/src/gtk/CreateNodeTool.h create mode 100644 tikzit/src/gtk/CreateNodeTool.m create mode 100644 tikzit/src/gtk/DocumentContext.h create mode 100644 tikzit/src/gtk/GraphEditorPanel.h create mode 100644 tikzit/src/gtk/GraphEditorPanel.m delete mode 100644 tikzit/src/gtk/GraphInputHandler.h delete mode 100644 tikzit/src/gtk/GraphInputHandler.m create mode 100644 tikzit/src/gtk/HandTool.h create mode 100644 tikzit/src/gtk/HandTool.m create mode 100644 tikzit/src/gtk/SelectTool.h create mode 100644 tikzit/src/gtk/SelectTool.m create mode 100644 tikzit/src/gtk/Tool.h create mode 100644 tikzit/src/gtk/tzstockitems.h create mode 100644 tikzit/src/gtk/tzstockitems.m diff --git a/tikzit/src/Makefile.am b/tikzit/src/Makefile.am index 30d9815..83e0978 100644 --- a/tikzit/src/Makefile.am +++ b/tikzit/src/Makefile.am @@ -25,10 +25,13 @@ EDGEDECFILES = ../AH_*.png ../ED_*.png bin_PROGRAMS = tikzit BUILT_SOURCES = $(PARSERFILES) tikzit_SOURCES = gtk/Application.m \ + gtk/BoundingBoxTool.m \ gtk/CairoRenderContext.m \ gtk/ColorRGB+IntegerListStorage.m \ gtk/ColorRGB+Gtk.m \ gtk/Configuration.m \ + gtk/CreateEdgeTool.m \ + gtk/CreateNodeTool.m \ gtk/Edge+Render.m \ gtk/EdgeStyle+Gtk.m \ gtk/EdgeStyle+Storage.m \ @@ -36,7 +39,8 @@ tikzit_SOURCES = gtk/Application.m \ gtk/EdgeStyleSelector.m \ gtk/EdgeStylesPalette.m \ gtk/FileChooserDialog.m \ - gtk/GraphInputHandler.m \ + gtk/HandTool.m \ + gtk/GraphEditorPanel.m \ gtk/GraphRenderer.m \ gtk/Menu.m \ gtk/Node+Render.m \ @@ -51,6 +55,7 @@ tikzit_SOURCES = gtk/Application.m \ gtk/PropertyPane.m \ gtk/PropertyListEditor.m \ gtk/RecentManager.m \ + gtk/SelectTool.m \ gtk/Shape+Render.m \ gtk/StyleManager+Storage.m \ gtk/StylesPane.m \ @@ -63,6 +68,7 @@ tikzit_SOURCES = gtk/Application.m \ gtk/logo.m \ gtk/mkdtemp.m \ gtk/main.m \ + gtk/tzstockitems.m \ common/CircleShape.m \ common/ColorRGB.m \ common/DiamondShape.m \ diff --git a/tikzit/src/gtk/Application.h b/tikzit/src/gtk/Application.h index 1967132..b05a9cd 100644 --- a/tikzit/src/gtk/Application.h +++ b/tikzit/src/gtk/Application.h @@ -26,6 +26,7 @@ @class StyleManager; @class TikzDocument; @class Window; +@protocol Tool; extern Application* app; @@ -43,15 +44,16 @@ extern Application* app; NSString *lastOpenFolder; NSString *lastSaveAsFolder; - // the open (active) document - TikzDocument *activeDocument; - PreambleEditor *preambleWindow; PreviewWindow *previewWindow; SettingsDialog *settingsDialog; // the open windows (array of Window*) NSMutableArray *openWindows; + + // tools + id activeTool; + NSArray *tools; } /** @@ -70,9 +72,14 @@ extern Application* app; @property (readonly) Preambles *preambles; /** - * The document the user is currently editing + * The tools + */ +@property (readonly) NSArray *tools; + +/** + * The currently-selected tool */ -@property (retain) TikzDocument *activeDocument; +@property (retain) id activeTool; /** * The folder last actively chosen by a user for opening a file diff --git a/tikzit/src/gtk/Application.m b/tikzit/src/gtk/Application.m index 2631bb3..ac6a451 100644 --- a/tikzit/src/gtk/Application.m +++ b/tikzit/src/gtk/Application.m @@ -27,12 +27,19 @@ #ifdef HAVE_POPPLER #import "SettingsDialog.h" #endif +#import "Shape.h" #import "StyleManager.h" #import "StyleManager+Storage.h" #import "SupportDir.h" #import "TikzDocument.h" #import "Window.h" +#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" @@ -47,7 +54,7 @@ Application* app = nil; @synthesize mainConfiguration=configFile; @synthesize styleManager, preambles; @synthesize lastOpenFolder, lastSaveAsFolder; -@synthesize activeDocument; +@synthesize tools; + (Application*) app { if (app == nil) { @@ -93,6 +100,15 @@ Application* app = nil; openWindows = [[NSMutableArray alloc] init]; + tools = [[NSArray alloc] initWithObjects: + [SelectTool tool], + [CreateNodeTool tool], + [CreateEdgeTool tool], + [BoundingBoxTool tool], + [HandTool tool], + nil]; + activeTool = [[tools objectAtIndex:0] retain]; + // FIXME: toolboxes app = [self retain]; @@ -142,23 +158,25 @@ Application* app = nil; [preambles release]; [lastOpenFolder release]; [lastSaveAsFolder release]; - [activeDocument release]; [preambleWindow release]; [previewWindow release]; [settingsDialog release]; [openWindows release]; + [tools release]; + [activeTool release]; [super dealloc]; } -- (void) newWindow { - [self newWindowWithDocument:nil]; +- (id) activeTool { return activeTool; } +- (void) setActiveTool:(id)tool { + for (Window* window in openWindows) { + [window setActiveTool:tool]; + } } -- (void) newWindowWithDocument:(TikzDocument*)doc { - Window *window = (doc == nil) - ? [Window window] - : [Window windowWithDocument:doc]; +- (void) _addWindow:(Window*)window { + [window setActiveTool:activeTool]; [openWindows addObject:window]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowClosed:) @@ -167,6 +185,14 @@ Application* app = nil; // FIXME: focus? } +- (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) { @@ -217,10 +243,6 @@ Application* app = nil; return configFile; } -- (TikzDocument*) activeDocument { - return activeDocument; -} - - (void) saveConfiguration { NSError *error = nil; @@ -259,8 +281,6 @@ Application* app = nil; object:window]; if ([openWindows count] == 0) { gtk_main_quit(); - } else if ([[window document] isEqual:activeDocument]) { - [self setActiveDocument:[[openWindows objectAtIndex:0] document]]; } } @end 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" + +typedef enum { + NoHandle, + EastHandle, + SouthEastHandle, + SouthHandle, + SouthWestHandle, + WestHandle, + NorthWestHandle, + NorthHandle, + NorthEastHandle +} ResizeHandle; + +@interface BoundingBoxTool : NSObject { + 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..03bd341 --- /dev/null +++ b/tikzit/src/gtk/BoundingBoxTool.m @@ -0,0 +1,353 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "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 Tool"; } +- (const gchar*) stockIcon { 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)context onSurface:(id)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/CreateEdgeTool.h b/tikzit/src/gtk/CreateEdgeTool.h new file mode 100644 index 0000000..cd5787b --- /dev/null +++ b/tikzit/src/gtk/CreateEdgeTool.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@class EdgeStyle; +@class EdgeStyleSelector; +@class Node; +@class StyleManager; + +@interface CreateEdgeTool : NSObject { + GraphRenderer *renderer; + StyleManager *styleManager; + EdgeStyleSelector *stylePicker; + Node *sourceNode; + NSPoint sourceNodeScreenPoint; + NSPoint halfEdgeEnd; +} + +@property (retain) StyleManager *styleManager; +@property (retain) EdgeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; +@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..4124a78 --- /dev/null +++ b/tikzit/src/gtk/CreateEdgeTool.m @@ -0,0 +1,166 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "CreateEdgeTool.h" + +#import "Configuration.h" +#import "EdgeStyleSelector.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +@implementation CreateEdgeTool +- (NSString*) name { return @"Create Edge Tool"; } +- (const gchar*) stockIcon { return TIKZIT_STOCK_CREATE_EDGE; } +- (NSString*) helpText { return @"Create new edges"; } +- (NSString*) shortcut { return @"e"; } +@synthesize activeRenderer=renderer; +@synthesize styleManager; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + +- (id) init { + return [self initWithStyleManager:[StyleManager manager]]; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + self = [super init]; + + if (self) { + styleManager = [sm retain]; + stylePicker = [[EdgeStyleSelector alloc] initWithStyleManager:sm]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [styleManager release]; + [stylePicker release]; + [sourceNode release]; + + [super dealloc]; +} + +- (GtkWidget*) configurationWidget { + return [stylePicker widget]; +} + +- (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)context onSurface:(id)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]; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateEdgeTool" + withDefault:nil]; + [self setActiveStyle:[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..b2205e3 --- /dev/null +++ b/tikzit/src/gtk/CreateNodeTool.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@class NodeStyle; +@class NodeStyleSelector; +@class StyleManager; + +@interface CreateNodeTool : NSObject { + GraphRenderer *renderer; + StyleManager *styleManager; + NodeStyleSelector *stylePicker; +} + +@property (retain) StyleManager *styleManager; +@property (retain) NodeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; +@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..581ace3 --- /dev/null +++ b/tikzit/src/gtk/CreateNodeTool.m @@ -0,0 +1,109 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "CreateNodeTool.h" + +#import "Configuration.h" +#import "GraphRenderer.h" +#import "NodeStyleSelector.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +@implementation CreateNodeTool +- (NSString*) name { return @"Create Node Tool"; } +- (const gchar*) stockIcon { return TIKZIT_STOCK_CREATE_NODE; } +- (NSString*) helpText { return @"Create new nodes"; } +- (NSString*) shortcut { return @"n"; } +@synthesize activeRenderer=renderer; +@synthesize styleManager; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + +- (id) init { + return [self initWithStyleManager:[StyleManager manager]]; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + self = [super init]; + + if (self) { + styleManager = [sm retain]; + stylePicker = [[NodeStyleSelector alloc] initWithStyleManager:sm]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [styleManager release]; + [stylePicker release]; + + [super dealloc]; +} + +- (GtkWidget*) configurationWidget { + return [stylePicker widget]; +} + +- (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)context onSurface:(id)surface {} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateNodeTool" + withDefault:nil]; + [self setActiveStyle:[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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" + +@class TikzDocument; + +@interface DocumentContext { + TikzDocument *document; + GraphRenderer *renderSurface; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" +#import + +@class GraphInputHandler; +@class GraphRenderer; +@class TikzDocument; +@class WidgetSurface; + +@interface GraphEditorPanel : NSObject { + GraphRenderer *renderer; + WidgetSurface *surface; + GraphInputHandler *inputHandler; + id tool; +} +@property (retain) TikzDocument *document; +@property (readonly) GtkWidget *widget; +@property (retain) id 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..6d98fa5 --- /dev/null +++ b/tikzit/src/gtk/GraphEditorPanel.m @@ -0,0 +1,170 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "GraphEditorPanel.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 { + 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 setGrabsFocusOnClick: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) activeTool { + return tool; +} +- (void) setActiveTool:(id)t { + if (t == tool) + return; + + BOOL hadOldTool = ([tool activeRenderer] == renderer); + + id oldTool = tool; + tool = [t retain]; + [oldTool release]; + + if (hadOldTool) { + [self grabTool]; + } +} + +- (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 = [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 = [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 { + [panel grabTool]; + id 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 { + [panel grabTool]; + id 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 { + [panel grabTool]; + id tool = [panel activeTool]; + if (mask == ControlMask) { + if (dir == ScrollUp) { + [panel zoomInAboutPoint:pos]; + } else if (dir == ScrollDown) { + [panel zoomOutAboutPoint:pos]; + } + } else if ([tool respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) { + [tool mouseScrolledAt:pos inDirection:dir withMask:mask]; + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/GraphInputHandler.h b/tikzit/src/gtk/GraphInputHandler.h deleted file mode 100644 index 4dbe5ca..0000000 --- a/tikzit/src/gtk/GraphInputHandler.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2011 Alex Merry - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#import "TZFoundation.h" -#import "GraphRenderer.h" -#import "InputDelegate.h" -#import "StyleManager.h" - -typedef enum { - SelectMode, - CreateNodeMode, - DrawEdgeMode, - BoundingBoxMode, - HandMode -} InputMode; - -typedef enum { - QuietState, - SelectBoxState, - ToggleSelectState, - MoveSelectedNodesState, - DragEdgeControlPoint1, - DragEdgeControlPoint2, - EdgeDragState, - BoundingBoxState, - CanvasDragState -} MouseState; - -@interface GraphInputHandler: NSObject { - GraphRenderer *renderer; - InputMode mode; - MouseState state; - float edgeFuzz; - NSPoint dragOrigin; - Node *leaderNode; - NSPoint oldLeaderPos; - Edge *modifyEdge; - NSMutableSet *selectionBoxContents; - ResizeHandle currentResizeHandle; - NSPoint oldOrigin; -} - -@property (assign) float edgeFuzz; -@property (assign) InputMode mode; - -- (id) initWithGraphRenderer:(GraphRenderer*)r; - -- (void) resetState; - -@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 788dfeb..0000000 --- a/tikzit/src/gtk/GraphInputHandler.m +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright 2011 Alex Merry - * 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 . - */ - -#import "GraphInputHandler.h" -#import -#import "Edge+Render.h" - -static const InputMask unionSelectMask = ShiftMask; - -@implementation GraphInputHandler -- (id) initWithGraphRenderer:(GraphRenderer*)r { - self = [super init]; - - if (self) { - 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) - } - - 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]; - } - } -} - -- (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 - -// 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 { TikzDocument *doc; NSObject *surface; Grid *grid; - NSRect selectionBox; - Node *halfEdgeOrigin; - NSPoint halfEdgeOriginPoint; - NSPoint halfEdgeEnd; - BOOL showBoundingBoxHandles; + NSMutableSet *highlightedNodes; + id postRenderer; } +@property (retain) id postRenderer; + - (id) initWithSurface:(NSObject *)surface; - (id) initWithSurface:(NSObject *)surface document:(TikzDocument*)document; - (void) renderWithContext:(id)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)context; -- (void) renderSelectionBoxWithContext:(id)context; -- (void) renderImpendingEdgeWithContext:(id)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) postRenderer { + return postRenderer; +} +- (void) setPostRenderer:(id)r { + if (r == postRenderer) + return; + + [r retain]; + [postRenderer release]; + postRenderer = r; + + [self invalidateGraph]; +} + - (void) renderWithContext:(id)context onSurface:(id)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)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)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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@interface HandTool : NSObject { + 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..830fc28 --- /dev/null +++ b/tikzit/src/gtk/HandTool.m @@ -0,0 +1,75 @@ +/* + * Copyright 2011-2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "HandTool.h" + +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +@implementation HandTool +- (NSString*) name { return @"Drag Tool"; } +- (const gchar*) stockIcon { 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)context onSurface:(id)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/Menu.h b/tikzit/src/gtk/Menu.h index f195765..e0f78d4 100644 --- a/tikzit/src/gtk/Menu.h +++ b/tikzit/src/gtk/Menu.h @@ -25,14 +25,12 @@ * Manages the menu */ @interface Menu: NSObject { - Window *window; - 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; @@ -45,10 +43,6 @@ * The menubar widget, to be inserted into the window */ @property (readonly) GtkWidget *menubar; -/** - * The window object passed to initForWindow - */ -@property (readonly) Window *window; /** * Constructs the menu for @p window diff --git a/tikzit/src/gtk/Menu.m b/tikzit/src/gtk/Menu.m index c6f2fde..8d0cd97 100644 --- a/tikzit/src/gtk/Menu.m +++ b/tikzit/src/gtk/Menu.m @@ -23,10 +23,10 @@ #import "Application.h" #import "Window.h" -#import "GraphInputHandler.h" #import "Configuration.h" #import "PickSupport.h" #import "Shape.h" +#import "Tool.h" #import "TikzDocument.h" #import @@ -38,63 +38,42 @@ #import "gtkhelpers.h" -#define ACTION_GROUP_STATIC "TZStatic" -#define ACTION_GROUP_DOCUMENT "TZDocument" -#define ACTION_GROUP_DOCUMENTS_LIST_MENU "TZDocumentsList" - #import "logo.h" -#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpointer-sign" -#import "icondata.m" -#pragma GCC diagnostic pop - - -// {{{ Callbacks - -static void new_cb (GtkAction *action, Window *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [app newWindow]; - [pool drain]; -} - -static void open_cb (GtkAction *action, Window *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window openFile]; - [pool drain]; -} -static void save_cb (GtkAction *action, Window *window) { +// {{{ Application actions +static void new_cb (GtkAction *action, Application *appl) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window saveActiveDocument]; + [appl newWindow]; [pool drain]; } -static void save_as_cb (GtkAction *action, Window *window) { +static void refresh_shapes_cb (GtkAction *action, Application *appl) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window saveActiveDocumentAs]; + [Shape refreshShapeDictionary]; [pool drain]; } -static void save_as_shape_cb (GtkAction *action, Window *window) { +#ifdef HAVE_POPPLER +static void show_preferences_cb (GtkAction *action, Application *appl) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [window saveActiveDocumentAsShape]; + [appl showSettingsDialog]; [pool drain]; } -static void refresh_shapes_cb (GtkAction *action, Window *window) { +static void show_preamble_cb (GtkAction *action, Application *appl) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [Shape refreshShapeDictionary]; + [appl showPreamblesEditor]; [pool drain]; } +#endif -static void quit_cb (GtkAction *action, Window *window) { +static void quit_cb (GtkAction *action, Application *appl) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [app quit]; + [appl quit]; [pool drain]; } -static void help_cb (GtkAction *action, Window *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) { @@ -104,7 +83,7 @@ static void help_cb (GtkAction *action, Window *window) { } } -static void about_cb (GtkAction *action, Window *window) { +static void about_cb (GtkAction *action, Application *appl) { static const gchar * const authors[] = { "Aleks Kissinger ", "Chris Heunen ", @@ -131,7 +110,7 @@ static void about_cb (GtkAction *action, Window *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, @@ -146,6 +125,76 @@ static void about_cb (GtkAction *action, Window *window) { g_object_unref (logo); } +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, "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, "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 + + /* 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]; @@ -256,18 +305,6 @@ static void send_to_back_cb (GtkAction *action, Window *window) { } #ifdef HAVE_POPPLER -static void show_preferences_cb (GtkAction *action, Window *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [app showSettingsDialog]; - [pool drain]; -} - -static void show_preamble_cb (GtkAction *action, Window *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [app showPreamblesEditor]; - [pool drain]; -} - static void show_preview_cb (GtkAction *action, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [app showPreviewForDocument:[window document]]; @@ -293,25 +330,6 @@ static void zoom_reset_cb (GtkAction *action, Window *window) { [pool drain]; } -static void input_mode_change_cb (GtkRadioAction *action, GtkRadioAction *current, Window *window) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - InputMode mode = (InputMode)gtk_radio_action_get_current_value (action); - [[window graphInputHandler] setMode:mode]; - [pool drain]; -} - -/* -static void toolbar_style_change_cb (GtkRadioAction *action, GtkRadioAction *current, Menu *menu) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - 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, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @@ -336,121 +354,7 @@ static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, Window } - -// }}} -// {{{ UI XML - -static const gchar ui_info[] = -"" -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -//" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -#ifdef HAVE_POPPLER -" " -" " -#endif -" " -" " -/* -" " -" " -" " -" " -" " -" " -*/ -/* -" " -" " -*/ -#ifdef HAVE_POPPLER -" " -" " -#endif -" " -" " -" " -" " -" " -" " -/* -" " -" " -" " -*/ -" " -" " -" " -" " -" " -" " -/* -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -*/ -""; - - - -// }}} -// {{{ Actions - -static GtkActionEntry static_entries[] = { +static GtkActionEntry window_action_entries[] = { /* Fields: * action name @@ -463,45 +367,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, "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") ,"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, "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, "W", + N_("Close the current graph"), G_CALLBACK (close_cb) }, { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, "plus", NULL, G_CALLBACK (zoom_in_cb) }, @@ -512,19 +387,6 @@ static GtkActionEntry static_entries[] = { { "ZoomReset", GTK_STOCK_ZOOM_100, N_("_Reset zoom"), "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, "S", N_("Save the current graph"), G_CALLBACK (save_cb) }, @@ -534,12 +396,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, "W", - N_("Close the current graph"), G_CALLBACK (close_cb) }, -*/ - - /* EditMenu */ { "Undo", GTK_STOCK_UNDO, NULL, "Z", N_("Undo the last action"), G_CALLBACK (undo_cb) }, @@ -591,101 +447,114 @@ 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 guint n_window_action_entries = G_N_ELEMENTS (window_action_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"), "s", - N_("Select, move and edit nodes and edges"), (gint)SelectMode }, - - { "CreateNodeMode", NULL, N_("_Create nodes"), "c", - N_("Create new nodes"), (gint)CreateNodeMode }, - - { "DrawEdgeMode", NULL, N_("_Draw edges"), "e", - N_("Draw new edges"), (gint)DrawEdgeMode }, - - { "BoundingBoxMode", NULL, N_("_Bounding box"), "x", - N_("Set the bounding box"), (gint)BoundingBoxMode }, - - { "HandMode", NULL, N_("_Pan"), "f", - N_("Move the diagram to view different parts"), (gint)HandMode }, -}; -static guint n_mode_entries = G_N_ELEMENTS (mode_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[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +#ifdef HAVE_POPPLER +" " +" " +#endif +" " +" " +#ifdef HAVE_POPPLER +" " +" " +#endif +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +/* +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +*/ +""; - { "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) +static void configure_recent_chooser (GtkRecentChooser *chooser) { - 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); - } -} + 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); -GtkWidget * -create_recent_chooser_menu () -{ - GtkWidget *recent_menu; - GtkRecentFilter *filter; - - 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) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [app setActiveTool:tool]; + [pool drain]; } @@ -701,107 +570,102 @@ create_recent_chooser_menu () return nil; } -- (id) initForWindow:(Window*)w { +- (id) initForWindow:(Window*)window { self = [super init]; if (!self) { return nil; } - window = w; - GError *error = NULL; - staticActions = gtk_action_group_new (ACTION_GROUP_STATIC); - //gtk_action_group_set_translation_domain (staticActions, GETTEXT_PACKAGE); - - 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, - 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); - */ - - ui = gtk_ui_manager_new (); - - 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); - - 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; + 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 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 stockIcon]); + gtk_action_group_add_action_with_accel ( + appActions, + action, + [[tool shortcut] UTF8String]); + g_signal_connect ( + G_OBJECT (action), + "activate", + G_CALLBACK (tool_cb), + tool); + g_object_unref (action); } - /* 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); - */ + windowActions = gtk_action_group_new ("TZWindow"); + //gtk_action_group_set_translation_domain (windowActions, GETTEXT_PACKAGE); - /* 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"); + gtk_action_group_add_actions (windowActions, + window_action_entries, + n_window_action_entries, + window); + + 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); - /* 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); + /* 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"); 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"); + 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 (documentActions, "ReverseEdges"); + edgeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "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"); + selBasedActions[0] = gtk_action_group_get_action (windowActions, "Delete"); + selBasedActions[1] = gtk_action_group_get_action (windowActions, "DeselectAll"); - /* - Configuration *configFile = [app mainConfiguration]; - if ([configFile hasKey:@"toolbarStyle" inGroup:@"UI"]) { - int value = [configFile integerEntry:@"toolbarStyle" inGroup:@"UI"]; - gtk_radio_action_set_current_value ( - GTK_RADIO_ACTION (gtk_action_group_get_action (staticActions, "ToolbarIconsOnly")), - value); + + 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); + g_object_unref (ui); + [self release]; + return nil; } - */ + guint tool_merge_id = gtk_ui_manager_new_merge_id (ui); + for (id 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; } @@ -809,21 +673,13 @@ create_recent_chooser_menu () - (void) dealloc { g_free (nodeSelBasedActions); g_free (selBasedActions); + g_object_unref (menubar); + g_object_unref (windowActions); [super dealloc]; } -- (GtkWidget*) menubar { - return gtk_ui_manager_get_widget (ui, "/MenuBar"); -} - -/* -- (GtkWidget*) toolbar { - return gtk_ui_manager_get_widget (ui, "/ToolBar"); -} -*/ - -@synthesize window; +@synthesize menubar; - (void) setUndoActionEnabled:(BOOL)enabled { gtk_action_set_sensitive (undoAction, enabled); diff --git a/tikzit/src/gtk/SelectTool.h b/tikzit/src/gtk/SelectTool.h new file mode 100644 index 0000000..172a7ae --- /dev/null +++ b/tikzit/src/gtk/SelectTool.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@class Edge; +@class Node; + +// FIXME: replace this with delegates +typedef enum { + QuietState, + SelectBoxState, + ToggleSelectState, + MoveSelectedNodesState, + DragEdgeControlPoint1, + DragEdgeControlPoint2 +} SelectToolState; + +@interface SelectTool : NSObject { + GraphRenderer *renderer; + SelectToolState state; + float edgeFuzz; + NSPoint dragOrigin; + Node *leaderNode; + NSPoint oldLeaderPos; + Edge *modifyEdge; + NSRect selectionBox; + NSMutableSet *selectionBoxContents; + + GtkWidget *configWidget; +} + +@property (assign) float edgeFuzz; + +- (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..08abbea --- /dev/null +++ b/tikzit/src/gtk/SelectTool.m @@ -0,0 +1,366 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "SelectTool.h" + +#import "Edge+Render.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static const InputMask unionSelectMask = ShiftMask; + +@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 Tool"; } +- (const gchar*) stockIcon { 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; + selectionBoxContents = [[NSMutableSet alloc] initWithCapacity:10]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [leaderNode release]; + [modifyEdge release]; + [selectionBoxContents release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (GraphRenderer*) activeRenderer { return renderer; } +- (void) setActiveRenderer:(GraphRenderer*)r { + if (r == renderer) + return; + + [self deselectAll]; + + [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 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]; + [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) 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)) { + 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]; + } +} + +- (void) renderWithContext:(id)context onSurface:(id)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 {} +- (void) saveConfiguration:(Configuration*)config {} + +@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)]; +} + +- (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 + +// vim:ft=objc:ts=8:et:sts=4:sw=4 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..de3d3ff --- /dev/null +++ b/tikzit/src/gtk/Tool.h @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "TZFoundation.h" +#import "InputDelegate.h" +#import "Surface.h" + +#import +#import + +@class Configuration; +@class GraphRenderer; +@protocol InputDelegate; +@protocol RenderDelegate; + +@protocol Tool +@property (readonly) NSString *name; +@property (readonly) const gchar *stockIcon; +@property (readonly) NSString *helpText; +@property (readonly) NSString *shortcut; +@property (retain) GraphRenderer *activeRenderer; +@property (readonly) GtkWidget *configurationWidget; +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; ++ (id) tool; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/gtk/Window.h b/tikzit/src/gtk/Window.h index 6aa710a..089cc49 100644 --- a/tikzit/src/gtk/Window.h +++ b/tikzit/src/gtk/Window.h @@ -17,10 +17,8 @@ #import "TZFoundation.h" #import -#import "WidgetSurface.h" -@class GraphRenderer; -@class GraphInputHandler; +@class GraphEditorPanel; @class Menu; @class PropertyPane; @class Preambles; @@ -30,6 +28,7 @@ @class StyleManager; @class StylesPane; @class TikzDocument; +@protocol Tool; /** * Manages a document window @@ -44,10 +43,7 @@ // Classes that manage parts of the window Menu *menu; - GraphRenderer *renderer; - GraphInputHandler *inputHandler; - - WidgetSurface *surface; + GraphEditorPanel *graphPanel; // state variables BOOL suppressTikzUpdates; @@ -96,6 +92,15 @@ */ - (void) saveActiveDocumentAsShape; +/** + * Close the window. + * + * May terminate the application if this is the last window. + * + * Will ask for user confirmation if the document is not saved. + */ +- (void) close; + /** * Cut the current selection to the clipboard. */ @@ -109,10 +114,6 @@ */ - (void) paste; -/** - * The graph input handler - */ -- (GraphInputHandler*) graphInputHandler; /** * The GTK+ window that this class manages. */ @@ -149,6 +150,8 @@ */ - (void) presentGError:(GError*)error withMessage:(NSString*)message; +- (void) setActiveTool:(id)tool; + - (void) zoomIn; - (void) zoomOut; - (void) zoomReset; diff --git a/tikzit/src/gtk/Window.m b/tikzit/src/gtk/Window.m index f8e4aef..930b296 100644 --- a/tikzit/src/gtk/Window.m +++ b/tikzit/src/gtk/Window.m @@ -24,14 +24,12 @@ #import "Application.h" #import "Configuration.h" #import "FileChooserDialog.h" -#import "GraphInputHandler.h" -#import "GraphRenderer.h" +#import "GraphEditorPanel.h" #import "Menu.h" #import "RecentManager.h" #import "Shape.h" #import "SupportDir.h" #import "TikzDocument.h" -#import "WidgetSurface.h" // {{{ Internal interfaces @@ -53,6 +51,7 @@ static void clipboard_paste_contents (GtkClipboard *clipboard, // }}} // {{{ Signals +static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, GraphEditorPanel *panel); 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); @@ -127,9 +126,7 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc [[NSNotificationCenter defaultCenter] removeObserver:self]; [menu release]; - [renderer release]; - [inputHandler release]; - [surface release]; + [graphPanel release]; [document release]; g_object_unref (tikzBuffer); @@ -152,13 +149,12 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc [document release]; document = newDoc; - [renderer setDocument:document]; + [graphPanel setDocument:document]; [self _updateTikz]; [self _updateTitle]; [self _updateStatus]; [self _updateUndoActions]; [menu notifySelectionChanged:[document pickSupport]]; - [inputHandler resetState]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentTikzChanged:) name:@"TikzChanged" object:document]; @@ -324,6 +320,12 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_destroy (dialog); } +- (void) close { + if ([self _askCanClose]) { + gtk_widget_destroy (GTK_WIDGET (window)); + } +} + - (void) cut { if ([[[document pickSupport] selectedNodes] count] > 0) { [self _placeGraphOnClipboard:[document cutSelection]]; @@ -343,10 +345,6 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc document); } -- (GraphInputHandler*) graphInputHandler { - return inputHandler; -} - - (GtkWindow*) gtkWindow { return window; } @@ -405,16 +403,25 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_destroy (dialog); } +- (void) setActiveTool:(id)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]; + [graphPanel zoomReset]; } @end @@ -501,19 +508,13 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0); - surface = [[WidgetSurface alloc] init]; - gtk_widget_show ([surface widget]); + 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]; - [surface setInputDelegate:inputHandler]; tikzBuffer = gtk_text_buffer_new (NULL); g_object_ref_sink (tikzBuffer); @@ -568,6 +569,10 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc "key-press-event", G_CALLBACK (tz_hijack_key_press), NULL); + g_signal_connect (G_OBJECT (window), + "notify::has-toplevel-focus", + G_CALLBACK (window_toplevel_focus_changed_cb), + graphPanel); g_signal_connect (G_OBJECT (tikzPaneSplitter), "notify::position", G_CALLBACK (graph_divider_position_changed_cb), @@ -739,6 +744,16 @@ static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAc // }}} // {{{ GTK+ callbacks +static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, GraphEditorPanel *panel) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gboolean hasfocus; + g_object_get (gobject, "has-toplevel-focus", &hasfocus, NULL); + if (hasfocus) { + [panel grabTool]; + } + [pool drain]; +} + static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; gint position; @@ -755,9 +770,9 @@ static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window) { static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - BOOL canClose = ![window _askCanClose]; + [window close]; [pool drain]; - return canClose; + return TRUE; } static void main_window_destroy_cb (GtkWidget *widget, Window *window) { diff --git a/tikzit/src/gtk/main.m b/tikzit/src/gtk/main.m index 9d179ab..eb06449 100644 --- a/tikzit/src/gtk/main.m +++ b/tikzit/src/gtk/main.m @@ -25,6 +25,7 @@ #import #import "clipboard.h" #import "logo.h" +#import "tzstockitems.h" #import "Application.h" #import "TikzGraphAssembler.h" @@ -78,6 +79,7 @@ int main (int argc, char *argv[]) { NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + tz_register_stock_items(); clipboard_init(); [TikzGraphAssembler setup]; 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 + * + * 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 . + */ + +#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..6425d5f --- /dev/null +++ b/tikzit/src/gtk/tzstockitems.m @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Alex Merry + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tzstockitems.h" +#include +#include + +#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 Tool", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_NODE, "Create Node Tool", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_EDGE, "Create Edge Tool", 0, 0, NULL }, + { TIKZIT_STOCK_BOUNDING_BOX, "Bounding Box Tool", 0, 0, NULL }, + { TIKZIT_STOCK_DRAG, "Drag Tool", 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 -- cgit v1.2.3