From 11b8718ddfd44dc5786ac745f90fa390aaf57a06 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 11 May 2015 02:04:06 +0200 Subject: * Fully enable export of favorite groups * Favorites can now be imported, sorted even if "Quick Connect" is selected * Favorite files containing groups will now be imported correctly * If a favorite and the group containing said favorite are exported, the favorite will no longer be included twice --- Source/SPConnectionController.m | 5 +++-- Source/SPConnectionControllerDelegate.m | 12 +++++----- Source/SPFavoriteNode.h | 3 ++- Source/SPFavoriteNode.m | 8 ++++++- Source/SPFavoritesController.m | 40 ++++++++++++++++++++++++++++----- Source/SPFavoritesExporter.m | 5 ++++- Source/SPGroupNode.h | 5 ++++- Source/SPGroupNode.m | 15 +++++++++++++ Source/SPNamedNode.h | 37 ++++++++++++++++++++++++++++++ Source/SPTreeNode.h | 1 + Source/SPTreeNode.m | 36 ++++++++++++++++++++++++++--- sequel-pro.xcodeproj/project.pbxproj | 2 ++ 12 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 Source/SPNamedNode.h diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 25049d44..d7fce6fb 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -51,6 +51,7 @@ #import "SPFavoritesImporter.h" #import "SPThreadAdditions.h" #import "SPFavoriteColorSupport.h" +#import "SPNamedNode.h" #import @@ -738,7 +739,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, { SPTreeNode *node = [self selectedFavoriteNode]; - return (![node isGroup]) ? [[node representedObject] nodeFavorite] : nil; + return (![node isGroup]) ? [(SPFavoriteNode *)[node representedObject] nodeFavorite] : nil; } /** @@ -1045,7 +1046,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, NSSavePanel *savePanel = [NSSavePanel savePanel]; // suggest the name of the favorite or a default name for multiple selection - NSString *fileName = ([[self selectedFavoriteNodes] count] == 1)? [[[self selectedFavorite] objectForKey:SPFavoriteNameKey] stringByAppendingPathExtension:@"plist"] : nil; + NSString *fileName = ([[self selectedFavoriteNodes] count] == 1)? [[(id)[[self selectedFavoriteNode] representedObject] nodeName] stringByAppendingPathExtension:@"plist"] : nil; // This if() is so we can also catch nil due to favorite corruption (NSSavePanel will @throw if nil is passed in) if(!fileName) fileName = SPExportFavoritesFilename; diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m index f2a57e53..99af9e35 100644 --- a/Source/SPConnectionControllerDelegate.m +++ b/Source/SPConnectionControllerDelegate.m @@ -617,8 +617,6 @@ static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; SPTreeNode *node = [self selectedFavoriteNode]; NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows]; - - if (node == quickConnectItem) return NO; if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { @@ -634,7 +632,14 @@ static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; if (action == @selector(reverseSortFavorites:)) { [menuItem setState:reverseFavoritesSort]; } + + return YES; } + + // import does not depend on a selection + if(action == @selector(importFavorites:)) return YES; + + if (node == quickConnectItem) return NO; // Remove/rename the selected node if (action == @selector(removeNode:) || action == @selector(renameNode:)) { @@ -659,9 +664,6 @@ static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; if ([[favoritesRoot allChildLeafs] count] == 0 || selectedRows == 0) { return NO; } - else if (selectedRows == 1) { - return (![[self selectedFavoriteNode] isGroup]); - } else if (selectedRows > 1) { [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")]; } diff --git a/Source/SPFavoriteNode.h b/Source/SPFavoriteNode.h index a26c9a3b..6a132767 100644 --- a/Source/SPFavoriteNode.h +++ b/Source/SPFavoriteNode.h @@ -28,6 +28,7 @@ // // More info at +#import "SPNamedNode.h" /** * @class SPFavoriteNode SPFavoriteNode.h * @@ -35,7 +36,7 @@ * * Tree node the represents a connection favorite. */ -@interface SPFavoriteNode : NSObject +@interface SPFavoriteNode : NSObject { NSMutableDictionary *nodeFavorite; } diff --git a/Source/SPFavoriteNode.m b/Source/SPFavoriteNode.m index b2b99c64..b51980f7 100644 --- a/Source/SPFavoriteNode.m +++ b/Source/SPFavoriteNode.m @@ -81,6 +81,7 @@ static NSString *SPFavoriteNodeKey = @"SPFavoriteNode"; - (id)initWithCoder:(NSCoder *)coder { +#warning This is not a valid initializer. [self setNodeFavorite:[coder decodeObjectForKey:SPFavoriteNodeKey]]; return self; @@ -96,7 +97,12 @@ static NSString *SPFavoriteNodeKey = @"SPFavoriteNode"; - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p ('%@')>", [self className], self, [[self nodeFavorite] objectForKey:SPFavoriteNameKey]]; + return [NSString stringWithFormat:@"<%@: %p ('%@')>", [self className], self, [self nodeName]]; +} + +- (NSString *)nodeName +{ + return [[self nodeFavorite] objectForKey:SPFavoriteNameKey]; } #pragma mark - diff --git a/Source/SPFavoritesController.m b/Source/SPFavoritesController.m index ce6e5427..625b46cc 100644 --- a/Source/SPFavoritesController.m +++ b/Source/SPFavoritesController.m @@ -45,7 +45,7 @@ static SPFavoritesController *sharedFavoritesController = nil; - (void)_addNode:(SPTreeNode *)node asChildOfNode:(SPTreeNode *)parent; - (SPTreeNode *)_constructBranchForNodeData:(NSDictionary *)nodeData; - +- (SPTreeNode *)_addFavoriteNodeWithData:(NSMutableDictionary *)data asChildOfNode:(SPTreeNode *)parent; @end @implementation SPFavoritesController @@ -164,6 +164,7 @@ static SPFavoritesController *sharedFavoritesController = nil; [self _addNode:node asChildOfNode:parent]; + [self saveFavorites]; [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; return node; @@ -179,11 +180,40 @@ static SPFavoritesController *sharedFavoritesController = nil; */ - (SPTreeNode *)addFavoriteNodeWithData:(NSMutableDictionary *)data asChildOfNode:(SPTreeNode *)parent { - SPTreeNode *node = [SPTreeNode treeNodeWithRepresentedObject:[SPFavoriteNode favoriteNodeWithDictionary:data]]; - + SPTreeNode *node = [self _addFavoriteNodeWithData:data asChildOfNode:parent]; + + [self saveFavorites]; + [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; + + return node; +} + +/** + * Inner recursive variant of the method above + */ +- (SPTreeNode *)_addFavoriteNodeWithData:(NSMutableDictionary *)data asChildOfNode:(SPTreeNode *)parent +{ + id object; + NSArray *childs = nil; + //if it has "Children" it must be a group node, otherwise assume favorite node + if ([data objectForKey:SPFavoriteChildrenKey]) { + object = [SPGroupNode groupNodeWithDictionary:data]; + childs = [data objectForKey:SPFavoriteChildrenKey]; + } + else { + object = [SPFavoriteNode favoriteNodeWithDictionary:data]; + } + + SPTreeNode *node = [SPTreeNode treeNodeWithRepresentedObject:object]; + [self _addNode:node asChildOfNode:parent]; - [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; + //also add the children + if(childs) { + for (NSMutableDictionary *childData in childs) { + [self _addFavoriteNodeWithData:childData asChildOfNode:node]; + } + } return node; } @@ -460,8 +490,6 @@ static SPFavoritesController *sharedFavoritesController = nil; else { [[[[favoritesTree mutableChildNodes] objectAtIndex:0] mutableChildNodes] addObject:node]; } - - [self saveFavorites]; } #pragma mark - diff --git a/Source/SPFavoritesExporter.m b/Source/SPFavoritesExporter.m index a8b78a86..d37ce07d 100644 --- a/Source/SPFavoritesExporter.m +++ b/Source/SPFavoritesExporter.m @@ -74,7 +74,10 @@ // Get a dictionary representation of all favorites for (SPTreeNode *node in [self exportFavorites]) { - [favorites addObject:[node dictionaryRepresentation]]; + // The selection could contain a group as well as items in that group. + // So we skip those items, as their group will already export them. + if(![node isDescendantOfNodes:[self exportFavorites]]) + [favorites addObject:[node dictionaryRepresentation]]; } NSDictionary *dictionary = @{SPFavoritesDataRootKey : favorites}; diff --git a/Source/SPGroupNode.h b/Source/SPGroupNode.h index a6583dc1..d12f7465 100644 --- a/Source/SPGroupNode.h +++ b/Source/SPGroupNode.h @@ -28,6 +28,7 @@ // // More info at +#import "SPNamedNode.h" /** * @class SPGroupNode SPGroupNode.h * @@ -35,7 +36,7 @@ * * Tree node that represents a group. */ -@interface SPGroupNode : NSObject +@interface SPGroupNode : NSObject { BOOL nodeIsExpanded; @@ -53,7 +54,9 @@ @property (readwrite, assign) BOOL nodeIsExpanded; - (id)initWithName:(NSString *)name; +- (id)initWithDictionary:(NSDictionary *)dict; + (SPGroupNode *)groupNodeWithName:(NSString *)name; ++ (SPGroupNode *)groupNodeWithDictionary:(NSDictionary *)dict; @end diff --git a/Source/SPGroupNode.m b/Source/SPGroupNode.m index 4d90c0c1..e1a0dfb3 100644 --- a/Source/SPGroupNode.m +++ b/Source/SPGroupNode.m @@ -61,11 +61,25 @@ static NSString *SPGroupNodeIsExpandedKey = @"SPGroupNodeIsExpanded"; return self; } +- (id)initWithDictionary:(NSDictionary *)dict +{ + if ((self = [self initWithName:[dict objectForKey:SPFavoritesGroupNameKey]])) { + [self setNodeIsExpanded:[(NSNumber *)[dict objectForKey:SPFavoritesGroupIsExpandedKey] boolValue]]; + } + + return self; +} + + (SPGroupNode *)groupNodeWithName:(NSString *)name { return [[[self alloc] initWithName:name] autorelease]; } ++ (SPGroupNode *)groupNodeWithDictionary:(NSDictionary *)dict +{ + return [[[self alloc] initWithDictionary:dict] autorelease]; +} + #pragma mark - #pragma mark Copying protocol methods @@ -84,6 +98,7 @@ static NSString *SPGroupNodeIsExpandedKey = @"SPGroupNodeIsExpanded"; - (id)initWithCoder:(NSCoder *)coder { +#warning This is not a valid initializer. [self setNodeName:[coder decodeObjectForKey:SPGroupNodeNameKey]]; [self setNodeIsExpanded:[[coder decodeObjectForKey:SPGroupNodeIsExpandedKey] boolValue]]; diff --git a/Source/SPNamedNode.h b/Source/SPNamedNode.h new file mode 100644 index 00000000..2ce99fdf --- /dev/null +++ b/Source/SPNamedNode.h @@ -0,0 +1,37 @@ +// +// SPNamedNode.h +// sequel-pro +// +// Created by Max Lohrmann on 11.05.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +@protocol SPNamedNode + +- (NSString *)nodeName; + +@end diff --git a/Source/SPTreeNode.h b/Source/SPTreeNode.h index abbb179c..aa2edbdc 100644 --- a/Source/SPTreeNode.h +++ b/Source/SPTreeNode.h @@ -55,6 +55,7 @@ - (SPTreeNode *)parentFromArray:(NSArray *)array; - (BOOL)isDescendantOfOrOneOfNodes:(NSArray *)nodes; +- (BOOL)isDescendantOfNodes:(NSArray *)nodes; - (NSDictionary *)dictionaryRepresentation; diff --git a/Source/SPTreeNode.m b/Source/SPTreeNode.m index 13c86f27..22cfb1da 100644 --- a/Source/SPTreeNode.m +++ b/Source/SPTreeNode.m @@ -50,7 +50,7 @@ static NSString *SPTreeNodeIsGroupKey = @"SPTreeNodeIsGroup"; - (id)initWithRepresentedObject:(id)object { if ((self = [super initWithRepresentedObject:object])) { - [self setIsGroup:NO]; + [self setIsGroup:[object isKindOfClass:[SPGroupNode class]]]; } return self; @@ -197,11 +197,12 @@ static NSString *SPTreeNodeIsGroupKey = @"SPTreeNodeIsGroup"; /** * Returns YES if self is contained anywhere inside the children or children of - * sub-nodes of the nodes contained inside the supplied array. + * sub-nodes of the nodes contained inside the supplied array or is itself a + * member of the array. * * @param nodes The array of nodes to search * - * @return A BOOL indicating whether or not it's a descendent + * @return A BOOL indicating whether or not it's a descendent or array member */ - (BOOL)isDescendantOfOrOneOfNodes:(NSArray *)nodes { @@ -220,6 +221,34 @@ static NSString *SPTreeNodeIsGroupKey = @"SPTreeNodeIsGroup"; return NO; } +/** + * Returns YES if self is contained anywhere inside the children or children of + * sub-nodes of the nodes contained inside the supplied array, but NOT the given + * array itself. + * This means, if self is a member of nodes but not a child of any + * other node in nodes it will still return NO. + * + * @param nodes The array of nodes to search + * + * @return A BOOL indicating whether or not it's a descendent + */ +- (BOOL)isDescendantOfNodes:(NSArray *)nodes +{ + for (SPTreeNode *node in nodes) + { + if (node == self) continue; + + // Check all the sub-nodes + if ([node isGroup]) { + if ([self isDescendantOfOrOneOfNodes:[node childNodes]]) { + return YES; + } + } + } + + return NO; +} + /** * Constructs a dictionary representation of the favorite. * @@ -265,6 +294,7 @@ static NSString *SPTreeNodeIsGroupKey = @"SPTreeNodeIsGroup"; - (id)initWithCoder:(NSCoder *)coder { +#warning This is not a valid initializer. [self setIsGroup:[[coder decodeObjectForKey:SPTreeNodeIsGroupKey] boolValue]]; return self; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index ee3b0018..af6723bf 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -891,6 +891,7 @@ 4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = ""; }; 501B1D161728A3DA0017C92E /* SPCharsetCollationHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCharsetCollationHelper.h; sourceTree = ""; }; 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCharsetCollationHelper.m; sourceTree = ""; }; + 5037F79A1B00148000733564 /* SPNamedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPNamedNode.h; sourceTree = ""; }; 503B02C81AE82C5E0060CAB1 /* SPTableFilterParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFilterParser.h; sourceTree = ""; }; 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParser.m; sourceTree = ""; }; 503B02CE1AE95C2C0060CAB1 /* SPTableFilterParserTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParserTest.m; sourceTree = ""; }; @@ -1756,6 +1757,7 @@ 1798F1941550181B004B0AB8 /* SPGroupNode.m */, 17D3C22012859E070047709F /* SPFavoriteNode.h */, 17D3C22112859E070047709F /* SPFavoriteNode.m */, + 5037F79A1B00148000733564 /* SPNamedNode.h */, ); name = "Tree Nodes"; sourceTree = ""; -- cgit v1.2.3