diff options
Diffstat (limited to 'tikzit-1/src/gtk')
118 files changed, 18422 insertions, 0 deletions
diff --git a/tikzit-1/src/gtk/Application.h b/tikzit-1/src/gtk/Application.h new file mode 100644 index 0000000..1b48a79 --- /dev/null +++ b/tikzit-1/src/gtk/Application.h @@ -0,0 +1,155 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "InputDelegate.h" + +@class Application; +@class Configuration; +@class ContextWindow; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class TikzDocument; +@class ToolBox; +@class Window; +@protocol Tool; + +extern Application* app; + +/** + * Manages the main application window + */ +@interface Application: NSObject { + // the main application configuration + Configuration *configFile; + // maintains the known (user-defined) styles + StyleManager *styleManager; + // maintains the preambles used for previews + Preambles *preambles; + // the last-accessed folders (for open and save dialogs) + NSString *lastOpenFolder; + NSString *lastSaveAsFolder; + + ToolBox *toolBox; + PreambleEditor *preambleWindow; + ContextWindow *contextWindow; + SettingsDialog *settingsDialog; + + // the open windows (array of Window*) + NSMutableArray *openWindows; + + // tools + id<Tool> activeTool; + NSArray *tools; +} + +/** + * The main application configuration file + */ +@property (readonly) Configuration *mainConfiguration; + +/** + * The app-wide style manager instance + */ +@property (readonly) StyleManager *styleManager; + +/** + * The app-wide preambles registry + */ +@property (readonly) Preambles *preambles; + +/** + * The tools + */ +@property (readonly) NSArray *tools; + +/** + * The currently-selected tool + */ +@property (assign) id<Tool> activeTool; + +/** + * The folder last actively chosen by a user for opening a file + */ +@property (copy) NSString *lastOpenFolder; + +/** + * The folder last actively chosen by a user for saving a file + */ +@property (copy) NSString *lastSaveAsFolder; + +/** + * The application instance. + */ ++ (Application*) app; + +/** + * Starts the application with a single window containing an empty file + */ +- (id) init; +/** + * Starts the application with the given files open + */ +- (id) initWithFiles:(NSArray*)files; + +/** + * Loads a new, empty document in a new window + */ +- (void) newWindow; +/** + * Loads an existing document from a file in a new window + * + * @param doc the document the new window should show + */ +- (void) newWindowWithDocument:(TikzDocument*)doc; +/** + * Quit the application, confirming with the user if there are + * changes to any open documents. + */ +- (void) quit; + +/** + * Show the dialog for editing preambles. + */ +- (void) presentPreamblesEditor; +/** + * Show the context-aware window + */ +- (void) presentContextWindow; +/** + * Show the settings dialog. + */ +- (void) presentSettingsDialog; + +/** + * Save the application configuration to permanent storage + * + * Should be called just before the application exits + */ +- (void) saveConfiguration; + +/** + * @result YES if key event was processed, NO otherwise + */ +- (BOOL) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Application.m b/tikzit-1/src/gtk/Application.m new file mode 100644 index 0000000..e9935bd --- /dev/null +++ b/tikzit-1/src/gtk/Application.m @@ -0,0 +1,377 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Application.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "PreambleEditor.h" +#import "ContextWindow.h" +#import "Shape.h" +#import "SettingsDialog.h" +#import "StyleManager.h" +#import "StyleManager+Storage.h" +#import "SupportDir.h" +#import "TikzDocument.h" +#import "ToolBox.h" +#import "Window.h" + +#ifdef HAVE_POPPLER +#import "Preambles.h" +#import "Preambles+Storage.h" +#endif + +#import "BoundingBoxTool.h" +#import "CreateNodeTool.h" +#import "CreateEdgeTool.h" +#import "HandTool.h" +#import "SelectTool.h" + +// used for args to g_mkdir_with_parents +#import "stat.h" + +Application* app = nil; + +@interface Application (Notifications) +- (void) windowClosed:(NSNotification*)notification; +- (void) windowGainedFocus:(NSNotification*)notification; +- (void) selectedToolChanged:(NSNotification*)notification; +- (void) windowDocumentChanged:(NSNotification*)n; +@end + +@interface Application (Private) +- (void) setActiveWindow:(Window*)window; +@end + +@implementation Application + +@synthesize mainConfiguration=configFile; +@synthesize styleManager, preambles; +@synthesize lastOpenFolder, lastSaveAsFolder; +@synthesize tools; + ++ (Application*) app { + if (app == nil) { + [[[self alloc] init] release]; + } + return app; +} + +- (id) _initCommon { + if (app != nil) { + [self release]; + self = app; + return self; + } + self = [super init]; + + if (self) { + NSError *error = nil; + configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error]; + if (error != nil) { + logError (error, @"WARNING: Failed to load configuration"); + } + + styleManager = [[StyleManager alloc] init]; + [styleManager loadStylesUsingConfigurationName:@"styles"]; // FIXME: error message? + NodeStylesModel *nsm = [NodeStylesModel modelWithStyleManager:styleManager]; + EdgeStylesModel *esm = [EdgeStylesModel modelWithStyleManager:styleManager]; + +#ifdef HAVE_POPPLER + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + preambles = [[Preambles alloc] initFromDirectory:preamblesDir]; // FIXME: error message? + [preambles setStyleManager:styleManager]; + NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"]; + if (selectedPreamble != nil) { + [preambles setSelectedPreambleName:selectedPreamble]; + } +#endif + + lastOpenFolder = [[configFile stringEntry:@"lastOpenFolder" inGroup:@"Paths"] retain]; + if (lastOpenFolder == nil) + lastOpenFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + lastSaveAsFolder = [[configFile stringEntry:@"lastSaveAsFolder" inGroup:@"Paths"] retain]; + if (lastSaveAsFolder == nil) + lastSaveAsFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + + openWindows = [[NSMutableArray alloc] init]; + + tools = [[NSArray alloc] initWithObjects: + [SelectTool tool], + [CreateNodeTool toolWithNodeStylesModel:nsm], + [CreateEdgeTool toolWithEdgeStylesModel:esm], + [BoundingBoxTool tool], + [HandTool tool], + nil]; + activeTool = [tools objectAtIndex:0]; + for (id<Tool> tool in tools) { + [tool loadConfiguration:configFile]; + } + + toolBox = [[ToolBox alloc] initWithTools:tools]; + [toolBox loadConfiguration:configFile]; + [toolBox setSelectedTool:activeTool]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(selectedToolChanged:) + name:@"ToolSelectionChanged" + object:toolBox]; + [toolBox show]; + + contextWindow = [[ContextWindow alloc] initWithNodeStylesModel:nsm + andEdgeStylesModel:esm]; + [contextWindow loadConfiguration:configFile]; + + app = [self retain]; + } + + return self; +} + +- (id) init { + self = [self _initCommon]; + + if (self) { + [self newWindow]; + } + + return self; +} + +- (id) initWithFiles:(NSArray*)files { + self = [self _initCommon]; + + if (self) { + int fileOpenCount = 0; + for (NSString *file in files) { + NSError *error = nil; + TikzDocument *doc = [TikzDocument documentFromFile:file styleManager:styleManager error:&error]; + if (doc != nil) { + [self newWindowWithDocument:doc]; + ++fileOpenCount; + } else { + logError(error, @"WARNING: failed to open file"); + } + } + if (fileOpenCount == 0) { + [self newWindow]; + } + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [configFile release]; + [styleManager release]; + [preambles release]; + [lastOpenFolder release]; + [lastSaveAsFolder release]; + [preambleWindow release]; + [settingsDialog release]; + [openWindows release]; + [tools release]; + [activeTool release]; + [toolBox release]; + [contextWindow release]; + + [super dealloc]; +} + +- (id<Tool>) activeTool { return activeTool; } +- (void) setActiveTool:(id<Tool>)tool { + if (activeTool == tool) + return; + + activeTool = tool; + [toolBox setSelectedTool:tool]; + for (Window* window in openWindows) { + [window setActiveTool:tool]; + } +} + +- (BOOL) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask { + // FIXME: cache the accel info, rather than reparsing it every time? + for (id<Tool> tool in tools) { + guint toolKey = 0; + GdkModifierType toolMod = 0; + gtk_accelerator_parse ([[tool shortcut] UTF8String], &toolKey, &toolMod); + if (toolKey != 0 && toolKey == keyVal && (int)mask == (int)toolMod) { + [self setActiveTool:tool]; + return YES; + } + } + return NO; +} + +- (void) _addWindow:(Window*)window { + [window setActiveTool:activeTool]; + [openWindows addObject:window]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowClosed:) + name:@"WindowClosed" + object:window]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowGainedFocus:) + name:@"WindowGainedFocus" + object:window]; + if ([window hasFocus]) { + [self setActiveWindow:window]; + } +} + +- (void) newWindow { + [self _addWindow:[Window window]]; +} + +- (void) newWindowWithDocument:(TikzDocument*)doc { + [self _addWindow:[Window windowWithDocument:doc]]; +} + +- (void) quit { + NSMutableArray *unsavedDocs = [NSMutableArray arrayWithCapacity:[openWindows count]]; + for (Window *window in openWindows) { + TikzDocument *doc = [window document]; + if ([doc hasUnsavedChanges]) { + [unsavedDocs addObject:doc]; + } + } + if ([unsavedDocs count] > 0) { + // FIXME: show a dialog + return; + } + gtk_main_quit(); +} + +- (void) presentPreamblesEditor { +#ifdef HAVE_POPPLER + if (preambleWindow == nil) { + preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles]; + //[preambleWindow setParentWindow:mainWindow]; + } + [preambleWindow present]; +#endif +} + +- (void) presentContextWindow { + [contextWindow present]; +} + +- (void) presentSettingsDialog { + if (settingsDialog == nil) { + settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile + andStyleManager:styleManager]; + //[settingsDialog setParentWindow:mainWindow]; + } + [settingsDialog present]; +} + +- (Configuration*) mainConfiguration { + return configFile; +} + +- (void) saveConfiguration { + NSError *error = nil; + +#ifdef HAVE_POPPLER + if (preambles != nil) { + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([preamblesDir UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); + [preambles storeToDirectory:preamblesDir]; + [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; + } +#endif + + [styleManager saveStylesUsingConfigurationName:@"styles"]; + + for (id<Tool> tool in tools) { + [tool saveConfiguration:configFile]; + } + [toolBox saveConfiguration:configFile]; + + [contextWindow saveConfiguration:configFile]; + + if (lastOpenFolder != nil) { + [configFile setStringEntry:@"lastOpenFolder" inGroup:@"Paths" value:lastOpenFolder]; + } + if (lastSaveAsFolder != nil) { + [configFile setStringEntry:@"lastSaveAsFolder" inGroup:@"Paths" value:lastSaveAsFolder]; + } + + if (![configFile writeToStoreWithError:&error]) { + logError (error, @"Could not write config file"); + } +} + +@end + +@implementation Application (Notifications) +- (void) windowClosed:(NSNotification*)notification { + Window *window = [notification object]; + [openWindows removeObjectIdenticalTo:window]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:nil + object:window]; + if ([openWindows count] == 0) { + gtk_main_quit(); + } else { + [self setActiveWindow:[openWindows objectAtIndex:0]]; + } +} + +- (void) windowGainedFocus:(NSNotification*)notification { + Window *window = [notification object]; + [self setActiveWindow:window]; +} + +- (void) selectedToolChanged:(NSNotification*)n { + id<Tool> tool = [[n userInfo] objectForKey:@"tool"]; + if (tool != nil) + [self setActiveTool:tool]; + else + NSLog(@"nil tool!"); +} + +- (void) windowDocumentChanged:(NSNotification*)n { + [contextWindow setDocument:[[n userInfo] objectForKey:@"document"]]; +} +@end + +@implementation Application (Private) +- (void) setActiveWindow:(Window*)window { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"DocumentChanged" + object:nil]; + + [contextWindow setDocument:[window document]]; + + [contextWindow attachToWindow:window]; + [toolBox attachToWindow:window]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(windowDocumentChanged:) + name:@"DocumentChanged" + object:window]; +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/BoundingBoxTool.h b/tikzit-1/src/gtk/BoundingBoxTool.h new file mode 100644 index 0000000..f6498b0 --- /dev/null +++ b/tikzit-1/src/gtk/BoundingBoxTool.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Tool.h" + +typedef enum { + NoHandle, + EastHandle, + SouthEastHandle, + SouthHandle, + SouthWestHandle, + WestHandle, + NorthWestHandle, + NorthHandle, + NorthEastHandle +} ResizeHandle; + +@interface BoundingBoxTool : NSObject <Tool> { + GraphRenderer *renderer; + NSPoint dragOrigin; + ResizeHandle currentResizeHandle; + BOOL drawingNewBox; +} + ++ (id) tool; +- (id) init; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/BoundingBoxTool.m b/tikzit-1/src/gtk/BoundingBoxTool.m new file mode 100644 index 0000000..483705e --- /dev/null +++ b/tikzit-1/src/gtk/BoundingBoxTool.m @@ -0,0 +1,353 @@ +/* + * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "BoundingBoxTool.h" + +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static const float handle_size = 8.0; +float sideHandleTop(NSRect bbox) { + return (NSMinY(bbox) + NSMaxY(bbox) - handle_size)/2.0f; +} +float tbHandleLeft(NSRect bbox) { + return (NSMinX(bbox) + NSMaxX(bbox) - handle_size)/2.0f; +} + +@interface BoundingBoxTool (Private) +- (NSRect) screenBoundingBox; +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p; +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle; +- (void) setResizeCursorForHandle:(ResizeHandle)handle; +@end + +@implementation BoundingBoxTool +- (NSString*) name { return @"Bounding Box"; } +- (const gchar*) stockId { return TIKZIT_STOCK_BOUNDING_BOX; } +- (NSString*) helpText { return @"Set the bounding box"; } +- (NSString*) shortcut { return @"b"; } + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + self = [super init]; + + if (self) { + currentResizeHandle = NoHandle; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + + [super dealloc]; +} + +- (GraphRenderer*) activeRenderer { return renderer; } +- (void) setActiveRenderer:(GraphRenderer*)r { + if (r == renderer) + return; + + [[renderer surface] setCursor:NormalCursor]; + + [r retain]; + [renderer release]; + renderer = r; +} + +- (GtkWidget*) configurationWidget { return NULL; } + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + currentResizeHandle = [self boundingBoxResizeHandleAt:pos]; + [[renderer document] startChangeBoundingBox]; + if (currentResizeHandle == NoHandle) { + drawingNewBox = YES; + [[[renderer document] graph] setBoundingBox:NSZeroRect]; + } else { + drawingNewBox = NO; + } + [renderer invalidateGraph]; +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) { + ResizeHandle handle = [self boundingBoxResizeHandleAt:pos]; + [self setResizeCursorForHandle:handle]; + return; + } + + Transformer *transformer = [renderer transformer]; + Grid *grid = [renderer grid]; + Graph *graph = [[renderer document] graph]; + + if (currentResizeHandle == NoHandle) { + NSRect bbox = NSRectAroundPoints( + [grid snapScreenPoint:dragOrigin], + [grid snapScreenPoint:pos] + ); + [graph setBoundingBox:[transformer rectFromScreen:bbox]]; + } else { + NSRect bbox = [transformer rectToScreen:[graph boundingBox]]; + NSPoint p2 = [grid snapScreenPoint:pos]; + + if (currentResizeHandle == NorthWestHandle || + currentResizeHandle == NorthHandle || + currentResizeHandle == NorthEastHandle) { + + float dy = p2.y - NSMinY(bbox); + if (dy < bbox.size.height) { + bbox.origin.y += dy; + bbox.size.height -= dy; + } else { + bbox.origin.y = NSMaxY(bbox); + bbox.size.height = 0; + } + + } else if (currentResizeHandle == SouthWestHandle || + currentResizeHandle == SouthHandle || + currentResizeHandle == SouthEastHandle) { + + float dy = p2.y - NSMaxY(bbox); + if (-dy < bbox.size.height) { + bbox.size.height += dy; + } else { + bbox.size.height = 0; + } + } + + if (currentResizeHandle == NorthWestHandle || + currentResizeHandle == WestHandle || + currentResizeHandle == SouthWestHandle) { + + float dx = p2.x - NSMinX(bbox); + if (dx < bbox.size.width) { + bbox.origin.x += dx; + bbox.size.width -= dx; + } else { + bbox.origin.x = NSMaxX(bbox); + bbox.size.width = 0; + } + + } else if (currentResizeHandle == NorthEastHandle || + currentResizeHandle == EastHandle || + currentResizeHandle == SouthEastHandle) { + + float dx = p2.x - NSMaxX(bbox); + if (-dx < bbox.size.width) { + bbox.size.width += dx; + } else { + bbox.size.width = 0; + } + } + [graph setBoundingBox:[transformer rectFromScreen:bbox]]; + } + [[renderer document] changeBoundingBoxCheckPoint]; + [renderer invalidateGraph]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + [[renderer document] endChangeBoundingBox]; + drawingNewBox = NO; + [renderer invalidateGraph]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + if (!drawingNewBox && [[[renderer document] graph] hasBoundingBox]) { + [context saveState]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + + [context startPath]; + [context rect:[self boundingBoxResizeHandleRect:EastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthEastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:WestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthEastHandle]]; + [context strokePathWithColor:MakeSolidRColor (0.5, 0.5, 0.5)]; + + [context restoreState]; + } +} +- (void) loadConfiguration:(Configuration*)config {} +- (void) saveConfiguration:(Configuration*)config {} +@end + +@implementation BoundingBoxTool (Private) +- (NSRect) screenBoundingBox { + Transformer *transformer = [[renderer surface] transformer]; + Graph *graph = [[renderer document] graph]; + return [transformer rectToScreen:[graph boundingBox]]; +} + +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p { + NSRect bbox = [self screenBoundingBox]; + if (p.x >= NSMaxX(bbox)) { + if (p.x <= NSMaxX(bbox) + handle_size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + return SouthEastHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + return NorthEastHandle; + } + } else { + float eastHandleTop = sideHandleTop(bbox); + if (p.y >= eastHandleTop && p.y <= (eastHandleTop + handle_size)) { + return EastHandle; + } + } + } + } else if (p.x <= NSMinX(bbox)) { + if (p.x >= NSMinX(bbox) - handle_size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + return SouthWestHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + return NorthWestHandle; + } + } else { + float westHandleTop = sideHandleTop(bbox); + if (p.y >= westHandleTop && p.y <= (westHandleTop + handle_size)) { + return WestHandle; + } + } + } + } else if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + float southHandleLeft = tbHandleLeft(bbox); + if (p.x >= southHandleLeft && p.x <= (southHandleLeft + handle_size)) { + return SouthHandle; + } + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + float northHandleLeft = tbHandleLeft(bbox); + if (p.x >= northHandleLeft && p.x <= (northHandleLeft + handle_size)) { + return NorthHandle; + } + } + } + return NoHandle; +} + +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle { + Graph *graph = [[renderer document] graph]; + if (![graph hasBoundingBox]) { + return NSZeroRect; + } + NSRect bbox = [self screenBoundingBox]; + float x; + float y; + switch (handle) { + case NorthEastHandle: + case EastHandle: + case SouthEastHandle: + x = NSMaxX(bbox); + break; + case NorthWestHandle: + case WestHandle: + case SouthWestHandle: + x = NSMinX(bbox) - handle_size; + break; + case SouthHandle: + case NorthHandle: + x = tbHandleLeft(bbox); + break; + default: + return NSZeroRect; + } + switch (handle) { + case EastHandle: + case WestHandle: + y = sideHandleTop(bbox); + break; + case SouthEastHandle: + case SouthHandle: + case SouthWestHandle: + y = NSMaxY(bbox); + break; + case NorthEastHandle: + case NorthHandle: + case NorthWestHandle: + y = NSMinY(bbox) - handle_size; + break; + default: + return NSZeroRect; + } + return NSMakeRect(x, y, handle_size, handle_size); +} + +- (void) setResizeCursorForHandle:(ResizeHandle)handle { + if (handle != currentResizeHandle) { + currentResizeHandle = handle; + Cursor c = NormalCursor; + switch (handle) { + case EastHandle: + c = ResizeRightCursor; + break; + case SouthEastHandle: + c = ResizeBottomRightCursor; + break; + case SouthHandle: + c = ResizeBottomCursor; + break; + case SouthWestHandle: + c = ResizeBottomLeftCursor; + break; + case WestHandle: + c = ResizeLeftCursor; + break; + case NorthWestHandle: + c = ResizeTopLeftCursor; + break; + case NorthHandle: + c = ResizeTopCursor; + break; + case NorthEastHandle: + c = ResizeTopRightCursor; + break; + default: + c = NormalCursor; + break; + } + [[renderer surface] setCursor:c]; + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CairoRenderContext.h b/tikzit-1/src/gtk/CairoRenderContext.h new file mode 100644 index 0000000..ac9c5ee --- /dev/null +++ b/tikzit-1/src/gtk/CairoRenderContext.h @@ -0,0 +1,59 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "RenderContext.h" +#import "Transformer.h" +#import <cairo/cairo.h> +#import <pango/pango.h> +#import <gtk/gtk.h> + +@interface PangoTextLayout: NSObject<TextLayout> { + PangoLayout *layout; + cairo_t *context; +} + ++ (PangoTextLayout*) layoutForContext:(cairo_t*)cr withFontSize:(CGFloat)fontSize; +- (id) initWithContext:(cairo_t*)cr fontSize:(CGFloat)fontSize; +- (void) setText:(NSString*)text; + +@end + +@interface CairoRenderContext: NSObject<RenderContext> { + cairo_t *context; +} + ++ (CairoRenderContext*) contextForSurface:(cairo_surface_t*)surface; +- (id) initForSurface:(cairo_surface_t*)surface; + ++ (CairoRenderContext*) contextForWidget:(GtkWidget*)widget; +- (id) initForWidget:(GtkWidget*)widget; + ++ (CairoRenderContext*) contextForDrawable:(GdkDrawable*)d; +- (id) initForDrawable:(GdkDrawable*)d; + ++ (CairoRenderContext*) contextForPixbuf:(GdkPixbuf*)buf; +- (id) initForPixbuf:(GdkPixbuf*)buf; + +- (cairo_t*) cairoContext; +- (void) applyTransform:(Transformer*)transformer; + +- (void) clearSurface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CairoRenderContext.m b/tikzit-1/src/gtk/CairoRenderContext.m new file mode 100644 index 0000000..77e10b5 --- /dev/null +++ b/tikzit-1/src/gtk/CairoRenderContext.m @@ -0,0 +1,344 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "CairoRenderContext.h" + +#import "cairo_helpers.h" +#import "util.h" + +#import <pango/pangocairo.h> + +@implementation PangoTextLayout + +- (id) init { + [self release]; + return nil; +} + ++ (PangoTextLayout*) layoutForContext:(cairo_t*)cr withFontSize:(CGFloat)fontSize { + return [[[self alloc] initWithContext:cr fontSize:fontSize] autorelease]; +} + +- (id) initWithContext:(cairo_t*)cr fontSize:(CGFloat)fontSize { + self = [super init]; + + if (self) { + cairo_reference (cr); + context = cr; + layout = pango_cairo_create_layout (cr); + + PangoFontDescription *font_desc = pango_font_description_new (); + pango_font_description_set_family_static (font_desc, "Sans"); + pango_font_description_set_size (font_desc, pango_units_from_double (fontSize)); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + } + + return self; +} + +- (void) setText:(NSString*)text { + pango_layout_set_text (layout, [text UTF8String], -1); +} + +- (NSSize) size { + int width, height; + pango_layout_get_size (layout, &width, &height); + return NSMakeSize (pango_units_to_double (width), pango_units_to_double (height)); +} + +- (NSString*) text { + return [NSString stringWithUTF8String:pango_layout_get_text (layout)]; +} + +- (void) showTextAt:(NSPoint)topLeft withColor:(RColor)color { + cairo_save (context); + + cairo_move_to(context, topLeft.x, topLeft.y); + cairo_set_source_rcolor (context, color); + pango_cairo_show_layout (context, layout); + + cairo_restore (context); +} + +- (void) dealloc { + if (layout) + g_object_unref (G_OBJECT (layout)); + if (context) + cairo_destroy (context); + + [super dealloc]; +} + +@end + +@implementation CairoRenderContext + +- (id) init { + [self release]; + return nil; +} + ++ (CairoRenderContext*) contextForSurface:(cairo_surface_t*)surface { + return [[[self alloc] initForSurface:surface] autorelease]; +} + +- (id) initForSurface:(cairo_surface_t*)surface { + self = [super init]; + + if (self) { + context = cairo_create (surface); + } + + return self; +} + ++ (CairoRenderContext*) contextForWidget:(GtkWidget*)widget { + return [[[self alloc] initForWidget:widget] autorelease]; +} + +- (id) initForWidget:(GtkWidget*)widget { + self = [super init]; + + if (self) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + context = gdk_cairo_create (window); + } else { + [self release]; + self = nil; + } + } + + return self; +} + ++ (CairoRenderContext*) contextForDrawable:(GdkDrawable*)d { + return [[[self alloc] initForDrawable:d] autorelease]; +} + +- (id) initForDrawable:(GdkDrawable*)d { + self = [super init]; + + if (self) { + context = gdk_cairo_create (d); + } + + return self; +} + ++ (CairoRenderContext*) contextForPixbuf:(GdkPixbuf*)pixbuf { + return [[[self alloc] initForPixbuf:pixbuf] autorelease]; +} + +- (id) initForPixbuf:(GdkPixbuf*)pixbuf { + self = [super init]; + + if (self) { + cairo_format_t format = -1; + + if (gdk_pixbuf_get_colorspace (pixbuf) != GDK_COLORSPACE_RGB) { + NSLog(@"Unsupported colorspace (must be RGB)"); + [self release]; + return nil; + } + if (gdk_pixbuf_get_bits_per_sample (pixbuf) != 8) { + NSLog(@"Unsupported bits per sample (must be 8)"); + [self release]; + return nil; + } + if (gdk_pixbuf_get_has_alpha (pixbuf)) { + if (gdk_pixbuf_get_n_channels (pixbuf) != 4) { + NSLog(@"Unsupported bits per sample (must be 4 for an image with alpha)"); + [self release]; + return nil; + } + format = CAIRO_FORMAT_ARGB32; + } else { + if (gdk_pixbuf_get_n_channels (pixbuf) != 3) { + NSLog(@"Unsupported bits per sample (must be 3 for an image without alpha)"); + [self release]; + return nil; + } + format = CAIRO_FORMAT_RGB24; + } + + cairo_surface_t *surface = cairo_image_surface_create_for_data( + gdk_pixbuf_get_pixels(pixbuf), + format, + gdk_pixbuf_get_width(pixbuf), + gdk_pixbuf_get_height(pixbuf), + gdk_pixbuf_get_rowstride(pixbuf)); + context = cairo_create (surface); + cairo_surface_destroy (surface); + } + + return self; +} + +- (cairo_t*) cairoContext { + return context; +} + +- (void) applyTransform:(Transformer*)transformer { + NSPoint origin = [transformer toScreen:NSZeroPoint]; + cairo_translate (context, origin.x, origin.y); + NSPoint scale = [transformer toScreen:NSMakePoint (1.0f, 1.0f)]; + scale.x -= origin.x; + scale.y -= origin.y; + cairo_scale (context, scale.x, scale.y); +} + +- (void) saveState { + cairo_save (context); +} + +- (void) restoreState { + cairo_restore (context); +} + +- (NSRect) clipBoundingBox { + double clipx1, clipx2, clipy1, clipy2; + cairo_clip_extents (context, &clipx1, &clipy1, &clipx2, &clipy2); + return NSMakeRect (clipx1, clipy1, clipx2-clipx1, clipy2-clipy1); +} + +- (BOOL) strokeIncludesPoint:(NSPoint)p { + return cairo_in_stroke (context, p.x, p.y); +} + +- (BOOL) fillIncludesPoint:(NSPoint)p { + return cairo_in_fill (context, p.x, p.y); +} + +- (id<TextLayout>) layoutText:(NSString*)text withSize:(CGFloat)fontSize { + PangoTextLayout *layout = [PangoTextLayout layoutForContext:context withFontSize:fontSize]; + [layout setText:text]; + return layout; +} + +- (void) setAntialiasMode:(AntialiasMode)mode { + if (mode == AntialiasDisabled) { + cairo_set_antialias (context, CAIRO_ANTIALIAS_NONE); + } else { + cairo_set_antialias (context, CAIRO_ANTIALIAS_DEFAULT); + } +} + +- (void) setLineWidth:(CGFloat)width { + cairo_set_line_width (context, width); +} + +- (void) setLineDash:(CGFloat)dashLength { + if (dashLength <= 0.0) { + cairo_set_dash (context, NULL, 0, 0); + } else { + double dashes[] = { dashLength }; + cairo_set_dash (context, dashes, 1, 0); + } +} + +// paths +- (void) startPath { + cairo_new_path (context); +} + +- (void) closeSubPath { + cairo_close_path (context); +} + +- (void) moveTo:(NSPoint)p { + cairo_move_to(context, p.x, p.y); +} + +- (void) curveTo:(NSPoint)end withCp1:(NSPoint)cp1 andCp2:(NSPoint)cp2 { + cairo_curve_to (context, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y); +} + +- (void) lineTo:(NSPoint)end { + cairo_line_to (context, end.x, end.y); +} + +- (void) rect:(NSRect)rect { + cairo_rectangle (context, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +- (void) circleAt:(NSPoint)c withRadius:(CGFloat)r { + cairo_new_sub_path (context); + cairo_arc (context, c.x, c.y, r, 0, 2 * M_PI); +} + +- (void) strokePathWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_stroke (context); +} + +- (void) fillPathWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_fill (context); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor { + cairo_set_source_rcolor (context, fcolor); + cairo_fill_preserve (context); + cairo_set_source_rcolor (context, scolor); + cairo_stroke (context); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor + usingAlpha:(CGFloat)alpha { + cairo_push_group (context); + cairo_set_source_rcolor (context, fcolor); + cairo_fill_preserve (context); + cairo_set_source_rcolor (context, scolor); + cairo_stroke (context); + cairo_pop_group_to_source (context); + cairo_paint_with_alpha (context, alpha); +} + +- (void) clipToPath { + cairo_clip (context); +} + +- (void) paintWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_paint (context); +} + +- (void) clearSurface { + cairo_operator_t old_op = cairo_get_operator (context); + + cairo_set_operator (context, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (context, 0.0, 0.0, 0.0, 0.0); + cairo_paint (context); + + cairo_set_operator (context, old_op); +} + +- (void) dealloc { + if (context) { + cairo_destroy (context); + } + + [super dealloc]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ColorRGB+Gtk.h b/tikzit-1/src/gtk/ColorRGB+Gtk.h new file mode 100644 index 0000000..5cfb4d7 --- /dev/null +++ b/tikzit-1/src/gtk/ColorRGB+Gtk.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "ColorRGB.h" +#import <gdk/gdk.h> + +@interface ColorRGB (Gtk) + ++ (ColorRGB*) colorWithGdkColor:(GdkColor)color; +- (id) initWithGdkColor:(GdkColor)color; +- (GdkColor) gdkColor; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ColorRGB+Gtk.m b/tikzit-1/src/gtk/ColorRGB+Gtk.m new file mode 100644 index 0000000..be5dd56 --- /dev/null +++ b/tikzit-1/src/gtk/ColorRGB+Gtk.m @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "ColorRGB+Gtk.h" + +// 257 = 65535/255 +// GdkColor values run from 0 to 65535, not from 0 to 255 +#define GDK_FACTOR 257 + +@implementation ColorRGB (Gtk) + ++ (ColorRGB*) colorWithGdkColor:(GdkColor)color { + return [ColorRGB colorWithRed:color.red/GDK_FACTOR green:color.green/GDK_FACTOR blue:color.blue/GDK_FACTOR]; +} + +- (id) initWithGdkColor:(GdkColor)color { + return [self initWithRed:color.red/GDK_FACTOR green:color.green/GDK_FACTOR blue:color.blue/GDK_FACTOR]; +} + +- (GdkColor) gdkColor { + GdkColor color; + color.pixel = 0; + color.red = GDK_FACTOR * [self red]; + color.green = GDK_FACTOR * [self green]; + color.blue = GDK_FACTOR * [self blue]; + return color; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.h b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.h new file mode 100644 index 0000000..118eaee --- /dev/null +++ b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.h @@ -0,0 +1,32 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "ColorRGB.h" + +/** + * Stores a ColorRGB as a list of short integers + */ +@interface ColorRGB (IntegerListStorage) + ++ (ColorRGB*) colorFromValueList:(NSArray*)values; +- (id) initFromValueList:(NSArray*)values; +- (NSArray*) valueList; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.m b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.m new file mode 100644 index 0000000..0103a3c --- /dev/null +++ b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.m @@ -0,0 +1,57 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "ColorRGB+IntegerListStorage.h" + +@implementation ColorRGB (IntegerListStorage) + ++ (ColorRGB*) colorFromValueList:(NSArray*)values { + if ([values count] != 3) { + return nil; + } + + unsigned short redValue = [[values objectAtIndex:0] intValue]; + unsigned short greenValue = [[values objectAtIndex:1] intValue]; + unsigned short blueValue = [[values objectAtIndex:2] intValue]; + return [ColorRGB colorWithRed:redValue green:greenValue blue:blueValue]; +} + +- (id) initFromValueList:(NSArray*)values { + if ([values count] != 3) { + [self release]; + return nil; + } + + unsigned short redValue = [[values objectAtIndex:0] intValue]; + unsigned short greenValue = [[values objectAtIndex:1] intValue]; + unsigned short blueValue = [[values objectAtIndex:2] intValue]; + + return [self initWithRed:redValue green:greenValue blue:blueValue]; +} + +- (NSArray*) valueList { + NSMutableArray *array = [NSMutableArray arrayWithCapacity:3]; + [array addObject:[NSNumber numberWithInt:[self red]]]; + [array addObject:[NSNumber numberWithInt:[self green]]]; + [array addObject:[NSNumber numberWithInt:[self blue]]]; + return array; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Configuration.h b/tikzit-1/src/gtk/Configuration.h new file mode 100644 index 0000000..6c68681 --- /dev/null +++ b/tikzit-1/src/gtk/Configuration.h @@ -0,0 +1,447 @@ +// +// Configuration.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TZFoundation.h" + +/** + * Manages configuration information in a grouped key-value format. + */ +@interface Configuration : NSObject { + NSString *name; + GKeyFile *file; +} + +/** + * Check whether there is any existing configuration. + */ ++ (BOOL) configurationExistsWithName:(NSString*)name; +/** + * Create a blank configuration with the given name, without loading + * any existing configuration information. + * + * @param name the name of the configuration + */ ++ (Configuration*) emptyConfigurationWithName:(NSString*)name; +/** + * Load an existing configuration for the given name. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + */ ++ (Configuration*) configurationWithName:(NSString*)name; +/** + * Load an existing configuration for the given name. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + * @param error this will be set if the configuration exists, but could + * not be opened. + */ ++ (Configuration*) configurationWithName:(NSString*)name loadError:(NSError**)error; + +/** + * Initialise the configuration to be empty + * + * Does not attempt to load any existing configuration data. + * + * @param name the name of the configuration + */ +- (id) initEmptyWithName:(NSString*)name; +/** + * Initialise a configuration, loading it if it had previously been stored. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + */ +- (id) initWithName:(NSString*)name; +/** + * Initialise a configuration, loading it if it had previously been stored. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + * @param error this will be set if the configuration exists, but could + * not be opened. + */ +- (id) initWithName:(NSString*)name loadError:(NSError**)error; + +/** + * The name of the configuration. + * + * Configurations with different names are stored independently. + */ +- (NSString*) name; +/** + * Set the name of the configuration. + * + * This will affect the behaviour of [-writeToStore] + * + * Configurations with different names are stored independently. + */ +- (void) setName:(NSString*)name; + +/** + * Writes the configuration to the backing store. + * + * The location the configuration is written to is determined by the + * [-name] property. + * + * @result YES if the configuration was successfully written, NO otherwise + */ +- (BOOL) writeToStore; +/** + * Writes the configuration to the backing store. + * + * The location the configuration is written to is determined by the + * [-name] property. + * + * @param error this will be set if the configuration could not be written + * to the backing store + * @result YES if the configuration was successfully written, NO otherwise + */ +- (BOOL) writeToStoreWithError:(NSError**)error; + +/** + * Check whether a particular key exists within a group + * + * @param key the key to check for + * @param group the name of the group to look in + * @result YES if the key exists, NO otherwise + */ +- (BOOL) hasKey:(NSString*)key inGroup:(NSString*)group; +/** + * Check whether a particular group exists + * + * @param group the name of the group to check for + * @result YES if the group exists, NO otherwise + */ +- (BOOL) hasGroup:(NSString*)group; +/** + * List the groups in the configuration. + * + * @result a list of group names + */ +- (NSArray*) groups; + +/** + * Get the value associated with a key as a string + * + * This is only guaranteed to work if the value was stored as a string. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a string, or nil + * if no string value was associated with key + */ +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a string + * + * This is only guaranteed to work if the value was stored as a string. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no string value was associated with key + * @result the value associated with key as a string, or default + */ +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSString*)def; +/** + * Get the value associated with a key as a boolean + * + * This is only guaranteed to work if the value was stored as a boolean. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a boolean, or NO + * if no boolean value was associated with key + */ +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a boolean + * + * This is only guaranteed to work if the value was stored as a boolean. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no boolean value was associated with key + * @result the value associated with key as a boolean, or def + */ +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group withDefault:(BOOL)def; +/** + * Get the value associated with a key as a integer + * + * This is only guaranteed to work if the value was stored as a integer. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a integer, or 0 + * if no integer value was associated with key + */ +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a integer + * + * This is only guaranteed to work if the value was stored as a integer. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no integer value was associated with key + * @result the value associated with key as a integer, or def + */ +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group withDefault:(int)def; +/** + * Get the value associated with a key as a double + * + * This is only guaranteed to work if the value was stored as a double. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a double, or 0 + * if no double value was associated with key + */ +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a double + * + * This is only guaranteed to work if the value was stored as a double. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no double value was associated with key + * @result the value associated with key as a double, or def + */ +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group withDefault:(double)def; + +/** + * Get the value associated with a key as a list of strings + * + * This is only guaranteed to work if the value was stored as a + * list of strings. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of strings, + * or nil if no list of strings was associated with key + */ +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of strings + * + * This is only guaranteed to work if the value was stored as a + * list of strings. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no string list value was associated with key + * @result the value associated with key as a list of strings, or def + */ +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of booleans + * + * This is only guaranteed to work if the value was stored as a + * list of booleans. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing boolean values, or nil + */ +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of booleans + * + * This is only guaranteed to work if the value was stored as a + * list of booleans. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no boolean list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing boolean values, or def + */ +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of integers + * + * This is only guaranteed to work if the value was stored as a + * list of integers. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing integer values, or nil + */ +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of integers + * + * This is only guaranteed to work if the value was stored as a + * list of integers. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no integer list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing integer values, or def + */ +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of doubles + * + * This is only guaranteed to work if the value was stored as a + * list of doubles. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing double values, or nil + */ +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of doubles + * + * This is only guaranteed to work if the value was stored as a + * list of doubles. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no double list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing double values, or def + */ +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; + +/** + * Associate a string value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setStringEntry:(NSString*)key inGroup:(NSString*)group value:(NSString*)value; +/** + * Associate a boolean value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setBooleanEntry:(NSString*)key inGroup:(NSString*)group value:(BOOL)value; +/** + * Associate a integer value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setIntegerEntry:(NSString*)key inGroup:(NSString*)group value:(int)value; +/** + * Associate a double value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setDoubleEntry:(NSString*)key inGroup:(NSString*)group value:(double)value; + +/** + * Associate a list of string values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of strings + */ +- (void) setStringListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of boolean values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setBooleanListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of integer values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setIntegerListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of double values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setDoubleListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; + +/** + * Remove a group from the configuration + * + * This will remove all the groups key-value associations. + */ +- (void) removeGroup:(NSString*)group; +/** + * Remove a key from the configuration + * + * @param key the key to remove + * @param group the group to remove it from + */ +- (void) removeKey:(NSString*)key inGroup:(NSString*)group; + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/Configuration.m b/tikzit-1/src/gtk/Configuration.m new file mode 100644 index 0000000..4a3ed79 --- /dev/null +++ b/tikzit-1/src/gtk/Configuration.m @@ -0,0 +1,450 @@ +// +// Configuration.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Configuration.h" +#import "SupportDir.h" + +@implementation Configuration + ++ (NSString*) _pathFromName:(NSString*)name { + return [NSString stringWithFormat:@"%@/%@.conf", [SupportDir userSupportDir], name]; +} + ++ (BOOL) configurationExistsWithName:(NSString*)name { + BOOL isDir; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:[self _pathFromName:name] isDirectory:&isDir]; + return exists && !isDir; +} + ++ (Configuration*) emptyConfigurationWithName:(NSString*)name + { return [[[self alloc] initEmptyWithName:name] autorelease]; } ++ (Configuration*) configurationWithName:(NSString*)name + { return [[[self alloc] initWithName:name] autorelease]; } ++ (Configuration*) configurationWithName:(NSString*)name loadError:(NSError**)error + { return [[[self alloc] initWithName:name loadError:error] autorelease]; } + +- (id) init +{ + [self release]; + return nil; +} + +- (id) initEmptyWithName:(NSString*)n +{ + self = [super init]; + if (self) { + name = [n retain]; + file = g_key_file_new (); + } + + return self; +} + +- (id) _initFromFile:(NSString*)path error:(NSError**)error +{ + self = [super init]; + if (self) { + file = g_key_file_new (); + + NSFileManager *manager = [NSFileManager defaultManager]; + if ([manager fileExistsAtPath:path]) { + gchar *filename = [path glibFilename]; + + GError *gerror = NULL; + g_key_file_load_from_file (file, + filename, + G_KEY_FILE_NONE, + &gerror); + g_free (filename); + + if (gerror) { + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + } + } + + return self; +} + +- (id) initWithName:(NSString*)n { + return [self initWithName:n loadError:NULL]; +} + +- (id) initWithName:(NSString*)n loadError:(NSError**)error { + self = [self _initFromFile:[Configuration _pathFromName:n] error:error]; + + if (self) { + name = [n retain]; + } + + return self; +} + +- (BOOL) _ensureParentExists:(NSString*)path error:(NSError**)error { + NSString *directory = [path stringByDeletingLastPathComponent]; + return [[NSFileManager defaultManager] ensureDirectoryExists:directory error:error]; +} + +- (BOOL) _writeFileTo:(NSString*)path error:(NSError**)error +{ + if (![self _ensureParentExists:path error:error]) { + return NO; + } + + BOOL success = NO; + gsize length; + gchar *data = g_key_file_to_data (file, &length, NULL); // never reports an error + if (data && length) { + GError *gerror = NULL; + gchar* nativePath = [path glibFilename]; + success = g_file_set_contents (nativePath, data, length, &gerror) ? YES : NO; + g_free (data); + g_free (nativePath); + if (gerror) { + g_warning ("Failed to write file: %s\n", gerror->message); + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + } else { + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; + success = YES; + } + + return success; +} + +- (NSString*) name { + return name; +} + +- (void) setName:(NSString*)n { + [n retain]; + [name release]; + name = n; +} + +- (BOOL) writeToStore { + return [self writeToStoreWithError:NULL]; +} + +- (BOOL) writeToStoreWithError:(NSError**)error { + return [self _writeFileTo:[Configuration _pathFromName:name] error:error]; +} + +- (BOOL) hasKey:(NSString*)key inGroup:(NSString*)group +{ + gboolean result = g_key_file_has_key (file, [group UTF8String], [key UTF8String], NULL); + return result ? YES : NO; +} + +- (BOOL) hasGroup:(NSString*)group +{ + gboolean result = g_key_file_has_group (file, [group UTF8String]); + return result ? YES : NO; +} + +- (NSArray*) keys:(NSString*)group +{ + gsize length; + gchar **keys = g_key_file_get_keys (file, [group UTF8String], &length, NULL); + if (!keys) + length = 0; + + NSMutableArray *array = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [array addObject:[NSString stringWithUTF8String:keys[i]]]; + } + g_strfreev (keys); + return array; +} + +- (NSArray*) groups +{ + gsize length; + gchar **groups = g_key_file_get_groups (file, &length); + NSMutableArray *array = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [array addObject:[NSString stringWithUTF8String:groups[i]]]; + } + g_strfreev (groups); + return array; +} + +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self stringEntry:key inGroup:group withDefault:nil]; +} + +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSString*)def +{ + NSString *result = def; + gchar *entry = g_key_file_get_string (file, [group UTF8String], [key UTF8String], NULL); + if (entry) { + result = [NSString stringWithUTF8String:entry]; + g_free (entry); + } + return result; +} + +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group withDefault:(BOOL)def +{ + GError *error = NULL; + gboolean result = g_key_file_get_boolean (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result ? YES : NO; + } +} + +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group +{ + gboolean result = g_key_file_get_boolean (file, [group UTF8String], [key UTF8String], NULL); + return result ? YES : NO; +} + +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group withDefault:(int)def +{ + GError *error = NULL; + int result = g_key_file_get_integer (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result; + } +} + +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group +{ + return g_key_file_get_integer (file, [group UTF8String], [key UTF8String], NULL); +} + +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group withDefault:(double)def +{ + GError *error = NULL; + double result = g_key_file_get_double (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result; + } +} + +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group +{ + return g_key_file_get_double (file, [group UTF8String], [key UTF8String], NULL); +} + +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self stringListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gchar **list = g_key_file_get_string_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSString stringWithUTF8String:list[i]]]; + } + g_strfreev (list); + return result; + } else { + return def; + } +} + +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self booleanListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gboolean *list = g_key_file_get_boolean_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithBool:list[i]]]; + } + g_free (list); + return result; + } else { + return def; + } +} + +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self integerListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gint *list = g_key_file_get_integer_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithInt:list[i]]]; + } + g_free (list); + return result; + } else { + return def; + } +} + +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self doubleListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + double *list = g_key_file_get_double_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithDouble:list[i]]]; + } + g_free (list); + return result; + } else { + return def; + } +} + +- (void) setStringEntry:(NSString*)key inGroup:(NSString*)group value:(NSString*)value +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + g_key_file_set_string (file, [group UTF8String], [key UTF8String], [value UTF8String]); +} + +- (void) setBooleanEntry:(NSString*)key inGroup:(NSString*)group value:(BOOL)value; +{ + g_key_file_set_boolean (file, [group UTF8String], [key UTF8String], value); +} + +- (void) setIntegerEntry:(NSString*)key inGroup:(NSString*)group value:(int)value; +{ + g_key_file_set_integer (file, [group UTF8String], [key UTF8String], value); +} + +- (void) setDoubleEntry:(NSString*)key inGroup:(NSString*)group value:(double)value; +{ + g_key_file_set_double (file, [group UTF8String], [key UTF8String], value); +} + + +- (void) setStringListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + const gchar * *list = g_new (const gchar *, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] UTF8String]; + } + g_key_file_set_string_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setBooleanListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gboolean *list = g_new (gboolean, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] boolValue]; + } + g_key_file_set_boolean_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setIntegerListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gint *list = g_new (gint, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] intValue]; + } + g_key_file_set_integer_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setDoubleListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gdouble *list = g_new (gdouble, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] doubleValue]; + } + g_key_file_set_double_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) removeGroup:(NSString*)group +{ + g_key_file_remove_group (file, [group UTF8String], NULL); +} + +- (void) removeKey:(NSString*)key inGroup:(NSString*)group; +{ + g_key_file_remove_key (file, [group UTF8String], [key UTF8String], NULL); +} + +- (void) dealloc +{ + [name release]; + g_key_file_free (file); + file = NULL; + [super dealloc]; +} + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/ContextWindow.h b/tikzit-1/src/gtk/ContextWindow.h new file mode 100644 index 0000000..fcad9df --- /dev/null +++ b/tikzit-1/src/gtk/ContextWindow.h @@ -0,0 +1,53 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class EdgeStylesModel; +@class NodeStylesModel; +@class PropertiesPane; +@class SelectionPane; +@class StyleManager; +@class TikzDocument; +@class Window; + +@interface ContextWindow: NSObject { + PropertiesPane *propsPane; + SelectionPane *selPane; + + GtkWidget *window; + GtkWidget *layout; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; + +- (id) initWithStyleManager:(StyleManager*)mgr; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm; + +- (void) present; +- (void) attachToWindow:(Window*)parent; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/ContextWindow.m b/tikzit-1/src/gtk/ContextWindow.m new file mode 100644 index 0000000..d8e9d20 --- /dev/null +++ b/tikzit-1/src/gtk/ContextWindow.m @@ -0,0 +1,169 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "ContextWindow.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "PropertiesPane.h" +#import "SelectionPane.h" +#import "StyleManager.h" +#import "Window.h" + +#import "gtkhelpers.h" + +static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window); + +@implementation ContextWindow + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm] + andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_object_ref_sink (window); + gtk_window_set_title (GTK_WINDOW (window), "Context"); + gtk_window_set_role (GTK_WINDOW (window), "context"); + gtk_window_set_type_hint (GTK_WINDOW (window), + GDK_WINDOW_TYPE_HINT_UTILITY); + gtk_window_set_default_size (GTK_WINDOW (window), 200, 500); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (props_window_delete_event_cb), + self); + + layout = gtk_vbox_new (FALSE, 3); + g_object_ref_sink (layout); + gtk_widget_show (layout); + gtk_container_set_border_width (GTK_CONTAINER (layout), 6); + + gtk_container_add (GTK_CONTAINER (window), layout); + + propsPane = [[PropertiesPane alloc] init]; + gtk_box_pack_start (GTK_BOX (layout), [propsPane gtkWidget], + TRUE, TRUE, 0); + + GtkWidget *sep = gtk_hseparator_new (); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (layout), sep, + FALSE, FALSE, 0); + + selPane = [[SelectionPane alloc] initWithNodeStylesModel:nsm + andEdgeStylesModel:esm]; + gtk_box_pack_start (GTK_BOX (layout), [selPane gtkWidget], + FALSE, FALSE, 0); + + // hack to position the context window somewhere sensible + // (upper right) + gtk_window_parse_geometry (GTK_WINDOW (window), "-0+0"); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (layout); + g_object_unref (window); + + [propsPane release]; + + [super dealloc]; +} + +- (TikzDocument*) document { + return [propsPane document]; +} + +- (void) setDocument:(TikzDocument*)doc { + [propsPane setDocument:doc]; + [selPane setDocument:doc]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (window); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (window, visible); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) attachToWindow:(Window*)parent { + utility_window_attach (GTK_WINDOW (window), [parent gtkWindow]); +} + +- (void) loadConfiguration:(Configuration*)config { + [propsPane loadConfiguration:config]; + [selPane loadConfiguration:config]; + + if ([config hasGroup:@"ContextWindow"]) { + tz_restore_window (GTK_WINDOW (window), + [config integerEntry:@"x" inGroup:@"ContextWindow"], + [config integerEntry:@"y" inGroup:@"ContextWindow"], + [config integerEntry:@"w" inGroup:@"ContextWindow"], + [config integerEntry:@"h" inGroup:@"ContextWindow"]); + } + [self setVisible:[config booleanEntry:@"visible" + inGroup:@"ContextWindow" + withDefault:YES]]; +} + +- (void) saveConfiguration:(Configuration*)config { + gint x, y, w, h; + + gtk_window_get_position (GTK_WINDOW (window), &x, &y); + gtk_window_get_size (GTK_WINDOW (window), &w, &h); + + [config setIntegerEntry:@"x" inGroup:@"ContextWindow" value:x]; + [config setIntegerEntry:@"y" inGroup:@"ContextWindow" value:y]; + [config setIntegerEntry:@"w" inGroup:@"ContextWindow" value:w]; + [config setIntegerEntry:@"h" inGroup:@"ContextWindow" value:h]; + [config setBooleanEntry:@"visible" + inGroup:@"ContextWindow" + value:[self visible]]; + + [propsPane saveConfiguration:config]; + [selPane saveConfiguration:config]; +} + +@end + +static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window setVisible:NO]; + [pool drain]; + return TRUE; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/CreateEdgeTool.h b/tikzit-1/src/gtk/CreateEdgeTool.h new file mode 100644 index 0000000..d33efce --- /dev/null +++ b/tikzit-1/src/gtk/CreateEdgeTool.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@class EdgeStyle; +@class EdgeStylesModel; +@class EdgeStyleSelector; +@class Node; +@class StyleManager; + +@interface CreateEdgeTool : NSObject <Tool> { + GraphRenderer *renderer; + EdgeStyleSelector *stylePicker; + GtkWidget *configWidget; + Node *sourceNode; + NSPoint sourceNodeScreenPoint; + NSPoint halfEdgeEnd; +} + +@property (retain) EdgeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; ++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm; +- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateEdgeTool.m b/tikzit-1/src/gtk/CreateEdgeTool.m new file mode 100644 index 0000000..f3fb2c0 --- /dev/null +++ b/tikzit-1/src/gtk/CreateEdgeTool.m @@ -0,0 +1,226 @@ +/* + * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "CreateEdgeTool.h" + +#import "Configuration.h" +#import "EdgeStyleSelector.h" +#import "EdgeStylesModel.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static void clear_style_button_cb (GtkButton *widget, + EdgeStyleSelector *selector); + +@implementation CreateEdgeTool +- (NSString*) name { return @"Create Edge"; } +- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_EDGE; } +- (NSString*) helpText { return @"Create new edges"; } +- (NSString*) shortcut { return @"e"; } +@synthesize activeRenderer=renderer; +@synthesize configurationWidget=configWidget; + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + ++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm { + return [[[self alloc] initWithEdgeStylesModel:esm] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + stylePicker = [[EdgeStyleSelector alloc] initWithModel:esm]; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Edge style:"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (selWindow); + gtk_container_add (GTK_CONTAINER (selWindow), + [stylePicker widget]); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_show ([stylePicker widget]); + + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_widget_show (selectorFrame); + gtk_box_pack_start (GTK_BOX (configWidget), + selectorFrame, + TRUE, + TRUE, + 0); + gtk_container_add (GTK_CONTAINER (selectorFrame), + selWindow); + + GtkWidget *button = gtk_button_new_with_label ("No style"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (configWidget), + button, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (button), + "clicked", + G_CALLBACK (clear_style_button_cb), + stylePicker); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [stylePicker release]; + [sourceNode release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (EdgeStyle*) activeStyle { + return [stylePicker selectedStyle]; +} + +- (void) setActiveStyle:(EdgeStyle*)style { + return [stylePicker setSelectedStyle:style]; +} + +- (void) invalidateHalfEdge { + NSRect invRect = NSRectAroundPoints(sourceNodeScreenPoint, halfEdgeEnd); + [renderer invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)]; +} + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + sourceNode = [renderer anyNodeAt:pos]; + if (sourceNode != nil) { + Transformer *transformer = [[renderer surface] transformer]; + sourceNodeScreenPoint = [transformer toScreen:[sourceNode point]]; + halfEdgeEnd = pos; + [renderer setNode:sourceNode highlighted:YES]; + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + if (sourceNode == nil) + return; + + [self invalidateHalfEdge]; + + [renderer clearHighlightedNodes]; + [renderer setNode:sourceNode highlighted:YES]; + halfEdgeEnd = pos; + Node *targ = [renderer anyNodeAt:pos]; + if (targ != nil) { + [renderer setNode:targ highlighted:YES]; + } + + [self invalidateHalfEdge]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + if (sourceNode == nil) + return; + + [renderer clearHighlightedNodes]; + [self invalidateHalfEdge]; + + Node *targ = [renderer anyNodeAt:pos]; + if (targ != nil) { + Edge *edge = [Edge edgeWithSource:sourceNode andTarget:targ]; + [edge setStyle:[self activeStyle]]; + [[renderer document] addEdge:edge]; + [renderer invalidateEdge:edge]; + } + + sourceNode = nil; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + if (sourceNode == nil) { + return; + } + [context saveState]; + + [context setLineWidth:1.0]; + [context startPath]; + [context moveTo:sourceNodeScreenPoint]; + [context lineTo:halfEdgeEnd]; + [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)]; + + [context restoreState]; +} + +- (StyleManager*) styleManager { + return [[stylePicker model] styleManager]; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateEdgeTool" + withDefault:nil]; + [self setActiveStyle:[[self styleManager] edgeStyleForName:styleName]]; +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"ActiveStyle" + inGroup:@"CreateEdgeTool" + value:[[self activeStyle] name]]; +} +@end + +static void clear_style_button_cb (GtkButton *widget, + EdgeStyleSelector *selector) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [selector setSelectedStyle:nil]; + [pool drain]; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateNodeTool.h b/tikzit-1/src/gtk/CreateNodeTool.h new file mode 100644 index 0000000..94d6b31 --- /dev/null +++ b/tikzit-1/src/gtk/CreateNodeTool.h @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> +#import "Tool.h" + +@class NodeStyle; +@class NodeStyleSelector; +@class NodeStylesModel; +@class StyleManager; + +@interface CreateNodeTool : NSObject <Tool> { + GraphRenderer *renderer; + NodeStyleSelector *stylePicker; + GtkWidget *configWidget; +} + +@property (retain) NodeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; ++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateNodeTool.m b/tikzit-1/src/gtk/CreateNodeTool.m new file mode 100644 index 0000000..77b24f0 --- /dev/null +++ b/tikzit-1/src/gtk/CreateNodeTool.m @@ -0,0 +1,169 @@ +/* + * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "CreateNodeTool.h" + +#import "Configuration.h" +#import "GraphRenderer.h" +#import "NodeStyleSelector.h" +#import "NodeStylesModel.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static void clear_style_button_cb (GtkButton *widget, + NodeStyleSelector *selector); + +@implementation CreateNodeTool +- (NSString*) name { return @"Create Node"; } +- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_NODE; } +- (NSString*) helpText { return @"Create new nodes"; } +- (NSString*) shortcut { return @"n"; } +@synthesize activeRenderer=renderer; +@synthesize configurationWidget=configWidget; + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + ++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm { + return [[[self alloc] initWithNodeStylesModel:nsm] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm { + self = [super init]; + + if (self) { + stylePicker = [[NodeStyleSelector alloc] initWithModel:nsm]; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Node style:"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (selWindow); + gtk_container_add (GTK_CONTAINER (selWindow), + [stylePicker widget]); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_show ([stylePicker widget]); + + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_widget_show (selectorFrame); + gtk_box_pack_start (GTK_BOX (configWidget), + selectorFrame, + TRUE, + TRUE, + 0); + gtk_container_add (GTK_CONTAINER (selectorFrame), + selWindow); + + GtkWidget *button = gtk_button_new_with_label ("No style"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (configWidget), + button, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (button), + "clicked", + G_CALLBACK (clear_style_button_cb), + stylePicker); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [stylePicker release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (NodeStyle*) activeStyle { + return [stylePicker selectedStyle]; +} + +- (void) setActiveStyle:(NodeStyle*)style { + return [stylePicker setSelectedStyle:style]; +} + +// FIXME: create node on press, and drag it around? +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + Transformer *transformer = [renderer transformer]; + NSPoint nodePoint = [transformer fromScreen:[[renderer grid] snapScreenPoint:pos]]; + Node *node = [Node nodeWithPoint:nodePoint]; + [node setStyle:[self activeStyle]]; + [[renderer document] addNode:node]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {} + +- (StyleManager*) styleManager { + return [[stylePicker model] styleManager]; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateNodeTool" + withDefault:nil]; + [self setActiveStyle:[[self styleManager] nodeStyleForName:styleName]]; +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"ActiveStyle" + inGroup:@"CreateNodeTool" + value:[[self activeStyle] name]]; +} +@end + +static void clear_style_button_cb (GtkButton *widget, + NodeStyleSelector *selector) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [selector setSelectedStyle:nil]; + [pool drain]; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/DocumentContext.h b/tikzit-1/src/gtk/DocumentContext.h new file mode 100644 index 0000000..e4c1065 --- /dev/null +++ b/tikzit-1/src/gtk/DocumentContext.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" + +@class TikzDocument; + +@interface DocumentContext { + TikzDocument *document; + GraphRenderer *renderSurface; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Edge+Render.h b/tikzit-1/src/gtk/Edge+Render.h new file mode 100644 index 0000000..e88b28a --- /dev/null +++ b/tikzit-1/src/gtk/Edge+Render.h @@ -0,0 +1,34 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Edge.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface Edge (Render) + ++ (float) controlPointRadius; +- (void) renderControlsInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer; +- (void) renderBasicEdgeInContext:(id<RenderContext>)context withTransformer:(Transformer*)t selected:(BOOL)selected; +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context selected:(BOOL)selected; +- (NSRect) renderedBoundsWithTransformer:(Transformer*)t whenSelected:(BOOL)selected; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface withFuzz:(float)fuzz; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Edge+Render.m b/tikzit-1/src/gtk/Edge+Render.m new file mode 100644 index 0000000..3cc2a14 --- /dev/null +++ b/tikzit-1/src/gtk/Edge+Render.m @@ -0,0 +1,267 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Edge+Render.h" +#import "Node+Render.h" +#import "../common/util.h" + +static const float edgeWidth = 2.0; +static const float cpRadius = 3.0; +static const float cpLineWidth = 1.0; + +@implementation Edge (Render) + ++ (float) controlPointRadius { + return cpRadius; +} + +- (float) controlDistance { + const float dx = (targ.x - src.x); + const float dy = (targ.y - src.y); + if (dx == 0 && dy == 0) { + return weight; + } else { + return NSDistanceBetweenPoints(src, targ) * weight; + } +} + +- (void) renderControlsInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer { + [context saveState]; + + [self updateControls]; + + NSPoint c_source = [transformer toScreen:src]; + NSPoint c_target = [transformer toScreen:targ]; + NSPoint c_mid = [transformer toScreen:mid]; + + const float dx = (c_target.x - c_source.x); + const float dy = (c_target.y - c_source.y); + + [context setLineWidth:cpLineWidth]; + RColor fillColor = MakeRColor (1.0, 1.0, 1.0, 0.5); + + // draw a circle at the mid point + [context startPath]; + [context circleAt:c_mid withRadius:cpRadius]; + [context strokePathWithColor:MakeSolidRColor(0, 0, 1) andFillWithColor:fillColor]; + + //[context setAntialiasMode:AntialiasDisabled]; + + // size of control circles + float c_dist = 0.0f; + if (dx == 0 && dy == 0) { + c_dist = [transformer scaleToScreen:weight]; + } else { + c_dist = NSDistanceBetweenPoints(c_source, c_target) * weight; + } + + // basic bend is blue, in-out is green + RColor controlTrackColor; + if ([self bendMode] == EdgeBendModeBasic) { + controlTrackColor = MakeRColor (0.0, 0.0, 1.0, 0.4); + } else { + controlTrackColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + + [context startPath]; + [context circleAt:c_source withRadius:c_dist]; + if (dx != 0 || dy != 0) { + [context circleAt:c_target withRadius:c_dist]; + } + [context strokePathWithColor:controlTrackColor]; + + RColor handleColor = MakeRColor (1.0, 0.0, 1.0, 0.6); + if ([self bendMode] == EdgeBendModeBasic) { + if (bend % 45 != 0) { + handleColor = MakeRColor (0.0, 0.0, 0.1, 0.4); + } + } else if ([self bendMode] == EdgeBendModeInOut) { + if (outAngle % 45 != 0) { + handleColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + } + + NSPoint c_cp1 = [transformer toScreen:cp1]; + [context moveTo:c_source]; + [context lineTo:c_cp1]; + [context circleAt:c_cp1 withRadius:cpRadius]; + [context strokePathWithColor:handleColor]; + + if ([self bendMode] == EdgeBendModeInOut) { + // recalculate color based on inAngle + if (inAngle % 45 == 0) { + handleColor = MakeRColor (1.0, 0.0, 1.0, 0.6); + } else { + handleColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + } + + NSPoint c_cp2 = [transformer toScreen:cp2]; + [context moveTo:c_target]; + [context lineTo:c_cp2]; + [context circleAt:c_cp2 withRadius:cpRadius]; + [context strokePathWithColor:handleColor]; + + [context restoreState]; +} + +- (void) renderArrowStrokePathInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer color:(RColor)color { + + if ([self style] != nil) { + switch ([[self style] headStyle]) { + case AH_None: + break; + case AH_Plain: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftHeadNormal]]]; + [context lineTo:[transformer toScreen:head]]; + [context lineTo:[transformer toScreen:[self rightHeadNormal]]]; + [context strokePathWithColor:color]; + break; + case AH_Latex: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftHeadNormal]]]; + [context lineTo:[transformer toScreen:head]]; + [context lineTo:[transformer toScreen:[self rightHeadNormal]]]; + [context closeSubPath]; + [context strokePathWithColor:color andFillWithColor:color]; + break; + } + switch ([[self style] tailStyle]) { + case AH_None: + break; + case AH_Plain: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftTailNormal]]]; + [context lineTo:[transformer toScreen:tail]]; + [context lineTo:[transformer toScreen:[self rightTailNormal]]]; + [context strokePathWithColor:color]; + break; + case AH_Latex: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftTailNormal]]]; + [context lineTo:[transformer toScreen:tail]]; + [context lineTo:[transformer toScreen:[self rightTailNormal]]]; + [context closeSubPath]; + [context strokePathWithColor:color andFillWithColor:color]; + break; + } + } +} + +- (void) createStrokePathInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer { + NSPoint c_tail = [transformer toScreen:tail]; + NSPoint c_cp1 = [transformer toScreen:cp1]; + NSPoint c_cp2 = [transformer toScreen:cp2]; + NSPoint c_head = [transformer toScreen:head]; + + [context startPath]; + [context moveTo:c_tail]; + [context curveTo:c_head withCp1:c_cp1 andCp2:c_cp2]; + + if ([self style] != nil) { + // draw edge decoration + switch ([[self style] decorationStyle]) { + case ED_None: + break; + case ED_Tick: + [context moveTo:[transformer toScreen:[self leftNormal]]]; + [context lineTo:[transformer toScreen:[self rightNormal]]]; + break; + case ED_Arrow: + [context moveTo:[transformer toScreen:[self leftNormal]]]; + [context lineTo:[transformer toScreen:[self midTan]]]; + [context lineTo:[transformer toScreen:[self rightNormal]]]; + break; + } + + } +} + +- (RColor) color { + if (style) { + return [[style colorRGB] rColor]; + } else { + return BlackRColor; + } +} + +- (void) renderBasicEdgeInContext:(id<RenderContext>)context withTransformer:(Transformer*)t selected:(BOOL)selected { + [self updateControls]; + [context saveState]; + + const CGFloat lineWidth = style ? [style thickness] : edgeWidth; + [context setLineWidth:lineWidth]; + RColor color = [self color]; + if (selected) { + color.alpha = 0.5; + } + + [self createStrokePathInContext:context withTransformer:t]; + [context strokePathWithColor:color]; + + [self renderArrowStrokePathInContext:context withTransformer:t color:color]; + + [context restoreState]; +} + +- (void) renderToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context selected:(BOOL)selected { + [self renderBasicEdgeInContext:context withTransformer:[surface transformer] selected:selected]; + + if (selected) { + [self renderControlsInContext:context withTransformer:[surface transformer]]; + } + + if ([self hasEdgeNode]) { + NSPoint labelPt = [[surface transformer] toScreen:[self mid]]; + [[self edgeNode] renderLabelAt:labelPt + withContext:context]; + } +} + +- (NSRect) renderedBoundsWithTransformer:(Transformer*)t whenSelected:(BOOL)selected { + if (selected) { + float c_dist = [self controlDistance] + cpRadius; // include handle + NSRect cp1circ = NSMakeRect (head.x - c_dist, head.y - c_dist, 2*c_dist, 2*c_dist); + NSRect cp2circ = NSMakeRect (tail.x - c_dist, tail.y - c_dist, 2*c_dist, 2*c_dist); + NSRect rect = NSUnionRect ([self boundingRect], NSUnionRect (cp1circ, cp2circ)); + return [t rectToScreen:rect]; + } else { + return [t rectToScreen:[self boundingRect]]; + } +} + +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface withFuzz:(float)fuzz { + [self updateControls]; + + NSRect boundingRect = [[surface transformer] rectToScreen:[self boundingRect]]; + if (!NSPointInRect(p, NSInsetRect(boundingRect, -fuzz, -fuzz))) { + return NO; + } + + id<RenderContext> cr = [surface createRenderContext]; + + [cr setLineWidth:edgeWidth + 2 * fuzz]; + [self createStrokePathInContext:cr withTransformer:[surface transformer]]; + + return [cr strokeIncludesPoint:p]; +} + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Gtk.h b/tikzit-1/src/gtk/EdgeStyle+Gtk.h new file mode 100644 index 0000000..4323593 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Gtk.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "EdgeStyle.h" +#import <gtk/gtk.h> + +@interface EdgeStyle (Gtk) + +- (GdkColor) color; +- (void) setColor:(GdkColor)color; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Gtk.m b/tikzit-1/src/gtk/EdgeStyle+Gtk.m new file mode 100644 index 0000000..886329e --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Gtk.m @@ -0,0 +1,33 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStyle+Gtk.h" +#import "ColorRGB+Gtk.h" + +@implementation EdgeStyle (Gtk) + +- (GdkColor) color { + return [[self colorRGB] gdkColor]; +} + +- (void) setColor:(GdkColor)color { + [self setColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Storage.h b/tikzit-1/src/gtk/EdgeStyle+Storage.h new file mode 100644 index 0000000..74881f3 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Storage.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "EdgeStyle.h" +#import "Configuration.h" + +@interface EdgeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Storage.m b/tikzit-1/src/gtk/EdgeStyle+Storage.m new file mode 100644 index 0000000..45e2a20 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Storage.m @@ -0,0 +1,55 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStyle+Storage.h" +#import "ColorRGB+IntegerListStorage.h" + +@implementation EdgeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + self = [self init]; + + if (self) { + [self setName:[configFile stringEntry:@"Name" inGroup:groupName withDefault:name]]; + [self setCategory:[configFile stringEntry:@"Category" inGroup:groupName withDefault:category]]; + headStyle = [configFile integerEntry:@"HeadStyle" inGroup:groupName withDefault:headStyle]; + tailStyle = [configFile integerEntry:@"TailStyle" inGroup:groupName withDefault:tailStyle]; + decorationStyle = [configFile integerEntry:@"DecorationStyle" inGroup:groupName withDefault:decorationStyle]; + thickness = [configFile doubleEntry:@"Thickness" inGroup:groupName withDefault:thickness]; + [self setColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"Color" + inGroup:groupName + withDefault:[colorRGB valueList]]]]; + } + + return self; +} + +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + [configFile setStringEntry:@"Name" inGroup:groupName value:name]; + [configFile setStringEntry:@"Category" inGroup:groupName value:category]; + [configFile setIntegerEntry:@"HeadStyle" inGroup:groupName value:headStyle]; + [configFile setIntegerEntry:@"TailStyle" inGroup:groupName value:tailStyle]; + [configFile setIntegerEntry:@"DecorationStyle" inGroup:groupName value:decorationStyle]; + [configFile setDoubleEntry:@"Thickness" inGroup:groupName value:thickness]; + [configFile setIntegerListEntry:@"Color" inGroup:groupName value:[[self colorRGB] valueList]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyleEditor.h b/tikzit-1/src/gtk/EdgeStyleEditor.h new file mode 100644 index 0000000..2224bbb --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleEditor.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class EdgeStyle; + +@interface EdgeStyleEditor: NSObject { + EdgeStyle *style; + GtkTable *table; + GtkEntry *nameEdit; + GtkComboBox *decorationCombo; + GtkComboBox *headArrowCombo; + GtkComboBox *tailArrowCombo; + GtkColorButton *colorButton; + GtkWidget *makeColorTexSafeButton; + GtkAdjustment *thicknessAdj; + BOOL blockSignals; +} + +@property (retain) EdgeStyle *style; +@property (readonly) GtkWidget *widget; + +- (id) init; + +- (void) selectNameField; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleEditor.m b/tikzit-1/src/gtk/EdgeStyleEditor.m new file mode 100644 index 0000000..c7ca8bd --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleEditor.m @@ -0,0 +1,499 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStyleEditor.h" + +#import "EdgeStyle.h" +#import "EdgeStyle+Gtk.h" +#import "Shape.h" + +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "edgedecdata.m" +#pragma GCC diagnostic pop + +// {{{ Data Types +enum { + DEC_NAME_COL = 0, + DEC_PREVIEW_COL, + DEC_VALUE_COL, + DEC_N_COLS +}; + +struct dec_info { + const gchar *name; + const GdkPixdata *pixdata; + int value; +}; +static struct dec_info ed_entries[] = { + { "None", &ED_none_pixdata, ED_None }, + { "Arrow", &ED_arrow_pixdata, ED_Arrow }, + { "Tick", &ED_tick_pixdata, ED_Tick } +}; +static guint n_ed_entries = G_N_ELEMENTS (ed_entries); +static struct dec_info ah_head_entries[] = { + { "None", &AH_none_pixdata, AH_None }, + { "Plain", &AH_plain_head_pixdata, AH_Plain }, + { "Latex", &AH_latex_head_pixdata, AH_Latex } +}; +static guint n_ah_head_entries = G_N_ELEMENTS (ah_head_entries); +static struct dec_info ah_tail_entries[] = { + { "None", &AH_none_pixdata, AH_None }, + { "Plain", &AH_plain_tail_pixdata, AH_Plain }, + { "Latex", &AH_latex_tail_pixdata, AH_Latex } +}; +static guint n_ah_tail_entries = G_N_ELEMENTS (ah_tail_entries); + +static const guint row_count = 6; + +// }}} +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void style_name_edit_changed_cb (GtkEditable *widget, EdgeStyleEditor *editor); +static void decoration_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void head_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void tail_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void thickness_adjustment_changed_cb (GtkAdjustment *widget, EdgeStyleEditor *editor); +static void color_changed_cb (GtkColorButton *widget, EdgeStyleEditor *editor); +static void make_color_safe_button_clicked_cb (GtkButton *widget, EdgeStyleEditor *editor); +// }}} +// {{{ Notifications + +@interface EdgeStyleEditor (Notifications) +- (void) nameChangedTo:(NSString*)value; +- (void) edgeDecorationChangedTo:(EdgeDectorationStyle)value; +- (void) headArrowChangedTo:(ArrowHeadStyle)value; +- (void) tailArrowChangedTo:(ArrowHeadStyle)value; +- (void) thicknessChangedTo:(double)value; +- (void) makeColorTexSafe; +- (void) colorChangedTo:(GdkColor)value; +@end + +// }}} +// {{{ Private + +@interface EdgeStyleEditor (Private) +- (void) load:(guint)count decorationStylesFrom:(struct dec_info*)info into:(GtkListStore*)list; +- (void) clearDecCombo:(GtkComboBox*)combo; +- (void) setDecCombo:(GtkComboBox*)combo toValue:(int)value; +@end + +// }}} +// }}} +// {{{ API + +@implementation EdgeStyleEditor + +- (void) _addWidget:(GtkWidget*)w withLabel:(gchar *)label atRow:(guint)row { + NSAssert(row < row_count, @"row_count is wrong!"); + + GtkWidget *l = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (l), 0, 0.5); + gtk_widget_show (l); + gtk_widget_show (w); + + gtk_table_attach (table, l, + 0, 1, row, row+1, // l, r, t, b + GTK_FILL, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 5, // x padding + 0); // y padding + + gtk_table_attach (table, w, + 1, 2, row, row+1, // l, r, t, b + GTK_FILL | GTK_EXPAND, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 0, // x padding + 0); // y padding +} + +- (GtkComboBox*) _createDecComboWithEntries:(struct dec_info*)entries count:(guint)n { + GtkListStore *store = gtk_list_store_new (DEC_N_COLS, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_INT); + [self load:n decorationStylesFrom:entries into:store]; + + GtkComboBox *combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + g_object_unref (store); + GtkCellRenderer *cellRend = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), + cellRend, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cellRend, "pixbuf", DEC_PREVIEW_COL); + g_object_ref_sink (combo); + + return combo; +} + +- (GtkWidget*) _createMakeColorTexSafeButton { + GtkWidget *b = gtk_button_new (); + GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (icon); + gtk_container_add (GTK_CONTAINER (b), icon); + NSString *ttip = @"The colour is not a predefined TeX colour.\nClick here to choose the nearest TeX-safe colour."; + gtk_widget_set_tooltip_text (b, [ttip UTF8String]); + return b; +} + +- (id) init { + self = [super init]; + + if (self != nil) { + style = nil; + table = GTK_TABLE (gtk_table_new (row_count, 2, FALSE)); + gtk_table_set_col_spacings (table, 6); + gtk_table_set_row_spacings (table, 6); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + blockSignals = NO; + + /** + * Name + */ + nameEdit = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (nameEdit); + [self _addWidget:GTK_WIDGET (nameEdit) + withLabel:"Name" + atRow:0]; + g_signal_connect (G_OBJECT (nameEdit), + "changed", + G_CALLBACK (style_name_edit_changed_cb), + self); + + + /** + * Edge decoration style + */ + decorationCombo = [self _createDecComboWithEntries:ed_entries count:n_ed_entries]; + [self _addWidget:GTK_WIDGET (decorationCombo) + withLabel:"Decoration" + atRow:1]; + g_signal_connect (G_OBJECT (decorationCombo), + "changed", + G_CALLBACK (decoration_combo_changed_cb), + self); + + + /** + * Head arrow style + */ + headArrowCombo = [self _createDecComboWithEntries:ah_head_entries count:n_ah_head_entries]; + [self _addWidget:GTK_WIDGET (headArrowCombo) + withLabel:"Arrow head" + atRow:2]; + g_signal_connect (G_OBJECT (headArrowCombo), + "changed", + G_CALLBACK (head_arrow_combo_changed_cb), + self); + + + /** + * Tail arrow style + */ + tailArrowCombo = [self _createDecComboWithEntries:ah_tail_entries count:n_ah_tail_entries]; + [self _addWidget:GTK_WIDGET (tailArrowCombo) + withLabel:"Arrow tail" + atRow:3]; + g_signal_connect (G_OBJECT (tailArrowCombo), + "changed", + G_CALLBACK (tail_arrow_combo_changed_cb), + self); + + + /** + * Colour + */ + GtkWidget *colorBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:colorBox + withLabel:"Colour" + atRow:4]; + colorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (colorButton); + gtk_widget_show (GTK_WIDGET (colorButton)); + gtk_box_pack_start (GTK_BOX (colorBox), GTK_WIDGET (colorButton), + FALSE, FALSE, 0); + makeColorTexSafeButton = [self _createMakeColorTexSafeButton]; + g_object_ref_sink (makeColorTexSafeButton); + gtk_box_pack_start (GTK_BOX (colorBox), makeColorTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (colorButton), + "color-set", + G_CALLBACK (color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeColorTexSafeButton), + "clicked", + G_CALLBACK (make_color_safe_button_clicked_cb), + self); + + + /** + * Thickness + */ + thicknessAdj = GTK_ADJUSTMENT (gtk_adjustment_new ( + 1.0, // value + 0.0, // lower + 50.0, // upper + 0.20, // step + 1.0, // page + 0.0)); // (irrelevant) + g_object_ref_sink (thicknessAdj); + GtkWidget *scaleSpin = gtk_spin_button_new (thicknessAdj, 0.0, 2); + [self _addWidget:scaleSpin + withLabel:"Thickness" + atRow:5]; + g_signal_connect (G_OBJECT (thicknessAdj), + "value-changed", + G_CALLBACK (thickness_adjustment_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nameEdit); + g_object_unref (decorationCombo); + g_object_unref (colorButton); + g_object_unref (makeColorTexSafeButton); + g_object_unref (thicknessAdj); + g_object_unref (table); + [style release]; + + [super dealloc]; +} + +- (EdgeStyle*) style { + return style; +} + +- (void) setStyle:(EdgeStyle*)s { + blockSignals = YES; + EdgeStyle *oldStyle = style; + style = [s retain]; + + if (style != nil) { + gtk_widget_set_sensitive (GTK_WIDGET (table), TRUE); + + gtk_entry_set_text(nameEdit, [[style name] UTF8String]); + + [self setDecCombo:decorationCombo toValue:[style decorationStyle]]; + [self setDecCombo:headArrowCombo toValue:[style headStyle]]; + [self setDecCombo:tailArrowCombo toValue:[style tailStyle]]; + + GdkColor c = [style color]; + gtk_color_button_set_color(colorButton, &c); + gtk_widget_set_visible (makeColorTexSafeButton, ([[style colorRGB] name] == nil)); + + gtk_adjustment_set_value(thicknessAdj, [style thickness]); + } else { + gtk_entry_set_text(nameEdit, ""); + [self clearDecCombo:decorationCombo]; + [self clearDecCombo:headArrowCombo]; + [self clearDecCombo:tailArrowCombo]; + gtk_widget_set_visible (makeColorTexSafeButton, FALSE); + gtk_adjustment_set_value(thicknessAdj, 1.0); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + } + + [oldStyle release]; + blockSignals = NO; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (table); +} + +- (void) selectNameField { + gtk_widget_grab_focus (GTK_WIDGET (nameEdit)); + gtk_editable_select_region (GTK_EDITABLE (nameEdit), 0, -1); +} + +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStyleEditor (Notifications) +- (void) nameChangedTo:(NSString*)value { + [style setName:value]; +} + +- (void) edgeDecorationChangedTo:(EdgeDectorationStyle)value { + [style setDecorationStyle:value]; +} + +- (void) headArrowChangedTo:(ArrowHeadStyle)value { + [style setHeadStyle:value]; +} + +- (void) tailArrowChangedTo:(ArrowHeadStyle)value { + [style setTailStyle:value]; +} + +- (void) thicknessChangedTo:(double)value { + [style setThickness:(float)value]; +} + +- (void) colorChangedTo:(GdkColor)value { + [style setColor:value]; + gtk_widget_set_visible (makeColorTexSafeButton, + [[style colorRGB] name] == nil); +} + +- (void) makeColorTexSafe { + if (style != nil) { + [[style colorRGB] setToClosestHashed]; + GdkColor color = [style color]; + gtk_color_button_set_color(colorButton, &color); + gtk_widget_set_visible (makeColorTexSafeButton, FALSE); + } +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStyleEditor (Private) +- (BOOL) signalsBlocked { return blockSignals; } + +- (void) load:(guint)count decorationStylesFrom:(struct dec_info*)info into:(GtkListStore*)list { + GtkTreeIter iter; + + for (guint i = 0; i < count; ++i) { + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (info[i].pixdata, FALSE, NULL); + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, + DEC_NAME_COL, info[i].name, + DEC_PREVIEW_COL, buf, + DEC_VALUE_COL, info[i].value, + -1); + g_object_unref (buf); + } +} + +- (void) clearDecCombo:(GtkComboBox*)combo { + gtk_combo_box_set_active (combo, -1); +} + +- (void) setDecCombo:(GtkComboBox*)combo toValue:(int)value { + GtkTreeModel *model = gtk_combo_box_get_model (combo); + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + int rowValue; + gtk_tree_model_get (model, &iter, DEC_VALUE_COL, &rowValue, -1); + if (rowValue == value) { + gtk_combo_box_set_active_iter (combo, &iter); + return; + } + } while (gtk_tree_model_iter_next (model, &iter)); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void style_name_edit_changed_cb (GtkEditable *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + const gchar *contents = gtk_entry_get_text (GTK_ENTRY (widget)); + [editor nameChangedTo:[NSString stringWithUTF8String:contents]]; + + [pool drain]; +} + +static void decoration_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + EdgeDectorationStyle dec = ED_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor edgeDecorationChangedTo:dec]; + + [pool drain]; +} + +static void head_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + ArrowHeadStyle dec = AH_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor headArrowChangedTo:dec]; + + [pool drain]; +} + +static void tail_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + ArrowHeadStyle dec = AH_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor tailArrowChangedTo:dec]; + + [pool drain]; +} + +static void thickness_adjustment_changed_cb (GtkAdjustment *adj, EdgeStyleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor thicknessChangedTo:gtk_adjustment_get_value (adj)]; + [pool drain]; +} + +static void color_changed_cb (GtkColorButton *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor colorChangedTo:color]; + + [pool drain]; +} + +static void make_color_safe_button_clicked_cb (GtkButton *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeColorTexSafe]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleSelector.h b/tikzit-1/src/gtk/EdgeStyleSelector.h new file mode 100644 index 0000000..904bd93 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleSelector.h @@ -0,0 +1,61 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class EdgeStyle; +@class EdgeStylesModel; +@class StyleManager; + +@interface EdgeStyleSelector: NSObject { + EdgeStylesModel *model; + GtkTreeView *view; +} + +/*! + @property widget + @brief The GTK widget + */ +@property (readonly) GtkWidget *widget; + +/*! + @property model + @brief The model to use. + */ +@property (retain) EdgeStylesModel *model; + +/*! + @property selectedStyle + @brief The selected style. + + When this changes, a SelectedStyleChanged notification will be posted + */ +@property (assign) EdgeStyle *selectedStyle; + +/*! + * Initialise with a new model for the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; +/*! + * Initialise with the given model + */ +- (id) initWithModel:(EdgeStylesModel*)model; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleSelector.m b/tikzit-1/src/gtk/EdgeStyleSelector.m new file mode 100644 index 0000000..6a9db33 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleSelector.m @@ -0,0 +1,143 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStyleSelector.h" + +#import "EdgeStylesModel.h" + +// {{{ Internal interfaces +static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr); +// }}} +// {{{ API + +@implementation EdgeStyleSelector + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + return [self initWithModel:[EdgeStylesModel modelWithStyleManager:m]]; +} +- (id) initWithModel:(EdgeStylesModel*)m { + self = [super init]; + + if (self) { + model = [m retain]; + + view = GTK_TREE_VIEW (gtk_tree_view_new_with_model ([m model])); + gtk_tree_view_set_headers_visible (view, FALSE); + g_object_ref (view); + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new_with_attributes ( + "Preview", + renderer, + "pixbuf", EDGE_STYLES_ICON_COL, + NULL); + gtk_tree_view_append_column (view, column); + gtk_tree_view_set_tooltip_column (view, EDGE_STYLES_NAME_COL); + + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + + g_signal_connect (G_OBJECT (sel), + "changed", + G_CALLBACK (selection_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (view); + [model release]; + + [super dealloc]; +} + +- (EdgeStylesModel*) model { + return model; +} + +- (void) setModel:(EdgeStylesModel*)m { + if (m == model) + return; + + EdgeStylesModel *oldModel = model; + model = [m retain]; + gtk_tree_view_set_model (view, [model model]); + [oldModel release]; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (view); +} + +- (EdgeStyle*) selectedStyle { + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (sel, NULL, &iter)) { + return nil; + } + + EdgeStyle *style = nil; + gtk_tree_model_get ([model model], &iter, EDGE_STYLES_PTR_COL, &style, -1); + + return style; +} + +- (void) setSelectedStyle:(EdgeStyle*)style { + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + + if (style == nil) { + gtk_tree_selection_unselect_all (sel); + return; + } + + GtkTreePath *path = [model pathFromStyle:style]; + if (path) { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"SelectedStyleChanged" + object:mgr]; + + [pool drain]; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker + diff --git a/tikzit-1/src/gtk/EdgeStylesModel.h b/tikzit-1/src/gtk/EdgeStylesModel.h new file mode 100644 index 0000000..1166f92 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesModel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class EdgeStyle; +@class StyleManager; + +enum { + EDGE_STYLES_NAME_COL = 0, + EDGE_STYLES_ICON_COL, + EDGE_STYLES_PTR_COL, + EDGE_STYLES_N_COLS +}; + +@interface EdgeStylesModel: NSObject { + GtkListStore *store; + StyleManager *styleManager; +} + +/*! + @property model + @brief The GTK+ tree model + */ +@property (readonly) GtkTreeModel *model; + +/*! + @property manager + @brief The StyleManager to use. + */ +@property (retain) StyleManager *styleManager; + +/*! + * Initialise with the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; + ++ (id) modelWithStyleManager:(StyleManager*)m; + +- (EdgeStyle*) styleFromPath:(GtkTreePath*)path; +- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style; +- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter; +- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStylesModel.m b/tikzit-1/src/gtk/EdgeStylesModel.m new file mode 100644 index 0000000..2de57ed --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesModel.m @@ -0,0 +1,367 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStylesModel.h" + +#import "CairoRenderContext.h" +#import "Edge.h" +#import "Edge+Render.h" +#import "EdgeStyle.h" +#import "Node.h" +#import "StyleManager.h" + +#import "gtkhelpers.h" + +#import <gdk-pixbuf/gdk-pixbuf.h> + +// {{{ Internal interfaces + +@interface EdgeStylesModel (Notifications) +- (void) edgeStylesReplaced:(NSNotification*)notification; +- (void) edgeStyleAdded:(NSNotification*)notification; +- (void) edgeStyleRemoved:(NSNotification*)notification; +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context; +@end + +@interface EdgeStylesModel (Private) +- (cairo_surface_t*) createEdgeIconSurface; +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style; +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) addEdgeStyle:(EdgeStyle*)style; +- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) observeEdgeStyle:(EdgeStyle*)style; +- (void) stopObservingEdgeStyle:(EdgeStyle*)style; +- (void) clearEdgeStylesModel; +- (void) reloadEdgeStyles; +@end + +// }}} +// {{{ API + +@implementation EdgeStylesModel + ++ (id) modelWithStyleManager:(StyleManager*)m { + return [[[self alloc] initWithStyleManager:m] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + store = gtk_list_store_new (EDGE_STYLES_N_COLS, + G_TYPE_STRING, + GDK_TYPE_PIXBUF, + G_TYPE_POINTER); + g_object_ref_sink (store); + + [self setStyleManager:m]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self clearEdgeStylesModel]; + g_object_unref (store); + [styleManager release]; + + [super dealloc]; +} + +- (GtkTreeModel*) model { + return GTK_TREE_MODEL (store); +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)m { + if (m == nil) { + [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"]; + } + [m retain]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylesReplaced:) + name:@"EdgeStylesReplaced" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStyleAdded:) + name:@"EdgeStyleAdded" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStyleRemoved:) + name:@"EdgeStyleRemoved" + object:m]; + + [styleManager release]; + styleManager = m; + + [self reloadEdgeStyles]; +} + +- (EdgeStyle*) styleFromPath:(GtkTreePath*)path { + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + EdgeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, EDGE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_model_get_path (m, &row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} + +- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter { + EdgeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EDGE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_iter_copy (&row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStylesModel (Notifications) + +- (void) edgeStylesReplaced:(NSNotification*)notification { + [self reloadEdgeStyles]; +} + +- (void) edgeStyleAdded:(NSNotification*)notification { + [self addEdgeStyle:[[notification userInfo] objectForKey:@"style"]]; +} + +- (void) edgeStyleRemoved:(NSNotification*)notification { + EdgeStyle *style = [[notification userInfo] objectForKey:@"style"]; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_list_store_remove (store, &row); + [self stopObservingEdgeStyle:rowStyle]; + [rowStyle release]; + return; + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + if ([object class] != [EdgeStyle class]) + return; + + EdgeStyle *style = object; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + if ([@"name" isEqual:keyPath]) { + gtk_list_store_set (store, &row, EDGE_STYLES_NAME_COL, [[style name] UTF8String], -1); + } else { + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style]; + gtk_list_store_set (store, &row, EDGE_STYLES_ICON_COL, pixbuf, -1); + g_object_unref (pixbuf); + } + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStylesModel (Private) +- (cairo_surface_t*) createEdgeIconSurface { + return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 48, 18); +} + +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style { + cairo_surface_t *surface = [self createEdgeIconSurface]; + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); + return pixbuf; +} + +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface { + Transformer *transformer = [Transformer defaultTransformer]; + [transformer setFlippedAboutXAxis:YES]; + + int width = cairo_image_surface_get_width (surface); + int height = cairo_image_surface_get_height (surface); + NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height); + NSRect graphBounds = [transformer rectFromScreen:pixbufBounds]; + + NSPoint start = NSMakePoint (NSMinX (graphBounds) + 0.1f, NSMidY (graphBounds)); + NSPoint end = NSMakePoint (NSMaxX (graphBounds) - 0.1f, NSMidY (graphBounds)); + Node *src = [Node nodeWithPoint:start]; + Node *tgt = [Node nodeWithPoint:end]; + Edge *e = [Edge edgeWithSource:src andTarget:tgt]; + [e setStyle:style]; + + CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface]; + [context clearSurface]; + [e renderBasicEdgeInContext:context withTransformer:transformer selected:NO]; + [context release]; + + return pixbuf_get_from_surface (surface); +} + +- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface]; + gtk_list_store_set (store, &iter, + EDGE_STYLES_NAME_COL, [[style name] UTF8String], + EDGE_STYLES_ICON_COL, pixbuf, + EDGE_STYLES_PTR_COL, (gpointer)[style retain], + -1); + g_object_unref (pixbuf); + [self observeEdgeStyle:style]; +} + +- (void) addEdgeStyle:(EdgeStyle*)style { + cairo_surface_t *surface = [self createEdgeIconSurface]; + [self addEdgeStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); +} + +- (void) observeEdgeStyle:(EdgeStyle*)style { + [style addObserver:self + forKeyPath:@"name" + options:NSKeyValueObservingOptionNew + context:NULL]; + [style addObserver:self + forKeyPath:@"thickness" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"headStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"tailStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"decorationStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.blue" + options:0 + context:NULL]; +} + +- (void) stopObservingEdgeStyle:(EdgeStyle*)style { + [style removeObserver:self forKeyPath:@"name"]; + [style removeObserver:self forKeyPath:@"thickness"]; + [style removeObserver:self forKeyPath:@"headStyle"]; + [style removeObserver:self forKeyPath:@"tailStyle"]; + [style removeObserver:self forKeyPath:@"decorationStyle"]; + [style removeObserver:self forKeyPath:@"colorRGB.red"]; + [style removeObserver:self forKeyPath:@"colorRGB.green"]; + [style removeObserver:self forKeyPath:@"colorRGB.blue"]; +} + +- (void) clearEdgeStylesModel { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + [self stopObservingEdgeStyle:rowStyle]; + [rowStyle release]; + } while (gtk_tree_model_iter_next (model, &row)); + } + gtk_list_store_clear (store); +} + +- (void) reloadEdgeStyles { + [self clearEdgeStylesModel]; + cairo_surface_t *surface = [self createEdgeIconSurface]; + for (EdgeStyle *style in [styleManager edgeStyles]) { + [self addEdgeStyle:style usingSurface:surface]; + } + cairo_surface_destroy (surface); +} +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStylesPalette.h b/tikzit-1/src/gtk/EdgeStylesPalette.h new file mode 100644 index 0000000..c0c6c4b --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesPalette.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class StyleManager; +@class EdgeStyleSelector; +@class EdgeStyleEditor; + +@interface EdgeStylesPalette: NSObject { + EdgeStyleSelector *selector; + EdgeStyleEditor *editor; + + GtkWidget *palette; + + GtkWidget *removeStyleButton; + GtkWidget *applyStyleButton; + GtkWidget *clearStyleButton; +} + +@property (retain) StyleManager *styleManager; +@property (readonly) GtkWidget *widget; + +- (id) initWithManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStylesPalette.m b/tikzit-1/src/gtk/EdgeStylesPalette.m new file mode 100644 index 0000000..33264cf --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesPalette.m @@ -0,0 +1,198 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStylesPalette.h" + +#import "EdgeStylesModel.h" +#import "EdgeStyleSelector.h" +#import "EdgeStyleEditor.h" +#import "StyleManager.h" + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void add_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette); +static void remove_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette); +// }}} +// {{{ Notifications + +@interface EdgeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ Private + +@interface EdgeStylesPalette (Private) +- (void) updateButtonState; +- (void) removeSelectedStyle; +- (void) addStyle; +@end + +// }}} +// }}} +// {{{ API + +@implementation EdgeStylesPalette + +@synthesize widget=palette; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithManager:(StyleManager*)m { + self = [super init]; + + if (self) { + selector = [[EdgeStyleSelector alloc] initWithStyleManager:m]; + editor = [[EdgeStyleEditor alloc] init]; + + palette = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (palette), 6); + g_object_ref_sink (palette); + + GtkWidget *mainBox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (palette), mainBox, FALSE, FALSE, 0); + gtk_widget_show (mainBox); + + GtkWidget *selectorScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selectorScroller), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (selectorScroller), [selector widget]); + gtk_container_add (GTK_CONTAINER (selectorFrame), selectorScroller); + gtk_box_pack_start (GTK_BOX (mainBox), selectorFrame, TRUE, TRUE, 0); + gtk_widget_show (selectorScroller); + gtk_widget_show (selectorFrame); + gtk_widget_show ([selector widget]); + + gtk_box_pack_start (GTK_BOX (mainBox), [editor widget], TRUE, TRUE, 0); + gtk_widget_show ([editor widget]); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (palette), GTK_WIDGET (buttonBox), FALSE, FALSE, 0); + + GtkWidget *addStyleButton = gtk_button_new (); + gtk_widget_set_tooltip_text (addStyleButton, "Add a new style"); + GtkWidget *addIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addStyleButton), addIcon); + gtk_box_pack_start (buttonBox, addStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addStyleButton), + "clicked", + G_CALLBACK (add_style_button_cb), + self); + + removeStyleButton = gtk_button_new (); + g_object_ref_sink (removeStyleButton); + gtk_widget_set_tooltip_text (removeStyleButton, "Delete selected style"); + GtkWidget *removeIcon = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (removeStyleButton), removeIcon); + gtk_box_pack_start (buttonBox, removeStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeStyleButton), + "clicked", + G_CALLBACK (remove_style_button_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(selectedStyleChanged:) + name:@"SelectedStyleChanged" + object:selector]; + + [self updateButtonState]; + } + + return self; +} + +- (StyleManager*) styleManager { + return [[selector model] styleManager]; +} + +- (void) setStyleManager:(StyleManager*)m { + [[selector model] setStyleManager:m]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [editor release]; + [selector release]; + + g_object_unref (palette); + g_object_unref (removeStyleButton); + + [super dealloc]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification { + [editor setStyle:[selector selectedStyle]]; + [self updateButtonState]; +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStylesPalette (Private) +- (void) updateButtonState { + gboolean hasStyleSelection = [selector selectedStyle] != nil; + gtk_widget_set_sensitive (removeStyleButton, hasStyleSelection); +} + +- (void) removeSelectedStyle { + EdgeStyle *style = [selector selectedStyle]; + if (style) + [[[selector model] styleManager] removeEdgeStyle:style]; +} + +- (void) addStyle { + EdgeStyle *newStyle = [EdgeStyle defaultEdgeStyleWithName:@"newstyle"]; + [[self styleManager] addEdgeStyle:newStyle]; + [selector setSelectedStyle:newStyle]; + [editor selectNameField]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void add_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette addStyle]; + [pool drain]; +} + +static void remove_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette removeSelectedStyle]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/FileChooserDialog.h b/tikzit-1/src/gtk/FileChooserDialog.h new file mode 100644 index 0000000..80b03f5 --- /dev/null +++ b/tikzit-1/src/gtk/FileChooserDialog.h @@ -0,0 +1,56 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@interface FileChooserDialog: NSObject { + GtkFileChooser *dialog; +} + ++ (FileChooserDialog*) saveDialog; ++ (FileChooserDialog*) saveDialogWithParent:(GtkWindow*)parent; ++ (FileChooserDialog*) saveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; ++ (FileChooserDialog*) openDialog; ++ (FileChooserDialog*) openDialogWithParent:(GtkWindow*)parent; ++ (FileChooserDialog*) openDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; + +- (id) initSaveDialog; +- (id) initSaveDialogWithParent:(GtkWindow*)parent; +- (id) initSaveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; +- (id) initOpenDialog; +- (id) initOpenDialogWithParent:(GtkWindow*)parent; +- (id) initOpenDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; + +- (void) addStandardFilters; +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern; +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern setSelected:(BOOL)selected; + +- (void) setCurrentFolder:(NSString*)path; +- (NSString*) currentFolder; + +- (void) setSuggestedName:(NSString*)fileName; + +- (NSString*) filePath; + +- (BOOL) showDialog; + +- (void) destroy; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/FileChooserDialog.m b/tikzit-1/src/gtk/FileChooserDialog.m new file mode 100644 index 0000000..9498e4c --- /dev/null +++ b/tikzit-1/src/gtk/FileChooserDialog.m @@ -0,0 +1,152 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "FileChooserDialog.h" + +@implementation FileChooserDialog: NSObject + ++ (FileChooserDialog*) saveDialog { return [[[self alloc] initSaveDialog] autorelease]; } ++ (FileChooserDialog*) saveDialogWithParent:(GtkWindow*)parent + { return [[[self alloc] initSaveDialogWithParent:parent] autorelease]; } ++ (FileChooserDialog*) saveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent + { return [[[self alloc] initSaveDialogWithTitle:title parent:parent] autorelease]; } ++ (FileChooserDialog*) openDialog { return [[[self alloc] initOpenDialog] autorelease]; } ++ (FileChooserDialog*) openDialogWithParent:(GtkWindow*)parent + { return [[[self alloc] initOpenDialogWithParent:parent] autorelease]; } ++ (FileChooserDialog*) openDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent + { return [[[self alloc] initOpenDialogWithTitle:title parent:parent] autorelease]; } + +- (id) initSaveDialog { return [self initSaveDialogWithParent:NULL]; } +- (id) initSaveDialogWithParent:(GtkWindow*)parent + { return [self initSaveDialogWithTitle:@"Save file" parent:parent]; } +- (id) initSaveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent { + self = [super init]; + + if (self) { + dialog = GTK_FILE_CHOOSER (gtk_file_chooser_dialog_new ( + [title UTF8String], + parent, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL)); + gtk_file_chooser_set_do_overwrite_confirmation (dialog, TRUE); + } + + return self; +} + +- (id) initOpenDialog { return [self initOpenDialogWithParent:NULL]; } +- (id) initOpenDialogWithParent:(GtkWindow*)parent + { return [self initOpenDialogWithTitle:@"Open file" parent:parent]; } +- (id) initOpenDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent { + self = [super init]; + + if (self) { + dialog = GTK_FILE_CHOOSER (gtk_file_chooser_dialog_new ( + [title UTF8String], + parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL)); + } + + return self; +} + +- (void) addStandardFilters { + GtkFileFilter *tikzfilter = gtk_file_filter_new(); + gtk_file_filter_set_name(tikzfilter, ".tikz files"); + gtk_file_filter_add_pattern(tikzfilter, "*.tikz"); + gtk_file_chooser_add_filter(dialog, tikzfilter); + GtkFileFilter *allfilter = gtk_file_filter_new(); + gtk_file_filter_set_name(allfilter, "all files"); + gtk_file_filter_add_pattern(allfilter, "*"); + gtk_file_chooser_add_filter(dialog, allfilter); + gtk_file_chooser_set_filter(dialog, tikzfilter); +} + +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern { + [self addFileFilter:filterName withPattern:filePattern setSelected:NO]; +} + +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern setSelected:(BOOL)selected { + GtkFileFilter *oldFilter = selected ? NULL : gtk_file_chooser_get_filter (dialog); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, [filterName UTF8String]); + gtk_file_filter_add_pattern(filter, [filePattern UTF8String]); + gtk_file_chooser_add_filter(dialog, filter); + if (selected) { + gtk_file_chooser_set_filter (dialog, filter); + } else if (oldFilter) { + gtk_file_chooser_set_filter (dialog, oldFilter); + } +} + +- (void) setCurrentFolder:(NSString*)path { + gchar *folder = [path glibFilename]; + if (folder) { + gtk_file_chooser_set_current_folder(dialog, folder); + g_free (folder); + } +} + +- (NSString*) currentFolder { + NSString *path = nil; + gchar *folder = gtk_file_chooser_get_current_folder(dialog); + if (folder) { + path = [NSString stringWithGlibFilename:folder]; + g_free (folder); + } + return path; +} + +- (void) setSuggestedName:(NSString*)fileName { + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), [fileName UTF8String]); +} + +- (NSString*) filePath { + NSString *path = nil; + gchar *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + if (filename) { + path = [NSString stringWithGlibFilename:filename]; + g_free (filename); + } + return path; +} + +- (BOOL) showDialog { + return (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) ? YES : NO; +} + +- (void) destroy { + gtk_widget_destroy (GTK_WIDGET (dialog)); + dialog = NULL; +} + +- (void) dealloc { + if (dialog) { + g_warning ("Failed to destroy file chooser dialog!\n"); + gtk_widget_destroy (GTK_WIDGET (dialog)); + } + [super dealloc]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphEditorPanel.h b/tikzit-1/src/gtk/GraphEditorPanel.h new file mode 100644 index 0000000..2b93259 --- /dev/null +++ b/tikzit-1/src/gtk/GraphEditorPanel.h @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Tool.h" +#import <gtk/gtk.h> + +@class GraphInputHandler; +@class GraphRenderer; +@class TikzDocument; +@class WidgetSurface; + +@protocol PreviewHandler <NSObject> +- (void) showPreview; +@end +@interface GraphEditorPanel : NSObject { + GraphRenderer *renderer; + WidgetSurface *surface; + GraphInputHandler *inputHandler; + id<PreviewHandler> previewHandler; + id<Tool> tool; +} +@property (retain) TikzDocument *document; +@property (readonly) GtkWidget *widget; +@property (retain) id<Tool> activeTool; +@property (assign) id<PreviewHandler> previewHandler; + +- (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-1/src/gtk/GraphEditorPanel.m b/tikzit-1/src/gtk/GraphEditorPanel.m new file mode 100644 index 0000000..dac52a0 --- /dev/null +++ b/tikzit-1/src/gtk/GraphEditorPanel.m @@ -0,0 +1,240 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "GraphEditorPanel.h" + +#import "Application.h" +#import "GraphRenderer.h" +#import "HandTool.h" +#import "InputDelegate.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + +#import <gdk/gdkkeysyms.h> + +@class GraphRenderer; +@class WidgetSurface; + +static const InputMask zoomPanMask = ControlMask; + +/** + * Mostly just a multiplexer, but also handles zoom and pan + * when ctrl is held + */ +@interface GraphInputHandler : NSObject<InputDelegate> { + GraphEditorPanel *panel; + NSPoint dragOrigin; + NSPoint oldGraphOrigin; + BOOL zoomPanActive; +} +- (id) initForPanel:(GraphEditorPanel*)p; +@end + +@implementation GraphEditorPanel + +@synthesize previewHandler; + +- (id) init { + return [self initWithDocument:nil]; +} +- (id) initWithDocument:(TikzDocument*)document { + self = [super init]; + if (self) { + surface = [[WidgetSurface alloc] init]; + [surface setDefaultScale:50.0f]; + [surface setKeepCentered:YES]; + [surface setCanFocus:YES]; + renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; + + inputHandler = [[GraphInputHandler alloc] initForPanel:self]; + [surface setInputDelegate:inputHandler]; + } + return self; +} + +- (void) dealloc { + [renderer release]; + [surface release]; + [inputHandler release]; + + [super dealloc]; +} + +- (GraphRenderer*) renderer { + return renderer; +} +- (TikzDocument*) document { + return [renderer document]; +} +- (void) setDocument:(TikzDocument*)doc { + [renderer setDocument:doc]; +} +- (GtkWidget*) widget { + return [surface widget]; +} +- (id<Tool>) activeTool { + return tool; +} +- (void) setActiveTool:(id<Tool>)t { + if (t == tool) + return; + + [[[renderer document] pickSupport] deselectAllNodes]; + [[[renderer document] pickSupport] deselectAllEdges]; + + id oldTool = tool; + BOOL weHadTool = ([oldTool activeRenderer] == renderer); + if (weHadTool) { + [oldTool setActiveRenderer:nil]; + } + + tool = [t retain]; + [oldTool release]; + + if (weHadTool) { + [self grabTool]; + } +} + +- (BOOL) hasTool { + return [tool activeRenderer] == renderer; +} + +- (void) grabTool { + if ([tool activeRenderer] != renderer) { + [[tool activeRenderer] setPostRenderer:nil]; + [tool setActiveRenderer:renderer]; + } + [renderer setPostRenderer:tool]; +} + +- (void) zoomInAboutPoint:(NSPoint)pos { [surface zoomInAboutPoint:pos]; } +- (void) zoomOutAboutPoint:(NSPoint)pos { [surface zoomOutAboutPoint:pos]; } +- (void) zoomIn { [surface zoomIn]; } +- (void) zoomOut { [surface zoomOut]; } +- (void) zoomReset { [surface zoomReset]; } + +@end + +@implementation GraphInputHandler +- (id) initForPanel:(GraphEditorPanel*)p { + self = [super init]; + if (self) { + // NB: no retention! + panel = p; + } + return self; +} +- (id) init { + [self release]; + return nil; +} +- (void) dealloc { + [super dealloc]; +} + +// FIXME: share code with HandTool? +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (mask == zoomPanMask && button == LeftButton) { + dragOrigin = pos; + oldGraphOrigin = [[[panel renderer] transformer] origin]; + zoomPanActive = YES; + } else { + zoomPanActive = NO; + [panel grabTool]; + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + [tool mousePressAt:pos withButton:button andMask:mask]; + } + } +} + +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + [panel grabTool]; + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + [tool mouseDoubleClickAt:pos withButton:button andMask:mask]; + } +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (zoomPanActive && button == LeftButton) { + zoomPanActive = NO; + } else if ([panel hasTool]) { + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + [tool mouseReleaseAt:pos withButton:button andMask:mask]; + } + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (zoomPanActive && (buttons & LeftButton)) { + NSPoint newGraphOrigin = oldGraphOrigin; + newGraphOrigin.x += pos.x - dragOrigin.x; + newGraphOrigin.y += pos.y - dragOrigin.y; + [[[panel renderer] transformer] setOrigin:newGraphOrigin]; + [[panel renderer] invalidateGraph]; + } else if ([panel hasTool]) { + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + [tool mouseMoveTo:pos withButtons:buttons andMask:mask]; + } + } +} + +- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask { + if (mask == zoomPanMask) { + if (dir == ScrollUp) { + [panel zoomInAboutPoint:pos]; + } else if (dir == ScrollDown) { + [panel zoomOutAboutPoint:pos]; + } + } else { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) { + [tool mouseScrolledAt:pos inDirection:dir withMask:mask]; + } + } +} + +- (void) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_space && !mask) { + return; + } + if (![app activateToolForKey:keyVal withMask:mask]) { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(keyPressed:withMask:)]) { + [tool keyPressed:keyVal withMask:mask]; + } + } +} + +- (void) keyReleased:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_space && !mask) { + [[panel previewHandler] showPreview]; + } + if (![app activateToolForKey:keyVal withMask:mask]) { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(keyReleased:withMask:)]) { + [tool keyReleased:keyVal withMask:mask]; + } + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphRenderer.h b/tikzit-1/src/gtk/GraphRenderer.h new file mode 100644 index 0000000..730d606 --- /dev/null +++ b/tikzit-1/src/gtk/GraphRenderer.h @@ -0,0 +1,84 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +// classes +#import "Graph.h" +#import "Grid.h" +#import "PickSupport.h" +#import "TikzDocument.h" + +// protocols +#import "Surface.h" + +@interface GraphRenderer: NSObject <RenderDelegate> { + TikzDocument *doc; + NSObject<Surface> *surface; + Grid *grid; + NSMutableSet *highlightedNodes; + id<RenderDelegate> postRenderer; +} + +@property (retain) id<RenderDelegate> postRenderer; + +- (id) initWithSurface:(NSObject <Surface> *)surface; +- (id) initWithSurface:(NSObject <Surface> *)surface document:(TikzDocument*)document; +- (void) renderWithContext:(id<RenderContext>)context; +- (void) invalidateRect:(NSRect)rect; +- (void) invalidateGraph; +- (void) invalidateNode:(Node*)node; +- (void) invalidateEdge:(Edge*)edge; +- (void) invalidateNodesHitBy:(NSPoint)point; +- (BOOL) point:(NSPoint)p hitsNode:(Node*)node; +- (BOOL) point:(NSPoint)p hitsEdge:(Edge*)edge withFuzz:(float)fuzz; +/** + * Finds a node at the given screen location. + * + * If there is more than one node at this point (because they overlap), + * an arbitrary one is returned. + */ +- (Node*) anyNodeAt:(NSPoint)p; +/** + * Finds an edge at the given screen location. + * + * If there is more than one edge at this point (because they overlap), + * an arbitrary one is returned. + * + * @param fuzz the fuzz for detecting edges: this will pick up + * edges that are close to the point + */ +- (Edge*) anyEdgeAt:(NSPoint)p withFuzz:(float)fuzz; + +- (id<Surface>) surface; +- (Transformer*) transformer; +- (Grid*) grid; +- (PickSupport*) pickSupport; + +- (Graph*) graph; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)document; + +- (BOOL) isNodeHighlighted:(Node*)node; +- (void) setNode:(Node*)node highlighted:(BOOL)h; +- (void) clearHighlightedNodes; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphRenderer.m b/tikzit-1/src/gtk/GraphRenderer.m new file mode 100644 index 0000000..b413d3e --- /dev/null +++ b/tikzit-1/src/gtk/GraphRenderer.m @@ -0,0 +1,476 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "GraphRenderer.h" +#import "Edge+Render.h" +#import "Node+Render.h" +#import "Shape.h" + +void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); + +@interface GraphRenderer (Private) +- (enum NodeState) nodeState:(Node*)node; +- (void) renderBoundingBoxWithContext:(id<RenderContext>)context; +- (void) nodeNeedsRefreshing:(NSNotification*)notification; +- (void) edgeNeedsRefreshing:(NSNotification*)notification; +- (void) graphNeedsRefreshing:(NSNotification*)notification; +- (void) graphChanged:(NSNotification*)notification; +- (void) nodeStylePropertyChanged:(NSNotification*)notification; +- (void) edgeStylePropertyChanged:(NSNotification*)notification; +- (void) shapeDictionaryReplaced:(NSNotification*)notification; +@end + +@implementation GraphRenderer + +- (id) initWithSurface:(NSObject <Surface> *)s { + self = [super init]; + + if (self) { + surface = [s retain]; + grid = [[Grid alloc] initWithSpacing:1.0f subdivisions:4 transformer:[s transformer]]; + highlightedNodes = [[NSMutableSet alloc] initWithCapacity:10]; + [surface setRenderDelegate:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:nil]; + } + + return self; +} + +- (id) initWithSurface:(NSObject <Surface> *)s document:(TikzDocument*)document { + self = [self initWithSurface:s]; + + if (self) { + [self setDocument:document]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [doc release]; + [grid release]; + [highlightedNodes release]; + [surface release]; + + [super dealloc]; +} + +- (id<RenderDelegate>) postRenderer { + return postRenderer; +} +- (void) setPostRenderer:(id<RenderDelegate>)r { + if (r == postRenderer) + return; + + [r retain]; + [postRenderer release]; + postRenderer = r; + + [self invalidateGraph]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)s { + [self renderWithContext:context]; + if ([s hasFocus]) { + [s renderFocus]; + } +} + +- (void) renderWithContext:(id<RenderContext>)context { + // blank surface + [context paintWithColor:WhiteRColor]; + + // draw grid + [grid renderGridInContext:context]; + + // draw edges + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + [edge renderToSurface:surface withContext:context selected:[doc isEdgeSelected:edge]]; + } + + // draw nodes + enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + [node renderToSurface:surface withContext:context state:[self nodeState:node]]; + } + + [self renderBoundingBoxWithContext: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]; + } +} + +- (void) invalidateEdges:(NSSet*)edges { + for (Edge *edge in edges) { + [self invalidateEdge:edge]; + } +} + +- (void) invalidateNode:(Node*)node { + if (node == nil) { + return; + } + NSRect nodeRect = [node renderBoundsWithLabelForSurface:surface]; + nodeRect = NSInsetRect (nodeRect, -2.0f, -2.0f); + [surface invalidateRect:nodeRect]; +} + +- (void) invalidateEdge:(Edge*)edge { + if (edge == nil) { + return; + } + BOOL selected = [doc isEdgeSelected:edge]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:selected]; + edgeRect = NSInsetRect (edgeRect, -2.0f, -2.0f); + [surface invalidateRect:edgeRect]; +} + +- (void) invalidateNodesHitBy:(NSPoint)point { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:point hitsNode:node]) { + [self invalidateNode:node]; + } + } +} + +- (BOOL) point:(NSPoint)p hitsNode:(Node*)node { + return [node hitByPoint:p onSurface:surface]; +} + +- (BOOL) point:(NSPoint)p fuzzyHitsNode:(Node*)node { + NSRect bounds = [node renderBoundsForSurface:surface]; + return NSPointInRect(p, bounds); +} + +- (BOOL) point:(NSPoint)p hitsEdge:(Edge*)edge withFuzz:(float)fuzz { + return [edge hitByPoint:p onSurface:surface withFuzz:fuzz]; +} + +- (Node*) anyNodeAt:(NSPoint)p { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:p hitsNode:node]) { + return node; + } + } + return nil; +} + +- (Edge*) anyEdgeAt:(NSPoint)p withFuzz:(float)fuzz { + // FIXME: is there an efficient way to find the "nearest" edge + // if the fuzz is the reason we hit more than one? + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + if ([self point:p hitsEdge:edge withFuzz:fuzz]) { + return edge; + } + } + return nil; +} + +- (id<Surface>) surface { + return surface; +} + +- (Transformer*) transformer { + return [surface transformer]; +} + +- (Grid*) grid { + return grid; +} + +- (PickSupport*) pickSupport { + return [doc pickSupport]; +} + +- (Graph*) graph { + return [doc graph]; +} + +- (TikzDocument*) document { + return doc; +} + +- (void) setDocument:(TikzDocument*)document { + if (doc == document) { + return; + } + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:doc]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[doc pickSupport]]; + } + + [document retain]; + [doc release]; + doc = document; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"GraphReplaced" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphBeingChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChangeCancelled" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"NodeSelectionReplaced" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"EdgeSelectionReplaced" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + [surface invalidate]; +} + +- (BOOL) isNodeHighlighted:(Node*)node { + return [highlightedNodes containsObject:node]; +} +- (void) setNode:(Node*)node highlighted:(BOOL)h { + if (h) { + if (![highlightedNodes containsObject:node]) { + [highlightedNodes addObject:node]; + [self invalidateNode:node]; + } + } else { + if ([highlightedNodes containsObject:node]) { + [highlightedNodes removeObject:node]; + [self invalidateNode:node]; + } + } +} +- (void) clearHighlightedNodes { + [self invalidateNodes:highlightedNodes]; + [highlightedNodes removeAllObjects]; +} + +@end + +@implementation GraphRenderer (Private) +- (enum NodeState) nodeState:(Node*)node { + if ([doc isNodeSelected:node]) { + return NodeSelected; + } else if ([self isNodeHighlighted:node]) { + return NodeHighlighted; + } else { + return NodeNormal; + } +} + +- (void) renderBoundingBoxWithContext:(id<RenderContext>)context { + if ([[self graph] hasBoundingBox]) { + [context saveState]; + + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:bbox]; + [context strokePathWithColor:MakeSolidRColor (1.0, 0.7, 0.5)]; + + [context restoreState]; + } +} + +- (void) nodeNeedsRefreshing:(NSNotification*)notification { + [self invalidateNode:[[notification userInfo] objectForKey:@"node"]]; +} + +- (void) edgeNeedsRefreshing:(NSNotification*)notification { + Edge *edge = [[notification userInfo] objectForKey:@"edge"]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:YES]; + edgeRect = NSInsetRect (edgeRect, -2, -2); + [surface invalidateRect:edgeRect]; +} + +- (void) graphNeedsRefreshing:(NSNotification*)notification { + [self invalidateGraph]; +} + +- (void) invalidateBentIncidentEdgesForNode:(Node*)nd { + for (Edge *e in [[self graph] inEdgesForNode:nd]) { + if (![e isStraight]) { + [self invalidateEdge:e]; + } + } + for (Edge *e in [[self graph] outEdgesForNode:nd]) { + if (![e isStraight]) { + [self invalidateEdge:e]; + } + } +} + +- (void) graphChanged:(NSNotification*)notification { + GraphChange *change = [[notification userInfo] objectForKey:@"change"]; + switch ([change changeType]) { + case GraphAddition: + case GraphDeletion: + [self invalidateNodes:[change affectedNodes]]; + [self invalidateEdges:[change affectedEdges]]; + break; + case NodePropertyChange: + if (!NSEqualPoints ([[change oldNode] point], [[change nwNode] point])) { + // if the node has moved, it may be affecting edges + [surface invalidate]; + } else if ([[change oldNode] style] != [[change nwNode] style]) { + // change in style means that edges may touch at a different point, + // but this only matters for bent edges + [self invalidateBentIncidentEdgesForNode:[change nodeRef]]; + // invalide both old and new (old node may be larger) + [self invalidateNode:[change oldNode]]; + [self invalidateNode:[change nwNode]]; + } else { + // invalide both old and new (old node may be larger) + [self invalidateNode:[change oldNode]]; + [self invalidateNode:[change nwNode]]; + } + break; + case EdgePropertyChange: + // invalide both old and new (old bend may increase bounds) + [self invalidateEdge:[change oldEdge]]; + [self invalidateEdge:[change nwEdge]]; + [self invalidateEdge:[change edgeRef]]; + break; + case NodesPropertyChange: + { + NSEnumerator *enumerator = [[change oldNodeTable] keyEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + NSPoint oldPos = [[[change oldNodeTable] objectForKey:node] point]; + NSPoint newPos = [[[change nwNodeTable] objectForKey:node] point]; + NodeStyle *oldStyle = [[[change oldNodeTable] objectForKey:node] style]; + NodeStyle *newStyle = [[[change nwNodeTable] objectForKey:node] style]; + if (!NSEqualPoints (oldPos, newPos)) { + [surface invalidate]; + break; + } else if (oldStyle != newStyle) { + [self invalidateBentIncidentEdgesForNode:node]; + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } else { + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } + } + } + break; + case NodesShift: + case NodesFlip: + case BoundingBoxChange: + [surface invalidate]; + break; + default: + // unknown change + [surface invalidate]; + break; + }; +} + +- (void) nodeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Node *node in [[self graph] nodes]) { + if ([node style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +- (void) edgeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Edge *edge in [[self graph] edges]) { + if ([edge style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +- (void) shapeDictionaryReplaced:(NSNotification*)notification { + [surface invalidate]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/HandTool.h b/tikzit-1/src/gtk/HandTool.h new file mode 100644 index 0000000..c96de36 --- /dev/null +++ b/tikzit-1/src/gtk/HandTool.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@interface HandTool : NSObject <Tool> { + GraphRenderer *renderer; + NSPoint dragOrigin; + NSPoint oldGraphOrigin; +} + + ++ (id) tool; +- (id) init; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/HandTool.m b/tikzit-1/src/gtk/HandTool.m new file mode 100644 index 0000000..c3a0fb4 --- /dev/null +++ b/tikzit-1/src/gtk/HandTool.m @@ -0,0 +1,75 @@ +/* + * Copyright 2011-2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "HandTool.h" + +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +@implementation HandTool +- (NSString*) name { return @"Drag"; } +- (const gchar*) stockId { return TIKZIT_STOCK_DRAG; } +- (NSString*) helpText { return @"Move the diagram to view different parts"; } +- (NSString*) shortcut { return @"m"; } +@synthesize activeRenderer=renderer; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + return [super init]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + + [super dealloc]; +} + +- (GtkWidget*) configurationWidget { return NULL; } + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + oldGraphOrigin = [[renderer transformer] origin]; +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + + NSPoint newGraphOrigin = oldGraphOrigin; + newGraphOrigin.x += pos.x - dragOrigin.x; + newGraphOrigin.y += pos.y - dragOrigin.y; + [[renderer transformer] setOrigin:newGraphOrigin]; + [renderer invalidateGraph]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {} +- (void) loadConfiguration:(Configuration*)config {} +- (void) saveConfiguration:(Configuration*)config {} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/InputDelegate.h b/tikzit-1/src/gtk/InputDelegate.h new file mode 100644 index 0000000..9f9b426 --- /dev/null +++ b/tikzit-1/src/gtk/InputDelegate.h @@ -0,0 +1,77 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" + +typedef enum { + LeftButton = 1, + MiddleButton = 2, + RightButton = 3, + Button4 = 4, + Button5 = 5 +} MouseButton; + +typedef enum { + ShiftMask = 1, + ControlMask = 2, + MetaMask = 4 +} InputMask; + +typedef enum { + ScrollUp = 1, + ScrollDown = 2, + ScrollLeft = 3, + ScrollRight = 4, +} ScrollDirection; + +@protocol InputDelegate <NSObject> +@optional +/** + * A mouse button was pressed. + */ +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * A mouse button was released. + */ +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * A mouse button was double-clicked. + * + * Note that mouseDown and mouseUp events will still be delivered. + * This will be triggered between the second mouseDown and the second + * mouseUp. + */ +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * The mouse was moved + */ +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)button andMask:(InputMask)mask; +/** + * The mouse was scrolled + */ +- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask; +/** + * A key was pressed + */ +- (void) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask; +/** + * A key was released + */ +- (void) keyReleased:(unsigned int)keyVal withMask:(InputMask)mask; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Menu.h b/tikzit-1/src/gtk/Menu.h new file mode 100644 index 0000000..e0f78d4 --- /dev/null +++ b/tikzit-1/src/gtk/Menu.h @@ -0,0 +1,86 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Window; +@class PickSupport; + +/** + * Manages the menu + */ +@interface Menu: NSObject { + GtkWidget *menubar; + GtkActionGroup *appActions; + GtkActionGroup *windowActions; + GtkAction *undoAction; // no ref + GtkAction *redoAction; // no ref + GtkAction *pasteAction; // no ref + GtkAction **nodeSelBasedActions; + guint nodeSelBasedActionCount; + GtkAction **edgeSelBasedActions; + guint edgeSelBasedActionCount; + GtkAction **selBasedActions; + guint selBasedActionCount; +} + +/** + * The menubar widget, to be inserted into the window + */ +@property (readonly) GtkWidget *menubar; + +/** + * Constructs the menu for @p window + * + * @param window the window that will be acted upon + */ +- (id) initForWindow:(Window*)window; + +/** + * Enables or disables the undo action + */ +- (void) setUndoActionEnabled:(BOOL)enabled; +/** + * Sets the text that describes what action will be undone + * + * @param detail a text description of the action, or nil + */ +- (void) setUndoActionDetail:(NSString*)detail; +/** + * Enables or disables the redo action + */ +- (void) setRedoActionEnabled:(BOOL)enabled; +/** + * Sets the text that describes what action will be redone + * + * @param detail a text description of the action, or nil + */ +- (void) setRedoActionDetail:(NSString*)detail; + +/** + * Gets the paste action + */ +- (GtkAction*) pasteAction; + +/** + * Enables or disables the actions that act on a selection + */ +- (void) notifySelectionChanged:(PickSupport*)pickSupport; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Menu.m b/tikzit-1/src/gtk/Menu.m new file mode 100644 index 0000000..04c9c31 --- /dev/null +++ b/tikzit-1/src/gtk/Menu.m @@ -0,0 +1,737 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * Stuff stolen from glade-window.c in Glade: + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2007 Vincent Geddes. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Menu.h" + +#import "Application.h" +#import "Window.h" +#import "Configuration.h" +#import "PickSupport.h" +#import "Shape.h" +#import "Tool.h" +#import "TikzDocument.h" + +#import <glib.h> +#ifdef _ +#undef _ +#endif +#import <glib/gi18n.h> +#import <gtk/gtk.h> + +#import "gtkhelpers.h" + +#import "logo.h" + +// {{{ Application actions +static void new_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl newWindow]; + [pool drain]; +} + +static void refresh_shapes_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [Shape refreshShapeDictionary]; + [pool drain]; +} + +static void show_preferences_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentSettingsDialog]; + [pool drain]; +} + +#ifdef HAVE_POPPLER +static void show_preamble_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentPreamblesEditor]; + [pool drain]; +} +#endif + +static void show_context_window_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentContextWindow]; + [pool drain]; +} + +static void quit_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl quit]; + [pool drain]; +} + +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) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + logGError (gerror, @"Could not show help"); + [pool drain]; + } +} + +static void about_cb (GtkAction *action, Application *appl) { + static const gchar * const authors[] = + { "Aleks Kissinger <aleks0@gmail.com>", + "Chris Heunen <chrisheunen@gmail.com>", + "Alex Merry <dev@randomguy3.me.uk>", + NULL }; + + static const gchar license[] = + N_("TikZiT 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." + "\n\n" + "TikZiT 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." + "\n\n" + "You should have received a copy of the GNU General Public License " + "along with TikZiT; if not, write to the Free Software " + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, " + "MA 02110-1301, USA."); + + static const gchar copyright[] = + "Copyright \xc2\xa9 2010-2011 Aleks Kissinger, Chris Heunen and Alex Merry."; + + GdkPixbuf *logo = get_logo (LOGO_SIZE_128); + gtk_show_about_dialog (NULL, + "program-name", PACKAGE_NAME, + "logo", logo, + "authors", authors, + "translator-credits", _("translator-credits"), + "comments", _("A graph manipulation program for pgf/tikz graphs"), + "license", _(license), + "wrap-license", TRUE, + "copyright", copyright, + "version", PACKAGE_VERSION, + "website", "http://tikzit.sourceforge.net", + NULL); + 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, "<control>N", + N_("Create a new graph"), G_CALLBACK (new_cb) }, + + { "RefreshShapes", NULL, N_("_Refresh shapes"), NULL, + N_(""), G_CALLBACK (refresh_shapes_cb) }, + + { "Quit", GTK_STOCK_QUIT, NULL, "<control>Q", + N_("Quit the program"), G_CALLBACK (quit_cb) }, + + { "Tool", NULL, N_("_Tool") }, + + { "ShowPreferences", GTK_STOCK_PREFERENCES, N_("Configure TikZiT..."), NULL, + N_("Edit the TikZiT configuration"), G_CALLBACK (show_preferences_cb) }, + +#ifdef HAVE_POPPLER + { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL, + N_("Edit the preambles used to generate the preview"), G_CALLBACK (show_preamble_cb) }, +#endif + + { "ShowContextWindow", NULL, N_("_Context Window"), NULL, + N_("Show the contextual tools window"), G_CALLBACK (show_context_window_cb) }, + + /* HelpMenu */ + { "HelpManual", GTK_STOCK_HELP, N_("_Online manual"), "F1", + N_("TikZiT manual (online)"), G_CALLBACK (help_cb) }, + + { "About", GTK_STOCK_ABOUT, NULL, NULL, + N_("About this application"), G_CALLBACK (about_cb) }, +}; +static guint n_app_action_entries = G_N_ELEMENTS (app_action_entries); +// }}} +// {{{ Window actions + +static void open_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window openFile]; + [pool drain]; +} + +static void close_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window close]; + [pool drain]; +} + +static void save_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocument]; + [pool drain]; +} + +static void save_as_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAs]; + [pool drain]; +} + +static void save_as_shape_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAsShape]; + [pool drain]; +} + +static void undo_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window document]; + if ([document canUndo]) { + [document undo]; + } else { + g_warning ("Can't undo!\n"); + gtk_action_set_sensitive (action, FALSE); + } + + [pool drain]; +} + +static void redo_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window document]; + if ([document canRedo]) { + [document redo]; + } else { + g_warning ("Can't redo!\n"); + gtk_action_set_sensitive (action, FALSE); + } + + [pool drain]; +} + +static void cut_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window selectionCutToClipboard]; + [pool drain]; +} + +static void copy_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window selectionCopyToClipboard]; + [pool drain]; +} + +static void paste_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window pasteFromClipboard]; + [pool drain]; +} + +static void delete_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] removeSelected]; + [pool drain]; +} + +static void select_all_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window document]; + [[document pickSupport] selectAllNodes:[NSSet setWithArray:[[document graph] nodes]]]; + [pool drain]; +} + +static void deselect_all_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window document]; + [[document pickSupport] deselectAllNodes]; + [[document pickSupport] deselectAllEdges]; + [pool drain]; +} + +static void flip_horiz_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] flipSelectedNodesHorizontally]; + [pool drain]; +} + +static void flip_vert_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] flipSelectedNodesVertically]; + [pool drain]; +} + +static void reverse_edges_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] reverseSelectedEdges]; + [pool drain]; +} + +static void bring_forward_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] bringSelectionForward]; + [pool drain]; +} + +static void send_backward_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] sendSelectionBackward]; + [pool drain]; +} + +static void bring_to_front_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] bringSelectionToFront]; + [pool drain]; +} + +static void send_to_back_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] sendSelectionToBack]; + [pool drain]; +} + +#ifdef HAVE_POPPLER +static void show_preview_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window presentPreview]; + [pool drain]; +} +#endif + +static void zoom_in_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomIn]; + [pool drain]; +} + +static void zoom_out_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomOut]; + [pool drain]; +} + +static void zoom_reset_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomReset]; + [pool drain]; +} + +static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gchar *uri, *path; + GError *error = NULL; + + uri = gtk_recent_chooser_get_current_uri (chooser); + + path = g_filename_from_uri (uri, NULL, NULL); + if (error) { + g_warning ("Could not convert uri \"%s\" to a local path: %s", uri, error->message); + g_error_free (error); + return; + } + + [window openFileAtPath:[NSString stringWithGlibFilename:path]]; + + g_free (uri); + g_free (path); + + [pool drain]; +} + + +static GtkActionEntry window_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 + */ + { "FileMenu", NULL, N_("_File") }, + { "EditMenu", NULL, N_("_Edit") }, + { "ViewMenu", NULL, N_("_View") }, + { "HelpMenu", NULL, N_("_Help") }, + + { "Arrange", NULL, N_("_Arrange") }, + { "Zoom", NULL, N_("_Zoom") }, + + { "Open", GTK_STOCK_OPEN, N_("_Open\342\200\246") ,"<control>O", + N_("Open a graph"), G_CALLBACK (open_cb) }, + + { "Close", GTK_STOCK_CLOSE, NULL, "<control>W", + N_("Close the current graph"), G_CALLBACK (close_cb) }, + + { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, "<control>plus", + NULL, G_CALLBACK (zoom_in_cb) }, + + { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, "<control>minus", + NULL, G_CALLBACK (zoom_out_cb) }, + + { "ZoomReset", GTK_STOCK_ZOOM_100, N_("_Reset zoom"), "<control>0", + NULL, G_CALLBACK (zoom_reset_cb) }, + + { "Save", GTK_STOCK_SAVE, NULL, "<control>S", + N_("Save the current graph"), G_CALLBACK (save_cb) }, + + { "SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As\342\200\246"), NULL, + N_("Save the current graph with a different name"), G_CALLBACK (save_as_cb) }, + + { "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) }, + + { "Undo", GTK_STOCK_UNDO, NULL, "<control>Z", + N_("Undo the last action"), G_CALLBACK (undo_cb) }, + + { "Redo", GTK_STOCK_REDO, NULL, "<shift><control>Z", + N_("Redo the last action"), G_CALLBACK (redo_cb) }, + + { "Cut", GTK_STOCK_CUT, NULL, NULL, + N_("Cut the selection"), G_CALLBACK (cut_cb) }, + + { "Copy", GTK_STOCK_COPY, NULL, NULL, + N_("Copy the selection"), G_CALLBACK (copy_cb) }, + + { "Paste", GTK_STOCK_PASTE, NULL, NULL, + N_("Paste the clipboard"), G_CALLBACK (paste_cb) }, + + { "Delete", GTK_STOCK_DELETE, NULL, "Delete", + N_("Delete the selection"), G_CALLBACK (delete_cb) }, + + { "SelectAll", GTK_STOCK_SELECT_ALL, NULL, "<control>A", + N_("Select all nodes on the graph"), G_CALLBACK (select_all_cb) }, + + { "DeselectAll", NULL, N_("D_eselect all"), "<shift><control>A", + N_("Deselect everything"), G_CALLBACK (deselect_all_cb) }, + + { "FlipHoriz", NULL, N_("Flip nodes _horizonally"), NULL, + N_("Flip the selected nodes horizontally"), G_CALLBACK (flip_horiz_cb) }, + + { "FlipVert", NULL, N_("Flip nodes _vertically"), NULL, + N_("Flip the selected nodes vertically"), G_CALLBACK (flip_vert_cb) }, + + { "ReverseEdges", NULL, N_("Rever_se edges"), NULL, + N_("Reverse the selected edges"), G_CALLBACK (reverse_edges_cb) }, + + { "SendToBack", NULL, N_("Send to _back"), NULL, + N_("Send the selected nodes and edges to the back of the graph"), G_CALLBACK (send_to_back_cb) }, + + { "SendBackward", NULL, N_("Send b_ackward"), NULL, + N_("Send the selected nodes and edges backward"), G_CALLBACK (send_backward_cb) }, + + { "BringForward", NULL, N_("Bring f_orward"), NULL, + N_("Bring the selected nodes and edges forward"), G_CALLBACK (bring_forward_cb) }, + + { "BringToFront", NULL, N_("Bring to _front"), NULL, + N_("Bring the selected nodes and edges to the front of the graph"), G_CALLBACK (bring_to_front_cb) }, + + /* ViewMenu */ +#ifdef HAVE_POPPLER + { "ShowPreview", NULL, N_("_Preview"), "<control>L", + N_("See the graph as it will look when rendered in LaTeX"), G_CALLBACK (show_preview_cb) }, +#endif +}; +static guint n_window_action_entries = G_N_ELEMENTS (window_action_entries); + +// }}} +// {{{ UI XML + +static const gchar ui_info[] = +"<ui>" +" <menubar name='MenuBar'>" +" <menu action='FileMenu'>" +" <menuitem action='New'/>" +" <menuitem action='Open'/>" +" <menuitem action='OpenRecent'/>" +" <separator/>" +" <menuitem action='Save'/>" +" <menuitem action='SaveAs'/>" +" <separator/>" +" <menuitem action='SaveAsShape'/>" +" <menuitem action='RefreshShapes'/>" +" <separator/>" +" <menuitem action='Close'/>" +" <menuitem action='Quit'/>" +" </menu>" +" <menu action='EditMenu'>" +" <menu action='Tool'>" +" </menu>" +" <separator/>" +" <menuitem action='Undo'/>" +" <menuitem action='Redo'/>" +" <separator/>" +" <menuitem action='Cut'/>" +" <menuitem action='Copy'/>" +" <menuitem action='Paste'/>" +" <menuitem action='Delete'/>" +" <separator/>" +" <menuitem action='SelectAll'/>" +" <menuitem action='DeselectAll'/>" +" <separator/>" +" <menuitem action='FlipVert'/>" +" <menuitem action='FlipHoriz'/>" +" <menuitem action='ReverseEdges'/>" +" <separator/>" +" <menu action='Arrange'>" +" <menuitem action='SendToBack'/>" +" <menuitem action='SendBackward'/>" +" <menuitem action='BringForward'/>" +" <menuitem action='BringToFront'/>" +" </menu>" +" <separator/>" +" <menuitem action='ShowPreferences'/>" +" </menu>" +" <menu action='ViewMenu'>" +" <menuitem action='ShowContextWindow'/>" +#ifdef HAVE_POPPLER +" <menuitem action='ShowPreamble'/>" +" <menuitem action='ShowPreview'/>" +#endif +" <menu action='Zoom'>" +" <menuitem action='ZoomIn'/>" +" <menuitem action='ZoomOut'/>" +" <menuitem action='ZoomReset'/>" +" </menu>" +" </menu>" +" <menu action='HelpMenu'>" +" <menuitem action='HelpManual'/>" +" <separator/>" +" <menuitem action='About'/>" +" </menu>" +" </menubar>" +/* +" <toolbar name='ToolBar'>" +" <toolitem action='New'/>" +" <toolitem action='Open'/>" +" <toolitem action='Save'/>" +" <separator/>" +" <toolitem action='Cut'/>" +" <toolitem action='Copy'/>" +" <toolitem action='Paste'/>" +" <separator/>" +" <toolitem action='SelectMode'/>" +" <toolitem action='CreateNodeMode'/>" +" <toolitem action='DrawEdgeMode'/>" +" <toolitem action='BoundingBoxMode'/>" +" <toolitem action='HandMode'/>" +" </toolbar>" +*/ +"</ui>"; + + + +// }}} +// {{{ Helper methods + +static void configure_recent_chooser (GtkRecentChooser *chooser) +{ + 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); + + GtkRecentFilter *filter = gtk_recent_filter_new (); + gtk_recent_filter_add_application (filter, g_get_application_name()); + gtk_recent_chooser_set_filter (chooser, filter); +} + +static void tool_cb (GtkAction *action, id<Tool> tool) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [app setActiveTool:tool]; + [pool drain]; +} + + + +// }}} +// {{{ API + +@implementation Menu + +- (id) init { + [self release]; + return nil; +} + +- (id) initForWindow:(Window*)window { + self = [super init]; + if (!self) { + return nil; + } + + GError *error = 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> tool in [app tools]) { + NSString *tooltip = [NSString stringWithFormat: + @"%@: %@ (%@)", [tool name], [tool helpText], [tool shortcut]]; + GtkAction *action = gtk_action_new ( + [[tool name] UTF8String], + [[tool name] UTF8String], + [tooltip UTF8String], + [tool stockId]); + gtk_action_group_add_action_with_accel ( + appActions, + action, + NULL); + g_signal_connect ( + G_OBJECT (action), + "activate", + G_CALLBACK (tool_cb), + tool); + g_object_unref (action); + } + + windowActions = gtk_action_group_new ("TZWindow"); + //gtk_action_group_set_translation_domain (windowActions, GETTEXT_PACKAGE); + + gtk_action_group_add_actions (windowActions, + window_action_entries, + n_window_action_entries, + window); + + 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); + + /* 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 (windowActions, "Cut"); + nodeSelBasedActions[1] = gtk_action_group_get_action (windowActions, "Copy"); + nodeSelBasedActions[2] = gtk_action_group_get_action (windowActions, "FlipHoriz"); + nodeSelBasedActions[3] = gtk_action_group_get_action (windowActions, "FlipVert"); + edgeSelBasedActionCount = 1; + edgeSelBasedActions = g_new (GtkAction*, edgeSelBasedActionCount); + edgeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "ReverseEdges"); + selBasedActionCount = 2; + selBasedActions = g_new (GtkAction*, selBasedActionCount); + selBasedActions[0] = gtk_action_group_get_action (windowActions, "Delete"); + selBasedActions[1] = gtk_action_group_get_action (windowActions, "DeselectAll"); + + + 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> 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; +} + +- (void) dealloc { + g_free (nodeSelBasedActions); + g_free (edgeSelBasedActions); + g_free (selBasedActions); + g_object_unref (menubar); + g_object_unref (appActions); + g_object_unref (windowActions); + + [super dealloc]; +} + +@synthesize menubar; + +- (void) setUndoActionEnabled:(BOOL)enabled { + gtk_action_set_sensitive (undoAction, enabled); +} + +- (void) setUndoActionDetail:(NSString*)detail { + gtk_action_set_detailed_label (undoAction, "_Undo", [detail UTF8String]); +} + +- (void) setRedoActionEnabled:(BOOL)enabled { + gtk_action_set_sensitive (redoAction, enabled); +} + +- (void) setRedoActionDetail:(NSString*)detail { + gtk_action_set_detailed_label (redoAction, "_Redo", [detail UTF8String]); +} + +- (GtkAction*) pasteAction { + return pasteAction; +} + +- (void) notifySelectionChanged:(PickSupport*)pickSupport { + BOOL hasSelectedNodes = [[pickSupport selectedNodes] count] > 0; + BOOL hasSelectedEdges = [[pickSupport selectedEdges] count] > 0; + for (int i = 0; i < nodeSelBasedActionCount; ++i) { + if (nodeSelBasedActions[i]) { + gtk_action_set_sensitive (nodeSelBasedActions[i], hasSelectedNodes); + } + } + for (int i = 0; i < edgeSelBasedActionCount; ++i) { + if (edgeSelBasedActions[i]) { + gtk_action_set_sensitive (edgeSelBasedActions[i], hasSelectedEdges); + } + } + for (int i = 0; i < selBasedActionCount; ++i) { + if (selBasedActions[i]) { + gtk_action_set_sensitive (selBasedActions[i], hasSelectedNodes || hasSelectedEdges); + } + } +} + +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NSError+Glib.h b/tikzit-1/src/gtk/NSError+Glib.h new file mode 100644 index 0000000..137977e --- /dev/null +++ b/tikzit-1/src/gtk/NSError+Glib.h @@ -0,0 +1,28 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import <glib.h> + +@interface NSError(Glib) ++ (id) errorWithGError:(GError*)gerror; +@end + +void GErrorToNSError(GError *errorIn, NSError **errorOut); +void logGError (GError *error, NSString *message); + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NSError+Glib.m b/tikzit-1/src/gtk/NSError+Glib.m new file mode 100644 index 0000000..f466d9e --- /dev/null +++ b/tikzit-1/src/gtk/NSError+Glib.m @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSError+Glib.h" +#import "TZFoundation.h" + +@implementation NSError(Glib) ++ (id) errorWithGError:(GError*)gerror { + if (!gerror) + return nil; + + NSString *message = [NSString stringWithUTF8String:gerror->message]; + NSString *domain = [NSString stringWithUTF8String:g_quark_to_string(gerror->domain)]; + + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:domain code:gerror->code userInfo:errorDetail]; +} +@end + +void GErrorToNSError(GError *errorIn, NSError **errorOut) +{ + if (errorOut && errorIn) { + *errorOut = [NSError errorWithGError:errorIn]; + } +} + +void logGError (GError *error, NSString *message) { + if (message == nil) { + NSLog (@"%s", error->message); + } else { + NSLog (@"%@: %s", message, error->message); + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NSFileManager+Glib.h b/tikzit-1/src/gtk/NSFileManager+Glib.h new file mode 100644 index 0000000..cb49fcb --- /dev/null +++ b/tikzit-1/src/gtk/NSFileManager+Glib.h @@ -0,0 +1,31 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +@interface NSFileManager(Glib) +/** + * Creates a directory in the system temp directory + */ +- (NSString*) createTempDirectoryWithError:(NSError**)error; +/** + * Creates a directory in the system temp directory + */ +- (NSString*) createTempDirectory; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NSFileManager+Glib.m b/tikzit-1/src/gtk/NSFileManager+Glib.m new file mode 100644 index 0000000..b3e9de6 --- /dev/null +++ b/tikzit-1/src/gtk/NSFileManager+Glib.m @@ -0,0 +1,55 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSFileManager+Glib.h" +#import "TZFoundation.h" +#import "mkdtemp.h" + +@implementation NSFileManager(Glib) + +- (NSString*) createTempDirectoryWithError:(NSError**)error { + NSString *result = nil; +#if GLIB_CHECK_VERSION (2, 30, 0) + GError *gerror = NULL; + gchar *dir = g_dir_make_tmp ("tikzitXXXXXX", &gerror); + GErrorToNSError (gerror, error); + if (dir) + result = [NSString stringWithGlibFilename:dir]; + g_free (dir); +#else +//#if (!GLIB_CHECK_VERSION (2, 26, 0)) +#define g_mkdtemp mkdtemp +//#endif + gchar *dir = g_build_filename (g_get_tmp_dir(), "tikzitXXXXXX", NULL); + gchar *rdir = g_mkdtemp (dir); + if (rdir) { + result = [NSString stringWithGlibFilename:dir]; + } else if (error) { + *error = [NSError errorWithLibcError:errno]; + } + g_free (dir); +#endif + return result; +} + +- (NSString*) createTempDirectory { + return [self createTempDirectoryWithError:NULL]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NSString+Glib.h b/tikzit-1/src/gtk/NSString+Glib.h new file mode 100644 index 0000000..ac59833 --- /dev/null +++ b/tikzit-1/src/gtk/NSString+Glib.h @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import <glib.h> + +@interface NSString(Glib) +/** + * Initialise a string with a string in the GLib filename encoding + */ +- (id) initWithGlibFilename:(const gchar *)filename; +/** + * Create a string from a string in the GLib filename encoding + */ ++ (id) stringWithGlibFilename:(const gchar *)filename; +/** + * Get a copy of the string in GLib filename encoding. + * + * This will need to be freed with g_free. + */ +- (gchar*)glibFilename; +/** + * Get a copy of the string as a GLib URI + * + * This will need to be freed with g_free. + */ +- (gchar*)glibUriWithError:(NSError**)error; +/** + * Get a copy of the string as a GLib URI + * + * This will need to be freed with g_free. + */ +- (gchar*)glibUri; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NSString+Glib.m b/tikzit-1/src/gtk/NSString+Glib.m new file mode 100644 index 0000000..b6dc765 --- /dev/null +++ b/tikzit-1/src/gtk/NSString+Glib.m @@ -0,0 +1,96 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSString+Glib.h" +#import "TZFoundation.h" + +@implementation NSString(Glib) ++ (id) stringWithGlibFilename:(const gchar *)filename { + return [[[self alloc] initWithGlibFilename:filename] autorelease]; +} + +- (id) initWithGlibFilename:(const gchar *)filename { + if (self == nil) { + return nil; + } + + if (filename == NULL) { + [self release]; + return nil; + } + + GError *error = NULL; + gchar *utf8file = g_filename_to_utf8 (filename, -1, NULL, NULL, &error); + if (utf8file == NULL) { + if (error) + logGError (error, @"Failed to convert a GLib filename to UTF8"); + [self release]; + return nil; + } + + self = [self initWithUTF8String:utf8file]; + g_free (utf8file); + + return self; +} + +- (gchar*)glibFilenameWithError:(NSError**)error { + GError *gerror = NULL; + gchar *result = g_filename_from_utf8 ([self UTF8String], -1, NULL, NULL, &gerror); + GErrorToNSError (gerror, error); + if (gerror) { + logGError (gerror, @"Failed to convert a UTF8 string to a GLib filename"); + } + return result; +} + +- (gchar*)glibFilename { + return [self glibFilenameWithError:NULL]; +} + +- (gchar*)glibUriWithError:(NSError**)error { + gchar *filepath; + gchar *uri; + NSError *cause = nil; + + filepath = [self glibFilenameWithError:&cause]; + if (!filepath) { + if (error) { + NSString *message = [NSString stringWithFormat:@"Could not convert \"%@\" to the GLib filename encoding", self]; + *error = [NSError errorWithMessage:message code:TZ_ERR_OTHER cause:cause]; + } + return NULL; + } + + GError *gerror = NULL; + GError **gerrorptr = error ? &gerror : NULL; + uri = g_filename_to_uri (filepath, NULL, gerrorptr); + if (!uri && error) { + NSString *message = [NSString stringWithFormat:@"Could not convert \"%@\" to a GLib URI", self]; + *error = [NSError errorWithMessage:message code:TZ_ERR_BADFORMAT cause:[NSError errorWithGError:gerror]]; + } + g_free (filepath); + return uri; +} + +- (gchar*)glibUri { + return [self glibUriWithError:NULL]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Node+Render.h b/tikzit-1/src/gtk/Node+Render.h new file mode 100644 index 0000000..60d2573 --- /dev/null +++ b/tikzit-1/src/gtk/Node+Render.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Node.h" +#import "RenderContext.h" +#import "Surface.h" + +enum NodeState { + NodeNormal, + NodeSelected, + NodeHighlighted +}; + +@interface Node(Render) + +- (Transformer*) shapeTransformerForSurface:(id<Surface>)surface; +// the total rendered bounds, excluding label +- (NSRect) renderBoundsForSurface:(id<Surface>)surface; +- (NSRect) renderBoundsWithLabelForSurface:(id<Surface>)surface; +- (NSString*) renderedLabel; +- (NSSize) renderedLabelSizeInContext:(id<RenderContext>)context; +- (void) renderLabelToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context; +- (void) renderLabelAt:(NSPoint)point withContext:(id<RenderContext>)context; +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context state:(enum NodeState)state; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Node+Render.m b/tikzit-1/src/gtk/Node+Render.m new file mode 100644 index 0000000..907d818 --- /dev/null +++ b/tikzit-1/src/gtk/Node+Render.m @@ -0,0 +1,188 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Node+Render.h" +#import "Shape.h" +#import "Shape+Render.h" +#import "ShapeNames.h" + +#define MAX_LABEL_LENGTH 10 +#define LABEL_PADDING_X 2 +#define LABEL_PADDING_Y 2 + +@implementation Node (Render) + +- (Shape*) shapeToRender { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return [Shape shapeForName:SHAPE_CIRCLE]; + } +} + +- (Transformer*) shapeTransformerForSurface:(id<Surface>)surface { + return [self shapeTransformerFromTransformer:[surface transformer]]; +} + +- (NSRect) renderBoundsUsingShapeTransform:(Transformer*)shapeTrans { + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + NSRect screenBounds = [shapeTrans rectToScreen:[[self shapeToRender] boundingRect]]; + screenBounds = NSInsetRect(screenBounds, -strokeThickness, -strokeThickness); + return screenBounds; +} + +- (NSRect) renderBoundsForSurface:(id<Surface>)surface { + return [self renderBoundsUsingShapeTransform:[self shapeTransformerForSurface:surface]]; +} + +- (NSRect) renderBoundsWithLabelForSurface:(id<Surface>)surface { + NSRect nodeBounds = [self renderBoundsForSurface:surface]; + NSRect labelRect = NSZeroRect; + if (![label isEqual:@""]) { + id<RenderContext> cr = [surface createRenderContext]; + labelRect.size = [self renderedLabelSizeInContext:cr]; + NSPoint nodePos = [[surface transformer] toScreen:point]; + labelRect.origin.x = nodePos.x - (labelRect.size.width / 2); + labelRect.origin.y = nodePos.y - (labelRect.size.height / 2); + } + return NSUnionRect(nodeBounds, labelRect); +} + +- (RColor) strokeColor { + if (style) { + return [[style strokeColorRGB] rColor]; + } else { + return MakeRColor (0.4, 0.4, 0.7, 0.8); + } +} + +- (RColor) fillColor { + if (style) { + return [[style fillColorRGB] rColor]; + } else { + return MakeRColor (0.4, 0.4, 0.7, 0.3); + } +} + +- (NSString*) renderedLabel { + NSString *r_label = [label stringByExpandingLatexConstants]; + if ([r_label length] > MAX_LABEL_LENGTH) { + r_label = [[[r_label substringToIndex:MAX_LABEL_LENGTH-1] stringByTrimmingSpaces] stringByAppendingString:@"..."]; + } else { + r_label = [r_label stringByTrimmingSpaces]; + } + return r_label; +} + +- (NSSize) renderedLabelSizeInContext:(id<RenderContext>)context { + NSSize result = {0, 0}; + if (![label isEqual:@""]) { + NSString *r_label = [self renderedLabel]; + + id<TextLayout> layout = [context layoutText:r_label withSize:9]; + + result = [layout size]; + result.width += LABEL_PADDING_X; + result.height += LABEL_PADDING_Y; + } + return result; +} + +- (void) renderLabelToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context { + [self renderLabelAt:[[surface transformer] toScreen:point] withContext:context]; +} + +- (void) renderLabelAt:(NSPoint)p withContext:(id<RenderContext>)context { + // draw latex code overlayed on node + if (![label isEqual:@""]) { + [context saveState]; + + NSString *r_label = [self renderedLabel]; + id<TextLayout> layout = [context layoutText:r_label withSize:9]; + + NSSize labelSize = [layout size]; + + NSRect textBounds = NSMakeRect (p.x - labelSize.width/2, + p.y - labelSize.height/2, + labelSize.width, + labelSize.height); + NSRect backRect = NSInsetRect (textBounds, -LABEL_PADDING_X, -LABEL_PADDING_Y); + + [context startPath]; + [context setLineWidth:1.0]; + [context rect:backRect]; + RColor fColor = MakeRColor (1.0, 1.0, 0.5, 0.7); + RColor sColor = MakeRColor (0.5, 0.0, 0.0, 0.7); + [context strokePathWithColor:sColor andFillWithColor:fColor]; + + [layout showTextAt:textBounds.origin withColor:BlackRColor]; + + [context restoreState]; + } +} + +- (void) renderToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context state:(enum NodeState)state { + Transformer *shapeTrans = [self shapeTransformerForSurface:surface]; + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + + [context saveState]; + + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:context]; + + [context setLineWidth:strokeThickness]; + if (!style) { + [context setLineDash:3.0]; + } + [context strokePathWithColor:[self strokeColor] andFillWithColor:[self fillColor]]; + + if (state != NodeNormal) { + [context setLineWidth:strokeThickness + 4.0]; + [context setLineDash:0.0]; + float alpha = 0.0f; + if (state == NodeSelected) + alpha = 0.5f; + else if (state == NodeHighlighted) + alpha = 0.25f; + RColor selectionColor = MakeSolidRColor(0.61f, 0.735f, 1.0f); + + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:context]; + [context strokePathWithColor:selectionColor andFillWithColor:selectionColor usingAlpha:alpha]; + } + + [context restoreState]; + [self renderLabelToSurface:surface withContext:context]; +} + +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface { + Transformer *shapeTrans = [self shapeTransformerForSurface:surface]; + + NSRect screenBounds = [self renderBoundsUsingShapeTransform:shapeTrans]; + if (!NSPointInRect(p, screenBounds)) { + return NO; + } + + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + id<RenderContext> ctx = [surface createRenderContext]; + [ctx setLineWidth:strokeThickness]; + [[self shapeToRender] drawPathWithTransform:shapeTrans andContext:ctx]; + return [ctx strokeIncludesPoint:p] || [ctx fillIncludesPoint:p]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyle+Gtk.h b/tikzit-1/src/gtk/NodeStyle+Gtk.h new file mode 100644 index 0000000..4fa5edd --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyle+Gtk.h @@ -0,0 +1,31 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "NodeStyle.h" +#import <gtk/gtk.h> + +@interface NodeStyle (Gtk) + +- (GdkColor) strokeColor; +- (void) setStrokeColor:(GdkColor)color; +- (GdkColor) fillColor; +- (void) setFillColor:(GdkColor)color; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyle+Gtk.m b/tikzit-1/src/gtk/NodeStyle+Gtk.m new file mode 100644 index 0000000..1954def --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyle+Gtk.m @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStyle+Gtk.h" +#import "ColorRGB+Gtk.h" + +@implementation NodeStyle (Gtk) + +- (GdkColor) strokeColor { + return [[self strokeColorRGB] gdkColor]; +} + +- (void) setStrokeColor:(GdkColor)color { + [self setStrokeColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +- (GdkColor) fillColor { + return [[self fillColorRGB] gdkColor]; +} + +- (void) setFillColor:(GdkColor)color { + [self setFillColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyle+Render.h b/tikzit-1/src/gtk/NodeStyle+Render.h new file mode 100644 index 0000000..00edd27 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyle+Render.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "NodeStyle.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface NodeStyle (Render) + +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context at:(NSPoint)p; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyle+Storage.h b/tikzit-1/src/gtk/NodeStyle+Storage.h new file mode 100644 index 0000000..7649414 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyle+Storage.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "NodeStyle.h" +#import "Configuration.h" + +@interface NodeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyle+Storage.m b/tikzit-1/src/gtk/NodeStyle+Storage.m new file mode 100644 index 0000000..088b062 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyle+Storage.m @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStyle+Storage.h" +#import "ColorRGB+IntegerListStorage.h" + +@implementation NodeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + self = [self init]; + + if (self) { + [self setName:[configFile stringEntry:@"Name" inGroup:groupName withDefault:name]]; + [self setCategory:[configFile stringEntry:@"Category" inGroup:groupName withDefault:category]]; + [self setShapeName:[configFile stringEntry:@"ShapeName" inGroup:groupName withDefault:shapeName]]; + [self setScale:[configFile doubleEntry:@"Scale" inGroup:groupName withDefault:scale]]; + [self setStrokeThickness:[configFile integerEntry:@"StrokeThickness" + inGroup:groupName + withDefault:strokeThickness]]; + [self setStrokeColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"StrokeColor" + inGroup:groupName + withDefault:[strokeColorRGB valueList]]]]; + [self setFillColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"FillColor" + inGroup:groupName + withDefault:[fillColorRGB valueList]]]]; + } + + return self; +} + +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + [configFile setStringEntry:@"Name" inGroup:groupName value:[self name]]; + [configFile setStringEntry:@"Category" inGroup:groupName value:[self category]]; + [configFile setStringEntry:@"ShapeName" inGroup:groupName value:[self shapeName]]; + [configFile setDoubleEntry:@"Scale" inGroup:groupName value:[self scale]]; + [configFile setIntegerEntry:@"StrokeThickness" inGroup:groupName value:[self strokeThickness]]; + [configFile setIntegerListEntry:@"StrokeColor" inGroup:groupName value:[[self strokeColorRGB] valueList]]; + [configFile setIntegerListEntry:@"FillColor" inGroup:groupName value:[[self fillColorRGB] valueList]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyleEditor.h b/tikzit-1/src/gtk/NodeStyleEditor.h new file mode 100644 index 0000000..b45c992 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleEditor.h @@ -0,0 +1,45 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class NodeStyle; + +@interface NodeStyleEditor: NSObject { + NodeStyle *style; + GtkTable *table; + GtkEntry *nameEdit; + GtkComboBox *shapeNameCombo; + GtkColorButton *strokeColorButton; + GtkWidget *makeStrokeTexSafeButton; + GtkColorButton *fillColorButton; + GtkWidget *makeFillTexSafeButton; + GtkAdjustment *scaleAdj; + BOOL blockSignals; +} + +@property (retain) NodeStyle *style; +@property (readonly) GtkWidget *widget; + +- (id) init; + +- (void) selectNameField; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStyleEditor.m b/tikzit-1/src/gtk/NodeStyleEditor.m new file mode 100644 index 0000000..fcf4147 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleEditor.m @@ -0,0 +1,477 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStyleEditor.h" +#import "NodeStyle.h" +#import "NodeStyle+Gtk.h" +#import "Shape.h" + +static const guint row_count = 5; + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void style_name_edit_changed_cb (GtkEditable *widget, NodeStyleEditor *editor); +static void style_shape_combo_changed_cb (GtkComboBox *widget, NodeStyleEditor *editor); +static void stroke_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor); +static void fill_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor); +static void make_stroke_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor); +static void make_fill_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor); +static void scale_adjustment_changed_cb (GtkAdjustment *widget, NodeStyleEditor *editor); +// }}} +// {{{ Notifications + +@interface NodeStyleEditor (Notifications) +- (void) shapeDictionaryReplaced:(NSNotification*)n; +- (void) nameChangedTo:(NSString*)value; +- (void) shapeNameChangedTo:(NSString*)value; +- (void) strokeColorChangedTo:(GdkColor)value; +- (void) makeStrokeColorTexSafe; +- (void) fillColorChangedTo:(GdkColor)value; +- (void) makeFillColorTexSafe; +- (void) scaleChangedTo:(double)value; +@end + +// }}} +// {{{ Private + +@interface NodeStyleEditor (Private) +- (void) loadShapeNames; +- (void) setActiveShapeName:(NSString*)name; +@end + +// }}} +// }}} +// {{{ API + +@implementation NodeStyleEditor + +- (void) _addWidget:(GtkWidget*)w withLabel:(gchar *)label atRow:(guint)row { + NSAssert(row < row_count, @"row_count is wrong!"); + + GtkWidget *l = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (l), 0, 0.5); + gtk_widget_show (l); + gtk_widget_show (w); + + gtk_table_attach (table, l, + 0, 1, row, row+1, // l, r, t, b + GTK_FILL, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 5, // x padding + 0); // y padding + + gtk_table_attach (table, w, + 1, 2, row, row+1, // l, r, t, b + GTK_FILL | GTK_EXPAND, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 0, // x padding + 0); // y padding +} + +- (GtkWidget*) _createMakeColorTexSafeButton:(NSString*)type { + GtkWidget *b = gtk_button_new (); + GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (icon); + gtk_container_add (GTK_CONTAINER (b), icon); + NSString *ttip = [NSString stringWithFormat:@"The %@ colour is not a predefined TeX colour.\nClick here to choose the nearest TeX-safe colour.", type]; + gtk_widget_set_tooltip_text (b, [ttip UTF8String]); + return b; +} + +- (id) init { + self = [super init]; + + if (self != nil) { + style = nil; + table = GTK_TABLE (gtk_table_new (row_count, 2, FALSE)); + gtk_table_set_col_spacings (table, 6); + gtk_table_set_row_spacings (table, 6); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + blockSignals = NO; + + /** + * Name + */ + nameEdit = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (nameEdit); + [self _addWidget:GTK_WIDGET (nameEdit) + withLabel:"Name" + atRow:0]; + g_signal_connect (G_OBJECT (nameEdit), + "changed", + G_CALLBACK (style_name_edit_changed_cb), + self); + + + /** + * Shape + */ + GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING); + shapeNameCombo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + GtkCellRenderer *cellRend = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (shapeNameCombo), + cellRend, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (shapeNameCombo), cellRend, "text", 0); + g_object_ref_sink (shapeNameCombo); + [self _addWidget:GTK_WIDGET (shapeNameCombo) + withLabel:"Shape" + atRow:1]; + g_signal_connect (G_OBJECT (shapeNameCombo), + "changed", + G_CALLBACK (style_shape_combo_changed_cb), + self); + + + /** + * Stroke colour + */ + GtkWidget *strokeBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:strokeBox + withLabel:"Stroke colour" + atRow:2]; + strokeColorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (strokeColorButton); + gtk_widget_show (GTK_WIDGET (strokeColorButton)); + gtk_box_pack_start (GTK_BOX (strokeBox), GTK_WIDGET (strokeColorButton), + FALSE, FALSE, 0); + makeStrokeTexSafeButton = [self _createMakeColorTexSafeButton:@"stroke"]; + g_object_ref_sink (makeStrokeTexSafeButton); + gtk_box_pack_start (GTK_BOX (strokeBox), makeStrokeTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (strokeColorButton), + "color-set", + G_CALLBACK (stroke_color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeStrokeTexSafeButton), + "clicked", + G_CALLBACK (make_stroke_safe_button_clicked_cb), + self); + + + /** + * Fill colour + */ + GtkWidget *fillBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:fillBox + withLabel:"Fill colour" + atRow:3]; + fillColorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (fillColorButton); + gtk_widget_show (GTK_WIDGET (fillColorButton)); + gtk_box_pack_start (GTK_BOX (fillBox), GTK_WIDGET (fillColorButton), + FALSE, FALSE, 0); + makeFillTexSafeButton = [self _createMakeColorTexSafeButton:@"fill"]; + g_object_ref_sink (makeFillTexSafeButton); + gtk_box_pack_start (GTK_BOX (fillBox), makeFillTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (fillColorButton), + "color-set", + G_CALLBACK (fill_color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeFillTexSafeButton), + "clicked", + G_CALLBACK (make_fill_safe_button_clicked_cb), + self); + + + /** + * Scale + */ + scaleAdj = GTK_ADJUSTMENT (gtk_adjustment_new ( + 1.0, // value + 0.0, // lower + 50.0, // upper + 0.20, // step + 1.0, // page + 0.0)); // (irrelevant) + g_object_ref_sink (scaleAdj); + GtkWidget *scaleSpin = gtk_spin_button_new (scaleAdj, 0.0, 2); + [self _addWidget:scaleSpin + withLabel:"Scale" + atRow:4]; + g_signal_connect (G_OBJECT (scaleAdj), + "value-changed", + G_CALLBACK (scale_adjustment_changed_cb), + self); + + [self loadShapeNames]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nameEdit); + g_object_unref (shapeNameCombo); + g_object_unref (strokeColorButton); + g_object_unref (makeStrokeTexSafeButton); + g_object_unref (fillColorButton); + g_object_unref (makeFillTexSafeButton); + g_object_unref (scaleAdj); + g_object_unref (table); + [style release]; + + [super dealloc]; +} + +- (NodeStyle*) style { + return style; +} + +- (void) setStyle:(NodeStyle*)s { + blockSignals = YES; + NodeStyle *oldStyle = style; + style = [s retain]; + + if (style != nil) { + gtk_widget_set_sensitive (GTK_WIDGET (table), TRUE); + + gtk_entry_set_text(nameEdit, [[style name] UTF8String]); + + [self setActiveShapeName:[style shapeName]]; + + GdkColor c = [style strokeColor]; + gtk_color_button_set_color(strokeColorButton, &c); + + gtk_widget_set_visible (makeStrokeTexSafeButton, ([[style strokeColorRGB] name] == nil)); + + c = [style fillColor]; + gtk_color_button_set_color(fillColorButton, &c); + + gtk_widget_set_visible (makeFillTexSafeButton, ([[style fillColorRGB] name] == nil)); + + gtk_adjustment_set_value(scaleAdj, [style scale]); + } else { + gtk_entry_set_text(nameEdit, ""); + [self setActiveShapeName:nil]; + gtk_widget_set_visible (makeStrokeTexSafeButton, FALSE); + gtk_widget_set_visible (makeFillTexSafeButton, FALSE); + gtk_adjustment_set_value(scaleAdj, 1.0); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + } + + [oldStyle release]; + blockSignals = NO; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (table); +} + +- (void) selectNameField { + gtk_widget_grab_focus (GTK_WIDGET (nameEdit)); + gtk_editable_select_region (GTK_EDITABLE (nameEdit), 0, -1); +} + +@end + +// }}} +// {{{ Notifications + +@implementation NodeStyleEditor (Notifications) +- (void) shapeDictionaryReplaced:(NSNotification*)n { + blockSignals = YES; + + [self loadShapeNames]; + [self setActiveShapeName:[style shapeName]]; + + blockSignals = NO; +} + +- (void) nameChangedTo:(NSString*)value { + [style setName:value]; +} + +- (void) shapeNameChangedTo:(NSString*)value { + [style setShapeName:value]; +} + +- (void) strokeColorChangedTo:(GdkColor)value { + [style setStrokeColor:value]; + gtk_widget_set_visible (makeStrokeTexSafeButton, + [[style strokeColorRGB] name] == nil); +} + +- (void) makeStrokeColorTexSafe { + if (style != nil) { + [[style strokeColorRGB] setToClosestHashed]; + GdkColor color = [style strokeColor]; + gtk_color_button_set_color(strokeColorButton, &color); + gtk_widget_set_visible (makeStrokeTexSafeButton, FALSE); + } +} + +- (void) fillColorChangedTo:(GdkColor)value { + [style setFillColor:value]; + gtk_widget_set_visible (makeFillTexSafeButton, + [[style fillColorRGB] name] == nil); +} + +- (void) makeFillColorTexSafe { + if (style != nil) { + [[style fillColorRGB] setToClosestHashed]; + GdkColor color = [style fillColor]; + gtk_color_button_set_color(fillColorButton, &color); + gtk_widget_set_visible (makeFillTexSafeButton, FALSE); + } +} + +- (void) scaleChangedTo:(double)value { + [style setScale:value]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStyleEditor (Private) +- (BOOL) signalsBlocked { return blockSignals; } + +- (void) loadShapeNames { + blockSignals = YES; + + gtk_combo_box_set_active (shapeNameCombo, -1); + + GtkListStore *list = GTK_LIST_STORE (gtk_combo_box_get_model (shapeNameCombo)); + gtk_list_store_clear (list); + + NSEnumerator *en = [[Shape shapeDictionary] keyEnumerator]; + NSString *shapeName; + GtkTreeIter iter; + while ((shapeName = [en nextObject]) != nil) { + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, 0, [shapeName UTF8String], -1); + } + + blockSignals = NO; +} + +- (void) setActiveShapeName:(NSString*)name { + if (name == nil) { + gtk_combo_box_set_active (shapeNameCombo, -1); + return; + } + const gchar *shapeName = [name UTF8String]; + + GtkTreeModel *model = gtk_combo_box_get_model (shapeNameCombo); + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + gchar *rowShapeName; + gtk_tree_model_get (model, &iter, 0, &rowShapeName, -1); + if (g_strcmp0 (shapeName, rowShapeName) == 0) { + gtk_combo_box_set_active_iter (shapeNameCombo, &iter); + g_free (rowShapeName); + return; + } + g_free (rowShapeName); + } while (gtk_tree_model_iter_next (model, &iter)); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void style_name_edit_changed_cb (GtkEditable *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + const gchar *contents = gtk_entry_get_text (GTK_ENTRY (widget)); + [editor nameChangedTo:[NSString stringWithUTF8String:contents]]; + + [pool drain]; +} + +static void style_shape_combo_changed_cb (GtkComboBox *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + gchar *shapeName = NULL; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, 0, &shapeName, -1); + [editor shapeNameChangedTo:[NSString stringWithUTF8String:shapeName]]; + g_free (shapeName); + + [pool drain]; +} + +static void stroke_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor strokeColorChangedTo:color]; + + [pool drain]; +} + +static void fill_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor fillColorChangedTo:color]; + + [pool drain]; +} + +static void make_stroke_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeStrokeColorTexSafe]; + [pool drain]; +} + +static void make_fill_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeFillColorTexSafe]; + [pool drain]; +} + +static void scale_adjustment_changed_cb (GtkAdjustment *adj, NodeStyleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor scaleChangedTo:gtk_adjustment_get_value (adj)]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStyleSelector.h b/tikzit-1/src/gtk/NodeStyleSelector.h new file mode 100644 index 0000000..a699dc8 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleSelector.h @@ -0,0 +1,61 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class NodeStyle; +@class NodeStylesModel; +@class StyleManager; + +@interface NodeStyleSelector: NSObject { + NodeStylesModel *model; + GtkIconView *view; +} + +/*! + @property widget + @brief The GTK widget + */ +@property (readonly) GtkWidget *widget; + +/*! + @property model + @brief The model to use. + */ +@property (retain) NodeStylesModel *model; + +/*! + @property selectedStyle + @brief The selected style. + + When this changes, a SelectedStyleChanged notification will be posted + */ +@property (assign) NodeStyle *selectedStyle; + +/*! + * Initialise with a new model for the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)manager; +/*! + * Initialise with the given model + */ +- (id) initWithModel:(NodeStylesModel*)model; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyleSelector.m b/tikzit-1/src/gtk/NodeStyleSelector.m new file mode 100644 index 0000000..14cdc75 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleSelector.m @@ -0,0 +1,135 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStyleSelector.h" + +#import "NodeStylesModel.h" + +// {{{ Internal interfaces +static void selection_changed_cb (GtkIconView *widget, NodeStyleSelector *mgr); +// }}} +// {{{ API + +@implementation NodeStyleSelector + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + return [self initWithModel:[NodeStylesModel modelWithStyleManager:m]]; +} +- (id) initWithModel:(NodeStylesModel*)m { + self = [super init]; + + if (self) { + model = [m retain]; + + view = GTK_ICON_VIEW (gtk_icon_view_new ()); + g_object_ref_sink (view); + + gtk_icon_view_set_model (view, [model model]); + gtk_icon_view_set_pixbuf_column (view, NODE_STYLES_ICON_COL); + gtk_icon_view_set_tooltip_column (view, NODE_STYLES_NAME_COL); + gtk_icon_view_set_selection_mode (view, GTK_SELECTION_SINGLE); + + g_signal_connect (G_OBJECT (view), + "selection-changed", + G_CALLBACK (selection_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (view); + [model release]; + + [super dealloc]; +} + +- (NodeStylesModel*) model { + return model; +} + +- (void) setModel:(NodeStylesModel*)m { + if (m == model) + return; + + NodeStylesModel *oldModel = model; + model = [m retain]; + gtk_icon_view_set_model (view, [model model]); + [oldModel release]; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (view); +} + +- (NodeStyle*) selectedStyle { + GList *list = gtk_icon_view_get_selected_items (view); + if (!list) { + return nil; + } + if (list->next != NULL) { + NSLog(@"Multiple selected items in NodeStyleSelector!"); + } + + GtkTreePath *path = (GtkTreePath*) list->data; + NodeStyle *style = [model styleFromPath:path]; + + g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL); + g_list_free (list); + + return style; +} + +- (void) setSelectedStyle:(NodeStyle*)style { + if (style == nil) { + gtk_icon_view_unselect_all (view); + return; + } + + GtkTreePath *path = [model pathFromStyle:style]; + if (path) { + gtk_icon_view_unselect_all (view); + gtk_icon_view_select_path (view, path); + gtk_tree_path_free (path); + } +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void selection_changed_cb (GtkIconView *view, NodeStyleSelector *mgr) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"SelectedStyleChanged" + object:mgr]; + + [pool drain]; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesModel.h b/tikzit-1/src/gtk/NodeStylesModel.h new file mode 100644 index 0000000..a048560 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesModel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class NodeStyle; +@class StyleManager; + +enum { + NODE_STYLES_NAME_COL = 0, + NODE_STYLES_ICON_COL, + NODE_STYLES_PTR_COL, + NODE_STYLES_N_COLS +}; + +@interface NodeStylesModel: NSObject { + GtkListStore *store; + StyleManager *styleManager; +} + +/*! + @property model + @brief The GTK+ tree model + */ +@property (readonly) GtkTreeModel *model; + +/*! + @property manager + @brief The StyleManager to use. + */ +@property (retain) StyleManager *styleManager; + +/*! + * Initialise with the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; + ++ (id) modelWithStyleManager:(StyleManager*)m; + +- (NodeStyle*) styleFromPath:(GtkTreePath*)path; +- (GtkTreePath*) pathFromStyle:(NodeStyle*)style; +- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter; +- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStylesModel.m b/tikzit-1/src/gtk/NodeStylesModel.m new file mode 100644 index 0000000..3cc5771 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesModel.m @@ -0,0 +1,381 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStylesModel.h" + +#import "CairoRenderContext.h" +#import "NodeStyle.h" +#import "Shape.h" +#import "Shape+Render.h" +#import "ShapeNames.h" +#import "StyleManager.h" + +#import "gtkhelpers.h" + +#import <gdk-pixbuf/gdk-pixbuf.h> + +// {{{ Internal interfaces + +@interface NodeStylesModel (Notifications) +- (void) nodeStylesReplaced:(NSNotification*)notification; +- (void) nodeStyleAdded:(NSNotification*)notification; +- (void) nodeStyleRemoved:(NSNotification*)notification; +- (void) shapeDictionaryReplaced:(NSNotification*)n; +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context; +@end + +@interface NodeStylesModel (Private) +- (cairo_surface_t*) createNodeIconSurface; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) addNodeStyle:(NodeStyle*)style; +- (void) addNodeStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) observeNodeStyle:(NodeStyle*)style; +- (void) stopObservingNodeStyle:(NodeStyle*)style; +- (void) clearNodeStylesModel; +- (void) reloadNodeStyles; +@end + +// }}} +// {{{ API + +@implementation NodeStylesModel + ++ (id) modelWithStyleManager:(StyleManager*)m { + return [[[self alloc] initWithStyleManager:m] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + store = gtk_list_store_new (NODE_STYLES_N_COLS, + G_TYPE_STRING, + GDK_TYPE_PIXBUF, + G_TYPE_POINTER); + g_object_ref_sink (store); + + [self setStyleManager:m]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self clearNodeStylesModel]; + g_object_unref (store); + [styleManager release]; + + [super dealloc]; +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)m { + if (m == nil) { + [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"]; + } + [m retain]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylesReplaced:) + name:@"NodeStylesReplaced" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStyleAdded:) + name:@"NodeStyleAdded" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStyleRemoved:) + name:@"NodeStyleRemoved" + object:m]; + + [styleManager release]; + styleManager = m; + + [self reloadNodeStyles]; +} + +- (GtkTreeModel*) model { + return GTK_TREE_MODEL (store); +} + +- (NodeStyle*) styleFromPath:(GtkTreePath*)path { + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + NodeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, NODE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreePath*) pathFromStyle:(NodeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (m, &row, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_model_get_path (m, &row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} + +- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter { + NodeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, NODE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (m, &row, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_iter_copy (&row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} +@end + +// }}} +// {{{ Notifications + +@implementation NodeStylesModel (Notifications) + +- (void) nodeStylesReplaced:(NSNotification*)notification { + [self reloadNodeStyles]; +} + +- (void) nodeStyleAdded:(NSNotification*)notification { + [self addNodeStyle:[[notification userInfo] objectForKey:@"style"]]; +} + +- (void) nodeStyleRemoved:(NSNotification*)notification { + NodeStyle *style = [[notification userInfo] objectForKey:@"style"]; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_list_store_remove (store, &row); + [self stopObservingNodeStyle:rowStyle]; + [rowStyle release]; + return; + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + if ([object class] == [NodeStyle class]) { + NodeStyle *style = object; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + if ([@"name" isEqual:keyPath]) { + gtk_list_store_set (store, &row, NODE_STYLES_NAME_COL, [[style name] UTF8String], -1); + } else { + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style]; + gtk_list_store_set (store, &row, NODE_STYLES_ICON_COL, pixbuf, -1); + g_object_unref (pixbuf); + } + } + } while (gtk_tree_model_iter_next (model, &row)); + } + } +} + +- (void) shapeDictionaryReplaced:(NSNotification*)n { + [self reloadNodeStyles]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStylesModel (Private) +- (cairo_surface_t*) createNodeIconSurface { + return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 24, 24); +} + +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style { + cairo_surface_t *surface = [self createNodeIconSurface]; + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); + return pixbuf; +} + +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface { + Shape *shape = [Shape shapeForName:[style shapeName]]; + + int width = cairo_image_surface_get_width (surface); + int height = cairo_image_surface_get_height (surface); + NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height); + const CGFloat lineWidth = [style strokeThickness]; + Transformer *shapeTrans = [Transformer transformerToFit:[shape boundingRect] + intoScreenRect:NSInsetRect(pixbufBounds, lineWidth, lineWidth) + flippedAboutXAxis:YES]; + if ([style scale] < 1.0) + [shapeTrans setScale:[style scale] * [shapeTrans scale]]; + + CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface]; + [context clearSurface]; + [shape drawPathWithTransform:shapeTrans andContext:context]; + [context setLineWidth:lineWidth]; + [context strokePathWithColor:[[style strokeColorRGB] rColor] + andFillWithColor:[[style fillColorRGB] rColor]]; + [context release]; + + return pixbuf_get_from_surface (surface); +} + +- (void) addNodeStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface]; + gtk_list_store_set (store, &iter, + NODE_STYLES_NAME_COL, [[style name] UTF8String], + NODE_STYLES_ICON_COL, pixbuf, + NODE_STYLES_PTR_COL, (gpointer)[style retain], + -1); + g_object_unref (pixbuf); + [self observeNodeStyle:style]; +} + +- (void) addNodeStyle:(NodeStyle*)style { + cairo_surface_t *surface = [self createNodeIconSurface]; + [self addNodeStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); +} + +- (void) observeNodeStyle:(NodeStyle*)style { + [style addObserver:self + forKeyPath:@"name" + options:NSKeyValueObservingOptionNew + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeThickness" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.blue" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.blue" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"shapeName" + options:0 + context:NULL]; +} + +- (void) stopObservingNodeStyle:(NodeStyle*)style { + [style removeObserver:self forKeyPath:@"name"]; + [style removeObserver:self forKeyPath:@"strokeThickness"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.red"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.green"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.blue"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.red"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.green"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.blue"]; + [style removeObserver:self forKeyPath:@"shapeName"]; +} + +- (void) clearNodeStylesModel { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, NODE_STYLES_PTR_COL, &rowStyle, -1); + [self stopObservingNodeStyle:rowStyle]; + [rowStyle release]; + } while (gtk_tree_model_iter_next (model, &row)); + } + gtk_list_store_clear (store); +} + +- (void) reloadNodeStyles { + [self clearNodeStylesModel]; + cairo_surface_t *surface = [self createNodeIconSurface]; + for (NodeStyle *style in [styleManager nodeStyles]) { + [self addNodeStyle:style usingSurface:surface]; + } + cairo_surface_destroy (surface); +} +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesPalette.h b/tikzit-1/src/gtk/NodeStylesPalette.h new file mode 100644 index 0000000..ac712ea --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesPalette.h @@ -0,0 +1,43 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class StyleManager; +@class NodeStyleSelector; +@class NodeStyleEditor; + +@interface NodeStylesPalette: NSObject { + NodeStyleSelector *selector; + NodeStyleEditor *editor; + + GtkWidget *palette; + + GtkWidget *removeStyleButton; + GtkWidget *applyStyleButton; + GtkWidget *clearStyleButton; +} + +@property (retain) StyleManager *styleManager; +@property (readonly) GtkWidget *widget; + +- (id) initWithManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesPalette.m b/tikzit-1/src/gtk/NodeStylesPalette.m new file mode 100644 index 0000000..e28edbb --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesPalette.m @@ -0,0 +1,197 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NodeStylesPalette.h" + +#import "NodeStyleSelector.h" +#import "NodeStyleEditor.h" +#import "NodeStylesModel.h" +#import "StyleManager.h" + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +static void remove_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +// }}} +// {{{ Notifications + +@interface NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ Private + +@interface NodeStylesPalette (Private) +- (void) updateButtonState; +- (void) removeSelectedStyle; +- (void) addStyle; +@end + +// }}} +// }}} +// {{{ API + +@implementation NodeStylesPalette + +@synthesize widget=palette; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithManager:(StyleManager*)m { + self = [super init]; + + if (self) { + selector = [[NodeStyleSelector alloc] initWithStyleManager:m]; + editor = [[NodeStyleEditor alloc] init]; + + palette = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (palette), 6); + g_object_ref_sink (palette); + + GtkWidget *mainBox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (palette), mainBox, FALSE, FALSE, 0); + gtk_widget_show (mainBox); + + GtkWidget *selectorScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selectorScroller), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (selectorScroller), [selector widget]); + gtk_container_add (GTK_CONTAINER (selectorFrame), selectorScroller); + gtk_box_pack_start (GTK_BOX (mainBox), selectorFrame, TRUE, TRUE, 0); + gtk_widget_show (selectorScroller); + gtk_widget_show (selectorFrame); + gtk_widget_show ([selector widget]); + + gtk_box_pack_start (GTK_BOX (mainBox), [editor widget], TRUE, TRUE, 0); + gtk_widget_show ([editor widget]); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (palette), GTK_WIDGET (buttonBox), FALSE, FALSE, 0); + + GtkWidget *addStyleButton = gtk_button_new (); + gtk_widget_set_tooltip_text (addStyleButton, "Add a new style"); + GtkWidget *addIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addStyleButton), addIcon); + gtk_box_pack_start (buttonBox, addStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addStyleButton), + "clicked", + G_CALLBACK (add_style_button_cb), + self); + + removeStyleButton = gtk_button_new (); + g_object_ref_sink (removeStyleButton); + gtk_widget_set_tooltip_text (removeStyleButton, "Delete selected style"); + GtkWidget *removeIcon = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (removeStyleButton), removeIcon); + gtk_box_pack_start (buttonBox, removeStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeStyleButton), + "clicked", + G_CALLBACK (remove_style_button_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(selectedStyleChanged:) + name:@"SelectedStyleChanged" + object:selector]; + + [self updateButtonState]; + } + + return self; +} + +- (StyleManager*) styleManager { + return [[selector model] styleManager]; +} + +- (void) setStyleManager:(StyleManager*)m { + [[selector model] setStyleManager:m]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [editor release]; + [selector release]; + g_object_unref (palette); + g_object_unref (removeStyleButton); + + [super dealloc]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification { + [editor setStyle:[selector selectedStyle]]; + [self updateButtonState]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStylesPalette (Private) +- (void) updateButtonState { + gboolean hasStyleSelection = [selector selectedStyle] != nil; + + gtk_widget_set_sensitive (removeStyleButton, hasStyleSelection); +} + +- (void) removeSelectedStyle { + NodeStyle *style = [selector selectedStyle]; + if (style) + [[[selector model] styleManager] removeNodeStyle:style]; +} + +- (void) addStyle { + NodeStyle *newStyle = [NodeStyle defaultNodeStyleWithName:@"newstyle"]; + [[self styleManager] addNodeStyle:newStyle]; + [selector setSelectedStyle:newStyle]; + [editor selectNameField]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette addStyle]; + [pool drain]; +} + +static void remove_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette removeSelectedStyle]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PreambleEditor.h b/tikzit-1/src/gtk/PreambleEditor.h new file mode 100644 index 0000000..f181446 --- /dev/null +++ b/tikzit-1/src/gtk/PreambleEditor.h @@ -0,0 +1,51 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Preambles; + +@interface PreambleEditor: NSObject { + Preambles *preambles; + + // we don't keep any refs, as we control + // the top window + GtkWindow *parentWindow; + GtkWindow *window; + GtkListStore *preambleListStore; + GtkTreeView *preambleSelector; + GtkTextView *preambleView; + BOOL blockSignals; + BOOL adding; +} + +- (id) initWithPreambles:(Preambles*)p; + +- (void) setParentWindow:(GtkWindow*)parent; + +- (Preambles*) preambles; + +- (void) present; +- (void) show; +- (void) hide; +- (BOOL) isVisible; +- (void) setVisible:(BOOL)visible; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PreambleEditor.m b/tikzit-1/src/gtk/PreambleEditor.m new file mode 100644 index 0000000..d1f72ee --- /dev/null +++ b/tikzit-1/src/gtk/PreambleEditor.m @@ -0,0 +1,568 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "PreambleEditor.h" + +#import "Application.h" +#import "Preambles.h" +#import <gdk/gdk.h> + +enum { + NAME_COLUMN, + IS_CUSTOM_COLUMN, + N_COLUMNS +}; + +// {{{ Internal interfaces +// {{{ Signals +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor); +static gboolean window_focus_out_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor); +static void close_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void add_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void remove_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +/* +static void undo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void redo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +*/ +static void preamble_name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PreambleEditor *editor); +static void preamble_selection_changed_cb (GtkTreeSelection *treeselection, + PreambleEditor *editor); +// }}} + +@interface PreambleEditor (Private) +- (void) loadUi; +- (void) save; +- (void) revert; +- (void) update; +- (void) fillListStore; +- (BOOL) isDefaultPreambleSelected; +- (NSString*) selectedPreambleName; +- (void) addPreamble; +- (void) deletePreamble; +- (void) renamePreambleAtPath:(gchar*)path to:(gchar*)newValue; +- (void) nodeStylePropertyChanged:(NSNotification*)notification; +- (void) edgeStylePropertyChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ API + +@implementation PreambleEditor + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p { + self = [super init]; + + if (self) { + preambles = [p retain]; + parentWindow = NULL; + window = NULL; + preambleView = NULL; + preambleSelector = NULL; + blockSignals = NO; + adding = NO; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:nil]; + } + + return self; +} + +- (Preambles*) preambles { + return preambles; +} + +- (void) setParentWindow:(GtkWindow*)parent { + GtkWindow *oldParent = parentWindow; + + if (parentWindow) + g_object_ref (parentWindow); + parentWindow = parent; + if (oldParent) + g_object_unref (oldParent); + + if (window) { + gtk_window_set_transient_for (window, parentWindow); + } +} + +- (void) present { + [self loadUi]; + gtk_window_present (GTK_WINDOW (window)); + [self revert]; +} + +- (void) show { + [self loadUi]; + gtk_widget_show (GTK_WIDGET (window)); + [self revert]; +} + +- (void) hide { + if (!window) { + return; + } + [self save]; + gtk_widget_hide (GTK_WIDGET (window)); +} + +- (BOOL) isVisible { + if (!window) { + return NO; + } + gboolean visible; + g_object_get (G_OBJECT (window), "visible", &visible, NULL); + return visible ? YES : NO; +} + +- (void) setVisible:(BOOL)visible { + if (visible) { + [self show]; + } else { + [self hide]; + } +} + +- (void) dealloc { + [preambles release]; + preambles = nil; + if (window) { + gtk_widget_destroy (GTK_WIDGET (window)); + window = NULL; + } + if (parentWindow) { + g_object_ref (parentWindow); + } + + [super dealloc]; +} + +@end + +// }}} +// {{{ Private + +@implementation PreambleEditor (Private) +- (GtkWidget*) createPreambleList { + preambleListStore = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN); + preambleSelector = GTK_TREE_VIEW (gtk_tree_view_new_with_model ( + GTK_TREE_MODEL (preambleListStore))); + gtk_widget_set_size_request (GTK_WIDGET (preambleSelector), 150, -1); + gtk_tree_view_set_headers_visible (preambleSelector, FALSE); + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Preamble", + renderer, + "text", NAME_COLUMN, + "editable", IS_CUSTOM_COLUMN, + NULL); + gtk_tree_view_append_column (preambleSelector, column); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (preamble_name_edited_cb), + self); + + GtkWidget *listScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listScroller), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (listScroller), + GTK_WIDGET (preambleSelector)); + + [self fillListStore]; + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); + g_signal_connect (G_OBJECT (sel), + "changed", + G_CALLBACK (preamble_selection_changed_cb), + self); + + return listScroller; +} + +- (void) loadUi { + if (window) { + return; + } + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title (window, "Preamble editor"); + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_default_size (window, 600, 400); + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DIALOG); + if (parentWindow) { + gtk_window_set_transient_for (window, parentWindow); + } + GdkEventMask mask; + g_object_get (G_OBJECT (window), "events", &mask, NULL); + mask |= GDK_FOCUS_CHANGE_MASK; + g_object_set (G_OBJECT (window), "events", mask, NULL); + g_signal_connect (window, + "delete-event", + G_CALLBACK (window_delete_event_cb), + self); + g_signal_connect (window, + "focus-out-event", + G_CALLBACK (window_focus_out_event_cb), + self); + + GtkWidget *mainBox = gtk_vbox_new (FALSE, 18); + gtk_container_set_border_width (GTK_CONTAINER (mainBox), 12); + gtk_container_add (GTK_CONTAINER (window), mainBox); + + GtkPaned *paned = GTK_PANED (gtk_hpaned_new ()); + gtk_box_pack_start (GTK_BOX (mainBox), + GTK_WIDGET (paned), + TRUE, TRUE, 0); + + GtkWidget *listWidget = [self createPreambleList]; + GtkWidget *listFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (listFrame), listWidget); + + GtkBox *listBox = GTK_BOX (gtk_vbox_new (FALSE, 6)); + gtk_box_pack_start (listBox, listFrame, TRUE, TRUE, 0); + + GtkContainer *listButtonBox = GTK_CONTAINER (gtk_hbox_new (FALSE, 6)); + gtk_box_pack_start (listBox, GTK_WIDGET (listButtonBox), FALSE, TRUE, 0); + GtkWidget *addButton = gtk_button_new_from_stock (GTK_STOCK_ADD); + g_signal_connect (addButton, + "clicked", + G_CALLBACK (add_button_clicked_cb), + self); + gtk_container_add (listButtonBox, addButton); + GtkWidget *removeButton = gtk_button_new_from_stock (GTK_STOCK_REMOVE); + g_signal_connect (removeButton, + "clicked", + G_CALLBACK (remove_button_clicked_cb), + self); + gtk_container_add (listButtonBox, removeButton); + + gtk_paned_pack1 (paned, GTK_WIDGET (listBox), FALSE, TRUE); + + preambleView = GTK_TEXT_VIEW (gtk_text_view_new ()); + gtk_text_view_set_left_margin (preambleView, 3); + gtk_text_view_set_right_margin (preambleView, 3); + GtkWidget *scroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller), + GTK_POLICY_AUTOMATIC, // horiz + GTK_POLICY_ALWAYS); // vert + gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (preambleView)); + GtkWidget *editorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (editorFrame), scroller); + gtk_paned_pack2 (paned, editorFrame, TRUE, TRUE); + + GtkContainer *buttonBox = GTK_CONTAINER (gtk_hbutton_box_new ()); + gtk_box_set_spacing (GTK_BOX (buttonBox), 6); + gtk_button_box_set_layout (GTK_BUTTON_BOX (buttonBox), GTK_BUTTONBOX_END); + gtk_box_pack_start (GTK_BOX (mainBox), + GTK_WIDGET (buttonBox), + FALSE, TRUE, 0); + GtkWidget *closeButton = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + gtk_container_add (buttonBox, closeButton); + g_signal_connect (closeButton, + "clicked", + G_CALLBACK (close_button_clicked_cb), + self); + /* + GtkWidget *undoButton = gtk_button_new_from_stock (GTK_STOCK_UNDO); + gtk_container_add (buttonBox, undoButton); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (buttonBox), + undoButton, + TRUE); + g_signal_connect (undoButton, + "clicked", + G_CALLBACK (undo_button_clicked_cb), + self); + GtkWidget *redoButton = gtk_button_new_from_stock (GTK_STOCK_REDO); + gtk_container_add (buttonBox, redoButton); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (buttonBox), + redoButton, + TRUE); + g_signal_connect (redoButton, + "clicked", + G_CALLBACK (redo_button_clicked_cb), + self); + */ + [self revert]; + + gtk_widget_show_all (mainBox); +} + +- (void) save { + if (!preambleView) + return; + if ([self isDefaultPreambleSelected]) + return; + GtkTextIter start,end; + GtkTextBuffer *preambleBuffer = gtk_text_view_get_buffer (preambleView); + gtk_text_buffer_get_bounds(preambleBuffer, &start, &end); + gchar *text = gtk_text_buffer_get_text(preambleBuffer, &start, &end, FALSE); + NSString *preamble = [NSString stringWithUTF8String:text]; + g_free (text); + [preambles setCurrentPreamble:preamble]; + [app saveConfiguration]; +} + +- (void) revert { + if (!preambleView) + return; + GtkTextBuffer *preambleBuffer = gtk_text_view_get_buffer (preambleView); + gtk_text_buffer_set_text (preambleBuffer, [[preambles currentPreamble] UTF8String], -1); + gtk_text_view_set_editable (preambleView, ![self isDefaultPreambleSelected]); +} + +- (void) update { + if (!blockSignals) { + [self save]; + } + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + GtkTreeIter row; + GtkTreeModel *model; + if (gtk_tree_selection_get_selected (sel, &model, &row)) { + gchar *name; + gtk_tree_model_get (model, &row, NAME_COLUMN, &name, -1); + NSString *preambleName = [NSString stringWithUTF8String:name]; + [preambles setSelectedPreambleName:preambleName]; + g_free (name); + } + [self revert]; +} + +- (void) fillListStore { + blockSignals = YES; + + GtkTreeIter row; + gtk_list_store_clear (preambleListStore); + + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [[preambles defaultPreambleName] UTF8String], + IS_CUSTOM_COLUMN, FALSE, + -1); + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + if ([self isDefaultPreambleSelected]) { + gtk_tree_selection_select_iter (sel, &row); + } + + NSEnumerator *en = [preambles customPreambleNameEnumerator]; + NSString *preambleName; + while ((preambleName = [en nextObject])) { + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [preambleName UTF8String], + IS_CUSTOM_COLUMN, TRUE, + -1); + if ([preambleName isEqualToString:[self selectedPreambleName]]) { + gtk_tree_selection_select_iter (sel, &row); + } + } + + blockSignals = NO; +} + +- (BOOL) isDefaultPreambleSelected { + return [preambles selectedPreambleIsDefault]; +} + +- (NSString*) selectedPreambleName { + return [preambles selectedPreambleName]; +} + +- (void) addPreamble { + NSString *newName = [preambles addPreamble]; + + GtkTreeIter row; + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [newName UTF8String], + IS_CUSTOM_COLUMN, TRUE, + -1); + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_select_iter (sel, &row); +} + +- (void) deletePreamble { + if ([self isDefaultPreambleSelected]) + return; + + NSString *name = [self selectedPreambleName]; + + GtkTreeIter row; + GtkTreeModel *model = GTK_TREE_MODEL (preambleListStore); + + gtk_tree_model_get_iter_first (model, &row); + // ignore first; it is the default one + gboolean found = FALSE; + while (!found && gtk_tree_model_iter_next (model, &row)) { + gchar *candidate; + gtk_tree_model_get (model, &row, NAME_COLUMN, &candidate, -1); + if (g_strcmp0 (candidate, [name UTF8String]) == 0) { + found = TRUE; + } + g_free (candidate); + } + + if (!found) + return; + + if (![preambles removePreamble:name]) + return; + + blockSignals = YES; + + gboolean had_next = gtk_list_store_remove (preambleListStore, &row); + if (!had_next) { + // select the last item + gint length = gtk_tree_model_iter_n_children (model, NULL); + gtk_tree_model_iter_nth_child (model, &row, NULL, length - 1); + } + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_select_iter (sel, &row); + + [self revert]; + + blockSignals = NO; +} + +- (void) renamePreambleAtPath:(gchar*)path to:(gchar*)newValue { + NSString *newName = [NSString stringWithUTF8String:newValue]; + + GtkTreeIter row; + GtkTreeModel *model = GTK_TREE_MODEL (preambleListStore); + + if (!gtk_tree_model_get_iter_from_string (model, &row, path)) + return; + + gchar *oldValue; + gtk_tree_model_get (model, &row, NAME_COLUMN, &oldValue, -1); + + NSString* oldName = [NSString stringWithUTF8String:oldValue]; + if ([preambles renamePreambleFrom:oldName to:newName]) { + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, newValue, + -1); + } +} + +- (void) nodeStylePropertyChanged:(NSNotification*)notification { + if ([self isDefaultPreambleSelected]) { + [self revert]; + } +} + +- (void) edgeStylePropertyChanged:(NSNotification*)notification { + if ([self isDefaultPreambleSelected]) { + [self revert]; + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor hide]; + [pool drain]; + return TRUE; // we dealt with this event +} + +static gboolean window_focus_out_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor save]; + [pool drain]; + return FALSE; +} + +static void close_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor hide]; + [pool drain]; +} + +static void add_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addPreamble]; + [pool drain]; +} + +static void remove_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor deletePreamble]; + [pool drain]; +} + +/* +static void undo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog(@"Undo"); + [pool drain]; +} + +static void redo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog(@"Redo"); + [pool drain]; +} +*/ + +static void preamble_name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor renamePreambleAtPath:path to:new_text]; + [pool drain]; +} + +static void preamble_selection_changed_cb (GtkTreeSelection *treeselection, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor update]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Preambles+Storage.h b/tikzit-1/src/gtk/Preambles+Storage.h new file mode 100644 index 0000000..76f56cc --- /dev/null +++ b/tikzit-1/src/gtk/Preambles+Storage.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Preambles.h" + +@interface Preambles (Storage) + ++ (Preambles*) preamblesFromDirectory:(NSString*)directory; +- (id) initFromDirectory:(NSString*)directory; +- (void) loadFromDirectory:(NSString*)directory; +- (void) storeToDirectory:(NSString*)directory; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Preambles+Storage.m b/tikzit-1/src/gtk/Preambles+Storage.m new file mode 100644 index 0000000..bd3ea03 --- /dev/null +++ b/tikzit-1/src/gtk/Preambles+Storage.m @@ -0,0 +1,84 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Preambles+Storage.h" + +static NSString *ext = @"preamble"; + +@implementation Preambles (Storage) + ++ (Preambles*) preamblesFromDirectory:(NSString*)directory { + return [[[self alloc] initFromDirectory:directory] autorelease]; +} + +- (id) initFromDirectory:(NSString*)directory { + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDir] && isDir) { + self = [super init]; + + if (self) { + selectedPreambleName = @"default"; + preambleDict = nil; + [self loadFromDirectory:directory]; + } + } else { + self = [self init]; + } + + return self; +} + +- (void) loadFromDirectory:(NSString*)directory { + preambleDict = [[NSMutableDictionary alloc] initWithCapacity:1]; + NSDirectoryEnumerator *en = [[NSFileManager defaultManager] enumeratorAtPath:directory]; + NSString *filename; + while ((filename = [en nextObject]) != nil) { + if ([filename hasSuffix:ext] && [[en fileAttributes] fileType] == NSFileTypeRegular) { + NSString *path = [directory stringByAppendingPathComponent:filename]; + NSString *contents = [NSString stringWithContentsOfFile:path]; + if (contents) { + [preambleDict setObject:contents forKey:[filename stringByDeletingPathExtension]]; + } + } + } +} + +- (void) storeToDirectory:(NSString*)directory { + NSDirectoryEnumerator *den = [[NSFileManager defaultManager] enumeratorAtPath:directory]; + NSString *filename; + while ((filename = [den nextObject]) != nil) { + if ([filename hasSuffix:ext] && [[den fileAttributes] fileType] == NSFileTypeRegular) { + NSString *path = [directory stringByAppendingPathComponent:filename]; + NSString *entry = [filename stringByDeletingPathExtension]; + if ([preambleDict objectForKey:entry] == nil) { + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; + } + } + } + + NSEnumerator *en = [self customPreambleNameEnumerator]; + NSString *entry; + while ((entry = [en nextObject]) != nil) { + NSString *path = [directory stringByAppendingPathComponent:[entry stringByAppendingPathExtension:ext]]; + NSString *contents = [preambleDict objectForKey:entry]; + [contents writeToFile:path atomically:YES]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PreviewRenderer.h b/tikzit-1/src/gtk/PreviewRenderer.h new file mode 100644 index 0000000..d691722 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewRenderer.h @@ -0,0 +1,48 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <poppler.h> + +#import "Surface.h" + +@class Configuration; +@class Preambles; +@class TikzDocument; + +@interface PreviewRenderer: NSObject<RenderDelegate> { + Configuration *config; + Preambles *preambles; + TikzDocument *document; + PopplerDocument *pdfDocument; + PopplerPage *pdfPage; +} + +@property (readonly) Preambles *preambles; +@property (retain) TikzDocument *document; +@property (readonly) double height; +@property (readonly) double width; + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c; + +- (BOOL) updateWithError:(NSError**)error; +- (BOOL) update; +- (BOOL) isValid; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PreviewRenderer.m b/tikzit-1/src/gtk/PreviewRenderer.m new file mode 100644 index 0000000..28113d6 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewRenderer.m @@ -0,0 +1,250 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "PreviewRenderer.h" + +#import "CairoRenderContext.h" +#import "Configuration.h" +#import "Preambles.h" +#import "TikzDocument.h" + +@implementation PreviewRenderer + +@synthesize preambles, document; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c { + self = [super init]; + + if (self) { + document = nil; + config = [c retain]; + preambles = [p retain]; + pdfDocument = NULL; + pdfPage = NULL; + } + + return self; +} + +- (void) dealloc { + [document release]; + [config release]; + [preambles release]; + + if (pdfDocument) { + g_object_unref (pdfDocument); + pdfDocument = NULL; + } + if (pdfPage) { + g_object_unref (pdfPage); + pdfPage = NULL; + } + + [super dealloc]; +} + +- (BOOL) update { + NSError *error = nil; + BOOL result = [self updateWithError:&error]; + if (error) { + logError (error, @"Could not update preview"); + if ([error code] == TZ_ERR_TOOL_FAILED) { + NSLog (@"Output: %@", [[error userInfo] objectForKey:TZToolOutputErrorKey]); + } + } + return result; +} + +- (BOOL) updateWithError:(NSError**)error { + if (document == nil) { + if (error) { + *error = [NSError errorWithMessage:@"No document given" code:TZ_ERR_BADSTATE]; + } + if (pdfDocument) { + g_object_unref (pdfDocument); + pdfDocument = NULL; + } + if (pdfPage) { + g_object_unref (pdfPage); + pdfPage = NULL; + } + return NO; + } + + NSString *tex = [preambles buildDocumentForTikz:[document tikz]]; + + NSString *tempDir = [[NSFileManager defaultManager] createTempDirectoryWithError:error]; + if (!tempDir) { + if (error) { + *error = [NSError errorWithMessage:@"Could not create temporary directory" code:TZ_ERR_IO cause:*error]; + } + return NO; + } + + // write tex code to temporary file + NSString *texFile = [NSString stringWithFormat:@"%@/tikzit.tex", tempDir]; + NSString *pdfFile = [NSString stringWithFormat:@"file://%@/tikzit.pdf", tempDir]; + [tex writeToFile:texFile atomically:YES]; + + NSTask *latexTask = [[NSTask alloc] init]; + [latexTask setCurrentDirectoryPath:tempDir]; + + // GNUStep is clever enough to use PATH + NSString *path = [config stringEntry:@"pdflatex" + inGroup:@"Previews" + withDefault:@"pdflatex"]; + [latexTask setLaunchPath:path]; + + NSArray *args = [NSArray arrayWithObjects: + @"-fmt=latex", + @"-output-format=pdf", + @"-interaction=nonstopmode", + @"-halt-on-error", + texFile, + nil]; + [latexTask setArguments:args]; + + NSPipe *pout = [NSPipe pipe]; + [latexTask setStandardOutput:pout]; + + NSFileHandle *latexOut = [pout fileHandleForReading]; + + BOOL success = NO; + + NS_DURING { + [latexTask launch]; + [latexTask waitUntilExit]; + } NS_HANDLER { + NSLog(@"Failed to run '%@'; error was: %@", path, [localException reason]); + (void)localException; + if (error) { + NSString *desc = [NSString stringWithFormat:@"Failed to run '%@'", path]; + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:desc forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_IO userInfo:errorDetail]; + } + + // remove all temporary files + [[NSFileManager defaultManager] removeFileAtPath:tempDir handler:NULL]; + [latexTask release]; + + return NO; + } NS_ENDHANDLER + + if ([latexTask terminationStatus] != 0) { + if (error) { + NSData *data = [latexOut readDataToEndOfFile]; + NSString *str = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:@"Generating a PDF file with pdflatex failed" forKey:NSLocalizedDescriptionKey]; + [errorDetail setValue:str forKey:TZToolOutputErrorKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_TOOL_FAILED userInfo:errorDetail]; + [str release]; + } + } else { + // load pdf document + GError* gerror = NULL; + pdfDocument = poppler_document_new_from_file([pdfFile UTF8String], NULL, &gerror); + if (!pdfDocument) { + if (error) { + *error = [NSError errorWithMessage:[NSString stringWithFormat:@"Could not load PDF document", pdfFile] + code:TZ_ERR_IO + cause:[NSError errorWithGError:gerror]]; + } + g_error_free(gerror); + } else { + pdfPage = poppler_document_get_page(pdfDocument, 0); + if(!pdfPage) { + if (error) { + *error = [NSError errorWithMessage:@"Could not open first page of PDF document" + code:TZ_ERR_OTHER]; + } + g_object_unref(pdfDocument); + } else { + success = YES; + } + } + } + + // remove all temporary files + [[NSFileManager defaultManager] removeFileAtPath:tempDir handler:NULL]; + [latexTask release]; + + return success; +} + +- (BOOL) isValid { + return pdfPage ? YES : NO; +} + +- (double) width { + double w = 0.0; + if (pdfPage) + poppler_page_get_size(pdfPage, &w, NULL); + return w; +} + +- (double) height { + double h = 0.0; + if (pdfPage) + poppler_page_get_size(pdfPage, NULL, &h); + return h; +} + +- (void) renderWithContext:(id<RenderContext>)c onSurface:(id<Surface>)surface { + if (document != nil && pdfPage) { + CairoRenderContext *context = (CairoRenderContext*)c; + + double w = 0.0; + double h = 0.0; + poppler_page_get_size(pdfPage, &w, &h); + if (w==0) w = 1.0; + if (h==0) h = 1.0; + + double scale = ([surface height] / h) * 0.95; + if (w * scale > [surface width]) scale = [surface width] / w; + [[surface transformer] setScale:scale]; + + NSPoint origin; + w *= scale; + h *= scale; + origin.x = ([surface width] - w) / 2; + origin.y = ([surface height] - h) / 2; + + [[surface transformer] setOrigin:origin]; + + [context saveState]; + [context applyTransform:[surface transformer]]; + + // white-out + [context paintWithColor:WhiteRColor]; + + poppler_page_render (pdfPage, [context cairoContext]); + + [context restoreState]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PreviewWindow.h b/tikzit-1/src/gtk/PreviewWindow.h new file mode 100644 index 0000000..8bcd3e5 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewWindow.h @@ -0,0 +1,51 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class Preambles; +@class PreviewRenderer; +@class TikzDocument; +@class WidgetSurface; + +@interface PreviewWindow: NSObject { + PreviewRenderer *previewer; + GtkWindow *window; + WidgetSurface *surface; + GtkWindow *parent; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c; + +- (void) setParentWindow:(GtkWindow*)parent; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)doc; + +- (BOOL) update; + +- (void) present; +- (void) show; +- (void) hide; +- (BOOL) isVisible; +- (void) setVisible:(BOOL)visible; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PreviewWindow.m b/tikzit-1/src/gtk/PreviewWindow.m new file mode 100644 index 0000000..fc0e7a3 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewWindow.m @@ -0,0 +1,195 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "PreviewWindow.h" + +#import "Preambles.h" +#import "PreviewRenderer.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + +#import "gtkhelpers.h" + +@interface PreviewWindow (Private) +- (BOOL) updateOrShowError; +- (void) updateDefaultSize; +@end + +// {{{ API + +@implementation PreviewWindow + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c { + self = [super init]; + + if (self) { + parent = NULL; + previewer = [[PreviewRenderer alloc] initWithPreambles:p config:c]; + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title (window, "Preview"); + gtk_window_set_resizable (window, TRUE); + gtk_window_set_default_size (window, 150.0, 150.0); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), + NULL); + + GtkWidget *pdfArea = gtk_drawing_area_new (); + gtk_container_add (GTK_CONTAINER (window), pdfArea); + gtk_widget_show (pdfArea); + surface = [[WidgetSurface alloc] initWithWidget:pdfArea]; + [surface setRenderDelegate:previewer]; + Transformer *t = [surface transformer]; + [t setFlippedAboutXAxis:![t isFlippedAboutXAxis]]; + } + + return self; +} + +- (void) setParentWindow:(GtkWindow*)p { + parent = p; + gtk_window_set_transient_for (window, p); + if (p != NULL) { + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + } +} + +- (TikzDocument*) document { + return [previewer document]; +} + +- (void) setDocument:(TikzDocument*)doc { + [previewer setDocument:doc]; +} + +- (void) present { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + gtk_window_present (GTK_WINDOW (window)); + [surface invalidate]; + } +} + +- (BOOL) update { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + return YES; + } + + return NO; +} + +- (void) show { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + gtk_widget_show (GTK_WIDGET (window)); + [surface invalidate]; + } +} + +- (void) hide { + gtk_widget_hide (GTK_WIDGET (window)); +} + +- (BOOL) isVisible { + gboolean visible; + g_object_get (G_OBJECT (window), "visible", &visible, NULL); + return visible ? YES : NO; +} + +- (void) setVisible:(BOOL)visible { + if (visible) { + [self show]; + } else { + [self hide]; + } +} + +- (void) dealloc { + gtk_widget_destroy (GTK_WIDGET (window)); + [previewer release]; + [surface release]; + + [super dealloc]; +} + +@end +// }}} + +@implementation PreviewWindow (Private) +- (BOOL) updateOrShowError { + NSError *error = nil; + if (![previewer updateWithError:&error]) { + GtkWindow *dparent = gtk_widget_get_visible (GTK_WIDGET (window)) + ? window + : parent; + GtkWidget *dialog = gtk_message_dialog_new (dparent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to generate the preview: %s", + [[error localizedDescription] UTF8String]); +#if GTK_CHECK_VERSION(2,22,0) + if ([error code] == TZ_ERR_TOOL_FAILED) { + GtkBox *box = GTK_BOX (gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog))); + GtkWidget *label = gtk_label_new ("pdflatex said:"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5f); + gtk_widget_show (label); + gtk_box_pack_start (box, label, FALSE, TRUE, 0); + + GtkWidget *view = gtk_text_view_new (); + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gtk_text_buffer_set_text (buffer, [[error toolOutput] UTF8String], -1); + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE); + gtk_widget_show (view); + GtkWidget *scrolledView = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledView), + GTK_POLICY_NEVER, // horiz + GTK_POLICY_ALWAYS); // vert + gtk_widget_set_size_request (scrolledView, -1, 120); + gtk_container_add (GTK_CONTAINER (scrolledView), view); + gtk_widget_show (scrolledView); + gtk_box_pack_start (box, scrolledView, TRUE, TRUE, 0); + } +#endif // GTK+ 2.22.0 + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + return NO; + } + return YES; +} + +- (void) updateDefaultSize { + double width = 150; + double height = 150; + if ([previewer isValid]) { + double pWidth = [previewer width]; + double pHeight = [previewer height]; + width = (width < pWidth + 4) ? pWidth + 4 : width; + height = (height < pHeight + 4) ? pHeight + 4 : height; + } + gtk_window_set_default_size (window, width, height); +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PropertiesPane.h b/tikzit-1/src/gtk/PropertiesPane.h new file mode 100644 index 0000000..c76efae --- /dev/null +++ b/tikzit-1/src/gtk/PropertiesPane.h @@ -0,0 +1,69 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class EdgePropertyDelegate; +@class EdgeStylesModel; +@class GraphPropertyDelegate; +@class NodePropertyDelegate; +@class NodeStylesModel; +@class PropertyListEditor; +@class StyleManager; +@class TikzDocument; + +@interface PropertiesPane: NSObject { + TikzDocument *document; + BOOL blockUpdates; + + PropertyListEditor *graphProps; + PropertyListEditor *nodeProps; + PropertyListEditor *edgeProps; + PropertyListEditor *edgeNodeProps; + + GraphPropertyDelegate *graphPropDelegate; + NodePropertyDelegate *nodePropDelegate; + EdgePropertyDelegate *edgePropDelegate; + + GtkWidget *layout; + + GtkWidget *currentPropsWidget; // no ref! + + GtkWidget *graphPropsWidget; + GtkWidget *nodePropsWidget; + GtkWidget *edgePropsWidget; + + GtkEntry *nodeLabelEntry; + GtkToggleButton *edgeNodeToggle; + GtkWidget *edgeNodePropsWidget; + GtkEntry *edgeNodeLabelEntry; + GtkEntry *edgeSourceAnchorEntry; + GtkEntry *edgeTargetAnchorEntry; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; +@property (readonly) GtkWidget *gtkWidget; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PropertiesPane.m b/tikzit-1/src/gtk/PropertiesPane.m new file mode 100644 index 0000000..ba43298 --- /dev/null +++ b/tikzit-1/src/gtk/PropertiesPane.m @@ -0,0 +1,763 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "PropertiesPane.h" + +#import "GraphElementProperty.h" +#import "PropertyListEditor.h" +#import "TikzDocument.h" + +#import "gtkhelpers.h" + +// {{{ Internal interfaces +// {{{ GTK+ helpers +static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry); +static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry); +static GtkWidget *createBoldLabel (const gchar *text); +// }}} +// {{{ GTK+ callbacks +static void node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_node_toggled_cb (GtkToggleButton *widget, PropertiesPane *pane); +static void edge_source_anchor_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_target_anchor_changed_cb (GtkEditable *widget, PropertiesPane *pane); +// }}} + +@interface PropertiesPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n; +- (void) edgeSelectionChanged:(NSNotification*)n; +- (void) graphChanged:(NSNotification*)n; +- (void) nodeLabelEdited:(NSString*)newValue; +- (void) edgeNodeLabelEdited:(NSString*)newValue; +- (void) edgeNodeToggled:(BOOL)newValue; +- (BOOL) edgeSourceAnchorEdited:(NSString*)newValue; +- (BOOL) edgeTargetAnchorEdited:(NSString*)newValue; +@end + +@interface PropertiesPane (Private) +- (void) _updatePane; +- (void) _setDisplayedWidget:(GtkWidget*)widget; +@end + +// {{{ Delegates + +@interface GraphPropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; +} +- (void) setDocument:(TikzDocument*)d; +@end + +@interface NodePropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; + Node *node; +} +- (void) setDocument:(TikzDocument*)d; +- (void) setNode:(Node*)n; +@end + +@interface EdgePropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; + Edge *edge; +} +- (void) setDocument:(TikzDocument*)d; +- (void) setEdge:(Edge*)e; +@end + +// }}} + +// }}} +// {{{ API + +@implementation PropertiesPane + +- (id) init { + self = [super init]; + + if (self) { + document = nil; + blockUpdates = NO; + + graphProps = [[PropertyListEditor alloc] init]; + graphPropDelegate = [[GraphPropertyDelegate alloc] init]; + [graphProps setDelegate:graphPropDelegate]; + + nodeProps = [[PropertyListEditor alloc] init]; + nodePropDelegate = [[NodePropertyDelegate alloc] init]; + [nodeProps setDelegate:nodePropDelegate]; + + edgeProps = [[PropertyListEditor alloc] init]; + edgePropDelegate = [[EdgePropertyDelegate alloc] init]; + [edgeProps setDelegate:edgePropDelegate]; + + edgeNodeProps = [[PropertyListEditor alloc] init]; + [edgeNodeProps setDelegate:edgePropDelegate]; + + layout = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (layout); + gtk_widget_show (layout); + + /* + * Graph properties + */ + graphPropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (graphPropsWidget); + gtk_widget_show (graphPropsWidget); + + GtkWidget *label = createBoldLabel ("Graph properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (graphPropsWidget), + label, + FALSE, FALSE, 0); + + gtk_widget_show ([graphProps widget]); + gtk_box_pack_start (GTK_BOX (graphPropsWidget), + [graphProps widget], + TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (layout), + graphPropsWidget, + TRUE, TRUE, 0); + + + /* + * Node properties + */ + nodePropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (nodePropsWidget); + gtk_box_pack_start (GTK_BOX (layout), + nodePropsWidget, + TRUE, TRUE, 0); + + label = createBoldLabel ("Node properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + label, + FALSE, FALSE, 0); + + GtkWidget *labelWidget = createLabelledEntry ("Label", &nodeLabelEntry); + gtk_widget_show (labelWidget); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + labelWidget, + FALSE, FALSE, 0); + + gtk_widget_show ([nodeProps widget]); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + [nodeProps widget], + TRUE, TRUE, 0); + + g_signal_connect (G_OBJECT (nodeLabelEntry), + "changed", + G_CALLBACK (node_label_changed_cb), + self); + + /* + * Edge properties + */ + edgePropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (edgePropsWidget); + gtk_box_pack_start (GTK_BOX (layout), + edgePropsWidget, + TRUE, TRUE, 0); + + label = createBoldLabel ("Edge properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + label, + FALSE, FALSE, 0); + + gtk_widget_show ([edgeProps widget]); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + [edgeProps widget], + TRUE, TRUE, 0); + + GtkWidget *split = gtk_hseparator_new (); + gtk_widget_show (split); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + split, + FALSE, FALSE, 0); + + GtkWidget *anchorTable = gtk_table_new (2, 2, FALSE); + + label = gtk_label_new ("Source anchor:"); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), label, + 0, 1, 0, 1); + edgeSourceAnchorEntry = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (edgeSourceAnchorEntry); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), + GTK_WIDGET (edgeSourceAnchorEntry), + 1, 2, 0, 1); + g_signal_connect (G_OBJECT (edgeSourceAnchorEntry), + "changed", + G_CALLBACK (edge_source_anchor_changed_cb), + self); + + label = gtk_label_new ("Target anchor:"); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), label, + 0, 1, 1, 2); + edgeTargetAnchorEntry = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (edgeTargetAnchorEntry); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), + GTK_WIDGET (edgeTargetAnchorEntry), + 1, 2, 1, 2); + g_signal_connect (G_OBJECT (edgeTargetAnchorEntry), + "changed", + G_CALLBACK (edge_target_anchor_changed_cb), + self); + + gtk_widget_show_all (anchorTable); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + anchorTable, + FALSE, FALSE, 0); + + split = gtk_hseparator_new (); + gtk_widget_show (split); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + split, + FALSE, FALSE, 0); + + edgeNodeToggle = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_label ("Child node")); + g_object_ref_sink (edgeNodeToggle); + gtk_widget_show (GTK_WIDGET (edgeNodeToggle)); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + GTK_WIDGET (edgeNodeToggle), + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (GTK_WIDGET (edgeNodeToggle)), + "toggled", + G_CALLBACK (edge_node_toggled_cb), + self); + + edgeNodePropsWidget = createPropsPaneWithLabelEntry(edgeNodeProps, &edgeNodeLabelEntry); + g_object_ref_sink (edgeNodePropsWidget); + g_object_ref_sink (edgeNodeLabelEntry); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + edgeNodePropsWidget, + TRUE, TRUE, 0); + g_signal_connect (G_OBJECT (edgeNodeLabelEntry), + "changed", + G_CALLBACK (edge_node_label_changed_cb), + self); + + /* + * Misc setup + */ + + [self _setDisplayedWidget:graphPropsWidget]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (graphPropsWidget); + g_object_unref (nodePropsWidget); + g_object_unref (edgePropsWidget); + + g_object_unref (nodeLabelEntry); + g_object_unref (edgeNodeToggle); + g_object_unref (edgeNodePropsWidget); + g_object_unref (edgeNodeLabelEntry); + g_object_unref (edgeSourceAnchorEntry); + g_object_unref (edgeTargetAnchorEntry); + + g_object_unref (layout); + + [graphProps release]; + [nodeProps release]; + [edgeProps release]; + [edgeNodeProps release]; + + [graphPropDelegate release]; + [nodePropDelegate release]; + [edgePropDelegate release]; + + [document release]; + + [super dealloc]; +} + +- (TikzDocument*) document { + return document; +} + +- (void) setDocument:(TikzDocument*)doc { + if (document != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + } + + [doc retain]; + [document release]; + document = doc; + + [graphPropDelegate setDocument:doc]; + [nodePropDelegate setDocument:doc]; + [edgePropDelegate setDocument:doc]; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeSelectionChanged:) + name:@"NodeSelectionChanged" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeSelectionChanged:) + name:@"EdgeSelectionChanged" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(graphChanged:) + name:@"TikzChanged" object:doc]; + } + + [self _updatePane]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (layout); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (layout, visible); +} + +- (GtkWidget*) gtkWidget { + return layout; +} + +- (void) loadConfiguration:(Configuration*)config { +} + +- (void) saveConfiguration:(Configuration*)config { +} + +@end +// }}} +// {{{ Notifications + +@implementation PropertiesPane (Notifications) + +- (void) nodeSelectionChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (void) edgeSelectionChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (void) graphChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (void) nodeLabelEdited:(NSString*)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedNodes]; + if ([sel count] != 1) { + NSLog(@"Expected single node selected; got %lu", [sel count]); + return; + } + + if ([newValue isValidTikzPropertyNameOrValue]) { + Node *node = [sel anyObject]; + [document startModifyNode:node]; + [node setLabel:newValue]; + [document endModifyNode]; + } else { + widget_set_error (GTK_WIDGET (nodeLabelEntry)); + } +} + +- (void) edgeNodeLabelEdited:(NSString*)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return; + } + + Edge *edge = [sel anyObject]; + if (![edge hasEdgeNode]) { + NSLog(@"Expected edge with edge node"); + return; + } + + if ([newValue isValidTikzPropertyNameOrValue]) { + [document startModifyEdge:edge]; + [[edge edgeNode] setLabel:newValue]; + [document endModifyEdge]; + } else { + widget_set_error (GTK_WIDGET (edgeNodeLabelEntry)); + } +} + +- (void) edgeNodeToggled:(BOOL)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return; + } + + Edge *edge = [sel anyObject]; + + [document startModifyEdge:edge]; + [edge setHasEdgeNode:newValue]; + [document endModifyEdge]; +} + +- (BOOL) edgeSourceAnchorEdited:(NSString*)newValue { + if (blockUpdates) + return YES; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return YES; + } + + Edge *edge = [sel anyObject]; + if ([newValue isValidAnchor]) { + [document startModifyEdge:edge]; + [edge setSourceAnchor:newValue]; + [document endModifyEdge]; + return YES; + } else { + return NO; + } +} + +- (BOOL) edgeTargetAnchorEdited:(NSString*)newValue { + if (blockUpdates) + return YES; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return YES; + } + + Edge *edge = [sel anyObject]; + if ([newValue isValidAnchor]) { + [document startModifyEdge:edge]; + [edge setTargetAnchor:newValue]; + [document endModifyEdge]; + return YES; + } else { + return NO; + } +} + +@end +// }}} +// {{{ Private + +@implementation PropertiesPane (Private) + +- (void) _setDisplayedWidget:(GtkWidget*)widget { + if (currentPropsWidget != widget) { + if (currentPropsWidget) + gtk_widget_hide (currentPropsWidget); + currentPropsWidget = widget; + if (widget) + gtk_widget_show (widget); + } +} + +- (void) _updatePane { + blockUpdates = YES; + + BOOL editGraphProps = YES; + GraphElementData *data = [[document graph] data]; + [graphProps setData:data]; + + NSSet *nodeSel = [[document pickSupport] selectedNodes]; + if ([nodeSel count] == 1) { + Node *n = [nodeSel anyObject]; + [nodePropDelegate setNode:n]; + [nodeProps setData:[n data]]; + gtk_entry_set_text (nodeLabelEntry, [[n label] UTF8String]); + widget_clear_error (GTK_WIDGET (nodeLabelEntry)); + [self _setDisplayedWidget:nodePropsWidget]; + editGraphProps = NO; + } else { + [nodePropDelegate setNode:nil]; + [nodeProps setData:nil]; + gtk_entry_set_text (nodeLabelEntry, ""); + + NSSet *edgeSel = [[document pickSupport] selectedEdges]; + if ([edgeSel count] == 1) { + Edge *e = [edgeSel anyObject]; + [edgePropDelegate setEdge:e]; + [edgeProps setData:[e data]]; + gtk_entry_set_text (edgeSourceAnchorEntry, + [[e sourceAnchor] UTF8String]); + gtk_entry_set_text (edgeTargetAnchorEntry, + [[e targetAnchor] UTF8String]); + widget_clear_error (GTK_WIDGET (edgeSourceAnchorEntry)); + widget_clear_error (GTK_WIDGET (edgeTargetAnchorEntry)); + widget_clear_error (GTK_WIDGET (edgeNodeLabelEntry)); + if ([e hasEdgeNode]) { + gtk_toggle_button_set_active (edgeNodeToggle, TRUE); + gtk_widget_show (edgeNodePropsWidget); + gtk_entry_set_text (edgeNodeLabelEntry, [[[e edgeNode] label] UTF8String]); + [edgeNodeProps setData:[[e edgeNode] data]]; + gtk_widget_set_sensitive (edgeNodePropsWidget, TRUE); + } else { + gtk_toggle_button_set_active (edgeNodeToggle, FALSE); + gtk_widget_hide (edgeNodePropsWidget); + gtk_entry_set_text (edgeNodeLabelEntry, ""); + [edgeNodeProps setData:nil]; + gtk_widget_set_sensitive (edgeNodePropsWidget, FALSE); + } + [self _setDisplayedWidget:edgePropsWidget]; + editGraphProps = NO; + } else { + [edgePropDelegate setEdge:nil]; + [edgeProps setData:nil]; + [edgeNodeProps setData:nil]; + gtk_entry_set_text (edgeNodeLabelEntry, ""); + } + } + + if (editGraphProps) { + [self _setDisplayedWidget:graphPropsWidget]; + } + + blockUpdates = NO; +} + +@end + +// }}} +// {{{ Delegates + +@implementation GraphPropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + } + return self; +} +- (void) dealloc { + // doc is not retained + [super dealloc]; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; +} +- (BOOL)startEdit { + if ([doc graph] != nil) { + [doc startChangeGraphProperties]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endChangeGraphProperties]; +} +- (void)cancelEdit { + [doc cancelChangeGraphProperties]; +} +@end + +@implementation NodePropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + node = nil; + } + return self; +} +- (void) dealloc { + // doc,node not retained + [super dealloc]; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; + node = nil; +} +- (void) setNode:(Node*)n { + node = n; +} +- (BOOL)startEdit { + if (node != nil) { + [doc startModifyNode:node]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endModifyNode]; +} +- (void)cancelEdit { + [doc cancelModifyNode]; +} +@end + +@implementation EdgePropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + edge = nil; + } + return self; +} +- (void) dealloc { + // doc,edge not retained + [super dealloc]; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; + edge = nil; +} +- (void) setEdge:(Edge*)e { + edge = e; +} +- (BOOL)startEdit { + if (edge != nil) { + [doc startModifyEdge:edge]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endModifyEdge]; +} +- (void)cancelEdit { + [doc cancelModifyEdge]; +} +@end + +// }}} +// {{{ GTK+ helpers + +static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry) { + GtkBox *box = GTK_BOX (gtk_hbox_new (FALSE, 0)); + GtkWidget *label = gtk_label_new (labelText); + gtk_widget_show (label); + GtkWidget *entryWidget = gtk_entry_new (); + gtk_widget_show (entryWidget); + // container widget expand fill pad + gtk_box_pack_start (box, label, FALSE, TRUE, 5); + gtk_box_pack_start (box, entryWidget, TRUE, TRUE, 0); + if (entry) + *entry = GTK_ENTRY (entryWidget); + return GTK_WIDGET (box); +} + +static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry) { + GtkBox *box = GTK_BOX (gtk_vbox_new (FALSE, 6)); + + GtkWidget *labelWidget = createLabelledEntry ("Label", labelEntry); + gtk_widget_show (labelWidget); + // box widget expand fill pad + gtk_box_pack_start (box, labelWidget, FALSE, FALSE, 0); + gtk_box_pack_start (box, [props widget], TRUE, TRUE, 0); + gtk_widget_show ([props widget]); + return GTK_WIDGET (box); +} + +static GtkWidget *createBoldLabel (const gchar *text) { + GtkWidget *label = gtk_label_new (text); + label_set_bold (GTK_LABEL (label)); + return label; +} + +// }}} +// {{{ GTK+ callbacks + +static void node_label_changed_cb (GtkEditable *editable, PropertiesPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + [pane nodeLabelEdited:newValue]; + + [pool drain]; +} + +static void edge_node_label_changed_cb (GtkEditable *editable, PropertiesPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + [pane edgeNodeLabelEdited:newValue]; + + [pool drain]; +} + +static void edge_node_toggled_cb (GtkToggleButton *toggle, PropertiesPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (toggle))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gboolean newValue = gtk_toggle_button_get_active (toggle); + [pane edgeNodeToggled:newValue]; + + [pool drain]; +} + +static void edge_source_anchor_changed_cb (GtkEditable *editable, PropertiesPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + if (![pane edgeSourceAnchorEdited:newValue]) + widget_set_error (GTK_WIDGET (editable)); + + [pool drain]; +} + +static void edge_target_anchor_changed_cb (GtkEditable *editable, PropertiesPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + if (![pane edgeTargetAnchorEdited:newValue]) + widget_set_error (GTK_WIDGET (editable)); + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PropertyListEditor.h b/tikzit-1/src/gtk/PropertyListEditor.h new file mode 100644 index 0000000..2d3166a --- /dev/null +++ b/tikzit-1/src/gtk/PropertyListEditor.h @@ -0,0 +1,65 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> +#import "GraphElementData.h" +#import "GraphElementProperty.h" + +@protocol PropertyChangeDelegate +@optional +- (BOOL)startEdit; +- (void)endEdit; +- (void)cancelEdit; +@end + +@interface PropertyListEditor: NSObject { + GraphElementData *data; + NSObject<PropertyChangeDelegate> *delegate; + + GtkListStore *list; + GtkWidget *view; + GtkWidget *widget; + GtkWidget *removeButton; +} + +/*! + @property widget + @brief The widget displaying the editable list + */ +@property (readonly) GtkWidget *widget; + +/*! + @property data + @brief The GraphElementData that should be reflected in the list + */ +@property (retain) GraphElementData *data; + +/*! + @property delegate + @brief A delegate for dealing with property changes + */ +@property (retain) NSObject<PropertyChangeDelegate> *delegate; + +/*! + * Reload the properties from the data store + */ +- (void) reloadProperties; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PropertyListEditor.m b/tikzit-1/src/gtk/PropertyListEditor.m new file mode 100644 index 0000000..9760618 --- /dev/null +++ b/tikzit-1/src/gtk/PropertyListEditor.m @@ -0,0 +1,501 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "PropertyListEditor.h" +#import "gtkhelpers.h" + +// {{{ Constants + +enum { + PLM_NAME_COL = 0, + PLM_VALUE_COL, + PLM_IS_PROPERTY_COL, + PLM_PROPERTY_COL, + PLM_N_COLS +}; + +// }}} +// {{{ Internal interfaces +// {{{ Signals + +static void value_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor); +static void name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor); +static void add_prop_clicked_cb (GtkButton *button, + PropertyListEditor *editor); +static void add_atom_clicked_cb (GtkButton *button, + PropertyListEditor *editor); +static void remove_clicked_cb (GtkButton *button, + PropertyListEditor *editor); +static void text_editing_started (GtkCellRenderer *cell, + GtkCellEditable *editable, + const gchar *path, + PropertyListEditor *editor); +static void text_changed_cb (GtkEditable *editable, + PropertyListEditor *pane); +static void selection_changed_cb (GtkTreeSelection *selection, + PropertyListEditor *pane); + +// }}} +// {{{ Private + +@interface PropertyListEditor (Private) +- (void) updatePath:(gchar*)path withValue:(NSString*)newText; +- (void) updatePath:(gchar*)path withName:(NSString*)newText; +- (void) addProperty; +- (void) addAtom; +- (void) removeSelected; +- (void) selectionCountChanged:(int)newSelectionCount; +- (void) clearStore; +@end + +// }}} +// }}} +// {{{ API + +@implementation PropertyListEditor + +- (id) init { + self = [super init]; + + if (self) { + list = gtk_list_store_new (PLM_N_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); + g_object_ref_sink (G_OBJECT (list)); + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list)); + g_object_ref_sink (G_OBJECT (view)); + GtkWidget *scrolledview = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledview), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (scrolledview), view); + gtk_widget_set_size_request (view, -1, 150); + data = nil; + delegate = nil; + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "editable", TRUE, + NULL); + column = gtk_tree_view_column_new_with_attributes ("Key/Atom", + renderer, + "text", PLM_NAME_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + g_signal_connect (G_OBJECT (renderer), + "editing-started", + G_CALLBACK (text_editing_started), + self); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (name_edited_cb), + self); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Value", + renderer, + "text", PLM_VALUE_COL, + "editable", PLM_IS_PROPERTY_COL, + "sensitive", PLM_IS_PROPERTY_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (value_edited_cb), + self); + + widget = gtk_vbox_new (FALSE, 0); + gtk_box_set_spacing (GTK_BOX (widget), 6); + g_object_ref_sink (G_OBJECT (widget)); + + GtkWidget *listFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (listFrame), scrolledview); + gtk_widget_show (listFrame); + gtk_container_add (GTK_CONTAINER (widget), listFrame); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (widget), GTK_WIDGET (buttonBox), FALSE, FALSE, 0); + + GtkWidget *addPropButton = gtk_button_new (); + //gtk_widget_set_size_request (addPropButton, 27, 27); + gtk_widget_set_tooltip_text (addPropButton, "Add property"); + GtkWidget *addPropContents = gtk_hbox_new(FALSE, 0); + GtkWidget *addPropIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addPropContents), addPropIcon); + gtk_container_add (GTK_CONTAINER (addPropContents), gtk_label_new ("P")); + gtk_container_add (GTK_CONTAINER (addPropButton), addPropContents); + gtk_box_pack_start (buttonBox, addPropButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addPropButton), + "clicked", + G_CALLBACK (add_prop_clicked_cb), + self); + + GtkWidget *addAtomButton = gtk_button_new (); + //gtk_widget_set_size_request (addAtomButton, 27, 27); + gtk_widget_set_tooltip_text (addAtomButton, "Add atom"); + GtkWidget *addAtomContents = gtk_hbox_new(FALSE, 0); + GtkWidget *addAtomIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addAtomContents), addAtomIcon); + gtk_container_add (GTK_CONTAINER (addAtomContents), gtk_label_new ("A")); + gtk_container_add (GTK_CONTAINER (addAtomButton), addAtomContents); + gtk_box_pack_start (buttonBox, addAtomButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addAtomButton), + "clicked", + G_CALLBACK (add_atom_clicked_cb), + self); + + removeButton = gtk_button_new (); + g_object_ref_sink (G_OBJECT (removeButton)); + gtk_widget_set_sensitive (removeButton, FALSE); + //gtk_widget_set_size_request (removeButton, 27, 27); + gtk_widget_set_tooltip_text (removeButton, "Remove selected"); + GtkWidget *removeIcon = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (removeButton), removeIcon); + gtk_box_pack_start (buttonBox, removeButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeButton), + "clicked", + G_CALLBACK (remove_clicked_cb), + self); + + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + g_signal_connect (G_OBJECT (selection), + "changed", + G_CALLBACK (selection_changed_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + gtk_widget_show_all (scrolledview); + + gtk_widget_set_sensitive (widget, FALSE); + } + + return self; +} + +- (void) dealloc { + [self clearStore]; + + [data release]; + [delegate release]; + + g_object_unref (list); + g_object_unref (view); + g_object_unref (widget); + g_object_unref (removeButton); + + [super dealloc]; +} + +@synthesize widget, delegate; + +- (GraphElementData*) data { return data; } +- (void) setData:(GraphElementData*)d { + [d retain]; + [data release]; + data = d; + [self reloadProperties]; + gtk_widget_set_sensitive (widget, data != nil); +} + +- (void) reloadProperties { + [self clearStore]; + int pos = 0; + for (GraphElementProperty *p in data) { + GtkTreeIter iter; + [p retain]; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, [[p key] UTF8String], + PLM_VALUE_COL, [[p value] UTF8String], + PLM_IS_PROPERTY_COL, ![p isAtom], + PLM_PROPERTY_COL, (void *)p, + -1); + ++pos; + } +} + +@end + +// }}} +// {{{ Private + +@implementation PropertyListEditor (Private) +- (void) updatePath:(gchar*)pathStr withValue:(NSString*)newText { + if (![newText isValidTikzPropertyNameOrValue]) + return; + + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string (pathStr); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path)) { + gtk_tree_path_free (path); + return; + } + + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + + if (![prop isAtom]) { + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [prop setValue:newText]; + gtk_list_store_set (list, &iter, + PLM_VALUE_COL, [newText UTF8String], + -1); + [delegate endEdit]; + } + } + + gtk_tree_path_free (path); +} + +- (void) updatePath:(gchar*)pathStr withName:(NSString*)newText { + if (![newText isValidTikzPropertyNameOrValue]) + return; + + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string (pathStr); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path)) { + gtk_tree_path_free (path); + return; + } + + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + if ([newText isEqualToString:@""]) { + [data removeObjectIdenticalTo:prop]; + gtk_list_store_remove (list, &iter); + [prop release]; + } else { + [prop setKey:newText]; + gtk_list_store_set (list, &iter, + PLM_NAME_COL, [newText UTF8String], + -1); + } + [delegate endEdit]; + } + + gtk_tree_path_free (path); +} + +- (void) addProperty { + GtkTreeIter iter; + GraphElementProperty *p = [[GraphElementProperty alloc] initWithPropertyValue:@"" forKey:@"new property"]; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data addObject:p]; + gint pos = [data count] - 1; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, "new property", + PLM_VALUE_COL, "", + PLM_IS_PROPERTY_COL, TRUE, + PLM_PROPERTY_COL, (void *)p, + -1); + [delegate endEdit]; + } else { + [p release]; + } +} + +- (void) addAtom { + GtkTreeIter iter; + GraphElementProperty *p = [[GraphElementProperty alloc] initWithAtomName:@"new atom"]; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data addObject:p]; + gint pos = [data count] - 1; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, "new atom", + PLM_VALUE_COL, [[p value] UTF8String], + PLM_IS_PROPERTY_COL, FALSE, + PLM_PROPERTY_COL, (void *)p, + -1); + [delegate endEdit]; + } else { + [p release]; + } +} + +- (void) removeSelected { + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + GList *selPaths = gtk_tree_selection_get_selected_rows (selection, NULL); + GList *selIters = NULL; + + // Convert to iters, as GtkListStore has persistent iters + GList *curr = selPaths; + while (curr != NULL) { + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (list), + &iter, + (GtkTreePath*)curr->data); + selIters = g_list_prepend (selIters, gtk_tree_iter_copy (&iter)); + curr = g_list_next (curr); + } + + // remove all iters + curr = selIters; + while (curr != NULL) { + GtkTreeIter *iter = (GtkTreeIter*)curr->data; + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data removeObjectIdenticalTo:prop]; + gtk_list_store_remove (list, iter); + [prop release]; + [delegate endEdit]; + } + curr = g_list_next (curr); + } + + g_list_foreach (selIters, (GFunc) gtk_tree_iter_free, NULL); + g_list_free (selIters); + g_list_foreach (selPaths, (GFunc) gtk_tree_path_free, NULL); + g_list_free (selPaths); +} + +- (void) selectionCountChanged:(int)count { + gtk_widget_set_sensitive (removeButton, count > 0); +} + +- (void) clearStore { + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list), &iter)) { + do { + void *prop; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &prop, + -1); + [(id)prop release]; + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &iter)); + gtk_list_store_clear (list); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void value_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor updatePath:path withValue:[NSString stringWithUTF8String:new_text]]; + [pool drain]; +} + +static void name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor updatePath:path withName:[NSString stringWithUTF8String:new_text]]; + [pool drain]; +} + +static void add_prop_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addProperty]; + [pool drain]; +} + +static void add_atom_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addAtom]; + [pool drain]; +} + +static void remove_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor removeSelected]; + [pool drain]; +} + +static void text_editing_started (GtkCellRenderer *cell, + GtkCellEditable *editable, + const gchar *path, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if (GTK_IS_EDITABLE (editable) && GTK_IS_WIDGET (editable)) { + g_signal_handlers_disconnect_by_func (G_OBJECT (editable), + G_CALLBACK (text_changed_cb), + editor); + widget_clear_error (GTK_WIDGET (editable)); + g_signal_connect (G_OBJECT (editable), + "changed", + G_CALLBACK (text_changed_cb), + editor); + } + + [pool drain]; +} + +static void text_changed_cb (GtkEditable *editable, PropertyListEditor *pane) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + if (![newValue isValidTikzPropertyNameOrValue]) { + widget_set_error (GTK_WIDGET (editable)); + } else { + widget_clear_error (GTK_WIDGET (editable)); + } + + [pool drain]; +} + +static void selection_changed_cb (GtkTreeSelection *selection, + PropertyListEditor *pane) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int selcount = gtk_tree_selection_count_selected_rows (selection); + [pane selectionCountChanged:selcount]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/RecentManager.h b/tikzit-1/src/gtk/RecentManager.h new file mode 100644 index 0000000..e2c2793 --- /dev/null +++ b/tikzit-1/src/gtk/RecentManager.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" + +@interface RecentManager: NSObject { +} + ++ (RecentManager*) defaultManager; + +- (void)addRecentFile:(NSString*)path; +- (void)removeRecentFile:(NSString*)path; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/RecentManager.m b/tikzit-1/src/gtk/RecentManager.m new file mode 100644 index 0000000..c6074c6 --- /dev/null +++ b/tikzit-1/src/gtk/RecentManager.m @@ -0,0 +1,74 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "RecentManager.h" +#import <gtk/gtk.h> + +static RecentManager *defMan = nil; + +@implementation RecentManager +- (id) init { + self = [super init]; + return self; +} + ++ (RecentManager*) defaultManager { + if (defMan == nil) { + defMan = [[self alloc] init]; + } + return defMan; +} + +- (void)addRecentFile:(NSString*)path { + NSError *error = nil; + gchar *uri = [path glibUriWithError:&error]; + if (error) { + logError (error, @"Could not add recent file"); + return; + } + + GtkRecentData recent_data; + recent_data.display_name = NULL; + recent_data.description = NULL; + recent_data.mime_type = "text/x-tikz"; + recent_data.app_name = (gchar *) g_get_application_name (); + recent_data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); + recent_data.groups = NULL; + recent_data.is_private = FALSE; + + gtk_recent_manager_add_full (gtk_recent_manager_get_default(), uri, &recent_data); + + g_free (uri); + g_free (recent_data.app_exec); +} + +- (void)removeRecentFile:(NSString*)path { + NSError *error = nil; + gchar *uri = [path glibUriWithError:&error]; + if (error) { + logError (error, @"Could not remove recent file"); + return; + } + + gtk_recent_manager_remove_item (gtk_recent_manager_get_default(), uri, NULL); + + g_free (uri); +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/SelectTool.h b/tikzit-1/src/gtk/SelectTool.h new file mode 100644 index 0000000..65f511a --- /dev/null +++ b/tikzit-1/src/gtk/SelectTool.h @@ -0,0 +1,63 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Tool.h" + +@class Edge; +@class Node; + +// FIXME: replace this with delegates +typedef enum { + QuietState, + SelectBoxState, + ToggleSelectState, + MoveSelectedNodesState, + DragEdgeControlPoint1, + DragEdgeControlPoint2 +} SelectToolState; + +typedef enum { + DragSelectsNodes = 1, + DragSelectsEdges = 2, + DragSelectsBoth = DragSelectsNodes | DragSelectsEdges +} DragSelectMode; + +@interface SelectTool : NSObject <Tool> { + GraphRenderer *renderer; + SelectToolState state; + float edgeFuzz; + DragSelectMode dragSelectMode; + NSPoint dragOrigin; + Node *leaderNode; + NSPoint oldLeaderPos; + Edge *modifyEdge; + NSRect selectionBox; + NSMutableSet *selectionBoxContents; + + GtkWidget *configWidget; + GSList *dragSelectModeButtons; +} + +@property (assign) float edgeFuzz; +@property (assign) DragSelectMode dragSelectMode; + +- (id) init; ++ (id) tool; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/SelectTool.m b/tikzit-1/src/gtk/SelectTool.m new file mode 100644 index 0000000..b3121ae --- /dev/null +++ b/tikzit-1/src/gtk/SelectTool.m @@ -0,0 +1,590 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "SelectTool.h" + +#import "Configuration.h" +#import "Edge+Render.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +#import <gdk/gdkkeysyms.h> + +#define DRAG_SELECT_MODE_KEY "tikzit-drag-select-mode" + +static const InputMask unionSelectMask = ShiftMask; + +static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool); + +@interface SelectTool (Private) +- (TikzDocument*) doc; +- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to; +- (void) deselectAllNodes; +- (void) deselectAllEdges; +- (void) deselectAll; +- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p; +- (void) lookForControlPointAt:(NSPoint)pos; +- (void) setSelectionBox:(NSRect)box; +- (void) clearSelectionBox; +- (BOOL) selectionBoxContainsNode:(Node*)node; +@end + +@implementation SelectTool +- (NSString*) name { return @"Select"; } +- (const gchar*) stockId { return TIKZIT_STOCK_SELECT; } +- (NSString*) helpText { return @"Select, move and edit nodes and edges"; } +- (NSString*) shortcut { return @"s"; } +@synthesize configurationWidget=configWidget; +@synthesize edgeFuzz; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + self = [super init]; + + if (self) { + state = QuietState; + edgeFuzz = 3.0f; + dragSelectMode = DragSelectsNodes; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Drag selects:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *nodeOpt = gtk_radio_button_new_with_label (NULL, "nodes (N)"); + g_object_set_data (G_OBJECT (nodeOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsNodes); + gtk_box_pack_start (GTK_BOX (configWidget), + nodeOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (nodeOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + + GtkWidget *edgeOpt = gtk_radio_button_new_with_label ( + gtk_radio_button_get_group (GTK_RADIO_BUTTON (nodeOpt)), + "edges (E)"); + g_object_set_data (G_OBJECT (edgeOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsEdges); + gtk_box_pack_start (GTK_BOX (configWidget), + edgeOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (edgeOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + + GtkWidget *bothOpt = gtk_radio_button_new_with_label ( + gtk_radio_button_get_group (GTK_RADIO_BUTTON (edgeOpt)), + "both (B)"); + g_object_set_data (G_OBJECT (bothOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsBoth); + gtk_box_pack_start (GTK_BOX (configWidget), + bothOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (bothOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + dragSelectModeButtons = gtk_radio_button_get_group (GTK_RADIO_BUTTON (bothOpt)); + + gtk_widget_show_all (configWidget); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [leaderNode release]; + [modifyEdge release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (DragSelectMode) dragSelectMode { + return dragSelectMode; +} + +- (void) setDragSelectMode:(DragSelectMode)mode { + if (dragSelectMode == mode) + return; + + dragSelectMode = mode; + + GSList *entry = dragSelectModeButtons; + while (entry) { + GtkToggleButton *button = GTK_TOGGLE_BUTTON (entry->data); + DragSelectMode buttonMode = + (DragSelectMode) g_object_get_data ( + G_OBJECT (button), + DRAG_SELECT_MODE_KEY); + if (buttonMode == dragSelectMode) { + gtk_toggle_button_set_active (button, TRUE); + break; + } + + entry = g_slist_next (entry); + } +} + +- (GraphRenderer*) activeRenderer { return renderer; } +- (void) setActiveRenderer:(GraphRenderer*)r { + if (r == renderer) + return; + + [r retain]; + [renderer release]; + renderer = r; + + state = QuietState; +} + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + + // we should already be in a quiet state, but no harm in making sure + state = QuietState; + + modifyEdge = nil; + [self lookForControlPointAt:pos]; + + if (modifyEdge == nil) { + // we didn't find a control point + + BOOL unionSelect = (mask & unionSelectMask); + + leaderNode = [renderer anyNodeAt:pos]; + // if we hit a node, deselect other nodes (if Shift is up) and go to move mode + if (leaderNode != nil) { + BOOL alreadySelected = [[self doc] isNodeSelected:leaderNode]; + if (!unionSelect && !alreadySelected) { + [self deselectAll]; + } + if (unionSelect && alreadySelected) { + state = ToggleSelectState; + } else { + [[[self doc] pickSupport] selectNode:leaderNode]; + state = MoveSelectedNodesState; + oldLeaderPos = [leaderNode point]; + [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]]; + } + } + + // if mouse did not hit a node, check if mouse hit an edge + if (leaderNode == nil) { + Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz]; + if (edge != nil) { + BOOL alreadySelected = [[self doc] isEdgeSelected:edge]; + if (!unionSelect) { + [self deselectAll]; + } + if (unionSelect && alreadySelected) { + [[[self doc] pickSupport] deselectEdge:edge]; + } else { + [[[self doc] pickSupport] selectEdge:edge]; + } + } else { + // if mouse did not hit anything, put us in box mode + if (!unionSelect) { + [self deselectAll]; + } + [renderer clearHighlightedNodes]; + state = SelectBoxState; + } + } + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + + Transformer *transformer = [renderer transformer]; + + if (state == ToggleSelectState) { + state = MoveSelectedNodesState; + oldLeaderPos = [leaderNode point]; + [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]]; + } + + if (state == SelectBoxState) { + [self setSelectionBox:NSRectAroundPoints(dragOrigin, pos)]; + + NSEnumerator *enumerator = [[self doc] nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + NSPoint nodePos = [transformer toScreen:[node point]]; + if (NSPointInRect(nodePos, selectionBox)) { + [renderer setNode:node highlighted:YES]; + } else { + [renderer setNode:node highlighted:NO]; + } + } + } else if (state == MoveSelectedNodesState) { + if (leaderNode != nil) { + [self shiftNodesByMovingLeader:leaderNode to:pos]; + NSPoint shiftSoFar; + shiftSoFar.x = [leaderNode point].x - oldLeaderPos.x; + shiftSoFar.y = [leaderNode point].y - oldLeaderPos.y; + [[self doc] shiftNodesUpdate:shiftSoFar]; + } + } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) { + // invalidate once before we start changing it: we may be shrinking + // the control circles + [[self doc] modifyEdgeCheckPoint]; + if (state == DragEdgeControlPoint1) { + [modifyEdge moveCp1To:[transformer fromScreen:pos] + withWeightCourseness:0.1f + andBendCourseness:15 + forceLinkControlPoints:(mask & ControlMask)]; + } else { + [modifyEdge moveCp2To:[transformer fromScreen:pos] + withWeightCourseness:0.1f + andBendCourseness:15 + forceLinkControlPoints:(mask & ControlMask)]; + } + [[self doc] modifyEdgeCheckPoint]; + } +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + if (state == SelectBoxState) { + PickSupport *ps = [[self doc] pickSupport]; + Transformer *transformer = [renderer transformer]; + + if (!(mask & unionSelectMask)) { + [ps deselectAllNodes]; + [ps deselectAllEdges]; + } + + Graph *graph = [[self doc] graph]; + if (dragSelectMode & DragSelectsNodes) { + for (Node *node in [graph nodes]) { + NSPoint nodePos = [transformer toScreen:[node point]]; + if (NSPointInRect(nodePos, selectionBox)) { + [ps selectNode:node]; + } + } + } + if (dragSelectMode & DragSelectsEdges) { + for (Edge *edge in [graph edges]) { + NSPoint edgePos = [transformer toScreen:[edge mid]]; + if (NSPointInRect(edgePos, selectionBox)) { + [ps selectEdge:edge]; + } + } + } + + [self clearSelectionBox]; + } else if (state == ToggleSelectState) { + [[[self doc] pickSupport] deselectNode:leaderNode]; + leaderNode = nil; + } else if (state == MoveSelectedNodesState) { + if (NSEqualPoints (oldLeaderPos, [leaderNode point])) { + [[self doc] cancelShiftNodes]; + } else { + [[self doc] endShiftNodes]; + } + leaderNode = nil; + } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) { + // FIXME: check if there was any real change + [[self doc] endModifyEdge]; + } + + state = QuietState; +} + +- (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) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_N && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsNodes]; + } else if (keyVal == GDK_KEY_E && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsEdges]; + } else if (keyVal == GDK_KEY_B && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsBoth]; + } else if (keyVal == GDK_KEY_D && (!mask || mask == ShiftMask)) { + PickSupport *ps = [[self doc] pickSupport]; + for (Node* node in [ps selectedNodes]) { + NSRect b = [node boundingRect]; + NSLog(@"%@ @ (%f,%f) {style=%@, label=%@, data=%@, bounds=(%f,%f),(%fx%f)}", + [node name], + [node point].x, + [node point].y, + [[node style] name], + [node label], + [[node data] tikzList], + b.origin.x, b.origin.y, b.size.width, b.size.height); + } + for (Edge* edge in [ps selectedEdges]) { + NSRect b = [edge boundingRect]; + NSLog(@"%@:%@->%@:%@ {\n" + @" style=%@, data=%@,\n" + @" bend=%d, weight=%f, inAngle=%d, outAngle=%d, bendMode=%d,\n" + @" head=(%f,%f), headTan=(%f,%f) leftHeadNormal=(%f,%f), rightHeadNormal=(%f,%f),\n" + @" cp1=(%f,%f),\n" + @" mid=(%f,%f), midTan=(%f,%f), leftNormal=(%f,%f), rightNormal=(%f,%f)\n" + @" cp2=(%f,%f),\n" + @" tail=(%f,%f), tailTan=(%f,%f), leftTailNormal=(%f,%f), rightTailNormal=(%f,%f),\n" + @" isSelfLoop=%s, isStraight=%s,\n" + @" bounds=(%f,%f),(%fx%f)\n" + @"}", + [[edge source] name], + [edge sourceAnchor], + [[edge target] name], + [edge targetAnchor], + [[edge style] name], + [[edge data] tikzList], + [edge bend], + [edge weight], + [edge inAngle], + [edge outAngle], + [edge bendMode], + [edge head].x, + [edge head].y, + [edge headTan].x, + [edge headTan].y, + [edge leftHeadNormal].x, + [edge leftHeadNormal].y, + [edge rightHeadNormal].x, + [edge rightHeadNormal].y, + [edge cp1].x, + [edge cp1].y, + [edge mid].x, + [edge mid].y, + [edge midTan].x, + [edge midTan].y, + [edge leftNormal].x, + [edge leftNormal].y, + [edge rightNormal].x, + [edge rightNormal].y, + [edge cp2].x, + [edge cp2].y, + [edge tail].x, + [edge tail].y, + [edge tailTan].x, + [edge tailTan].y, + [edge leftTailNormal].x, + [edge leftTailNormal].y, + [edge rightTailNormal].x, + [edge rightTailNormal].y, + [edge isSelfLoop] ? "yes" : "no", + [edge isStraight] ? "yes" : "no", + b.origin.x, b.origin.y, b.size.width, b.size.height); + } + } +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + if (!NSIsEmptyRect (selectionBox)) { + [context saveState]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:selectionBox]; + RColor fColor = MakeRColor (0.8, 0.8, 0.8, 0.2); + RColor sColor = MakeSolidRColor (0.6, 0.6, 0.6); + [context strokePathWithColor:sColor andFillWithColor:fColor]; + + [context restoreState]; + } +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *mode = [config stringEntry:@"Drag select mode" + inGroup:@"SelectTool"]; + if ([mode isEqualToString:@"nodes"]) { + [self setDragSelectMode:DragSelectsNodes]; + } else if ([mode isEqualToString:@"edges"]) { + [self setDragSelectMode:DragSelectsEdges]; + } else if ([mode isEqualToString:@"both"]) { + [self setDragSelectMode:DragSelectsBoth]; + } +} + +- (void) saveConfiguration:(Configuration*)config { + switch (dragSelectMode) { + case DragSelectsNodes: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"nodes"]; + break; + case DragSelectsEdges: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"edges"]; + break; + case DragSelectsBoth: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"both"]; + break; + } +} + +@end + +@implementation SelectTool (Private) +- (TikzDocument*) doc { + return [renderer document]; +} + +- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to { + Transformer *transformer = [renderer transformer]; + + NSPoint from = [transformer toScreen:[leader point]]; + to = [[renderer grid] snapScreenPoint:to]; + float dx = to.x - from.x; + float dy = to.y - from.y; + + for (Node *node in [[[self doc] pickSupport] selectedNodes]) { + NSPoint p = [transformer toScreen:[node point]]; + p.x += dx; + p.y += dy; + [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; + + selectionBox = NSZeroRect; + + [renderer invalidateRect:NSInsetRect (oldRect, -2, -2)]; + [renderer clearHighlightedNodes]; +} + +- (BOOL) selectionBoxContainsNode:(Node*)node { + if (!NSIsEmptyRect (selectionBox)) + return NO; + + Transformer *transf = [[renderer surface] transformer]; + NSPoint screenPt = [transf toScreen:[node point]]; + return NSPointInRect(screenPt, selectionBox); +} +@end + +static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool) { + if (gtk_toggle_button_get_active (button)) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + DragSelectMode buttonMode = + (DragSelectMode) g_object_get_data ( + G_OBJECT (button), + DRAG_SELECT_MODE_KEY); + [tool setDragSelectMode:buttonMode]; + [pool drain]; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/SelectionPane.h b/tikzit-1/src/gtk/SelectionPane.h new file mode 100644 index 0000000..57a766a --- /dev/null +++ b/tikzit-1/src/gtk/SelectionPane.h @@ -0,0 +1,56 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class EdgeStylesModel; +@class NodeStylesModel; +@class StyleManager; +@class TikzDocument; + +@interface SelectionPane: NSObject { + TikzDocument *document; + + NodeStylesModel *nodeStylesModel; + EdgeStylesModel *edgeStylesModel; + + GtkWidget *layout; + + GtkWidget *nodeStyleCombo; + GtkWidget *applyNodeStyleButton; + GtkWidget *clearNodeStyleButton; + GtkWidget *edgeStyleCombo; + GtkWidget *applyEdgeStyleButton; + GtkWidget *clearEdgeStyleButton; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; +@property (readonly) GtkWidget *gtkWidget; + +- (id) initWithStyleManager:(StyleManager*)mgr; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SelectionPane.m b/tikzit-1/src/gtk/SelectionPane.m new file mode 100644 index 0000000..2931258 --- /dev/null +++ b/tikzit-1/src/gtk/SelectionPane.m @@ -0,0 +1,432 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "SelectionPane.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "TikzDocument.h" + +#import "gtkhelpers.h" + +// {{{ Internal interfaces + +static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane); +static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane); +static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane); + +static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col); + +@interface SelectionPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n; +- (void) edgeSelectionChanged:(NSNotification*)n; +@end + +@interface SelectionPane (Private) +- (void) _updateNodeStyleButtons; +- (void) _updateEdgeStyleButtons; +- (NodeStyle*) _selectedNodeStyle; +- (EdgeStyle*) _selectedEdgeStyle; +- (void) _applyNodeStyle; +- (void) _clearNodeStyle; +- (void) _applyEdgeStyle; +- (void) _clearEdgeStyle; +@end + +// }}} +// {{{ API + +@implementation SelectionPane + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm] + andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + nodeStylesModel = [nsm retain]; + edgeStylesModel = [esm retain]; + + layout = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (layout); + gtk_widget_show (layout); + + GtkWidget *label = gtk_label_new ("Selection"); + label_set_bold (GTK_LABEL (label)); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (layout), label, + FALSE, FALSE, 0); + + GtkWidget *lvl1_box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (layout), lvl1_box, + FALSE, FALSE, 3); + + nodeStyleCombo = gtk_combo_box_new_with_model ([nodeStylesModel model]); + g_object_ref_sink (nodeStyleCombo); + setup_style_cell_layout (GTK_CELL_LAYOUT (nodeStyleCombo), + NODE_STYLES_ICON_COL, + NODE_STYLES_NAME_COL); + g_signal_connect (G_OBJECT (nodeStyleCombo), + "changed", + G_CALLBACK (node_style_changed_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl1_box), nodeStyleCombo, + FALSE, FALSE, 0); + + GtkWidget *lvl2_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box, + FALSE, FALSE, 0); + + applyNodeStyleButton = gtk_button_new_with_label ("Apply"); + g_object_ref_sink (applyNodeStyleButton); + gtk_widget_set_tooltip_text (applyNodeStyleButton, "Apply style to selected nodes"); + gtk_widget_set_sensitive (applyNodeStyleButton, FALSE); + g_signal_connect (G_OBJECT (applyNodeStyleButton), + "clicked", + G_CALLBACK (apply_node_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), applyNodeStyleButton, + FALSE, FALSE, 0); + + clearNodeStyleButton = gtk_button_new_with_label ("Clear"); + g_object_ref_sink (clearNodeStyleButton); + gtk_widget_set_tooltip_text (clearNodeStyleButton, "Clear style from selected nodes"); + gtk_widget_set_sensitive (clearNodeStyleButton, FALSE); + g_signal_connect (G_OBJECT (clearNodeStyleButton), + "clicked", + G_CALLBACK (clear_node_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), clearNodeStyleButton, + FALSE, FALSE, 0); + + lvl1_box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (layout), lvl1_box, + FALSE, FALSE, 3); + + edgeStyleCombo = gtk_combo_box_new_with_model ([edgeStylesModel model]); + g_object_ref_sink (edgeStyleCombo); + setup_style_cell_layout (GTK_CELL_LAYOUT (edgeStyleCombo), + EDGE_STYLES_ICON_COL, + EDGE_STYLES_NAME_COL); + g_signal_connect (G_OBJECT (edgeStyleCombo), + "changed", + G_CALLBACK (edge_style_changed_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl1_box), edgeStyleCombo, + FALSE, FALSE, 0); + + lvl2_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box, + FALSE, FALSE, 0); + + applyEdgeStyleButton = gtk_button_new_with_label ("Apply"); + g_object_ref_sink (applyEdgeStyleButton); + gtk_widget_set_tooltip_text (applyEdgeStyleButton, "Apply style to selected edges"); + gtk_widget_set_sensitive (applyEdgeStyleButton, FALSE); + g_signal_connect (G_OBJECT (applyEdgeStyleButton), + "clicked", + G_CALLBACK (apply_edge_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), applyEdgeStyleButton, + FALSE, FALSE, 0); + + clearEdgeStyleButton = gtk_button_new_with_label ("Clear"); + g_object_ref_sink (clearEdgeStyleButton); + gtk_widget_set_tooltip_text (clearEdgeStyleButton, "Clear style from selected edges"); + gtk_widget_set_sensitive (clearEdgeStyleButton, FALSE); + g_signal_connect (G_OBJECT (clearEdgeStyleButton), + "clicked", + G_CALLBACK (clear_edge_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), clearEdgeStyleButton, + FALSE, FALSE, 0); + + gtk_widget_show_all (layout); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nodeStyleCombo); + g_object_unref (applyNodeStyleButton); + g_object_unref (clearNodeStyleButton); + g_object_unref (edgeStyleCombo); + g_object_unref (applyEdgeStyleButton); + g_object_unref (clearEdgeStyleButton); + + g_object_unref (layout); + + [nodeStylesModel release]; + [edgeStylesModel release]; + + [document release]; + + [super dealloc]; +} + +- (TikzDocument*) document { + return document; +} + +- (void) setDocument:(TikzDocument*)doc { + if (document != nil) { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:nil + object:[document pickSupport]]; + } + + [doc retain]; + [document release]; + document = doc; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeSelectionChanged:) + name:@"NodeSelectionChanged" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeSelectionChanged:) + name:@"EdgeSelectionChanged" object:[doc pickSupport]]; + } + + [self _updateNodeStyleButtons]; + [self _updateEdgeStyleButtons]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (layout); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (layout, visible); +} + +- (GtkWidget*) gtkWidget { + return layout; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *nodeStyleName = [config stringEntry:@"SelectedNodeStyle" + inGroup:@"SelectionPane" + withDefault:nil]; + NodeStyle *nodeStyle = [[nodeStylesModel styleManager] nodeStyleForName:nodeStyleName]; + if (nodeStyle == nil) { + gtk_combo_box_set_active (GTK_COMBO_BOX (nodeStyleCombo), -1); + } else { + GtkTreeIter *iter = [nodeStylesModel iterFromStyle:nodeStyle]; + if (iter) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (nodeStyleCombo), iter); + gtk_tree_iter_free (iter); + } + } + + NSString *edgeStyleName = [config stringEntry:@"SelectedEdgeStyle" + inGroup:@"SelectionPane" + withDefault:nil]; + EdgeStyle *edgeStyle = [[edgeStylesModel styleManager] edgeStyleForName:edgeStyleName]; + if (edgeStyle == nil) { + gtk_combo_box_set_active (GTK_COMBO_BOX (edgeStyleCombo), -1); + } else { + GtkTreeIter *iter = [edgeStylesModel iterFromStyle:edgeStyle]; + if (iter) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (edgeStyleCombo), iter); + gtk_tree_iter_free (iter); + } + } +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"SelectedNodeStyle" + inGroup:@"SelectionPane" + value:[[self _selectedNodeStyle] name]]; + [config setStringEntry:@"SelectedEdgeStyle" + inGroup:@"SelectionPane" + value:[[self _selectedEdgeStyle] name]]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation SelectionPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n { + [self _updateNodeStyleButtons]; +} + +- (void) edgeSelectionChanged:(NSNotification*)n { + [self _updateEdgeStyleButtons]; +} +@end + +// }}} +// {{{ Private + +@implementation SelectionPane (Private) +- (void) _updateNodeStyleButtons { + gboolean hasNodeSelection = [[[document pickSupport] selectedNodes] count] > 0; + + gtk_widget_set_sensitive (applyNodeStyleButton, + hasNodeSelection && [self _selectedNodeStyle] != nil); + gtk_widget_set_sensitive (clearNodeStyleButton, hasNodeSelection); +} + +- (void) _updateEdgeStyleButtons { + gboolean hasEdgeSelection = [[[document pickSupport] selectedEdges] count] > 0; + + gtk_widget_set_sensitive (applyEdgeStyleButton, + hasEdgeSelection && [self _selectedEdgeStyle] != nil); + gtk_widget_set_sensitive (clearEdgeStyleButton, hasEdgeSelection); +} + +- (NodeStyle*) _selectedNodeStyle { + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (nodeStyleCombo), &iter)) { + return [nodeStylesModel styleFromIter:&iter]; + } else { + return nil; + } +} + +- (EdgeStyle*) _selectedEdgeStyle { + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (edgeStyleCombo), &iter)) { + return [edgeStylesModel styleFromIter:&iter]; + } else { + return nil; + } +} + +- (void) _applyNodeStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + NodeStyle *style = [self _selectedNodeStyle]; + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:style]; + } + + [document endModifyNodes]; +} + +- (void) _clearNodeStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:nil]; + } + + [document endModifyNodes]; +} + +- (void) _applyEdgeStyle { + [document startModifyEdges:[[document pickSupport] selectedEdges]]; + + EdgeStyle *style = [self _selectedEdgeStyle]; + for (Edge *edge in [[document pickSupport] selectedEdges]) { + [edge setStyle:style]; + } + + [document endModifyEdges]; +} + +- (void) _clearEdgeStyle { + [document startModifyEdges:[[document pickSupport] selectedEdges]]; + + for (Edge *edge in [[document pickSupport] selectedEdges]) { + [edge setStyle:nil]; + } + + [document endModifyEdges]; +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _updateNodeStyleButtons]; + [pool drain]; +} + +static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _applyNodeStyle]; + [pool drain]; +} + +static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _clearNodeStyle]; + [pool drain]; +} + +static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _updateEdgeStyleButtons]; + [pool drain]; +} + +static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _applyEdgeStyle]; + [pool drain]; +} + +static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _clearEdgeStyle]; + [pool drain]; +} + +// }}} +// +static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col) { + gtk_cell_layout_clear (cell_layout); + GtkCellRenderer *pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (cell_layout, pixbuf_renderer, FALSE); + gtk_cell_layout_set_attributes ( + cell_layout, + pixbuf_renderer, + "pixbuf", pixbuf_col, + NULL); + GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (cell_layout, text_renderer, FALSE); + gtk_cell_layout_set_attributes ( + cell_layout, + text_renderer, + "text", name_col, + NULL); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SettingsDialog.h b/tikzit-1/src/gtk/SettingsDialog.h new file mode 100644 index 0000000..0f687b3 --- /dev/null +++ b/tikzit-1/src/gtk/SettingsDialog.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class EdgeStylesPalette; +@class NodeStylesPalette; +@class StyleManager; + +@interface SettingsDialog: NSObject { + Configuration *configuration; + StyleManager *styleManager; + StyleManager *tempStyleManager; + NodeStylesPalette *nodePalette; + EdgeStylesPalette *edgePalette; + + GtkWindow *parentWindow; + GtkWindow *window; + + // we don't keep any refs, as we control + // the top window + GtkEntry *pdflatexPathEntry; +} + +@property (retain) Configuration *configuration; +@property (retain) StyleManager *styleManager; +@property (assign) GtkWindow *parentWindow; +@property (assign,getter=isVisible) BOOL visible; + +- (id) initWithConfiguration:(Configuration*)c andStyleManager:(StyleManager*)m; + +- (void) present; +- (void) show; +- (void) hide; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SettingsDialog.m b/tikzit-1/src/gtk/SettingsDialog.m new file mode 100644 index 0000000..bdb5db6 --- /dev/null +++ b/tikzit-1/src/gtk/SettingsDialog.m @@ -0,0 +1,328 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "SettingsDialog.h" + +#import "Application.h" +#import "Configuration.h" +#import "EdgeStylesPalette.h" +#import "NodeStylesPalette.h" +#import "StyleManager.h" + +// {{{ Internal interfaces +// {{{ Signals +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + SettingsDialog *dialog); +static void ok_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog); +static void cancel_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog); +// }}} + +@interface SettingsDialog (Private) +- (void) loadUi; +- (void) save; +- (void) revert; +@end + +// }}} +// {{{ API + +@implementation SettingsDialog + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithConfiguration:(Configuration*)c andStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + configuration = [c retain]; + styleManager = [m retain]; + tempStyleManager = [m copy]; + } + + return self; +} + +- (void) dealloc { + if (window) { + gtk_widget_destroy (GTK_WIDGET (window)); + } + if (parentWindow) { + g_object_unref (parentWindow); + } + + [configuration release]; + [tempStyleManager release]; + [styleManager release]; + [nodePalette release]; + [edgePalette release]; + + [super dealloc]; +} + +- (Configuration*) configuration { + return configuration; +} + +- (void) setConfiguration:(Configuration*)c { + [c retain]; + [configuration release]; + configuration = c; + [self revert]; +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)m { + [m retain]; + [styleManager release]; + styleManager = m; +} + +- (GtkWindow*) parentWindow { + return parentWindow; +} + +- (void) setParentWindow:(GtkWindow*)parent { + GtkWindow *oldParent = parentWindow; + + if (parent) + g_object_ref (parent); + parentWindow = parent; + if (oldParent) + g_object_unref (oldParent); + + if (window) { + gtk_window_set_transient_for (window, parentWindow); + } +} + +- (void) present { + [self loadUi]; + [self revert]; + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) show { + [self loadUi]; + [self revert]; + gtk_widget_show (GTK_WIDGET (window)); +} + +- (void) hide { + if (!window) { + return; + } + gtk_widget_hide (GTK_WIDGET (window)); +} + +- (BOOL) isVisible { + if (!window) { + return NO; + } + gboolean visible; + g_object_get (G_OBJECT (window), "visible", &visible, NULL); + return visible ? YES : NO; +} + +- (void) setVisible:(BOOL)visible { + if (visible) { + [self show]; + } else { + [self hide]; + } +} + +@end + +// }}} +// {{{ Private + +@implementation SettingsDialog (Private) +- (void) loadUi { + if (window) { + return; + } + + nodePalette = [[NodeStylesPalette alloc] initWithManager:tempStyleManager]; + edgePalette = [[EdgeStylesPalette alloc] initWithManager:tempStyleManager]; + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_default_size (window, 570, -1); + gtk_window_set_title (window, "TikZiT Configuration"); + gtk_window_set_modal (window, TRUE); + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DIALOG); + if (parentWindow) { + gtk_window_set_transient_for (window, parentWindow); + } + g_signal_connect (window, + "delete-event", + G_CALLBACK (window_delete_event_cb), + self); + + GtkWidget *mainBox = gtk_vbox_new (FALSE, 18); + gtk_container_set_border_width (GTK_CONTAINER (mainBox), 12); + gtk_container_add (GTK_CONTAINER (window), mainBox); + gtk_widget_show (mainBox); + +#ifdef HAVE_POPPLER + /* + * Path for pdflatex + */ + + GtkWidget *pdflatexFrame = gtk_frame_new ("Previews"); + gtk_box_pack_start (GTK_BOX (mainBox), pdflatexFrame, TRUE, TRUE, 0); + + GtkBox *pdflatexBox = GTK_BOX (gtk_hbox_new (FALSE, 6)); + gtk_container_add (GTK_CONTAINER (pdflatexFrame), GTK_WIDGET (pdflatexBox)); + gtk_container_set_border_width (GTK_CONTAINER (pdflatexBox), 6); + + GtkWidget *pdflatexLabel = gtk_label_new ("Path to pdflatex:"); + gtk_misc_set_alignment (GTK_MISC (pdflatexLabel), 0, 0.5); + gtk_box_pack_start (pdflatexBox, + pdflatexLabel, + FALSE, TRUE, 0); + + pdflatexPathEntry = GTK_ENTRY (gtk_entry_new ()); + gtk_box_pack_start (pdflatexBox, + GTK_WIDGET (pdflatexPathEntry), + TRUE, TRUE, 0); + + gtk_widget_show_all (pdflatexFrame); +#else + pdflatexPathEntry = NULL; +#endif + + /* + * Node styles + */ + GtkWidget *nodeStylesFrame = gtk_frame_new ("Node Styles"); + gtk_widget_show (nodeStylesFrame); + gtk_box_pack_start (GTK_BOX (mainBox), nodeStylesFrame, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (nodeStylesFrame), + GTK_WIDGET ([nodePalette widget])); + gtk_widget_show ([nodePalette widget]); + + + /* + * Edge styles + */ + GtkWidget *edgeStylesFrame = gtk_frame_new ("Edge Styles"); + gtk_widget_show (edgeStylesFrame); + gtk_box_pack_start (GTK_BOX (mainBox), edgeStylesFrame, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (edgeStylesFrame), + GTK_WIDGET ([edgePalette widget])); + gtk_widget_show ([edgePalette widget]); + + + /* + * Bottom buttons + */ + + GtkContainer *buttonBox = GTK_CONTAINER (gtk_hbutton_box_new ()); + gtk_box_set_spacing (GTK_BOX (buttonBox), 6); + gtk_button_box_set_layout (GTK_BUTTON_BOX (buttonBox), GTK_BUTTONBOX_END); + gtk_box_pack_start (GTK_BOX (mainBox), + GTK_WIDGET (buttonBox), + FALSE, TRUE, 0); + + GtkWidget *okButton = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_container_add (buttonBox, okButton); + g_signal_connect (okButton, + "clicked", + G_CALLBACK (ok_button_clicked_cb), + self); + + GtkWidget *cancelButton = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_container_add (buttonBox, cancelButton); + g_signal_connect (cancelButton, + "clicked", + G_CALLBACK (cancel_button_clicked_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + + [self revert]; +} + +- (void) save { + if (!window) + return; + +#ifdef HAVE_POPPLER + const gchar *path = gtk_entry_get_text (pdflatexPathEntry); + if (path && *path) { + [configuration setStringEntry:@"pdflatex" + inGroup:@"Previews" + value:[NSString stringWithUTF8String:path]]; + } +#endif + + [styleManager updateFromManager:tempStyleManager]; + + [app saveConfiguration]; +} + +- (void) revert { + if (!window) + return; + +#ifdef HAVE_POPPLER + NSString *path = [configuration stringEntry:@"pdflatex" + inGroup:@"Previews" + withDefault:@"pdflatex"]; + gtk_entry_set_text (pdflatexPathEntry, [path UTF8String]); +#endif + + [tempStyleManager updateFromManager:styleManager]; +} +@end + +// }}} +// {{{ GTK+ callbacks + +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog hide]; + [pool drain]; + return TRUE; // we dealt with this event +} + +static void ok_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog save]; + [dialog hide]; + [pool drain]; +} + +static void cancel_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog hide]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Shape+Render.h b/tikzit-1/src/gtk/Shape+Render.h new file mode 100644 index 0000000..a744c77 --- /dev/null +++ b/tikzit-1/src/gtk/Shape+Render.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "Shape.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface Shape (Render) + +- (void) drawPathWithTransform:(Transformer*)transform andContext:(id<RenderContext>)context; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Shape+Render.m b/tikzit-1/src/gtk/Shape+Render.m new file mode 100644 index 0000000..924bb24 --- /dev/null +++ b/tikzit-1/src/gtk/Shape+Render.m @@ -0,0 +1,57 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Shape+Render.h" + +#import "Edge.h" + +// we use cairo for finding the bounding box etc. +#import <cairo/cairo.h> + +@implementation Shape (Render) + +- (void) drawPathWithTransform:(Transformer*)transform andContext:(id<RenderContext>)context { + [context startPath]; + + for (NSArray *arr in [self paths]) { + BOOL fst = YES; + NSPoint p, cp1, cp2; + + for (Edge *e in arr) { + if (fst) { + fst = NO; + p = [transform toScreen:[[e source] point]]; + [context moveTo:p]; + } + + p = [transform toScreen:[[e target] point]]; + if ([e isStraight]) { + [context lineTo:p]; + } else { + cp1 = [transform toScreen:[e cp1]]; + cp2 = [transform toScreen:[e cp2]]; + [context curveTo:p withCp1:cp1 andCp2:cp2]; + } + } + + [context closeSubPath]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/StyleManager+Storage.h b/tikzit-1/src/gtk/StyleManager+Storage.h new file mode 100644 index 0000000..1727786 --- /dev/null +++ b/tikzit-1/src/gtk/StyleManager+Storage.h @@ -0,0 +1,26 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "StyleManager.h" + +@interface StyleManager (Storage) +- (void) loadStylesUsingConfigurationName:(NSString*)name; +- (void) saveStylesUsingConfigurationName:(NSString*)name; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/StyleManager+Storage.m b/tikzit-1/src/gtk/StyleManager+Storage.m new file mode 100644 index 0000000..f4c8232 --- /dev/null +++ b/tikzit-1/src/gtk/StyleManager+Storage.m @@ -0,0 +1,82 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "StyleManager+Storage.h" +#import "Configuration.h" +#import "NodeStyle+Storage.h" +#import "EdgeStyle+Storage.h" + +static NSString *nodeStyleGroupPrefix = @"Style "; +static NSString *edgeStyleGroupPrefix = @"EdgeStyle "; + +@implementation StyleManager (Storage) + +- (void) loadStylesUsingConfigurationName:(NSString*)name { + if (![Configuration configurationExistsWithName:name]) { + NSLog(@"No styles config found"); + return; + } + NSError *error = nil; + Configuration *stylesConfig = [Configuration configurationWithName:name loadError:&error]; + if (error != nil) { + logError (error, @"Could not load styles configuration"); + // stick with the default config + return; + } + NSArray *groups = [stylesConfig groups]; + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[groups count]]; + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[groups count]]; + + for (NSString *groupName in groups) { + if ([groupName hasPrefix:nodeStyleGroupPrefix]) { + NodeStyle *style = [[NodeStyle alloc] initFromConfigurationGroup:groupName config:stylesConfig]; + [ns addObject:style]; + } else if ([groupName hasPrefix:edgeStyleGroupPrefix]) { + EdgeStyle *style = [[EdgeStyle alloc] initFromConfigurationGroup:groupName config:stylesConfig]; + [es addObject:style]; + } + } + + [self _setNodeStyles:ns]; + [self _setEdgeStyles:es]; +} + +- (void) saveStylesUsingConfigurationName:(NSString*)name { + NSError *error = nil; + Configuration *stylesConfig = [Configuration emptyConfigurationWithName:name]; + NSArray *ns = [self nodeStyles]; + NSArray *es = [self edgeStyles]; + NSUInteger length = [ns count]; + for (int i = 0; i < length; ++i) { + NodeStyle *style = [ns objectAtIndex:i]; + NSString *groupName = [NSString stringWithFormat:@"%@%d", nodeStyleGroupPrefix, i]; + [style storeToConfigurationGroup:groupName config:stylesConfig]; + } + length = [es count]; + for (int i = 0; i < length; ++i) { + EdgeStyle *style = [es objectAtIndex:i]; + NSString *groupName = [NSString stringWithFormat:@"%@%d", edgeStyleGroupPrefix, i]; + [style storeToConfigurationGroup:groupName config:stylesConfig]; + } + if (![stylesConfig writeToStoreWithError:&error]) { + logError (error, @"Could not write styles configuration"); + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Surface.h b/tikzit-1/src/gtk/Surface.h new file mode 100644 index 0000000..db4288e --- /dev/null +++ b/tikzit-1/src/gtk/Surface.h @@ -0,0 +1,107 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "RenderContext.h" +#import "Transformer.h" + +typedef enum { + NormalCursor, + ResizeRightCursor, + ResizeBottomRightCursor, + ResizeBottomCursor, + ResizeBottomLeftCursor, + ResizeLeftCursor, + ResizeTopLeftCursor, + ResizeTopCursor, + ResizeTopRightCursor +} Cursor; + +@protocol Surface; + +@protocol RenderDelegate <NSObject> +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface; +@end + +/** + * Represents a surface that can be rendered to + * + * This protocol should be implemented by drawing surfaces. It + * provides geometry information and methods to invalidate + * regions of the surface, triggering a redraw. + * + * The surface should send a "SurfaceSizeChanged" notification + * when the width or height changes. + */ +@protocol Surface <NSObject> + +/** + * The width of the surface, in surface units + * + * The surface should send a "SurfaceSizeChanged" notification + * when this property changes. + */ +@property (readonly) int width; +/** + * The height of the surface, in surface units + * + * The surface should send a "SurfaceSizeChanged" notification + * when this property changes. + */ +@property (readonly) int height; +/** + * The transformer that converts between graph units and surface units + */ +@property (readonly) Transformer *transformer; +/** + * The render delegate. + * + * This will be used to redraw (parts of) the surface when necessary. + */ +@property (assign) id<RenderDelegate> renderDelegate; + +/** + * Create a render context for the surface. + */ +- (id<RenderContext>) createRenderContext; +/** + * Invalidate a portion of the surface. + * + * This will request that part of the surface be redrawn. + */ +- (void) invalidateRect:(NSRect)rect; +/** + * Invalidate the whole surface. + * + * This will request that the whole surface be redrawn. + */ +- (void) invalidate; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; +- (void) zoomInAboutPoint:(NSPoint)p; +- (void) zoomOutAboutPoint:(NSPoint)p; +- (void) zoomResetAboutPoint:(NSPoint)p; + +- (void) setCursor:(Cursor)c; + +- (BOOL) hasFocus; +- (void) renderFocus; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/TZFoundation.h b/tikzit-1/src/gtk/TZFoundation.h new file mode 100644 index 0000000..2ff20ca --- /dev/null +++ b/tikzit-1/src/gtk/TZFoundation.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import <glib.h> + +#import "NSError+Glib.h" +#import "NSError+Tikzit.h" +#import "NSFileManager+Glib.h" +#import "NSFileManager+Utils.h" +#import "NSString+Glib.h" +#import "NSString+LatexConstants.h" +#import "NSString+Tikz.h" +#import "NSString+Util.h" + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/TikzDocument.h b/tikzit-1/src/gtk/TikzDocument.h new file mode 100644 index 0000000..5d15d13 --- /dev/null +++ b/tikzit-1/src/gtk/TikzDocument.h @@ -0,0 +1,149 @@ +// +// TikzDocument.h +// TikZiT +// +// Copyright 2010 Chris Heunen +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TZFoundation.h" +#import <Graph.h> +#import "PickSupport.h" +#import "StyleManager.h" + +@interface TikzDocument : NSObject { + StyleManager *styleManager; + Graph *graph; + PickSupport *pickSupport; + NSUndoManager *undoManager; + NSString *tikz; + NSString *path; + NSSet *nodesetBeingModified; + NSMapTable *nodesetBeingModifiedOldCopy; + NSSet *edgesetBeingModified; + NSMapTable *edgesetBeingModifiedOldCopy; + NSPoint currentNodeShift; + Node *nodeBeingModified; + Node *nodeBeingModifiedOldCopy; + Edge *edgeBeingModified; + Edge *edgeBeingModifiedOldCopy; + NSRect oldGraphBounds; + GraphElementData *oldGraphData; + BOOL hasChanges; +} + ++ (TikzDocument*) documentWithStyleManager:(StyleManager*)manager; ++ (TikzDocument*) documentWithGraph:(Graph*)g styleManager:(StyleManager*)manager; ++ (TikzDocument*) documentWithTikz:(NSString*)t styleManager:(StyleManager*)manager error:(NSError**)error; ++ (TikzDocument*) documentFromFile:(NSString*)path styleManager:(StyleManager*)manager error:(NSError**)error; + +- (id) initWithStyleManager:(StyleManager*)manager; +- (id) initWithGraph:(Graph*)g styleManager:(StyleManager*)manager; +- (id) initWithTikz:(NSString*)t styleManager:(StyleManager*)manager error:(NSError**)error; +- (id) initFromFile:(NSString*)path styleManager:(StyleManager*)manager error:(NSError**)error; + +@property (readonly) Graph *graph; +@property (readonly) PickSupport *pickSupport; +@property (readonly) NSString *path; +@property (readonly) NSString *name; +@property (readonly) NSString *suggestedFileName; +@property (readonly) BOOL hasUnsavedChanges; +@property (retain) StyleManager *styleManager; +@property (readonly) NSString *tikz; +@property (readonly) BOOL canUndo; +@property (readonly) BOOL canRedo; +@property (readonly) NSString *undoName; +@property (readonly) NSString *redoName; + +- (BOOL) updateTikz:(NSString*)t error:(NSError**)error; + +- (Graph*) selectionCut; +- (Graph*) selectionCopy; +- (void) paste:(Graph*)graph; +- (void) pasteFromTikz:(NSString*)tikz; + +// some convenience methods: +- (BOOL) isNodeSelected:(Node*)node; +- (BOOL) isEdgeSelected:(Edge*)edge; +- (NSEnumerator*) nodeEnumerator; +- (NSEnumerator*) edgeEnumerator; + +- (void) undo; +- (void) redo; + +- (void) startUndoGroup; +- (void) nameAndEndUndoGroup:(NSString*)nm; +- (void) endUndoGroup; + +- (void) startModifyNode:(Node*)node; +- (void) modifyNodeCheckPoint; +- (void) endModifyNode; +- (void) cancelModifyNode; + +- (void) startModifyNodes:(NSSet*)nodes; +- (void) modifyNodesCheckPoint; +- (void) endModifyNodes; +- (void) cancelModifyNodes; + +- (void) startShiftNodes:(NSSet*)nodes; +- (void) shiftNodesUpdate:(NSPoint)shiftChange; +- (void) endShiftNodes; +- (void) cancelShiftNodes; + +- (void) startModifyEdge:(Edge*)edge; +- (void) modifyEdgeCheckPoint; +- (void) endModifyEdge; +- (void) cancelModifyEdge; + +- (void) startModifyEdges:(NSSet*)edges; +- (void) modifyEdgesCheckPoint; +- (void) endModifyEdges; +- (void) cancelModifyEdges; + +- (void) startChangeBoundingBox; +- (void) changeBoundingBoxCheckPoint; +- (void) endChangeBoundingBox; +- (void) cancelChangeBoundingBox; + +- (void) startChangeGraphProperties; +- (void) changeGraphPropertiesCheckPoint; +- (void) endChangeGraphProperties; +- (void) cancelChangeGraphProperties; + +- (void) removeSelected; +- (void) addNode:(Node*)node; +- (void) removeNode:(Node*)node; +- (void) addEdge:(Edge*)edge; +- (void) removeEdge:(Edge*)edge; +- (void) shiftSelectedNodesByPoint:(NSPoint)offset; +- (void) insertGraph:(Graph*)g; +- (void) flipSelectedNodesHorizontally; +- (void) flipSelectedNodesVertically; +- (void) reverseSelectedEdges; +- (void) bringSelectionForward; +- (void) bringSelectionToFront; +- (void) sendSelectionBackward; +- (void) sendSelectionToBack; + +- (BOOL) saveCopyToPath: (NSString*)path error: (NSError**)error; +- (BOOL) saveToPath: (NSString*)path error: (NSError**)error; +- (BOOL) save: (NSError**)error; + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/TikzDocument.m b/tikzit-1/src/gtk/TikzDocument.m new file mode 100644 index 0000000..bff5a2e --- /dev/null +++ b/tikzit-1/src/gtk/TikzDocument.m @@ -0,0 +1,911 @@ +// +// TikzDocument.h +// TikZiT +// +// Copyright 2010 Chris Heunen +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TikzDocument.h" + +@interface TikzDocument (Private) +- (void) styleRenamed:(NSNotification*)n; + +- (void) setPath:(NSString*)path; +- (void) setGraph:(Graph*)g; + +- (void) registerUndoForChange:(GraphChange*)change; +- (void) registerUndoGroupForChange:(GraphChange*)change withName:(NSString*)name; +- (void) undoGraphChange:(GraphChange*)change; +- (void) completedGraphChange:(GraphChange*)change withName:(NSString*)name; +- (void) attachStylesToGraph:(Graph*)g; + +- (void) regenerateTikz; +@end + +@implementation TikzDocument + ++ (TikzDocument*) documentWithStyleManager:(StyleManager*)manager +{ + return [[[TikzDocument alloc] initWithStyleManager:manager] autorelease]; +} + ++ (TikzDocument*) documentWithGraph:(Graph*)g + styleManager:(StyleManager*)manager +{ + return [[[TikzDocument alloc] initWithGraph:g + styleManager:manager] autorelease]; +} + ++ (TikzDocument*) documentWithTikz:(NSString*)t + styleManager:(StyleManager*)manager + error:(NSError**)error +{ + return [[[TikzDocument alloc] initWithTikz:t + styleManager:manager + error:error] autorelease]; +} + ++ (TikzDocument*) documentFromFile:(NSString*)pth + styleManager:(StyleManager*)manager + error:(NSError**)error +{ + return [[[TikzDocument alloc] initFromFile:pth + styleManager:manager + error:error] autorelease]; +} + + +- (id) initWithStyleManager:(StyleManager*)manager { + self = [self initWithGraph:[Graph graph] styleManager:manager]; + return self; +} + +- (id) initWithGraph:(Graph*)g styleManager:(StyleManager*)manager { + self = [super init]; + + if (self) { + graph = nil; + styleManager = [manager retain]; + pickSupport = [[PickSupport alloc] init]; + undoManager = [[NSUndoManager alloc] init]; + [undoManager setGroupsByEvent:NO]; + tikz = nil; + path = nil; + nodesetBeingModified = nil; + nodesetBeingModifiedOldCopy = nil; + nodeBeingModified = nil; + nodeBeingModifiedOldCopy = nil; + edgeBeingModified = nil; + edgeBeingModifiedOldCopy = nil; + + [undoManager disableUndoRegistration]; + [self setGraph:g]; + [undoManager enableUndoRegistration]; + + hasChanges = NO; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"NodeStyleRenamed" + object:styleManager]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"EdgeStyleRenamed" + object:styleManager]; + } + + return self; +} + +- (id) initWithTikz:(NSString*)t + styleManager:(StyleManager*)manager + error:(NSError**)error +{ + self = [self initWithStyleManager:manager]; + + if (self) { + [undoManager disableUndoRegistration]; + BOOL success = [self updateTikz:t error:error]; + if (!success) { + [self release]; + return nil; + } + [undoManager enableUndoRegistration]; + hasChanges = NO; + } + + return self; +} + +- (id) initFromFile:(NSString*)pth + styleManager:(StyleManager*)manager + error:(NSError**)error +{ + NSString *t = [NSString stringWithContentsOfFile:pth error:error]; + if (t == nil) { + [self release]; + return nil; + } + + self = [self initWithTikz:t styleManager:manager error:error]; + + if (self) { + [self setPath:pth]; + } + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [styleManager release]; + [graph release]; + [pickSupport release]; + [undoManager release]; + [tikz release]; + [path release]; + [nodesetBeingModified release]; + [nodesetBeingModifiedOldCopy release]; + [nodeBeingModified release]; + [nodeBeingModifiedOldCopy release]; + [edgeBeingModified release]; + [edgeBeingModifiedOldCopy release]; + [oldGraphData release]; + [super dealloc]; +} + +@synthesize graph, pickSupport, path; + +- (NSString*) name { + if (path) { + return [[NSFileManager defaultManager] displayNameAtPath: path]; + } else { + return @"Untitled"; + } +} + +- (NSString*) suggestedFileName { + if (path) { + return [path lastPathComponent]; + } else { + return @"untitled.tikz"; + } +} + +- (BOOL) hasUnsavedChanges { + return hasChanges; +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)manager { + StyleManager *oldManager = styleManager; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:nil + object:oldManager]; + + styleManager = [manager retain]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"NodeStyleRenamed" + object:styleManager]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"EdgeStyleRenamed" + object:styleManager]; + + [self attachStylesToGraph:graph]; + [oldManager release]; +} + +- (void) postGraphReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphReplaced" object:self]; +} + +- (void) postGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChanged" object:self userInfo:info]; +} + +- (void) postIncompleteGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphBeingChanged" object:self userInfo:info]; +} + +- (void) postCancelledGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChangeCancelled" object:self userInfo:info]; +} + +- (void) postTikzChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"TikzChanged" object:self]; +} + +- (void) postUndoStackChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"UndoStackChanged" object:self]; +} + +- (NSString*) tikz { + return tikz; +} + +- (BOOL) updateTikz:(NSString*)t error:(NSError**)error { + if (t == nil) { + t = [NSString string]; + } + if (t == tikz || [t isEqual:tikz]) { + return YES; + } + + Graph *g = [Graph graphFromTikz:t error:error]; + if (g) { + // updateTikz actually generates a graph from the tikz, + // and generates the final tikz from that + [self startUndoGroup]; + [self setGraph:g]; + [self nameAndEndUndoGroup:@"Update tikz"]; + return YES; + } + + return NO; +} + +- (Graph*) selectionCut { + Graph *selection = [self selectionCopy]; + [self startUndoGroup]; + [self removeSelected]; + [self nameAndEndUndoGroup:@"Cut"]; + return selection; +} + +- (Graph*) selectionCopy { + return [[graph copyOfSubgraphWithNodes:[pickSupport selectedNodes]] autorelease]; +} + +- (void) paste:(Graph*)g { + if (g == nil || [[g nodes] count] == 0) { + // nothing to paste + return; + } + + // place to the right of the existing graph + NSRect bounds = [graph bounds]; + NSRect gBounds = [g bounds]; + float dx = NSMaxX (bounds) - gBounds.origin.x + 0.5f; + [g shiftNodes:[g nodes] byPoint:NSMakePoint (dx, 0)]; + + GraphChange *change = [graph insertGraph:g]; + [self completedGraphChange:change withName:@"Paste"]; + + // select everything from the clipboard + [pickSupport deselectAllEdges]; + [pickSupport selectAllNodes:[NSSet setWithArray:[g nodes]] replacingSelection:YES]; +} + +- (void) pasteFromTikz:(NSString*)t { + Graph *clipboard = [Graph graphFromTikz:t]; + if (clipboard) { + [self attachStylesToGraph:clipboard]; + [self paste:clipboard]; + } +} + +- (BOOL) isNodeSelected:(Node*)node { + return [pickSupport isNodeSelected:node]; +} + +- (BOOL) isEdgeSelected:(Edge*)edge { + return [pickSupport isEdgeSelected:edge]; +} + +- (NSEnumerator*) nodeEnumerator { + return [[graph nodes] objectEnumerator]; +} + +- (NSEnumerator*) edgeEnumerator { + return [[graph edges] objectEnumerator]; +} + +- (BOOL) canUndo { + return [undoManager canUndo]; +} + +- (void) undo { + [undoManager undo]; + [self postUndoStackChanged]; +} + +- (BOOL) canRedo { + return [undoManager canRedo]; +} + +- (void) redo { + [undoManager redo]; + [self postUndoStackChanged]; +} + +- (NSString*) undoName { + return [undoManager undoActionName]; +} + +- (NSString*) redoName { + return [undoManager redoActionName]; +} + +- (void) startUndoGroup { + [undoManager beginUndoGrouping]; +} + +- (void) nameAndEndUndoGroup:(NSString*)nm { + [undoManager setActionName:nm]; + [undoManager endUndoGrouping]; + [self postUndoStackChanged]; +} + +- (void) endUndoGroup { + [undoManager endUndoGrouping]; + [self postUndoStackChanged]; +} + +- (void) startModifyNode:(Node*)node { + if (nodeBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node"]; + } + nodeBeingModified = [node retain]; + nodeBeingModifiedOldCopy = [node copy]; +} + +- (void) modifyNodeCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfNode:nodeBeingModified + fromOld:nodeBeingModifiedOldCopy + toNew:[[nodeBeingModified copy] autorelease]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifySequence:(GraphChange*)change withName:(NSString*)chName cancelled:(BOOL)cancelled { + if (cancelled) { + change = [change invert]; + [graph applyGraphChange:change]; + [self regenerateTikz]; + [self postCancelledGraphChange:change]; + } else { + [self registerUndoGroupForChange:change withName:chName]; + [self regenerateTikz]; + [self postGraphChange:change]; + } +} + +- (void) _finishModifyNodeCancelled:(BOOL)cancelled { + if (nodeBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node"]; + } + + GraphChange *change = [GraphChange propertyChangeOfNode:nodeBeingModified + fromOld:nodeBeingModifiedOldCopy + toNew:[[nodeBeingModified copy] autorelease]]; + [self _finishModifySequence:change withName:@"Modify node" cancelled:cancelled]; + + [nodeBeingModified release]; + nodeBeingModified = nil; + [nodeBeingModifiedOldCopy release]; + nodeBeingModifiedOldCopy = nil; +} + +- (void) endModifyNode { [self _finishModifyNodeCancelled:NO]; } +- (void) cancelModifyNode { [self _finishModifyNodeCancelled:YES]; } + +- (void) startModifyNodes:(NSSet*)nodes { + if (nodesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node set"]; + } + + nodesetBeingModified = [nodes copy]; + nodesetBeingModifiedOldCopy = [[Graph nodeTableForNodes:nodes] retain]; +} + +- (void) modifyNodesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfNodesFromOldCopies:nodesetBeingModifiedOldCopy + toNewCopies:[Graph nodeTableForNodes:nodesetBeingModified]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyNodes:(BOOL)cancelled { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + GraphChange *change = [GraphChange propertyChangeOfNodesFromOldCopies:nodesetBeingModifiedOldCopy + toNewCopies:[Graph nodeTableForNodes:nodesetBeingModified]]; + [self _finishModifySequence:change withName:@"Modify nodes" cancelled:cancelled]; + + [nodesetBeingModified release]; + nodesetBeingModified = nil; + [nodesetBeingModifiedOldCopy release]; + nodesetBeingModifiedOldCopy = nil; +} + +- (void) endModifyNodes { [self _finishModifyNodes:NO]; } +- (void) cancelModifyNodes { [self _finishModifyNodes:YES]; } + +- (void) startShiftNodes:(NSSet*)nodes { + if (nodesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node set"]; + } + + nodesetBeingModified = [nodes copy]; + currentNodeShift = NSZeroPoint; +} + +- (void) shiftNodesUpdate:(NSPoint)currentShift { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + currentNodeShift = currentShift; + [self regenerateTikz]; + GraphChange *change = [GraphChange shiftNodes:nodesetBeingModified + byPoint:currentNodeShift]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishShiftNodesCancelled:(BOOL)cancelled { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + if (!NSEqualPoints (currentNodeShift, NSZeroPoint)) { + GraphChange *change = [GraphChange shiftNodes:nodesetBeingModified + byPoint:currentNodeShift]; + [self _finishModifySequence:change withName:@"Move nodes" cancelled:cancelled]; + } + + [nodesetBeingModified release]; + nodesetBeingModified = nil; +} + +- (void) endShiftNodes { [self _finishShiftNodesCancelled:NO]; } +- (void) cancelShiftNodes { [self _finishShiftNodesCancelled:YES]; } + +- (void) startModifyEdge:(Edge*)edge { + if (edgeBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying an edge"]; + } + edgeBeingModified = [edge retain]; + edgeBeingModifiedOldCopy = [edge copy]; +} + +- (void) modifyEdgeCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfEdge:edgeBeingModified + fromOld:edgeBeingModifiedOldCopy + toNew:[[edgeBeingModified copy] autorelease]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyEdgeCancelled:(BOOL)cancelled { + if (edgeBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying an edge"]; + } + + GraphChange *change = [GraphChange propertyChangeOfEdge:edgeBeingModified + fromOld:edgeBeingModifiedOldCopy + toNew:[[edgeBeingModified copy] autorelease]]; + [self _finishModifySequence:change withName:@"Modify edge" cancelled:cancelled]; + + [edgeBeingModified release]; + edgeBeingModified = nil; + [edgeBeingModifiedOldCopy release]; + edgeBeingModifiedOldCopy = nil; +} + +- (void) endModifyEdge { [self _finishModifyEdgeCancelled:NO]; } +- (void) cancelModifyEdge { [self _finishModifyEdgeCancelled:YES]; } + +- (void) startModifyEdges:(NSSet*)edges { + if (edgesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying an edge set"]; + } + + edgesetBeingModified = [edges copy]; + edgesetBeingModifiedOldCopy = [[Graph edgeTableForEdges:edges] retain]; +} + +- (void) modifyEdgesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfEdgesFromOldCopies:edgesetBeingModifiedOldCopy + toNewCopies:[Graph edgeTableForEdges:edgesetBeingModified]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyEdgesCancelled:(BOOL)cancelled { + if (edgesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying an edge"]; + } + + GraphChange *change = [GraphChange propertyChangeOfEdgesFromOldCopies:edgesetBeingModifiedOldCopy + toNewCopies:[Graph edgeTableForEdges:edgesetBeingModified]]; + [self _finishModifySequence:change withName:@"Modify edges" cancelled:cancelled]; + + [edgesetBeingModified release]; + edgesetBeingModified = nil; + [edgesetBeingModifiedOldCopy release]; + edgesetBeingModifiedOldCopy = nil; +} + +- (void) endModifyEdges { [self _finishModifyEdgesCancelled:NO]; } +- (void) cancelModifyEdges { [self _finishModifyEdgesCancelled:YES]; } + +- (void) startChangeBoundingBox { + oldGraphBounds = [graph boundingBox]; +} + +- (void) changeBoundingBoxCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange changeBoundingBoxFrom:oldGraphBounds + to:[graph boundingBox]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishChangeBoundingBoxCancelled:(BOOL)cancelled { + GraphChange *change = [GraphChange changeBoundingBoxFrom:oldGraphBounds + to:[graph boundingBox]]; + [self _finishModifySequence:change withName:@"Set bounding box" cancelled:cancelled]; +} +- (void) endChangeBoundingBox { [self _finishChangeBoundingBoxCancelled:NO]; } +- (void) cancelChangeBoundingBox { [self _finishChangeBoundingBoxCancelled:YES]; } + +- (void) startChangeGraphProperties { + oldGraphData = [[graph data] copy]; +} + +- (void) changeGraphPropertiesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfGraphFrom:oldGraphData + to:[graph data]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishChangeGraphPropertiesCancelled:(BOOL)cancelled { + GraphChange *change = [GraphChange propertyChangeOfGraphFrom:oldGraphData + to:[graph data]]; + [self _finishModifySequence:change withName:@"Change graph properties" cancelled:cancelled]; + [oldGraphData release]; + oldGraphData = nil; +} +- (void) endChangeGraphProperties { [self _finishChangeGraphPropertiesCancelled:NO]; } +- (void) cancelChangeGraphProperties { [self _finishChangeGraphPropertiesCancelled:YES]; } + +- (void) removeSelected { + NSUInteger selEdges = [[pickSupport selectedEdges] count]; + NSUInteger selNodes = [[pickSupport selectedNodes] count]; + + if (selEdges == 0 && selNodes == 0) { + return; + } + + NSString *actionName = @"Remove selection"; + + [self startUndoGroup]; + if (selEdges > 0) { + GraphChange *change = [graph removeEdges:[pickSupport selectedEdges]]; + [self registerUndoForChange:change]; + [pickSupport deselectAllEdges]; + [self postGraphChange:change]; + } else { + actionName = (selNodes == 1 ? @"Remove node" : @"Remove nodes"); + } + if (selNodes > 0) { + GraphChange *change = [graph removeNodes:[pickSupport selectedNodes]]; + [self registerUndoForChange:change]; + [pickSupport deselectAllNodes]; + [self postGraphChange:change]; + } else { + actionName = (selEdges == 1 ? @"Remove edge" : @"Remove edges"); + } + [self nameAndEndUndoGroup:actionName]; + [self regenerateTikz]; +} + +- (void) addNode:(Node*)node { + GraphChange *change = [graph addNode:node]; + [self completedGraphChange:change withName:@"Add node"]; +} + +- (void) removeNode:(Node*)node { + [pickSupport deselectNode:node]; + GraphChange *change = [graph removeNode:node]; + [self completedGraphChange:change withName:@"Remove node"]; +} + +- (void) addEdge:(Edge*)edge { + GraphChange *change = [graph addEdge:edge]; + [self completedGraphChange:change withName:@"Add edge"]; +} + +- (void) removeEdge:(Edge*)edge { + [pickSupport deselectEdge:edge]; + GraphChange *change = [graph removeEdge:edge]; + [self completedGraphChange:change withName:@"Remove edge"]; +} + +- (void) shiftSelectedNodesByPoint:(NSPoint)offset { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph shiftNodes:[pickSupport selectedNodes] byPoint:offset]; + [self completedGraphChange:change withName:@"Move nodes"]; + } +} + +- (void) insertGraph:(Graph*)g { + GraphChange *change = [graph insertGraph:g]; + [self completedGraphChange:change withName:@"Insert graph"]; +} + +- (void) flipSelectedNodesHorizontally { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph flipHorizontalNodes:[pickSupport selectedNodes]]; + [self completedGraphChange:change withName:@"Flip nodes horizontally"]; + } +} + +- (void) flipSelectedNodesVertically { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph flipVerticalNodes:[pickSupport selectedNodes]]; + [self completedGraphChange:change withName:@"Flip nodes vertically"]; + } +} + +- (void) reverseSelectedEdges { + if ([[pickSupport selectedEdges] count] > 0) { + GraphChange *change = [graph reverseEdges:[pickSupport selectedEdges]]; + [self completedGraphChange:change withName:@"Reverse edges"]; + } +} + +- (void) bringSelectionForward { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph bringNodesForward:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph bringEdgesForward:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Bring forward"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) bringSelectionToFront { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph bringNodesToFront:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph bringEdgesToFront:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Bring to front"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) sendSelectionBackward { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph sendNodesBackward:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph sendNodesBackward:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Send backward"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) sendSelectionToBack { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph sendNodesToBack:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph sendNodesToBack:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Send to back"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (BOOL) saveCopyToPath: (NSString*)p error: (NSError**)error { + if (!p) { + [NSException raise:@"No document path" format:@"No path given"]; + } + // we use glib for writing the file, because GNUStep sucks in this regard + // (older versions don't have -[NSString writeToFile:atomically:encoding:error:]) + GError *gerror = NULL; + gchar *filename = [p glibFilename]; + BOOL success = g_file_set_contents (filename, [tikz UTF8String], -1, &gerror) ? YES : NO; + if (gerror) { + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + g_free (filename); + return success; +} + +- (BOOL) saveToPath: (NSString*)p error: (NSError**)error { + BOOL success = [self saveCopyToPath:p error:error]; + if (success) { + [self setPath:p]; + hasChanges = NO; + } + return success; +} + +- (BOOL) save: (NSError**)error { + if (!path) { + [NSException raise:@"No document path" format:@"Tried to save a document when there was no path"]; + } + return [self saveToPath:path error:error]; +} + +@end + +@implementation TikzDocument (Private) +- (void) styleRenamed:(NSNotification*)n { + [self regenerateTikz]; +} + +- (void) setPath:(NSString*)p { + [p retain]; + [path release]; + path = p; +} + +- (void) setGraph:(Graph*)g { + if (g == nil) { + g = [Graph graph]; + } + if (g == graph) { + return; + } + + [pickSupport deselectAllNodes]; + [pickSupport deselectAllEdges]; + + [self startUndoGroup]; + [undoManager registerUndoWithTarget:self selector:@selector(setGraph:) object:graph]; + [g retain]; + [graph release]; + graph = g; + + [self attachStylesToGraph:graph]; + + [self regenerateTikz]; + [self postGraphReplaced]; + [self nameAndEndUndoGroup:@"Replace graph"]; +} + +- (void) registerUndoForChange:(GraphChange*)change { + [undoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:change]; +} + +- (void) registerUndoGroupForChange:(GraphChange*)change withName:(NSString*)nm { + [self startUndoGroup]; + [self registerUndoForChange:change]; + [self nameAndEndUndoGroup:nm]; +} + +- (void) undoGraphChange:(GraphChange*)change { + GraphChange *inverse = [change invert]; + [graph applyGraphChange:inverse]; + [self startUndoGroup]; + [undoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:inverse]; + [self endUndoGroup]; + [self regenerateTikz]; + [self postGraphChange:change]; +} + +- (void) completedGraphChange:(GraphChange*)change withName:(NSString*)name { + if (change == nil) { + NSLog(@"No graph change given for change %@", name); + return; + } + [self registerUndoGroupForChange:change withName:name]; + [self regenerateTikz]; + [self postGraphChange:change]; +} + +- (void) attachStylesToGraph:(Graph*)g { + for (Node *n in [g nodes]) { + [n attachStyleFromTable:[styleManager nodeStyles]]; + } + for (Edge *e in [g edges]) { + [e attachStyleFromTable:[styleManager edgeStyles]]; + } +} + +- (void) regenerateTikz { + [tikz release]; + tikz = [[graph tikz] retain]; + hasChanges = YES; + [self postTikzChanged]; +} +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/Tool.h b/tikzit-1/src/gtk/Tool.h new file mode 100644 index 0000000..22c983e --- /dev/null +++ b/tikzit-1/src/gtk/Tool.h @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "InputDelegate.h" +#import "Surface.h" + +#import <gtk/gtk.h> +#import <gdk-pixbuf/gdk-pixdata.h> + +@class Configuration; +@class GraphRenderer; +@protocol InputDelegate; +@protocol RenderDelegate; + +@protocol Tool <RenderDelegate,InputDelegate> +@property (readonly) NSString *name; +@property (readonly) const gchar *stockId; +@property (readonly) NSString *helpText; +@property (readonly) NSString *shortcut; +@property (retain) GraphRenderer *activeRenderer; +@property (readonly) GtkWidget *configurationWidget; +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ToolBox.h b/tikzit-1/src/gtk/ToolBox.h new file mode 100644 index 0000000..60074c1 --- /dev/null +++ b/tikzit-1/src/gtk/ToolBox.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class Window; +@protocol Tool; + +@interface ToolBox : NSObject { + GtkWidget *window; + GtkToolItemGroup *toolGroup; + GtkWidget *titleLabel; + GtkWidget *configWidgetContainer; + GtkWidget *configWidget; +} + +@property (assign) id<Tool> selectedTool; + +- (id) initWithTools:(NSArray*)tools; + +- (void) show; +- (void) present; +- (void) attachToWindow:(Window*)parent; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ToolBox.m b/tikzit-1/src/gtk/ToolBox.m new file mode 100644 index 0000000..c6d2ccf --- /dev/null +++ b/tikzit-1/src/gtk/ToolBox.m @@ -0,0 +1,280 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "ToolBox.h" + +#import "Application.h" +#import "Configuration.h" +#import "Tool.h" +#import "Window.h" + +#import "gtkhelpers.h" +#import "tztoolpalette.h" + +static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox); + +#define TOOL_DATA_KEY "tikzit-tool" + +@implementation ToolBox + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithTools:(NSArray*)tools { + self = [super init]; + + if (self) { + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_object_ref_sink (window); + gtk_window_set_title (GTK_WINDOW (window), "Toolbox"); + gtk_window_set_role (GTK_WINDOW (window), "toolbox"); + gtk_window_set_type_hint (GTK_WINDOW (window), + GDK_WINDOW_TYPE_HINT_UTILITY); + gtk_window_set_deletable (GTK_WINDOW (window), FALSE); + + GtkWidget *mainLayout = gtk_vbox_new (FALSE, 5); + gtk_widget_show (mainLayout); + gtk_container_add (GTK_CONTAINER (window), mainLayout); + + GtkWidget *toolPalette = tz_tool_palette_new (); + gtk_widget_show (toolPalette); + gtk_box_pack_start (GTK_BOX (mainLayout), + toolPalette, + FALSE, + FALSE, + 0); + gtk_tool_palette_set_style (GTK_TOOL_PALETTE (toolPalette), + GTK_TOOLBAR_ICONS); + + toolGroup = GTK_TOOL_ITEM_GROUP (gtk_tool_item_group_new ("Tools")); + g_object_ref_sink (G_OBJECT (toolGroup)); + gtk_tool_item_group_set_label_widget ( + toolGroup, + NULL); + gtk_container_add (GTK_CONTAINER (toolPalette), GTK_WIDGET (toolGroup)); + gtk_widget_show (GTK_WIDGET (toolGroup)); + + GSList *item_group = NULL; + for (id<Tool> tool in tools) { + NSString *tooltip = [NSString stringWithFormat: + @"%@: %@ (%@)", + [tool name], [tool helpText], [tool shortcut]]; + GtkToolItem *item = gtk_radio_tool_button_new_from_stock ( + item_group, + [tool stockId]); + gtk_tool_item_set_tooltip_text (item, [tooltip UTF8String]); + item_group = gtk_radio_tool_button_get_group ( + GTK_RADIO_TOOL_BUTTON (item)); + gtk_tool_item_group_insert ( + toolGroup, + item, + -1); + gtk_widget_show (GTK_WIDGET (item)); + g_object_set_data_full ( + G_OBJECT(item), + TOOL_DATA_KEY, + [tool retain], + release_obj); + + g_signal_connect (item, "toggled", + G_CALLBACK (tool_button_toggled_cb), + self); + } + + GtkWidget *sep = gtk_hseparator_new (); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (mainLayout), + sep, + FALSE, + FALSE, + 0); + + titleLabel = gtk_label_new (""); + g_object_ref_sink (titleLabel); + gtk_widget_show (titleLabel); + + PangoAttrList *attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, + pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD)); + gtk_label_set_attributes (GTK_LABEL (titleLabel), attrs); + pango_attr_list_unref (attrs); + + gtk_box_pack_start (GTK_BOX (mainLayout), + titleLabel, + FALSE, + FALSE, + 0); + + configWidgetContainer = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + g_object_ref_sink (configWidgetContainer); + gtk_widget_show (configWidgetContainer); + gtk_box_pack_start (GTK_BOX (mainLayout), + configWidgetContainer, + TRUE, + TRUE, + 0); + gtk_alignment_set_padding (GTK_ALIGNMENT (configWidgetContainer), + 5, 5, 5, 5); + + gint button_width; + gint button_height; + + if (tz_tool_palette_get_button_size (TZ_TOOL_PALETTE (toolPalette), + &button_width, &button_height)) + { + GdkGeometry geometry; + + geometry.min_width = 2 * button_width; + geometry.min_height = -1; + geometry.base_width = button_width; + geometry.base_height = 0; + geometry.width_inc = button_width; + geometry.height_inc = 1; + + gtk_window_set_geometry_hints (GTK_WINDOW (window), + NULL, + &geometry, + GDK_HINT_MIN_SIZE | + GDK_HINT_BASE_SIZE | + GDK_HINT_RESIZE_INC | + GDK_HINT_USER_POS); + } + gtk_window_set_default_size (GTK_WINDOW (window), button_width * 5, 500); + + // hack to position the toolbox window somewhere sensible + // (upper left) + gtk_window_parse_geometry (GTK_WINDOW (window), "+0+0"); + } + + return self; +} + +- (void) dealloc { + if (window) { + g_object_unref (G_OBJECT (toolGroup)); + g_object_unref (G_OBJECT (titleLabel)); + g_object_unref (G_OBJECT (configWidgetContainer)); + if (configWidget) + g_object_unref (G_OBJECT (configWidget)); + gtk_widget_destroy (window); + g_object_unref (G_OBJECT (window)); + } + + [super dealloc]; +} + +- (id<Tool>) selectedTool { + guint count = gtk_tool_item_group_get_n_items (toolGroup); + for (guint i = 0; i < count; ++i) { + GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i); + if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (item))) { + return (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY); + } + } + return nil; +} + +- (void) _setToolWidget:(GtkWidget*)widget { + if (configWidget) { + gtk_widget_hide (configWidget); + gtk_container_remove (GTK_CONTAINER (configWidgetContainer), + configWidget); + g_object_unref (configWidget); + } + configWidget = widget; + if (configWidget) { + g_object_ref (configWidget); + gtk_container_add (GTK_CONTAINER (configWidgetContainer), + configWidget); + gtk_widget_show (configWidget); + } +} + +- (void) setSelectedTool:(id<Tool>)tool { + guint count = gtk_tool_item_group_get_n_items (toolGroup); + for (guint i = 0; i < count; ++i) { + GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i); + id<Tool> data = (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY); + if (data == tool) { + gtk_toggle_tool_button_set_active ( + GTK_TOGGLE_TOOL_BUTTON (item), + TRUE); + break; + } + } + gtk_label_set_label (GTK_LABEL (titleLabel), + [[tool name] UTF8String]); + [self _setToolWidget:[tool configurationWidget]]; +} + +- (void) show { + gtk_widget_show (window); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) attachToWindow:(Window*)parent { + utility_window_attach (GTK_WINDOW (window), [parent gtkWindow]); +} + +- (void) loadConfiguration:(Configuration*)config { + if ([config hasGroup:@"ToolBox"]) { + tz_restore_window (GTK_WINDOW (window), + [config integerEntry:@"x" inGroup:@"ToolBox"], + [config integerEntry:@"y" inGroup:@"ToolBox"], + [config integerEntry:@"w" inGroup:@"ToolBox"], + [config integerEntry:@"h" inGroup:@"ToolBox"]); + } +} + +- (void) saveConfiguration:(Configuration*)config { + gint x, y, w, h; + + gtk_window_get_position (GTK_WINDOW (window), &x, &y); + gtk_window_get_size (GTK_WINDOW (window), &w, &h); + + [config setIntegerEntry:@"x" inGroup:@"ToolBox" value:x]; + [config setIntegerEntry:@"y" inGroup:@"ToolBox" value:y]; + [config setIntegerEntry:@"w" inGroup:@"ToolBox" value:w]; + [config setIntegerEntry:@"h" inGroup:@"ToolBox" value:h]; +} + +@end + +static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox) { + if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (widget))) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<Tool> tool = (id)g_object_get_data (G_OBJECT(widget), TOOL_DATA_KEY); + [app setActiveTool:tool]; + NSDictionary *userInfo = [NSDictionary + dictionaryWithObject:tool + forKey:@"tool"]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"ToolSelectionChanged" + object:toolBox + userInfo:userInfo]; + + [pool drain]; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/WidgetSurface.h b/tikzit-1/src/gtk/WidgetSurface.h new file mode 100644 index 0000000..667749f --- /dev/null +++ b/tikzit-1/src/gtk/WidgetSurface.h @@ -0,0 +1,54 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> +#import <InputDelegate.h> +#import <Surface.h> + +/** + * Provides a surface for rendering to a widget. + */ +@interface WidgetSurface: NSObject <Surface> { + GtkWidget *widget; + Transformer *transformer; + id <RenderDelegate> renderDelegate; + id <InputDelegate> inputDelegate; + BOOL keepCentered; + BOOL buttonPressesRequired; + CGFloat defaultScale; + NSSize lastKnownSize; +} + +@property (assign) BOOL canFocus; +@property (assign) BOOL keepCentered; +@property (assign) CGFloat defaultScale; + +- (id) initWithWidget:(GtkWidget*)widget; +- (GtkWidget*) widget; + +- (id<InputDelegate>) inputDelegate; +- (void) setInputDelegate:(id<InputDelegate>)delegate; + +/** + * Set the minimum size that this widget wants + */ +- (void) setSizeRequestWidth:(double)width height:(double)height; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/WidgetSurface.m b/tikzit-1/src/gtk/WidgetSurface.m new file mode 100644 index 0000000..004e722 --- /dev/null +++ b/tikzit-1/src/gtk/WidgetSurface.m @@ -0,0 +1,630 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "WidgetSurface.h" +#import "gtkhelpers.h" +#import "InputDelegate.h" +#import "CairoRenderContext.h" + +// {{{ Internal interfaces +// {{{ GTK+ callbacks +static gboolean configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, WidgetSurface *surface); +static void realize_cb (GtkWidget *widget, WidgetSurface *surface); +static gboolean expose_event_cb (GtkWidget *widget, GdkEventExpose *event, WidgetSurface *surface); +static gboolean button_press_event_cb (GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface); +static gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface); +static gboolean motion_notify_event_cb (GtkWidget *widget, GdkEventMotion *event, WidgetSurface *surface); +static gboolean key_press_event_cb (GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface); +static gboolean key_release_event_cb (GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface); +static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, WidgetSurface *surface); +static void set_cursor (GtkWidget *widget, GdkCursor *cursor); +static void unref_cursor (gpointer cursor, GClosure *closure); +// }}} + +@interface WidgetSurface (Private) +- (void) updateTransformer; +- (void) widgetSizeChanged:(NSNotification*)notification; +- (void) handleExposeEvent:(GdkEventExpose*)event; +- (void) updateLastKnownSize; +- (void) zoomTo:(CGFloat)scale aboutPoint:(NSPoint)p; +- (void) zoomTo:(CGFloat)scale; +- (void) addToEventMask:(GdkEventMask)values; +- (void) removeFromEventMask:(GdkEventMask)values; +@end +// }}} +// {{{ API +@implementation WidgetSurface + +- (id) init { + return [self initWithWidget:gtk_drawing_area_new ()]; +} + +- (id) initWithWidget:(GtkWidget*)w { + self = [super init]; + + if (self) { + widget = w; + g_object_ref_sink (G_OBJECT (widget)); + defaultScale = 1.0f; + transformer = [[Transformer alloc] init]; + [transformer setFlippedAboutXAxis:YES]; + [self updateLastKnownSize]; + g_object_set (G_OBJECT (widget), "events", GDK_STRUCTURE_MASK, NULL); + g_signal_connect (widget, "expose-event", G_CALLBACK (expose_event_cb), self); + g_signal_connect (widget, "configure-event", G_CALLBACK (configure_event_cb), self); + g_signal_connect (widget, "realize", G_CALLBACK (realize_cb), self); + g_signal_connect (widget, "button-press-event", G_CALLBACK (button_press_event_cb), self); + g_signal_connect (widget, "button-release-event", G_CALLBACK (button_release_event_cb), self); + g_signal_connect (widget, "motion-notify-event", G_CALLBACK (motion_notify_event_cb), self); + g_signal_connect (widget, "key-press-event", G_CALLBACK (key_press_event_cb), self); + g_signal_connect (widget, "key-release-event", G_CALLBACK (key_release_event_cb), self); + g_signal_connect (widget, "scroll-event", G_CALLBACK (scroll_event_cb), self); + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(widgetSizeChanged:) + name:@"SurfaceSizeChanged" + object:self]; + if ([self canFocus]) { + [self addToEventMask:GDK_BUTTON_PRESS_MASK]; + } else { + [self removeFromEventMask:GDK_BUTTON_PRESS_MASK]; + } + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [transformer release]; + g_object_unref (G_OBJECT (widget)); + + [super dealloc]; +} + +- (void) invalidateRect:(NSRect)rect { + if (!NSIsEmptyRect (rect)) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + GdkRectangle g_rect = gdk_rectangle_from_ns_rect (rect); + gdk_window_invalidate_rect (window, &g_rect, TRUE); + } + } +} + +- (void) invalidate { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + GdkRegion *visible = gdk_drawable_get_visible_region (GDK_DRAWABLE (window)); + gdk_window_invalidate_region (window, visible, TRUE); + gdk_region_destroy (visible); + } +} + +- (id<RenderContext>) createRenderContext { + return [CairoRenderContext contextForWidget:widget]; +} + +- (int) width { + int width = 0; + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_drawable_get_size (window, &width, NULL); + } + return width; +} + +- (int) height { + int height = 0; + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_drawable_get_size (window, NULL, &height); + } + return height; +} + +- (void) setSizeRequestWidth:(double)width height:(double)height { + gtk_widget_set_size_request (widget, width, height); +} + +- (Transformer*) transformer { + return transformer; +} + +- (GtkWidget*) widget { + return widget; +} + +- (void) setRenderDelegate:(id <RenderDelegate>)delegate { + // NB: no retention! + renderDelegate = delegate; + if (renderDelegate == nil) { + [self removeFromEventMask:GDK_EXPOSURE_MASK]; + } else { + [self addToEventMask:GDK_EXPOSURE_MASK]; + } +} + +- (id<InputDelegate>) inputDelegate { + return inputDelegate; +} + +- (void) setInputDelegate:(id<InputDelegate>)delegate { + if (delegate == inputDelegate) { + return; + } + buttonPressesRequired = NO; + if (inputDelegate != nil) { + [self removeFromEventMask:GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK]; + } + inputDelegate = delegate; + if (delegate != nil) { + GdkEventMask mask = 0; + if ([delegate respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + buttonPressesRequired = YES; + mask |= GDK_BUTTON_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + mask |= GDK_BUTTON_RELEASE_MASK; + } + if ([delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + buttonPressesRequired = YES; + mask |= GDK_BUTTON_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + mask |= GDK_POINTER_MOTION_MASK; + } + if ([delegate respondsToSelector:@selector(keyPressed:withMask:)]) { + mask |= GDK_KEY_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(keyReleased:withMask:)]) { + mask |= GDK_KEY_RELEASE_MASK; + } + [self addToEventMask:mask]; + } +} + +- (id <RenderDelegate>) renderDelegate { + return renderDelegate; +} + +- (void) setKeepCentered:(BOOL)centered { + keepCentered = centered; + [self updateTransformer]; +} + +- (BOOL) keepCentered { + return keepCentered; +} + +- (BOOL) canFocus { + return gtk_widget_get_can_focus (widget); +} + +- (void) setCanFocus:(BOOL)focus { + gtk_widget_set_can_focus (widget, focus); + if (focus) { + [self addToEventMask:GDK_BUTTON_PRESS_MASK]; + } else if (!buttonPressesRequired) { + [self removeFromEventMask:GDK_BUTTON_PRESS_MASK]; + } +} + +- (BOOL) hasFocus { + return gtk_widget_has_focus (widget); +} + +- (void) renderFocus { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + gtk_paint_focus (gtk_widget_get_style (widget), + window, + GTK_STATE_NORMAL, + NULL, + widget, + NULL, + 0, + 0, + width, + height + ); + } +} + +- (CGFloat) defaultScale { + return defaultScale; +} + +- (void) setDefaultScale:(CGFloat)newDefault { + if (defaultScale != newDefault) { + CGFloat oldDefault = defaultScale; + defaultScale = newDefault; + + CGFloat scale = [transformer scale]; + scale *= (newDefault / oldDefault); + [transformer setScale:scale]; + [self invalidate]; + } +} + +- (void) zoomIn { + CGFloat scale = [transformer scale]; + scale *= 1.2f; + [self zoomTo:scale]; +} + +- (void) zoomOut { + CGFloat scale = [transformer scale]; + scale /= 1.2f; + [self zoomTo:scale]; +} + +- (void) zoomReset { + [self zoomTo:defaultScale]; +} + +- (void) zoomInAboutPoint:(NSPoint)p { + CGFloat scale = [transformer scale]; + scale *= 1.2f; + [self zoomTo:scale aboutPoint:p]; +} + +- (void) zoomOutAboutPoint:(NSPoint)p { + CGFloat scale = [transformer scale]; + scale /= 1.2f; + [self zoomTo:scale aboutPoint:p]; +} + +- (void) zoomResetAboutPoint:(NSPoint)p { + [self zoomTo:defaultScale aboutPoint:p]; +} + +- (void) setCursor:(Cursor)c { + GdkCursor *cursor = NULL; + switch (c) { + case ResizeRightCursor: + cursor = gdk_cursor_new (GDK_RIGHT_SIDE); + break; + case ResizeBottomRightCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER); + break; + case ResizeBottomCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_SIDE); + break; + case ResizeBottomLeftCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER); + break; + case ResizeLeftCursor: + cursor = gdk_cursor_new (GDK_LEFT_SIDE); + break; + case ResizeTopLeftCursor: + cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER); + break; + case ResizeTopCursor: + cursor = gdk_cursor_new (GDK_TOP_SIDE); + break; + case ResizeTopRightCursor: + cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER); + break; + default: break; + } + GdkWindow *window = gtk_widget_get_window (widget); + g_signal_handlers_disconnect_matched (window, + G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + G_CALLBACK (set_cursor), NULL); + if (window) { + gdk_window_set_cursor (window, cursor); + if (cursor != NULL) { + gdk_cursor_unref (cursor); + } + } else { + g_signal_connect_data (widget, + "realize", G_CALLBACK (set_cursor), cursor, + unref_cursor, 0); + } +} + +@end +// }}} +// {{{ Private +@implementation WidgetSurface (Private) +- (void) widgetSizeChanged:(NSNotification*)notification { + [self updateTransformer]; + [self updateLastKnownSize]; +} + +- (void) updateTransformer { + if (keepCentered) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + NSPoint origin; + if (lastKnownSize.width < 1 || lastKnownSize.height < 1) { + origin.x = (float)width / 2.0f; + origin.y = (float)height / 2.0f; + } else { + origin = [transformer origin]; + origin.x += ((float)width - lastKnownSize.width) / 2.0f; + origin.y += ((float)height - lastKnownSize.height) / 2.0f; + } + [transformer setOrigin:origin]; + } + } +} + +- (void) handleExposeEvent:(GdkEventExpose*)event { + if (renderDelegate != nil) { + NSRect area = gdk_rectangle_to_ns_rect (event->area); + + id<RenderContext> context = [CairoRenderContext contextForWidget:widget]; + [context rect:area]; + [context clipToPath]; + [renderDelegate renderWithContext:context onSurface:self]; + } +} + +- (void) updateLastKnownSize { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + lastKnownSize.width = (float)width; + lastKnownSize.height = (float)height; + } else { + lastKnownSize = NSZeroSize; + } +} + +- (void) zoomTo:(CGFloat)scale aboutPoint:(NSPoint)p { + NSPoint graphP = [transformer fromScreen:p]; + + [transformer setScale:scale]; + + NSPoint newP = [transformer toScreen:graphP]; + NSPoint origin = [transformer origin]; + origin.x += p.x - newP.x; + origin.y += p.y - newP.y; + [transformer setOrigin:origin]; + + [self invalidate]; +} + +- (void) zoomTo:(CGFloat)scale { + NSPoint centre = NSMakePoint (lastKnownSize.width/2.0f, lastKnownSize.height/2.0f); + [self zoomTo:scale aboutPoint:centre]; +} + +- (void) addToEventMask:(GdkEventMask)values { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask |= values; + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +- (void) removeFromEventMask:(GdkEventMask)values { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask ^= values; + if (buttonPressesRequired || [self canFocus]) { + mask |= GDK_BUTTON_PRESS_MASK; + } + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +@end +// }}} +// {{{ GTK+ callbacks +static gboolean configure_event_cb(GtkWidget *widget, GdkEventConfigure *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SurfaceSizeChanged" object:surface]; + [pool drain]; + return FALSE; +} + +static void realize_cb (GtkWidget *widget, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [surface updateTransformer]; + [pool drain]; +} + +static gboolean expose_event_cb(GtkWidget *widget, GdkEventExpose *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [surface handleExposeEvent:event]; + [pool drain]; + return FALSE; +} + +InputMask mask_from_gdk_modifier_state (GdkModifierType state) { + InputMask mask = 0; + if (state & GDK_SHIFT_MASK) { + mask |= ShiftMask; + } + if (state & GDK_CONTROL_MASK) { + mask |= ControlMask; + } + if (state & GDK_META_MASK) { + mask |= MetaMask; + } + return mask; +} + +ScrollDirection scroll_dir_from_gdk_scroll_dir (GdkScrollDirection dir) { + switch (dir) { + case GDK_SCROLL_UP: return ScrollUp; + case GDK_SCROLL_DOWN: return ScrollDown; + case GDK_SCROLL_LEFT: return ScrollLeft; + case GDK_SCROLL_RIGHT: return ScrollRight; + default: NSLog(@"Invalid scroll direction %i", (int)dir); return ScrollDown; + } +} + +MouseButton buttons_from_gdk_modifier_state (GdkModifierType state) { + MouseButton buttons = 0; + if (state & GDK_BUTTON1_MASK) { + buttons |= LeftButton; + } + if (state & GDK_BUTTON2_MASK) { + buttons |= MiddleButton; + } + if (state & GDK_BUTTON3_MASK) { + buttons |= RightButton; + } + if (state & GDK_BUTTON4_MASK) { + buttons |= Button4; + } + if (state & GDK_BUTTON5_MASK) { + buttons |= Button5; + } + return buttons; +} + +static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if ([surface canFocus]) { + if (!gtk_widget_has_focus (widget)) { + gtk_widget_grab_focus (widget); + } + } + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton button = (MouseButton)event->button; + InputMask mask = mask_from_gdk_modifier_state (event->state); + if (event->type == GDK_BUTTON_PRESS && [delegate respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + [delegate mousePressAt:pos withButton:button andMask:mask]; + } + if (event->type == GDK_2BUTTON_PRESS && [delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + [delegate mouseDoubleClickAt:pos withButton:button andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton button = (MouseButton)event->button; + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate mouseReleaseAt:pos withButton:button andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton buttons = buttons_from_gdk_modifier_state (event->state); + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate mouseMoveTo:pos withButtons:buttons andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean key_press_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(keyPressed:withMask:)]) { + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate keyPressed:event->keyval withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean key_release_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(keyReleased:withMask:)]) { + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate keyReleased:event->keyval withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<InputDelegate> delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + InputMask mask = mask_from_gdk_modifier_state (event->state); + ScrollDirection dir = scroll_dir_from_gdk_scroll_dir (event->direction); + [delegate mouseScrolledAt:pos + inDirection:dir + withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static void unref_cursor (gpointer cursor, GClosure *closure) { + if (cursor != NULL) { + gdk_cursor_unref ((GdkCursor*)cursor); + } +} + +static void set_cursor (GtkWidget *widget, GdkCursor *cursor) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_window_set_cursor (window, cursor); + if (cursor != NULL) { + gdk_cursor_unref (cursor); + } + } +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Window.h b/tikzit-1/src/gtk/Window.h new file mode 100644 index 0000000..a3ce8a4 --- /dev/null +++ b/tikzit-1/src/gtk/Window.h @@ -0,0 +1,182 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class GraphEditorPanel; +@class Menu; +@class PropertyPane; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class StylesPane; +@class TikzDocument; +@protocol Tool; + +/** + * Manages a document window + */ +@interface Window: NSObject { + // GTK+ widgets + GtkWindow *window; + GtkTextBuffer *tikzBuffer; + GtkStatusbar *statusBar; + GtkPaned *tikzPaneSplitter; + GtkWidget *tikzPane; + + gulong clipboard_handler_id; + GtkTextTag *errorHighlightTag; // owned by tikzBuffer + + // Classes that manage parts of the window + Menu *menu; + GraphEditorPanel *graphPanel; + + PreviewWindow *previewWindow; + + // state variables + BOOL suppressTikzUpdates; + BOOL hasParseError; + + // the document displayed by the window + TikzDocument *document; +} + +/** + * The document displayed by the window + */ +@property (retain) TikzDocument *document; +@property (readonly) BOOL hasFocus; +@property (readonly) GtkWindow *gtkWindow; + +/** + * Create a window with an empty document + */ +- (id) init; ++ (id) window; + +/** + * Create a window with the given document + */ +- (id) initWithDocument:(TikzDocument*)doc; ++ (id) windowWithDocument:(TikzDocument*)doc; + +/** + * Present the window to the user + */ +- (void) present; + +/** + * Open a file, asking the user which file to open + */ +- (void) openFile; +/** + * Open a file + */ +- (BOOL) openFileAtPath:(NSString*)path; +/** + * Save the active document to the path it was opened from + * or last saved to, or ask the user where to save it. + */ +- (BOOL) saveActiveDocument; +/** + * Save the active document, asking the user where to save it. + */ +- (BOOL) saveActiveDocumentAs; +/** + * Save the active document as a shape, asking the user what to name it. + */ +- (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. + */ +- (void) selectionCutToClipboard; +/** + * Copy the current selection to the clipboard. + */ +- (void) selectionCopyToClipboard; +/** + * Paste from the clipboard to the appropriate place. + */ +- (void) pasteFromClipboard; + +/** + * The GTK+ window that this class manages. + */ +- (GtkWindow*) gtkWindow; +/** + * The menu for the window. + */ +- (Menu*) menu; + +/** + * Present an error to the user + * + * @param error the error to present + */ +- (void) presentError:(NSError*)error; +/** + * Present an error to the user + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentError:(NSError*)error withMessage:(NSString*)message; +/** + * Present an error to the user + * + * @param error the error to present + */ +- (void) presentGError:(GError*)error; +/** + * Present an error to the user + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentGError:(GError*)error withMessage:(NSString*)message; + +- (void) setActiveTool:(id<Tool>)tool; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; + +/** + * Show or update the preview window. + */ +- (void) presentPreview; +/** + * Show or update the preview window without it grabbing focus + */ +- (void) updatePreview; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Window.m b/tikzit-1/src/gtk/Window.m new file mode 100644 index 0000000..2d9e63a --- /dev/null +++ b/tikzit-1/src/gtk/Window.m @@ -0,0 +1,991 @@ +/* + * Copyright 2011-2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Window.h" + +#import <gtk/gtk.h> +#import "gtkhelpers.h" +#import "clipboard.h" + +#import "Application.h" +#import "Configuration.h" +#import "FileChooserDialog.h" +#import "GraphEditorPanel.h" +#import "Menu.h" +#import "RecentManager.h" +#import "Shape.h" +#import "SupportDir.h" +#import "TikzDocument.h" + +#ifdef HAVE_POPPLER +#import "PreviewWindow.h" +#endif + +enum { + GraphInfoStatus, + ParseStatus +}; + +// {{{ Internal interfaces +// {{{ Clipboard support + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data); +static void clipboard_release_data (GtkClipboard *clipboard, gpointer clipboard_graph_data); +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action); +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document); + +// }}} +// {{{ Signals + +static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window); +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window); +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window); +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window); +static void main_window_destroy_cb (GtkWidget *widget, Window *window); +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window); +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action); + +// }}} + +@interface Window (Notifications) +- (void) tikzBufferChanged; +- (void) windowSizeChangedWidth:(int)width height:(int)height; +- (void) documentTikzChanged:(NSNotification*)notification; +- (void) documentSelectionChanged:(NSNotification*)notification; +- (void) undoStackChanged:(NSNotification*)notification; +@end + +@interface Window (InitHelpers) +- (void) _loadUi; +- (void) _restoreUiState; +- (void) _connectSignals; +@end + +@interface Window (Private) <PreviewHandler> +- (BOOL) _askCanClose; +/** Open a document, dealing with errors as necessary */ +- (TikzDocument*) _openDocument:(NSString*)path; +- (void) _placeGraphOnClipboard:(Graph*)graph; +- (void) _clearParseError; +- (void) _setParseError:(NSError*)error; +/** Update the window title. */ +- (void) _updateTitle; +/** Update the window status bar default text. */ +- (void) _updateStatus; +/** Update the displayed tikz code to match the active document. */ +- (void) _updateTikz; +/** Update the undo and redo actions to match the active document's + * undo stack. */ +- (void) _updateUndoActions; +- (void) showPreview; +@end + +// }}} +// {{{ API + +@implementation Window + +@synthesize gtkWindow=window; + +- (id) init { + return [self initWithDocument:[TikzDocument documentWithStyleManager:[app styleManager]]]; +} ++ (id) window { + return [[[self alloc] init] autorelease]; +} +- (id) initWithDocument:(TikzDocument*)doc { + self = [super init]; + + if (self) { + [self _loadUi]; + [self _restoreUiState]; + [self _connectSignals]; + + [self setDocument:doc]; + + gtk_widget_show (GTK_WIDGET (window)); + } + + return self; +} ++ (id) windowWithDocument:(TikzDocument*)doc { + return [[[self alloc] initWithDocument:doc] autorelease]; +} + +- (void) dealloc { + // The GTK+ window has already been destroyed at this point + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + g_signal_handler_disconnect ( + gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + clipboard_handler_id); + + [previewWindow release]; + [menu release]; + [graphPanel release]; + [document release]; + + g_object_unref (tikzBuffer); + g_object_unref (tikzPane); + g_object_unref (tikzPaneSplitter); + g_object_unref (statusBar); + g_object_unref (window); + + [super dealloc]; +} + +- (TikzDocument*) document { + return document; +} +- (void) setDocument:(TikzDocument*)newDoc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + + TikzDocument *oldDoc = document; + document = [newDoc retain]; + + [graphPanel setDocument:document]; + [previewWindow setDocument:document]; + [self _updateTikz]; + [self _updateTitle]; + [self _updateStatus]; + [self _updateUndoActions]; + [menu notifySelectionChanged:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentTikzChanged:) + name:@"TikzChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(undoStackChanged:) + name:@"UndoStackChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"NodeSelectionChanged" object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"EdgeSelectionChanged" object:[document pickSupport]]; + + if ([document path] != nil) { + [[RecentManager defaultManager] addRecentFile:[document path]]; + } + + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + document, @"document", + oldDoc, @"oldDocument", + nil]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"DocumentChanged" + object:self + userInfo:userInfo]; + [oldDoc release]; +} + +- (BOOL) hasFocus { + return gtk_window_has_toplevel_focus (GTK_WINDOW (window)); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) openFile { + FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path]) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if ([app lastOpenFolder]) { + [dialog setCurrentFolder:[app lastOpenFolder]]; + } + + if ([dialog showDialog]) { + if ([self openFileAtPath:[dialog filePath]]) { + [app setLastOpenFolder:[dialog currentFolder]]; + } + } + [dialog destroy]; +} + +- (BOOL) openFileAtPath:(NSString*)path { + TikzDocument *doc = [self _openDocument:path]; + if (doc != nil) { + if (![document hasUnsavedChanges] && [document path] == nil) { + // we just have a fresh, untitled document - replace it + [self setDocument:doc]; + } else { + [app newWindowWithDocument:doc]; + } + return YES; + } + return NO; +} + +- (BOOL) saveActiveDocument { + if ([document path] == nil) { + return [self saveActiveDocumentAs]; + } else { + NSError *error = nil; + if (![document save:&error]) { + [self presentError:error]; + return NO; + } else { + [self _updateTitle]; + return YES; + } + } +} + +- (BOOL) saveActiveDocumentAs { + FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path] != nil) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if ([app lastSaveAsFolder] != nil) { + [dialog setCurrentFolder:[app lastSaveAsFolder]]; + } + [dialog setSuggestedName:[document suggestedFileName]]; + + BOOL saved = NO; + if ([dialog showDialog]) { + NSString *nfile = [dialog filePath]; + + NSError *error = nil; + if (![document saveToPath:nfile error:&error]) { + [self presentError:error]; + } else { + [self _updateTitle]; + [[RecentManager defaultManager] addRecentFile:nfile]; + [app setLastSaveAsFolder:[dialog currentFolder]]; + saved = YES; + } + } + [dialog destroy]; + return saved; +} + +- (void) saveActiveDocumentAsShape { + GtkWidget *dialog = gtk_dialog_new_with_buttons ( + "Save as shape", + window, + GTK_DIALOG_MODAL, + GTK_STOCK_OK, + GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, + GTK_RESPONSE_REJECT, + NULL); + GtkBox *content = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))); + GtkWidget *label1 = gtk_label_new ("Please choose a name for the shape"); + GtkWidget *label2 = gtk_label_new ("Name:"); + GtkWidget *input = gtk_entry_new (); + GtkBox *hbox = GTK_BOX (gtk_hbox_new (FALSE, 5)); + gtk_box_pack_start (hbox, label2, FALSE, TRUE, 0); + gtk_box_pack_start (hbox, input, TRUE, TRUE, 0); + gtk_box_pack_start (content, label1, TRUE, TRUE, 5); + gtk_box_pack_start (content, GTK_WIDGET (hbox), TRUE, TRUE, 5); + gtk_widget_show_all (GTK_WIDGET (content)); + gint response = gtk_dialog_run (GTK_DIALOG (dialog)); + while (response == GTK_RESPONSE_ACCEPT) { + response = GTK_RESPONSE_NONE; + NSDictionary *shapeDict = [Shape shapeDictionary]; + const gchar *dialogInput = gtk_entry_get_text (GTK_ENTRY (input)); + NSString *shapeName = [NSString stringWithUTF8String:dialogInput]; + BOOL doSave = NO; + if ([shapeName isEqual:@""]) { + GtkWidget *emptyStrDialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "You must specify a shape name"); + gtk_dialog_run (GTK_DIALOG (emptyStrDialog)); + gtk_widget_destroy (emptyStrDialog); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } else if ([shapeDict objectForKey:shapeName] != nil) { + GtkWidget *overwriteDialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Do you want to replace the existing shape named '%s'?", + dialogInput); + gint overwriteResp = gtk_dialog_run (GTK_DIALOG (overwriteDialog)); + gtk_widget_destroy (overwriteDialog); + + if (overwriteResp == GTK_RESPONSE_YES) { + doSave = YES; + } else { + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } + } else { + doSave = YES; + } + if (doSave) { + NSError *error = nil; + NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; + NSString *file = [NSString stringWithFormat:@"%@/%@.tikz", userShapeDir, shapeName]; + if (![[NSFileManager defaultManager] ensureDirectoryExists:userShapeDir error:&error]) { + [self presentError:error withMessage:@"Could not create user shape directory"]; + } else { + if (![document saveCopyToPath:file error:&error]) { + [self presentError:error withMessage:@"Could not save shape file"]; + } else { + [Shape refreshShapeDictionary]; + } + } + } + } + gtk_widget_destroy (dialog); +} + +- (void) close { + if ([self _askCanClose]) { + gtk_widget_destroy (GTK_WIDGET (window)); + } +} + +- (void) selectionCutToClipboard { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document selectionCut]]; + } +} + +- (void) selectionCopyToClipboard { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document selectionCopy]]; + } +} + +- (void) pasteFromClipboard { + gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + tikzit_picture_atom, + clipboard_paste_contents, + document); +} + +- (GtkWindow*) gtkWindow { + return window; +} + +- (Configuration*) mainConfiguration { + return [app mainConfiguration]; +} + +- (Menu*) menu { + return menu; +} + +- (void) presentError:(NSError*)error { + const gchar *errorDesc = "unknown error"; + if (error && [error localizedDescription]) { + errorDesc = [[error localizedDescription] UTF8String]; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentError:(NSError*)error withMessage:(NSString*)message { + const gchar *errorDesc = "unknown error"; + if (error && [error localizedDescription]) { + errorDesc = [[error localizedDescription] UTF8String]; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error { + const gchar *errorDesc = "unknown error"; + if (error && error->message) { + errorDesc = error->message; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error withMessage:(NSString*)message { + const gchar *errorDesc = "unknown error"; + if (error && error->message) { + errorDesc = error->message; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) setActiveTool:(id<Tool>)tool { + [graphPanel setActiveTool:tool]; + gboolean hasfocus; + g_object_get (G_OBJECT (window), "has-toplevel-focus", &hasfocus, NULL); + if (hasfocus) { + [graphPanel grabTool]; + } +} + +- (void) zoomIn { + [graphPanel zoomIn]; +} + +- (void) zoomOut { + [graphPanel zoomOut]; +} + +- (void) zoomReset { + [graphPanel zoomReset]; +} + +- (void) presentPreview { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:[app preambles] + config:[app mainConfiguration]]; + //[previewWindow setParentWindow:self]; + [previewWindow setDocument:document]; + } + [previewWindow present]; +#endif +} + +- (void) updatePreview { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:[app preambles] + config:[app mainConfiguration]]; + //[previewWindow setParentWindow:self]; + [previewWindow setDocument:document]; + } + [previewWindow show]; +#endif +} + +@end + +// }}} +// {{{ Notifications + +@implementation Window (Notifications) +- (void) graphHeightChanged:(int)newHeight { + [[app mainConfiguration] setIntegerEntry:@"graphHeight" + inGroup:@"window" + value:newHeight]; +} + +- (void) tikzBufferChanged { + if (!suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + GtkTextIter start, end; + gtk_text_buffer_get_bounds (tikzBuffer, &start, &end); + gchar *text = gtk_text_buffer_get_text (tikzBuffer, &start, &end, FALSE); + + NSError *error = nil; + BOOL success = [document updateTikz:[NSString stringWithUTF8String:text] error:&error]; + if (success) + [self _clearParseError]; + else + [self _setParseError:error]; + + g_free (text); + + suppressTikzUpdates = FALSE; + } +} + +- (void) windowSizeChangedWidth:(int)width height:(int)height { + if (width > 0 && height > 0) { + NSNumber *w = [NSNumber numberWithInt:width]; + NSNumber *h = [NSNumber numberWithInt:height]; + NSMutableArray *size = [NSMutableArray arrayWithCapacity:2]; + [size addObject:w]; + [size addObject:h]; + [[app mainConfiguration] setIntegerListEntry:@"windowSize" + inGroup:@"window" + value:size]; + } +} + +- (void) documentTikzChanged:(NSNotification*)notification { + [self _updateTitle]; + [self _updateTikz]; +} + +- (void) documentSelectionChanged:(NSNotification*)notification { + [self _updateStatus]; + [menu notifySelectionChanged:[document pickSupport]]; +} + +- (void) undoStackChanged:(NSNotification*)notification { + [self _updateUndoActions]; +} +@end + +// }}} +// {{{ InitHelpers + +@implementation Window (InitHelpers) + +- (void) _loadUi { + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + g_object_ref_sink (window); + gtk_window_set_title (window, "TikZiT"); + gtk_window_set_default_size (window, 700, 400); + + GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0)); + gtk_widget_show (GTK_WIDGET (mainLayout)); + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (mainLayout)); + + menu = [[Menu alloc] initForWindow:self]; + + GtkWidget *menubar = [menu menubar]; + gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0); + gtk_box_reorder_child (mainLayout, menubar, 0); + gtk_widget_show (menubar); + + tikzPaneSplitter = GTK_PANED (gtk_vpaned_new ()); + g_object_ref_sink (tikzPaneSplitter); + gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0); + + graphPanel = [[GraphEditorPanel alloc] initWithDocument:document]; + [graphPanel setPreviewHandler:self]; + GtkWidget *graphEditorWidget = [graphPanel widget]; + gtk_widget_show (graphEditorWidget); + GtkWidget *graphFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (graphFrame), graphEditorWidget); + gtk_widget_show (graphFrame); + gtk_paned_pack1 (tikzPaneSplitter, graphFrame, TRUE, TRUE); + + tikzBuffer = gtk_text_buffer_new (NULL); + g_object_ref_sink (tikzBuffer); + errorHighlightTag = gtk_text_buffer_create_tag ( + tikzBuffer, NULL, + "foreground", "#d40000", + "foreground-set", TRUE, + "weight", PANGO_WEIGHT_SEMIBOLD, + "weight-set", TRUE, + NULL); + GtkWidget *tikzScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (tikzScroller); + + tikzPane = gtk_text_view_new_with_buffer (tikzBuffer); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tikzPane), 3); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tikzPane), 3); + g_object_ref_sink (tikzPane); + gtk_widget_show (tikzPane); + gtk_container_add (GTK_CONTAINER (tikzScroller), tikzPane); + GtkWidget *tikzFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (tikzFrame), tikzScroller); + gtk_widget_show (tikzFrame); + gtk_paned_pack2 (tikzPaneSplitter, tikzFrame, FALSE, TRUE); + + statusBar = GTK_STATUSBAR (gtk_statusbar_new ()); + g_object_ref_sink (statusBar); + gtk_widget_show (GTK_WIDGET (statusBar)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (statusBar), FALSE, TRUE, 0); + + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + update_paste_action (clipboard, NULL, [menu pasteAction]); +} + +- (void) _restoreUiState { + Configuration *config = [app mainConfiguration]; + NSArray *windowSize = [config integerListEntry:@"windowSize" + inGroup:@"window"]; + if (windowSize && [windowSize count] == 2) { + gint width = [[windowSize objectAtIndex:0] intValue]; + gint height = [[windowSize objectAtIndex:1] intValue]; + if (width > 0 && height > 0) { + gtk_window_set_default_size (window, width, height); + } + } + int panePos = [config integerEntry:@"graphHeight" + inGroup:@"window"]; + if (panePos > 0) { + gtk_paned_set_position (tikzPaneSplitter, panePos); + } +} + +- (void) _connectSignals { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + clipboard_handler_id = + g_signal_connect (G_OBJECT (clipboard), + "owner-change", + G_CALLBACK (update_paste_action), + [menu pasteAction]); + g_signal_connect (G_OBJECT (window), + "key-press-event", + G_CALLBACK (tz_hijack_key_press), + NULL); + g_signal_connect (G_OBJECT (window), + "notify::has-toplevel-focus", + G_CALLBACK (window_toplevel_focus_changed_cb), + self); + g_signal_connect (G_OBJECT (tikzPaneSplitter), + "notify::position", + G_CALLBACK (graph_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (tikzBuffer), + "changed", + G_CALLBACK (tikz_buffer_changed_cb), + self); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (main_window_delete_event_cb), + self); + g_signal_connect (G_OBJECT (window), + "destroy", + G_CALLBACK (main_window_destroy_cb), + self); + g_signal_connect (G_OBJECT (window), + "configure-event", + G_CALLBACK (main_window_configure_event_cb), + self); +} +@end + +// }}} +// {{{ Private + +@implementation Window (Private) + +- (BOOL) _askCanClose { + if ([document hasUnsavedChanges]) { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + "Save changes to the document \"%s\" before closing?", + [[document name] UTF8String]); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + "Save", GTK_RESPONSE_YES, + "Don't save", GTK_RESPONSE_NO, + "Cancel", GTK_RESPONSE_CANCEL, + NULL); + gint result = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + if (result == GTK_RESPONSE_YES) { + return [self saveActiveDocument]; + } else { + return result == GTK_RESPONSE_NO; + } + } else { + return YES; + } +} + +- (TikzDocument*) _openDocument:(NSString*)path { + NSError *error = nil; + TikzDocument *d = [TikzDocument documentFromFile:path + styleManager:[app styleManager] + error:&error]; + if (d != nil) { + return d; + } else { + if ([error code] == TZ_ERR_PARSE) { + [self presentError:error withMessage:@"Invalid file"]; + } else { + [self presentError:error withMessage:@"Could not open file"]; + } + [[RecentManager defaultManager] removeRecentFile:path]; + return nil; + } +} + +- (void) _placeGraphOnClipboard:(Graph*)graph { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + + static const GtkTargetEntry targets[] = { + { "TIKZITPICTURE", 0, TARGET_TIKZIT_PICTURE }, + { "UTF8_STRING", 0, TARGET_UTF8_STRING } }; + + gtk_clipboard_set_with_data (clipboard, + targets, G_N_ELEMENTS (targets), + clipboard_provide_data, + clipboard_release_data, + clipboard_graph_data_new (graph)); +} + +- (void) _clearParseError { + if (!hasParseError) + return; + gtk_statusbar_pop (statusBar, ParseStatus); + text_buffer_clear_tag (tikzBuffer, errorHighlightTag); + widget_clear_error (tikzPane); + hasParseError = NO; +} + +- (void) _setParseError:(NSError*)error { + if (!hasParseError) { + widget_set_error (tikzPane); + hasParseError = YES; + } + NSString *message = [NSString stringWithFormat:@"Parse error: %@", [error localizedDescription]]; + gtk_statusbar_pop (statusBar, ParseStatus); + gtk_statusbar_push (statusBar, ParseStatus, [message UTF8String]); + + text_buffer_clear_tag (tikzBuffer, errorHighlightTag); + + NSDictionary *errorInfo = [error userInfo]; + if ([errorInfo objectForKey:@"startLine"] != nil) { + GtkTextIter symbolStart; + GtkTextIter symbolEnd; + gtk_text_buffer_get_iter_at_line_index (tikzBuffer, &symbolStart, + [[errorInfo objectForKey:@"startLine"] intValue] - 1, + [[errorInfo objectForKey:@"startColumn"] intValue] - 1); + gtk_text_buffer_get_iter_at_line_index (tikzBuffer, &symbolEnd, + [[errorInfo objectForKey:@"endLine"] intValue] - 1, + [[errorInfo objectForKey:@"endColumn"] intValue]); + gtk_text_buffer_apply_tag (tikzBuffer, errorHighlightTag, + &symbolStart, &symbolEnd); + } +} + +- (void) _updateUndoActions { + [menu setUndoActionEnabled:[document canUndo]]; + [menu setUndoActionDetail:[document undoName]]; + + [menu setRedoActionEnabled:[document canRedo]]; + [menu setRedoActionDetail:[document redoName]]; +} + +- (void) _updateTitle { + NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s", + [document name], + ([document hasUnsavedChanges] ? "*" : "")]; + gtk_window_set_title(window, [title UTF8String]); +} + +- (void) _updateStatus { + // FIXME: show tooltips or something instead + GString *buffer = g_string_sized_new (30); + gchar *nextNode = 0; + + for (Node *n in [[document pickSupport] selectedNodes]) { + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Nodes %s", nextNode); + } else { + g_string_append_printf(buffer, ", %s", nextNode); + } + } + nextNode = (gchar *)[[n name] UTF8String]; + } + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Node %s is selected", nextNode); + } else { + g_string_append_printf(buffer, " and %s are selected", nextNode); + } + } + + if (buffer->len == 0) { + int nrNodes = [[[document graph] nodes] count]; + int nrEdges = [[[document graph] edges] count]; + g_string_printf(buffer, "Graph has %d node%s and %d edge%s", + nrNodes, + nrNodes!=1 ? "s" : "", + nrEdges, + nrEdges!=1 ? "s" : ""); + } + gtk_statusbar_pop(statusBar, GraphInfoStatus); + gtk_statusbar_push(statusBar, GraphInfoStatus, buffer->str); + + g_string_free (buffer, TRUE); +} + +- (void) _updateTikz { + if (document != nil && !suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + if (document != nil) { + const char *tikzString = [[document tikz] UTF8String]; + gtk_text_buffer_set_text (tikzBuffer, tikzString, -1); + } else { + gtk_text_buffer_set_text (tikzBuffer, "", -1); + } + [self _clearParseError]; + + suppressTikzUpdates = FALSE; + } +} + +- (GraphEditorPanel*) _graphPanel { + return graphPanel; +} + +- (void) showPreview { + [self updatePreview]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gboolean hasfocus; + g_object_get (gobject, "has-toplevel-focus", &hasfocus, NULL); + if (hasfocus) { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WindowGainedFocus" + object:window]; + [[window _graphPanel] grabTool]; + } else { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WindowLostFocus" + object:window]; + } + [pool drain]; +} + +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gint position; + g_object_get (gobject, "position", &position, NULL); + [window graphHeightChanged:position]; + [pool drain]; +} + +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window tikzBufferChanged]; + [pool drain]; +} + +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window close]; + [pool drain]; + return TRUE; +} + +static void main_window_destroy_cb (GtkWidget *widget, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"WindowClosed" + object:window]; + [pool drain]; +} + +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window windowSizeChangedWidth:event->width height:event->height]; + [pool drain]; + return FALSE; +} + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + ClipboardGraphData *data = (ClipboardGraphData*)clipboard_graph_data; + if (info == TARGET_UTF8_STRING || info == TARGET_TIKZIT_PICTURE) { + clipboard_graph_data_convert (data); + GdkAtom target = (info == TARGET_UTF8_STRING) ? utf8_atom : tikzit_picture_atom; + gtk_selection_data_set (selection_data, + target, + 8*sizeof(gchar), + (guchar*)data->tikz, + data->tikz_length); + } + + [pool drain]; +} + +static void clipboard_release_data (GtkClipboard *clipboard, gpointer data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + clipboard_graph_data_free ((ClipboardGraphData*)data); + [pool drain]; +} + +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gboolean found = FALSE; + for (gint i = 0; i < n_atoms; ++i) { + if (atoms[i] == tikzit_picture_atom) { + found = TRUE; + break; + } + } + gtk_action_set_sensitive (GTK_ACTION (action), found); + + [pool drain]; +} + +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action) { + gtk_action_set_sensitive (action, FALSE); + gtk_clipboard_request_targets (clipboard, clipboard_check_targets, action); +} + +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *doc = (TikzDocument*)document; + gint length = gtk_selection_data_get_length (selection_data); + if (length >= 0) { + const guchar *raw_data = gtk_selection_data_get_data (selection_data); + gchar *data = g_new (gchar, length+1); + g_strlcpy (data, (const gchar *)raw_data, length+1); + NSString *tikz = [NSString stringWithUTF8String:data]; + if (tikz != nil) { + [doc pasteFromTikz:tikz]; + } + g_free (data); + } + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/cairo_helpers.h b/tikzit-1/src/gtk/cairo_helpers.h new file mode 100644 index 0000000..e95357b --- /dev/null +++ b/tikzit-1/src/gtk/cairo_helpers.h @@ -0,0 +1,25 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import "RColor.h" +#import <cairo/cairo.h> + +void cairo_ns_rectangle (cairo_t* cr, NSRect rect); +void cairo_set_source_rcolor (cairo_t* cr, RColor color); + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/cairo_helpers.m b/tikzit-1/src/gtk/cairo_helpers.m new file mode 100644 index 0000000..104e686 --- /dev/null +++ b/tikzit-1/src/gtk/cairo_helpers.m @@ -0,0 +1,28 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "cairo_helpers.h" + +void cairo_ns_rectangle (cairo_t* cr, NSRect rect) { + cairo_rectangle (cr, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +void cairo_set_source_rcolor (cairo_t* cr, RColor color) { + cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha); +} + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit-1/src/gtk/clipboard.h b/tikzit-1/src/gtk/clipboard.h new file mode 100644 index 0000000..568fc50 --- /dev/null +++ b/tikzit-1/src/gtk/clipboard.h @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> +#import <Graph.h> + +enum { + TARGET_UTF8_STRING, + TARGET_TIKZIT_PICTURE +}; +typedef struct +{ + Graph *graph; + gchar *tikz; + gint tikz_length; +} ClipboardGraphData; + +extern GdkAtom utf8_atom; +extern GdkAtom tikzit_picture_atom; + +void clipboard_init (); +ClipboardGraphData *clipboard_graph_data_new (Graph *graph); +void clipboard_graph_data_free (ClipboardGraphData *data); +void clipboard_graph_data_convert (ClipboardGraphData *data); + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/clipboard.m b/tikzit-1/src/gtk/clipboard.m new file mode 100644 index 0000000..7001717 --- /dev/null +++ b/tikzit-1/src/gtk/clipboard.m @@ -0,0 +1,57 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "clipboard.h" + +GdkAtom utf8_atom; +GdkAtom tikzit_picture_atom; + +void clipboard_init () { + if (utf8_atom == GDK_NONE) { + utf8_atom = gdk_atom_intern ("UTF8_STRING", FALSE); + } + if (tikzit_picture_atom == GDK_NONE) { + tikzit_picture_atom = gdk_atom_intern ("TIKZITPICTURE", FALSE); + } +} + +ClipboardGraphData *clipboard_graph_data_new (Graph *graph) { + ClipboardGraphData *data = g_new (ClipboardGraphData, 1); + data->graph = [graph retain]; + data->tikz = NULL; + data->tikz_length = 0; + return data; +} + +void clipboard_graph_data_free (ClipboardGraphData *data) { + [data->graph release]; + if (data->tikz) { + g_free (data->tikz); + } + g_free (data); +} + +void clipboard_graph_data_convert (ClipboardGraphData *data) { + if (data->graph != nil && !data->tikz) { + data->tikz = g_strdup ([[data->graph tikz] UTF8String]); + data->tikz_length = strlen (data->tikz); + [data->graph release]; + data->graph = nil; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/gtkhelpers.h b/tikzit-1/src/gtk/gtkhelpers.h new file mode 100644 index 0000000..e4b79b8 --- /dev/null +++ b/tikzit-1/src/gtk/gtkhelpers.h @@ -0,0 +1,60 @@ +// +// gtkhelpers.h +// TikZiT +// +// Copyright 2010 Alex Merry. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "TZFoundation.h" +#include <gtk/gtk.h> +#import <gdk-pixbuf/gdk-pixbuf.h> + +/** + * Releases the Objective-C object pointed to by data + * + * Intended for use as a cleanup function in Glib/GObject-based + * code. + */ +void release_obj (gpointer data); + +NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end); + +GdkRectangle gdk_rectangle_from_ns_rect (NSRect rect); +NSRect gdk_rectangle_to_ns_rect (GdkRectangle rect); + +void gtk_action_set_detailed_label (GtkAction *action, const gchar *baseLabel, const gchar *actionName); + +gint tz_hijack_key_press (GtkWindow *win, + GdkEventKey *event, + gpointer user_data); + +// Equivalent of GTK+3's gdk_pixbuf_get_from_surface() +GdkPixbuf * pixbuf_get_from_surface(cairo_surface_t *surface); + +void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h); + +void label_set_bold (GtkLabel *label); + +void widget_set_error (GtkWidget *widget); +void widget_clear_error (GtkWidget *widget); + +void text_buffer_clear_tag (GtkTextBuffer *buffer, GtkTextTag *tag); + +void utility_window_attach (GtkWindow *util_win, GtkWindow *parent_win); + +// vim:ft=objc:sts=2:sw=2:et diff --git a/tikzit-1/src/gtk/gtkhelpers.m b/tikzit-1/src/gtk/gtkhelpers.m new file mode 100644 index 0000000..9d26af5 --- /dev/null +++ b/tikzit-1/src/gtk/gtkhelpers.m @@ -0,0 +1,275 @@ +// +// gtkhelpers.h +// TikZiT +// +// Copyright 2010 Alex Merry. All rights reserved. +// +// Some code from Glade: +// Copyright 2001 Ximian, Inc. +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "gtkhelpers.h" +#import <gdk/gdkkeysyms.h> + +void release_obj (gpointer data) { + id obj = (id)data; + [obj release]; +} + +NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end) +{ + gchar *text = gtk_editable_get_chars (editable, start, end); + NSString *string = [NSString stringWithUTF8String:text]; + g_free (text); + return string; +} + +GdkRectangle gdk_rectangle_from_ns_rect (NSRect box) { + GdkRectangle rect; + rect.x = box.origin.x; + rect.y = box.origin.y; + rect.width = box.size.width; + rect.height = box.size.height; + return rect; +} + +NSRect gdk_rectangle_to_ns_rect (GdkRectangle rect) { + NSRect result; + result.origin.x = rect.x; + result.origin.y = rect.y; + result.size.width = rect.width; + result.size.height = rect.height; + return result; +} + +void gtk_action_set_detailed_label (GtkAction *action, const gchar *baseLabel, const gchar *actionName) { + if (actionName == NULL || *actionName == '\0') { + gtk_action_set_label (action, baseLabel); + } else { + GString *label = g_string_sized_new (30); + g_string_printf(label, "%s: %s", baseLabel, actionName); + gtk_action_set_label (action, label->str); + g_string_free (label, TRUE); + } +} + +/** + * tz_hijack_key_press: + * @win: a #GtkWindow + * event: the GdkEventKey + * user_data: unused + * + * This function is meant to be attached to key-press-event of a toplevel, + * it simply allows the window contents to treat key events /before/ + * accelerator keys come into play (this way widgets dont get deleted + * when cutting text in an entry etc.). + * + * Returns: whether the event was handled + */ +gint +tz_hijack_key_press (GtkWindow *win, + GdkEventKey *event, + gpointer user_data) +{ + GtkWidget *focus_widget; + + focus_widget = gtk_window_get_focus (win); + if (focus_widget && + (event->keyval == GDK_Delete || /* Filter Delete from accelerator keys */ + ((event->state & GDK_CONTROL_MASK) && /* CTRL keys... */ + ((event->keyval == GDK_c || event->keyval == GDK_C) || /* CTRL-C (copy) */ + (event->keyval == GDK_x || event->keyval == GDK_X) || /* CTRL-X (cut) */ + (event->keyval == GDK_v || event->keyval == GDK_V) || /* CTRL-V (paste) */ + (event->keyval == GDK_a || event->keyval == GDK_A) || /* CTRL-A (select-all) */ + (event->keyval == GDK_n || event->keyval == GDK_N))))) /* CTRL-N (new document) ?? */ + { + return gtk_widget_event (focus_widget, + (GdkEvent *)event); + } + return FALSE; +} + +GdkPixbuf * pixbuf_get_from_surface(cairo_surface_t *surface) { + cairo_surface_flush (surface); + + int width = cairo_image_surface_get_width (surface); + int height = cairo_image_surface_get_height (surface); + int stride = cairo_image_surface_get_stride (surface); + unsigned char *data = cairo_image_surface_get_data (surface); + + GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + width, + height); + unsigned char *pbdata = gdk_pixbuf_get_pixels (pixbuf); + int pbstride = gdk_pixbuf_get_rowstride (pixbuf); + + for (int y = 0; y < height; ++y) { + uint32_t *line = (uint32_t*)(data + y*stride); + unsigned char *pbline = pbdata + (y*pbstride); + for (int x = 0; x < width; ++x) { + uint32_t pixel = *(line + x); + unsigned char *pbpixel = pbline + (x*4); + // NB: We should un-pre-mult the alpha here. + // However, in our world, alpha is always + // on or off, so it doesn't really matter + pbpixel[3] = ((pixel & 0xff000000) >> 24); + pbpixel[0] = ((pixel & 0x00ff0000) >> 16); + pbpixel[1] = ((pixel & 0x0000ff00) >> 8); + pbpixel[2] = (pixel & 0x000000ff); + } + } + + return pixbuf; +} + +/* This function mostly lifted from + * gtk+/gdk/gdkscreen.c:gdk_screen_get_monitor_at_window() + */ +static gint +get_appropriate_monitor (GdkScreen *screen, + gint x, + gint y, + gint w, + gint h) +{ + GdkRectangle rect; + gint area = 0; + gint monitor = -1; + gint num_monitors; + gint i; + + rect.x = x; + rect.y = y; + rect.width = w; + rect.height = h; + + num_monitors = gdk_screen_get_n_monitors (screen); + + for (i = 0; i < num_monitors; i++) + { + GdkRectangle geometry; + + gdk_screen_get_monitor_geometry (screen, i, &geometry); + + if (gdk_rectangle_intersect (&rect, &geometry, &geometry) && + geometry.width * geometry.height > area) + { + area = geometry.width * geometry.height; + monitor = i; + } + } + + if (monitor >= 0) + return monitor; + else + return gdk_screen_get_monitor_at_point (screen, + rect.x + rect.width / 2, + rect.y + rect.height / 2); +} + +/* This function mostly lifted from gimp_session_info_apply_geometry + * in gimp-2.6/app/widgets/gimpsessioninfo.c + */ +void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h) +{ + gint forced_w = w; + gint forced_h = h; + if (w <= 0 || h <= 0) { + gtk_window_get_default_size (window, &w, &h); + } + if (w <= 0 || h <= 0) { + gtk_window_get_size (window, &w, &h); + } + + GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (window)); + + gint monitor = 0; + if (w > 0 && h > 0) { + monitor = get_appropriate_monitor (screen, x, y, w, h); + } else { + monitor = gdk_screen_get_monitor_at_point (screen, x, y); + } + + GdkRectangle rect; + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + + x = CLAMP (x, + rect.x, + rect.x + rect.width - (w > 0 ? w : 128)); + y = CLAMP (y, + rect.y, + rect.y + rect.height - (h > 0 ? h : 128)); + + gchar geom[32]; + g_snprintf (geom, sizeof (geom), "%+d%+d", x, y); + + gtk_window_parse_geometry (window, geom); + + if (forced_w > 0 && forced_h > 0) { + gtk_window_set_default_size (window, forced_w, forced_h); + } +} + +void label_set_bold (GtkLabel *label) { + PangoAttrList *attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, + pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD)); + gtk_label_set_attributes (label, attrs); + pango_attr_list_unref (attrs); +} + +void widget_set_error (GtkWidget *widget) { + GdkColor color = {0, 65535, 61184, 61184}; + gtk_widget_modify_base (widget, GTK_STATE_NORMAL, &color); +} + +void widget_clear_error (GtkWidget *widget) { + gtk_widget_modify_base (widget, GTK_STATE_NORMAL, NULL); +} + +void text_buffer_clear_tag (GtkTextBuffer *buffer, GtkTextTag *tag) { + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + gtk_text_buffer_remove_tag (buffer, tag, &start, &end); +} + +void utility_window_attach (GtkWindow *util_win, GtkWindow *parent_win) { + if (parent_win == gtk_window_get_transient_for (util_win)) + return; + + // HACK: X window managers tend to move windows around when they are + // unmapped and mapped again, so we save the position + gint x, y; + gtk_window_get_position (util_win, &x, &y); + + // HACK: Altering WM_TRANSIENT_FOR on a non-hidden but unmapped window + // (eg: when you have minimised the original parent window) can + // cause the window to be lost forever, so we hide it first + gtk_widget_hide (GTK_WIDGET (util_win)); + gtk_window_set_focus_on_map (util_win, FALSE); + gtk_window_set_transient_for (util_win, parent_win); + gtk_widget_show (GTK_WIDGET (util_win)); + + // HACK: see above + gtk_window_move (util_win, x, y); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/logo.h b/tikzit-1/src/gtk/logo.h new file mode 100644 index 0000000..e778da9 --- /dev/null +++ b/tikzit-1/src/gtk/logo.h @@ -0,0 +1,32 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gdk-pixbuf/gdk-pixbuf.h> + +typedef enum { + LOGO_SIZE_16, + LOGO_SIZE_24, + LOGO_SIZE_32, + LOGO_SIZE_48, + LOGO_SIZE_64, + LOGO_SIZE_128, + LOGO_SIZE_COUNT +} LogoSize; + +GdkPixbuf *get_logo (LogoSize size); + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/logo.m b/tikzit-1/src/gtk/logo.m new file mode 100644 index 0000000..57533c7 --- /dev/null +++ b/tikzit-1/src/gtk/logo.m @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "logo.h" +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "logodata.m" +#pragma GCC diagnostic pop + +static GdkPixbuf *pixbufCache[LOGO_SIZE_COUNT]; + +GdkPixbuf *get_logo (LogoSize size) { + const GdkPixdata *data = NULL; + switch (size) { + case LOGO_SIZE_16: + data = &logo16; + break; + case LOGO_SIZE_24: + data = &logo24; + break; + case LOGO_SIZE_32: + data = &logo32; + break; + case LOGO_SIZE_48: + data = &logo48; + break; + case LOGO_SIZE_64: + data = &logo64; + break; + case LOGO_SIZE_128: + data = &logo128; + break; + default: + return NULL; + }; + if (pixbufCache[size]) { + g_object_ref (pixbufCache[size]); + return pixbufCache[size]; + } else { + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (data, FALSE, NULL); + pixbufCache[size] = buf; + g_object_add_weak_pointer (G_OBJECT (buf), (void**)(&(pixbufCache[size]))); + return buf; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 + diff --git a/tikzit-1/src/gtk/main.m b/tikzit-1/src/gtk/main.m new file mode 100644 index 0000000..5d9f4a4 --- /dev/null +++ b/tikzit-1/src/gtk/main.m @@ -0,0 +1,111 @@ +// +// main.m +// TikZiT +// +// Copyright 2010 Chris Heunen. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TZFoundation.h" +#import <gtk/gtk.h> +#import "clipboard.h" +#import "logo.h" +#import "tzstockitems.h" + +#import "Application.h" + +static GOptionEntry entries[] = +{ + //{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL }, + { NULL } +}; + +void onUncaughtException(NSException* exception) +{ + NSString *joinStr = @"\n "; + NSLog(@"uncaught exception: %@\n backtrace: %@", + [exception description], + [[exception callStackSymbols] componentsJoinedByString:joinStr]); +} + +int main (int argc, char *argv[]) { + NSSetUncaughtExceptionHandler(&onUncaughtException); + + [[NSAutoreleasePool alloc] init]; + + GError *error = NULL; + GOptionContext *context; + context = g_option_context_new ("[FILES] - PGF/TikZ-based graph editor"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + if (error->domain == G_OPTION_ERROR) { + g_print ("%s\nUse --help to see available options\n", error->message); + } else { + g_print ("Unexpected error parsing options: %s\n", error->message); + } + exit (1); + } + g_option_context_free (context); + +#ifndef WINDOWS + GList *icon_list = NULL; + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_128)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_64)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_48)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_32)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_24)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_16)); + gtk_window_set_default_icon_list (icon_list); + GList *list_head = icon_list; + while (list_head) { + g_object_unref ((GObject*)list_head->data); + list_head = list_head->next; + } +#endif + + NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + + tz_register_stock_items(); + clipboard_init(); + + Application *app = nil; + if (argc > 1) { + NSMutableArray *files = [NSMutableArray arrayWithCapacity:argc-1]; + for (int i = 1; i < argc; ++i) { + [files insertObject:[NSString stringWithGlibFilename:argv[i]] + atIndex:i-1]; + } + NSLog(@"Files: %@", files); + app = [[Application alloc] initWithFiles:files]; + } else { + app = [[Application alloc] init]; + } + + [initPool drain]; + + gtk_main (); + + [app saveConfiguration]; + [app release]; + + return 0; +} + +// vim:ft=objc:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/mkdtemp.h b/tikzit-1/src/gtk/mkdtemp.h new file mode 100644 index 0000000..65ee99e --- /dev/null +++ b/tikzit-1/src/gtk/mkdtemp.h @@ -0,0 +1,32 @@ +/* Creating a private temporary directory. + Copyright (C) 2001-2002 Free Software Foundation, Inc. + + 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, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_MKDTEMP + +/* Get mkdtemp() declaration. */ +#include <stdlib.h> + +#else + +/* Create a unique temporary directory from TEMPLATE. + The last six characters of TEMPLATE must be "XXXXXX"; + they are replaced with a string that makes the directory name unique. + Returns TEMPLATE, or a null pointer if it cannot get a unique name. + The directory is created mode 700. */ +extern char * mkdtemp (char *template); + +#endif diff --git a/tikzit-1/src/gtk/mkdtemp.m b/tikzit-1/src/gtk/mkdtemp.m new file mode 100644 index 0000000..ee3cd7c --- /dev/null +++ b/tikzit-1/src/gtk/mkdtemp.m @@ -0,0 +1,180 @@ +/* Copyright (C) 1999, 2001-2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + see <http://www.gnu.org/licenses/>. + */ + +/* Extracted from misc/mkdtemp.c and sysdeps/posix/tempname.c. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "mkdtemp.h" + +#include <errno.h> +#ifndef __set_errno +# define __set_errno(Val) errno = (Val) +#endif + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <stdio.h> +#ifndef TMP_MAX +# define TMP_MAX 238328 +#endif + +#if HAVE_STDINT_H || _LIBC +# include <stdint.h> +#endif +#if HAVE_INTTYPES_H +# include <inttypes.h> +#endif + +#if HAVE_UNISTD_H || _LIBC +# include <unistd.h> +#endif + +#if HAVE_GETTIMEOFDAY || _LIBC +# if HAVE_SYS_TIME_H || _LIBC +# include <sys/time.h> +# endif +#else +# if HAVE_TIME_H || _LIBC +# include <time.h> +# endif +#endif + +#include "stat.h" + +#ifdef __MINGW32__ +/* mingw's mkdir() function has 1 argument, but we pass 2 arguments. + Therefore we have to disable the argument count checking. */ +# define mkdir ((int (*)()) mkdir) +#endif + +#if !_LIBC +# define __getpid getpid +# define __gettimeofday gettimeofday +# define __mkdir mkdir +#endif + +/* Use the widest available unsigned type if uint64_t is not + available. The algorithm below extracts a number less than 62**6 + (approximately 2**35.725) from uint64_t, so ancient hosts where + uintmax_t is only 32 bits lose about 3.725 bits of randomness, + which is better than not having mkstemp at all. */ +#if !defined UINT64_MAX && !defined uint64_t +# define uint64_t uintmax_t +#endif + +/* These are the characters used in temporary filenames. */ +static const char letters[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* Generate a temporary file name based on TMPL. TMPL must match the + rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + does not exist at the time of the call to __gen_tempname. TMPL is + overwritten with the result. + + KIND is: + __GT_DIR: create a directory, which will be mode 0700. + + We use a clever algorithm to get hard-to-predict names. */ +static int +gen_tempname (char *tmpl) +{ + int len; + char *XXXXXX; + static uint64_t value; + uint64_t random_time_bits; + int count, fd = -1; + int save_errno = errno; + + len = strlen (tmpl); + if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) + { + __set_errno (EINVAL); + return -1; + } + + /* This is where the Xs start. */ + XXXXXX = &tmpl[len - 6]; + + /* Get some more or less random data. */ +#ifdef RANDOM_BITS + RANDOM_BITS (random_time_bits); +#else +# if HAVE_GETTIMEOFDAY || _LIBC + { + struct timeval tv; + __gettimeofday (&tv, NULL); + random_time_bits = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec; + } +# else + random_time_bits = time (NULL); +# endif +#endif + value += random_time_bits ^ __getpid (); + + for (count = 0; count < TMP_MAX; value += 7777, ++count) + { + uint64_t v = value; + + /* Fill in the random bits. */ + XXXXXX[0] = letters[v % 62]; + v /= 62; + XXXXXX[1] = letters[v % 62]; + v /= 62; + XXXXXX[2] = letters[v % 62]; + v /= 62; + XXXXXX[3] = letters[v % 62]; + v /= 62; + XXXXXX[4] = letters[v % 62]; + v /= 62; + XXXXXX[5] = letters[v % 62]; + + fd = __mkdir (tmpl, S_IRUSR | S_IWUSR | S_IXUSR); + + if (fd >= 0) + { + __set_errno (save_errno); + return fd; + } + else if (errno != EEXIST) + return -1; + } + + /* We got out of the loop because we ran out of combinations to try. */ + __set_errno (EEXIST); + return -1; +} + +/* Generate a unique temporary directory from TEMPLATE. + The last six characters of TEMPLATE must be "XXXXXX"; + they are replaced with a string that makes the filename unique. + The directory is created, mode 700, and its name is returned. + (This function comes from OpenBSD.) */ +char * +mkdtemp (char *template) +{ + if (gen_tempname (template)) + return NULL; + else + return template; +} diff --git a/tikzit-1/src/gtk/stat.h b/tikzit-1/src/gtk/stat.h new file mode 100644 index 0000000..a9829ae --- /dev/null +++ b/tikzit-1/src/gtk/stat.h @@ -0,0 +1,25 @@ +#include <sys/stat.h> +#if STAT_MACROS_BROKEN +# undef S_ISDIR +#endif +#if !defined S_ISDIR && defined S_IFDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !S_IRUSR && S_IREAD +# define S_IRUSR S_IREAD +#endif +#if !S_IRUSR +# define S_IRUSR 00400 +#endif +#if !S_IWUSR && S_IWRITE +# define S_IWUSR S_IWRITE +#endif +#if !S_IWUSR +# define S_IWUSR 00200 +#endif +#if !S_IXUSR && S_IEXEC +# define S_IXUSR S_IEXEC +#endif +#if !S_IXUSR +# define S_IXUSR 00100 +#endif diff --git a/tikzit-1/src/gtk/test/gtk.m b/tikzit-1/src/gtk/test/gtk.m new file mode 100644 index 0000000..aabb0f2 --- /dev/null +++ b/tikzit-1/src/gtk/test/gtk.m @@ -0,0 +1,27 @@ +// +// linux.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "test/test.h" + +void testGtk() { + +} diff --git a/tikzit-1/src/gtk/test/main.m b/tikzit-1/src/gtk/test/main.m new file mode 100644 index 0000000..639a335 --- /dev/null +++ b/tikzit-1/src/gtk/test/main.m @@ -0,0 +1,50 @@ +// +// main.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT 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 TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#include "config.h" +#import "test/test.h" +#include <string.h> +void testCommon(); + +int main(int argc, char **argv) { + if (argc == 2 && strcmp(argv[1], "--disable-color")==0) { + setColorEnabled(NO); + } else { + setColorEnabled(YES); + } + + PUTS(@""); + PUTS(@"**********************************************"); + PUTS(@"TikZiT TESTS, LINUX VERSION %@", VERSION); + PUTS(@"**********************************************"); + PUTS(@""); + + startTests(); + testCommon(); + testLinux(); + + PUTS(@""); + PUTS(@"**********************************************"); + endTests(); + PUTS(@"**********************************************"); + PUTS(@""); +} diff --git a/tikzit-1/src/gtk/tzstockitems.h b/tikzit-1/src/gtk/tzstockitems.h new file mode 100644 index 0000000..5ad0da9 --- /dev/null +++ b/tikzit-1/src/gtk/tzstockitems.h @@ -0,0 +1,26 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define TIKZIT_STOCK_SELECT "tikzit-select" +#define TIKZIT_STOCK_CREATE_NODE "tikzit-create-node" +#define TIKZIT_STOCK_CREATE_EDGE "tikzit-create-edge" +#define TIKZIT_STOCK_BOUNDING_BOX "tikzit-bounding-box" +#define TIKZIT_STOCK_DRAG "tikzit-drag" + +void tz_register_stock_items(); + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/tzstockitems.m b/tikzit-1/src/gtk/tzstockitems.m new file mode 100644 index 0000000..5eba912 --- /dev/null +++ b/tikzit-1/src/gtk/tzstockitems.m @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "tzstockitems.h" +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "icondata.m" +#pragma GCC diagnostic pop + +static GtkStockItem stock_items[] = { + // gchar *stock_id; + // gchar *label; + // GdkModifierType modifier; + // guint keyval; + // gchar *translation_domain; + { TIKZIT_STOCK_SELECT, "Select", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_NODE, "Create Node", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_EDGE, "Create Edge", 0, 0, NULL }, + { TIKZIT_STOCK_BOUNDING_BOX, "Bounding Box", 0, 0, NULL }, + { TIKZIT_STOCK_DRAG, "Drag", 0, 0, NULL }, +}; +static guint n_stock_items = G_N_ELEMENTS (stock_items); + +static void icon_factory_add_pixdata (GtkIconFactory *factory, + const gchar *stock_id, + const GdkPixdata *image_data) { + + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (image_data, FALSE, NULL); + GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf (buf); + gtk_icon_factory_add (factory, stock_id, icon_set); + gtk_icon_set_unref (icon_set); + g_object_unref (G_OBJECT (buf)); +} + +void tz_register_stock_items() { + gtk_stock_add_static (stock_items, n_stock_items); + + GtkIconFactory *factory = gtk_icon_factory_new (); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_SELECT, &select_rectangular); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_NODE, &draw_ellipse); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_EDGE, &draw_path); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_BOUNDING_BOX, &transform_crop_and_resize); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_DRAG, &transform_move); + gtk_icon_factory_add_default (factory); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/tztoolpalette.h b/tikzit-1/src/gtk/tztoolpalette.h new file mode 100644 index 0000000..45ec2ac --- /dev/null +++ b/tikzit-1/src/gtk/tztoolpalette.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * tztoolpalette.h, based on gimptoolpalette.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TZ_TOOL_PALETTE_H__ +#define __TZ_TOOL_PALETTE_H__ + + +#define TZ_TYPE_TOOL_PALETTE (tz_tool_palette_get_type ()) +#define TZ_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPalette)) +#define TZ_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass)) +#define TZ_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TZ_TYPE_TOOL_PALETTE)) +#define TZ_IS_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TZ_TYPE_TOOL_PALETTE)) +#define TZ_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass)) + + +typedef struct _TzToolPaletteClass TzToolPaletteClass; +typedef struct _TzToolPalette TzToolPalette; + +struct _TzToolPalette +{ + GtkToolPalette parent_instance; +}; + +struct _TzToolPaletteClass +{ + GtkToolPaletteClass parent_class; +}; + + +GType tz_tool_palette_get_type (void) G_GNUC_CONST; + +GtkWidget * tz_tool_palette_new (void); + +gboolean tz_tool_palette_get_button_size (TzToolPalette *widget, + gint *width, + gint *height); + + +#endif /* __TZ_TOOL_PALETTE_H__ */ diff --git a/tikzit-1/src/gtk/tztoolpalette.m b/tikzit-1/src/gtk/tztoolpalette.m new file mode 100644 index 0000000..a948127 --- /dev/null +++ b/tikzit-1/src/gtk/tztoolpalette.m @@ -0,0 +1,158 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * tztoolpalette.c, based on gimptoolpalette.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * Copyright (C) 2012 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gtk/gtk.h> + +#include "tztoolpalette.h" + + +#define DEFAULT_TOOL_ICON_SIZE GTK_ICON_SIZE_BUTTON +#define DEFAULT_BUTTON_RELIEF GTK_RELIEF_NONE + +#define TOOL_BUTTON_DATA_KEY "tz-tool-palette-item" +#define TOOL_INFO_DATA_KEY "tz-tool-info" + + +typedef struct _TzToolPalettePrivate TzToolPalettePrivate; + +struct _TzToolPalettePrivate +{ + gint tool_rows; + gint tool_columns; +}; + +#define GET_PRIVATE(p) G_TYPE_INSTANCE_GET_PRIVATE (p, \ + TZ_TYPE_TOOL_PALETTE, \ + TzToolPalettePrivate) + + +static void tz_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + + +G_DEFINE_TYPE (TzToolPalette, tz_tool_palette, GTK_TYPE_TOOL_PALETTE) + +#define parent_class tz_tool_palette_parent_class + + +static void +tz_tool_palette_class_init (TzToolPaletteClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->size_allocate = tz_tool_palette_size_allocate; + + g_type_class_add_private (klass, sizeof (TzToolPalettePrivate)); +} + +static void +tz_tool_palette_init (TzToolPalette *palette) +{ +} + +static GtkToolItemGroup * +tz_tool_palette_tool_group (TzToolPalette *palette) +{ + GList *children; + GtkToolItemGroup *group; + + children = gtk_container_get_children (GTK_CONTAINER (palette)); + g_return_val_if_fail (children, NULL); + group = GTK_TOOL_ITEM_GROUP (children->data); + g_list_free (children); + + return group; +} + +gboolean +tz_tool_palette_get_button_size (TzToolPalette *palette, + gint *width, + gint *height) +{ + g_return_val_if_fail (width || height, FALSE); + + GtkToolItemGroup *group = tz_tool_palette_tool_group (palette); + g_return_val_if_fail (group, FALSE); + + guint tool_count = gtk_tool_item_group_get_n_items (group); + if (tool_count > 0) + { + GtkWidget *tool_button; + GtkRequisition button_requisition; + + tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0)); + gtk_widget_size_request (tool_button, &button_requisition); + if (width) + *width = button_requisition.width; + if (height) + *height = button_requisition.height; + return TRUE; + } + return FALSE; +} + +static void +tz_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + TzToolPalettePrivate *private = GET_PRIVATE (widget); + GtkToolItemGroup *group = tz_tool_palette_tool_group (TZ_TOOL_PALETTE (widget)); + + g_return_if_fail (group); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + guint tool_count = gtk_tool_item_group_get_n_items (group); + if (tool_count > 0) + { + GtkWidget *tool_button; + GtkRequisition button_requisition; + gint tool_rows; + gint tool_columns; + + tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0)); + gtk_widget_size_request (tool_button, &button_requisition); + + tool_columns = MAX (1, (allocation->width / button_requisition.width)); + tool_rows = tool_count / tool_columns; + + if (tool_count % tool_columns) + tool_rows++; + + if (private->tool_rows != tool_rows || + private->tool_columns != tool_columns) + { + private->tool_rows = tool_rows; + private->tool_columns = tool_columns; + + gtk_widget_set_size_request (widget, -1, + tool_rows * button_requisition.height); + } + } +} + +GtkWidget * +tz_tool_palette_new (void) +{ + return g_object_new (TZ_TYPE_TOOL_PALETTE, NULL); +} + +// vim:ft=objc:ts=8:et:sts=2:sw=2:foldmethod=marker |