diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPFillView.h | 47 | ||||
-rw-r--r-- | Source/SPFillView.m | 61 | ||||
-rw-r--r-- | Source/SPTableContent.m | 6 | ||||
-rw-r--r-- | Source/SPTableContentFilterController.h | 69 | ||||
-rw-r--r-- | Source/SPTableContentFilterController.m | 410 |
5 files changed, 366 insertions, 227 deletions
diff --git a/Source/SPFillView.h b/Source/SPFillView.h new file mode 100644 index 00000000..21ea074b --- /dev/null +++ b/Source/SPFillView.h @@ -0,0 +1,47 @@ +// +// SPFillView.h +// sequel-pro +// +// Created by Max Lohrmann on 09.05.18. +// Copyright (c) 2018 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 <https://github.com/sequelpro/sequelpro> + +/** + * SPFillView is a very simple NSView that will + * fill its whole view rect with a solid color. + * The color can be set in Interface Builder. + */ +@interface SPFillView : NSView +{ + NSColor *currentColor; +} + +/** + * This method is invoked when unarchiving the View from the xib. + * The value is configured in IB under "User Defined Runtime Attributes" + */ +- (void)setSystemColorOfName:(NSString *)name; + +@end diff --git a/Source/SPFillView.m b/Source/SPFillView.m new file mode 100644 index 00000000..b2fae0fb --- /dev/null +++ b/Source/SPFillView.m @@ -0,0 +1,61 @@ +// +// SPFillView.m +// sequel-pro +// +// Created by Max Lohrmann on 09.05.18. +// Copyright (c) 2018 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 <https://github.com/sequelpro/sequelpro> + +#import "SPFillView.h" + +@implementation SPFillView + +- (void)setSystemColorOfName:(NSString *)name +{ + //TODO: xibs after 10.6 support storing colors as user defined attributes so we don't need the detour via strings anymore + NSColorList *scl = [NSColorList colorListNamed:@"System"]; + NSColor *color = [scl colorWithKey:name]; + if(color) { + [color retain]; + [currentColor release]; + currentColor = color; + [self setNeedsDisplay:YES]; + } +} + +- (void)drawRect:(NSRect)dirtyRect { + if(currentColor) { + [currentColor set]; + NSRectFill(dirtyRect); + } +} + +- (void)dealloc +{ + [currentColor release]; + [super dealloc]; +} + +@end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index da4fea05..7a48dccf 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -2375,9 +2375,9 @@ static void *TableContentKVOContext = &TableContentKVOContext; else if(navigateAsHex) filterComparison = @"= (Hex String)"; // Store the filter details to use when loading the target table - NSDictionary *filterSettings = [filterControllerInstance makeSerializedFilterForColumn:[refDictionary objectForKey:@"column"] - operator:filterComparison - values:@[targetFilterValue]]; + NSDictionary *filterSettings = [SPTableContentFilterController makeSerializedFilterForColumn:[refDictionary objectForKey:@"column"] + operator:filterComparison + values:@[targetFilterValue]]; // If the link is within the current table, apply filter settings manually if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) { diff --git a/Source/SPTableContentFilterController.h b/Source/SPTableContentFilterController.h index 24935d2a..3d4029d6 100644 --- a/Source/SPTableContentFilterController.h +++ b/Source/SPTableContentFilterController.h @@ -28,13 +28,9 @@ // // More info at <https://github.com/sequelpro/sequelpro> -#import <Foundation/Foundation.h> - -@class SPSplitView; @class SPTableData; @class SPDatabaseDocument; @class SPTablesList; -@class SPTableContent; @class SPContentFilterManager; NSString * const SPTableContentFilterHeightChangedNotification; @@ -62,34 +58,91 @@ NSString * const SPTableContentFilterHeightChangedNotification; /** * Makes the first NSTextField found in the rule editor the first responder + * + * MUST BE CALLED ON THE UI THREAD! */ - (void)focusFirstInputField; +/** + * Will reconfigure the columns of the rule editor from the given array. + * Call with nil to reset the editor to its initial empty state. + * Existing rows will be removed in any case! + * + * MUST BE CALLED ON THE UI THREAD! + */ - (void)setColumns:(NSArray *)dataColumns; -- (void)openContentFilterManagerForFilterType:(NSString *)filterType; - +/** + * Converts the current filter expression displayed in the UI into an + * SQL string suitable for use in a WHERE clause. + * + * @param isBINARY Indicates that the filter should use the BINARY qualifier for ignoring + * collations during search. + * @param err Upon return contains and object describing why the SQL conversion failed, + * if it failed or nil, if no errors occured. + * + * MUST BE CALLED ON THE UI THREAD! + */ - (NSString *)sqlWhereExpressionWithBinary:(BOOL)isBINARY error:(NSError **)err; +/** + * Returns the current filter configuration in a serialized form that can be exported and + * reapplied later. + * + * MUST BE CALLED ON THE UI THREAD! + */ - (NSDictionary *)serializedFilter; + +/** + * Restores the filter rule configuration from a given dictionary. + * The current column configuration must match the schema that was used when generating + * the serialized data, otherwise the invalid rules will be ignored. + * + * @param serialized A dictionary previously generated by calling -serializedFilter. + * @return A serialized filter + * + * MUST BE CALLED ON THE UI THREAD! + */ - (void)restoreSerializedFilters:(NSDictionary *)serialized; -- (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values; +/** + * Create a serialized filter from a given column, operator and operand. + * This is used when navigating foreign key links between tables to create the filter for the target table. + * + * @param colName Name of the column to filter (left side operand) + * @param opName Name of the filter (operator) + * @param values The values to filter with (right side operand) + * @return A serialized filter + * + * This method is thread-safe. + */ ++ (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values; +/** + * The view height the rule editor needs in order to not have to resort to scrollbars + * + * SHOULD be called on the UI thread, or results may be inconsistent! + */ @property (readonly, assign, nonatomic) CGFloat preferredHeight; /** - * Indicates whether the rule editor has no expressions + * Indicates whether the rule editor has no filter expressions + * + * SHOULD be called on the UI thread, or results may be inconsistent! */ - (BOOL)isEmpty; /** * Adds a new row to the rule editor + * + * MUST BE CALLED ON THE UI THREAD! */ - (void)addFilterExpression; /** * Used when the rule editor wants to trigger filtering + * + * SHOULD be called on the UI thread, or results may be inconsistent! */ @property (assign, nonatomic) id target; @property (assign, nonatomic) SEL action; diff --git a/Source/SPTableContentFilterController.m b/Source/SPTableContentFilterController.m index 0f928439..bdddad42 100644 --- a/Source/SPTableContentFilterController.m +++ b/Source/SPTableContentFilterController.m @@ -29,7 +29,6 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPTableContentFilterController.h" -#import "SPTableContent.h" #import "SPQueryController.h" #import "SPDatabaseDocument.h" #import "RegexKitLite.h" @@ -47,12 +46,31 @@ typedef NS_ENUM(NSInteger, RuleNodeType) { NSString * const SPTableContentFilterHeightChangedNotification = @"SPTableContentFilterHeightChanged"; +/** + * The type of filter rule that the current item represents. + */ const NSString * const SerFilterClass = @"filterClass"; +/** + * The current rule is a group row (an "AND" or "OR" expression with children) + */ const NSString * const SerFilterClassGroup = @"groupNode"; +/** + * The current rule is a filter expression + */ const NSString * const SerFilterClassExpression = @"expressionNode"; +/** + * Group Nodes only: + * Indicates whether the group is a conjunction. + * If YES, the children will be combined using "AND", otherwise using "OR". + */ const NSString * const SerFilterGroupIsConjunction = @"isConjunction"; +/** + * Group Nodes only: + * An array of child filter rules (which again can be group or expression rules) + */ const NSString * const SerFilterGroupChildren = @"children"; /** + * Expression Nodes only: * The name of the column to filter in (left side expression) * * Legacy names: @@ -60,10 +78,12 @@ const NSString * const SerFilterGroupChildren = @"children"; */ const NSString * const SerFilterExprColumn = @"column"; /** + * Expression Nodes only: * The data type grouping of the column for applicable filters */ const NSString * const SerFilterExprType = @"filterType"; /** + * Expression Nodes only: * The title of the filter operator to apply * * Legacy names: @@ -71,7 +91,8 @@ const NSString * const SerFilterExprType = @"filterType"; */ const NSString * const SerFilterExprComparison = @"filterComparison"; /** - * The values to apply the filter with + * Expression Nodes only: + * The values to apply the filter with (an array of 0 or more elements) * * Legacy names: * @"filterValue", argumentField @@ -79,11 +100,15 @@ const NSString * const SerFilterExprComparison = @"filterComparison"; */ const NSString * const SerFilterExprValues = @"filterValues"; /** + * Expression Nodes only: * the filter definition dictionary (as in ContentFilters.plist) + * for the filter represented by SerFilterExprComparison. * * This item is not designed to be serialized to disk */ -const NSString * const SerFilterExprDefinition = @"filterDefinition"; +const NSString * const SerFilterExprDefinition = @"_filterDefinition"; + +#pragma mark - @interface RuleNode : NSObject { RuleNodeType type; @@ -91,25 +116,6 @@ const NSString * const SerFilterExprDefinition = @"filterDefinition"; @property(assign, nonatomic) RuleNodeType type; @end -@implementation RuleNode - -@synthesize type = type; - -- (NSUInteger)hash { - return type; -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [(RuleNode *)other type] == type) return YES; - - return NO; -} - -@end - -#pragma mark - - @interface ColumnNode : RuleNode { NSString *name; NSString *typegrouping; @@ -120,74 +126,12 @@ const NSString * const SerFilterExprDefinition = @"filterDefinition"; @property(retain, nonatomic) NSArray *operatorCache; @end -@implementation ColumnNode - -@synthesize name = name; -@synthesize typegrouping = typegrouping; -@synthesize operatorCache = operatorCache; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeColumn; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"ColumnNode<%@@%p>",[self name],self]; -} - -- (NSUInteger)hash { - return ([name hash] ^ [typegrouping hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [name isEqualToString:[other name]] && [typegrouping isEqualToString:[other typegrouping]]) return YES; - - return NO; -} - -@end - -#pragma mark - - @interface StringNode : RuleNode { NSString *value; } @property(copy, nonatomic) NSString *value; @end -@implementation StringNode - -@synthesize value = value; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeString; - } - return self; -} - -- (NSUInteger)hash { - return ([value hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [value isEqualToString:[(StringNode *)other value]]) return YES; - - return NO; -} - - -@end - -#pragma mark - - @interface OpNode : RuleNode { // Note: The main purpose of this field is to have @"=" for column A and @"=" for column B to return NO in -isEqual: // because otherwise NSRuleEditor will get confused and blow up. @@ -200,42 +144,6 @@ const NSString * const SerFilterExprDefinition = @"filterDefinition"; @property (retain, nonatomic) NSDictionary *filter; @end -@implementation OpNode - -@synthesize parentColumn = parentColumn; -@synthesize settings = settings; -@synthesize filter = filter; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeOperator; - } - return self; -} - -- (void)dealloc -{ - [self setFilter:nil]; - [self setSettings:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - return (([parentColumn hash] << 16) ^ [settings hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [settings isEqualToDictionary:[(OpNode *)other settings]] && [parentColumn isEqual:[other parentColumn]]) return YES; - - return NO; -} - -@end - -#pragma mark - - @interface ArgNode : RuleNode { NSDictionary *filter; NSUInteger argIndex; @@ -246,44 +154,6 @@ const NSString * const SerFilterExprDefinition = @"filterDefinition"; @property (assign, nonatomic) NSUInteger argIndex; @end -@implementation ArgNode - -@synthesize filter = filter; -@synthesize argIndex = argIndex; -@synthesize initialValue = initialValue; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeArgument; - } - return self; -} - -- (void)dealloc -{ - [self setInitialValue:nil]; - [self setFilter:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - // initialValue does not count towards hash because two Args are not different if only the initialValue differs - return ((argIndex << 16) ^ [filter hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - // initialValue does not count towards isEqual: because two Args are not different if only the initialValue differs - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ArgNode *)other filter]] && argIndex == [(ArgNode *)other argIndex]) return YES; - - return NO; -} - -@end - -#pragma mark - - @interface ConnectorNode : RuleNode { NSDictionary *filter; NSUInteger labelIndex; @@ -292,38 +162,6 @@ const NSString * const SerFilterExprDefinition = @"filterDefinition"; @property (assign, nonatomic) NSUInteger labelIndex; @end -@implementation ConnectorNode - -@synthesize filter = filter; -@synthesize labelIndex = labelIndex; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeConnector; - } - return self; -} - -- (void)dealloc -{ - [self setFilter:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - return ((labelIndex << 16) ^ [filter hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ConnectorNode *)other filter]] && labelIndex == [(ConnectorNode *)other labelIndex]) return YES; - - return NO; -} - -@end - #pragma mark - @interface SPTableContentFilterController () <NSRuleEditorDelegate> @@ -347,6 +185,7 @@ static void _addIfNotNil(NSMutableArray *array, id toAdd); - (OpNode *)_operatorNamed:(NSString *)title forColumn:(ColumnNode *)col; - (BOOL)_focusOnFieldInSubtree:(NSDictionary *)dict; - (void)_resize; +- (void)openContentFilterManagerForFilterType:(NSString *)filterType; @end @@ -640,7 +479,7 @@ static void _addIfNotNil(NSMutableArray *array, id toAdd); - (void)_resize { // The situation with the sizing is a bit f'ed up: - // - When this method is invoked the NSRuleEditor has not yet updated its required frame size + // - When -ruleEditorRowsDidChange: is invoked the NSRuleEditor has not yet updated its required frame size // - We can't use KVO on -frame either, because SPTableContent will update the container size which // ultimately also updates the NSRuleEditor's frame, causing a loop // - Calling -sizeToFit works, but only when the NSRuleEditor is growing. It won't shrink @@ -663,7 +502,9 @@ static void _addIfNotNil(NSMutableArray *array, id toAdd); - (void)ruleEditorRowsDidChange:(NSNotification *)notification { - [self performSelector:@selector(_resize) withObject:nil afterDelay:0.2]; //TODO find a better way to trigger resize + //TODO find a better way to trigger resize + // We can't do this here, because it will cause rows to jump around when removing them (the add case works fine, though) + [self performSelector:@selector(_resize) withObject:nil afterDelay:0.2]; //[self _resize]; } @@ -915,6 +756,8 @@ static void _addIfNotNil(NSMutableArray *array, id toAdd); return nil; } + if(err) *err = nil; + NSString *out = [filterString copy]; [filterString release]; @@ -1131,7 +974,7 @@ fail: return nil; } -- (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values ++ (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values { return @{ SerFilterClass: SerFilterClassExpression, @@ -1306,46 +1149,181 @@ BOOL SerIsGroup(NSDictionary *dict) @end -//TODO move -@interface SPFillView : NSView +#pragma mark - + +@implementation RuleNode + +@synthesize type = type; + +- (NSUInteger)hash { + return type; +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [(RuleNode *)other type] == type) return YES; + + return NO; +} + +@end + +@implementation ColumnNode + +@synthesize name = name; +@synthesize typegrouping = typegrouping; +@synthesize operatorCache = operatorCache; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeColumn; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"ColumnNode<%@@%p>",[self name],self]; +} + +- (NSUInteger)hash { + return ([name hash] ^ [typegrouping hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [name isEqualToString:[other name]] && [typegrouping isEqualToString:[other typegrouping]]) return YES; + + return NO; +} + +@end + + +@implementation StringNode + +@synthesize value = value; + +- (instancetype)init { - NSColor *currentColor; + if((self = [super init])) { + type = RuleNodeTypeString; + } + return self; } -/** - * This method is invoked when unarchiving the View from the xib. - * The value is configured in IB under "User Defined Runtime Attributes" - */ -- (void)setSystemColorOfName:(NSString *)name; +- (NSUInteger)hash { + return ([value hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [value isEqualToString:[(StringNode *)other value]]) return YES; + + return NO; +} @end -@implementation SPFillView +@implementation OpNode + +@synthesize parentColumn = parentColumn; +@synthesize settings = settings; +@synthesize filter = filter; -- (void)setSystemColorOfName:(NSString *)name +- (instancetype)init { - //TODO: xibs after 10.6 support storing colors as user defined attributes - NSColorList *scl = [NSColorList colorListNamed:@"System"]; - NSColor *color = [scl colorWithKey:name]; - if(color) { - [color retain]; - [currentColor release]; - currentColor = color; - [self setNeedsDisplay:YES]; + if((self = [super init])) { + type = RuleNodeTypeOperator; + } + return self; +} + +- (void)dealloc +{ + [self setFilter:nil]; + [self setSettings:nil]; + [super dealloc]; +} + +- (NSUInteger)hash { + return (([parentColumn hash] << 16) ^ [settings hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [settings isEqualToDictionary:[(OpNode *)other settings]] && [parentColumn isEqual:[other parentColumn]]) return YES; + + return NO; +} + +@end + +@implementation ArgNode + +@synthesize filter = filter; +@synthesize argIndex = argIndex; +@synthesize initialValue = initialValue; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeArgument; } + return self; +} + +- (void)dealloc +{ + [self setInitialValue:nil]; + [self setFilter:nil]; + [super dealloc]; } -- (void)drawRect:(NSRect)dirtyRect { - if(currentColor) { - [currentColor set]; - NSRectFill(dirtyRect); +- (NSUInteger)hash { + // initialValue does not count towards hash because two Args are not different if only the initialValue differs + return ((argIndex << 16) ^ [filter hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + // initialValue does not count towards isEqual: because two Args are not different if only the initialValue differs + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ArgNode *)other filter]] && argIndex == [(ArgNode *)other argIndex]) return YES; + + return NO; +} + +@end + +@implementation ConnectorNode + +@synthesize filter = filter; +@synthesize labelIndex = labelIndex; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeConnector; } + return self; } - (void)dealloc { - [currentColor release]; + [self setFilter:nil]; [super dealloc]; } +- (NSUInteger)hash { + return ((labelIndex << 16) ^ [filter hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ConnectorNode *)other filter]] && labelIndex == [(ConnectorNode *)other labelIndex]) return YES; + + return NO; +} + @end |