diff options
-rw-r--r-- | Source/SPConstants.h | 3 | ||||
-rw-r--r-- | Source/SPConstants.m | 3 | ||||
-rw-r--r-- | Source/SPTableContent.h | 4 | ||||
-rw-r--r-- | Source/SPTableContent.m | 237 | ||||
-rw-r--r-- | Source/SPTableContentDataSource.m | 11 | ||||
-rw-r--r-- | Source/SPTableContentDelegate.m | 1 | ||||
-rw-r--r-- | Source/SPTableContentFilter.h | 43 | ||||
-rw-r--r-- | Source/SPTableContentFilter.m | 249 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 6 |
9 files changed, 340 insertions, 217 deletions
diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 989dfdb4..3978db6f 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -274,6 +274,9 @@ extern NSString *SPThemesSupportFolder; extern NSString *SPBundleSupportFolder; extern NSString *SPDataSupportFolder; +// Table filter +extern NSString *SPTableContentFilterKey; + // Preference key constants // // General Prefpane diff --git a/Source/SPConstants.m b/Source/SPConstants.m index fa9fce47..b72c0217 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -68,6 +68,9 @@ NSString *SPThemesSupportFolder = @"Themes"; NSString *SPBundleSupportFolder = @"Bundles"; NSString *SPDataSupportFolder = @"Data"; +// Table filter +NSString *SPTableContentFilterKey = @"filter"; + // Preference key constants // General Prefpane NSString *SPDefaultFavorite = @"DefaultFavorite"; diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 013897f9..c9fa1d7c 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -299,11 +299,7 @@ - (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; - (void)openContentFilterManager; -- (void)makeContentFilterHaveFocus; - (NSArray*)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; -- (void)updateFilterTableClause:(id)currentValue; -- (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator; - @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index e7f9a5be..8b52958d 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -32,6 +32,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPTableContent.h" +#import "SPTableContentFilter.h" #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPTableInfo.h" @@ -66,6 +67,8 @@ #import <pthread.h> #import <SPMySQL/SPMySQL.h> +static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; + @interface SPTableContent () - (BOOL)cancelRowEditing; @@ -571,7 +574,7 @@ [filterTableData setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: [columnDefinition objectForKey:@"name"], @"name", [columnDefinition objectForKey:@"typegrouping"], @"typegrouping", - [NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil], @"filter", + [NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil], SPTableContentFilterKey, nil] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; #endif @@ -2524,8 +2527,6 @@ #pragma mark - -// Additional methods - /** * Sets the connection (received from SPDatabaseDocument) and makes things that have to be done only once */ @@ -3328,7 +3329,7 @@ #ifndef SP_REFACTOR [sheet orderOut:self]; - if([contextInfo isEqualToString:@"setdefaultoperator"]) { + if([contextInfo isEqualToString:SPTableFilterSetDefaultOperator]) { if(returnCode) { if(filterTableDefaultOperator) [filterTableDefaultOperator release]; NSString *newOperator = [filterTableSetDefaultOperatorValue stringValue]; @@ -3559,11 +3560,13 @@ [filterTableView abortEditing]; - if(filterTableData && [filterTableData count]) { + if (filterTableData && [filterTableData count]) { // Clear filter data - for(NSNumber *col in [filterTableData allKeys]) - [[filterTableData objectForKey:col] setObject:[NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil] forKey:@"filter"]; + for (NSNumber *col in [filterTableData allKeys]) + { + [[filterTableData objectForKey:col] setObject:[NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil] forKey:SPTableContentFilterKey]; + } [filterTableView reloadData]; [filterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; @@ -3571,8 +3574,6 @@ // Reload table [self filterTable:nil]; - - } #endif } @@ -3593,6 +3594,7 @@ [filterTableWhereClause setCompletionWasReinvokedAutomatically:NO]; [filterTableWhereClause insertText:@""]; [filterTableWhereClause didChangeText]; + [[filterTableView window] makeFirstResponder:filterTableView]; #endif } @@ -3607,13 +3609,15 @@ if (filterTableNegate) { [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE NOT query", @"Title of filter preview area when the query WHERE is negated")]; - } else { + } + else { [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE query", @"Title of filter preview area when the query WHERE is normal")]; } // If live search is set perform filtering - if([filterTableLiveSearchCheckbox state] == NSOnState) + if ([filterTableLiveSearchCheckbox state] == NSOnState) { [self filterTable:filterTableFilterButton]; + } #endif } @@ -3629,8 +3633,9 @@ [filterTableDistinctCheckbox setState:(filterTableDistinct) ? NSOnState : NSOffState]; // If live search is set perform filtering - if([filterTableLiveSearchCheckbox state] == NSOnState) + if ([filterTableLiveSearchCheckbox state] == NSOnState) { [self filterTable:filterTableFilterButton]; + } #endif } @@ -3645,13 +3650,16 @@ [filterTableWindow makeFirstResponder:filterTableView]; // Load history - if([prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) { + if ([prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) { NSMutableArray *lastItems = [NSMutableArray array]; - NSString *defaultItem = @"LIKE '%@%'"; - [lastItems addObject:defaultItem]; + + [lastItems addObject:@"LIKE '%@%'"]; - for(NSString* item in [prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) + for (NSString* item in [prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) + { [lastItems addObject:item]; + } + [filterTableSetDefaultOperatorValue removeAllItems]; [filterTableSetDefaultOperatorValue addItemsWithObjectValues:lastItems]; } @@ -3662,7 +3670,7 @@ modalForWindow:filterTableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"setdefaultoperator"]; + contextInfo:SPTableFilterSetDefaultOperator]; #endif } @@ -3676,8 +3684,9 @@ #ifndef SP_REFACTOR // If live search is set perform filtering - if([filterTableLiveSearchCheckbox state] == NSOnState) + if ([filterTableLiveSearchCheckbox state] == NSOnState) { [self filterTable:filterTableFilterButton]; + } #endif } @@ -3705,7 +3714,7 @@ /** * Provide a getter for the table's sort column name */ -- (NSString *) sortColumnName +- (NSString *)sortColumnName { if (!sortCol || !dataColumns) return nil; @@ -3715,7 +3724,7 @@ /** * Provide a getter for the table current sort order */ -- (BOOL) sortColumnIsAscending +- (BOOL)sortColumnIsAscending { return !isDesc; } @@ -4234,194 +4243,6 @@ return YES; } -/** - * Escape passed operator for usage as filterTableDefaultOperator - */ -- (NSString*)escapeFilterTableDefaultOperator:(NSString*)anOperator -{ - - if(anOperator == nil) return @""; - - NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[anOperator length]] autorelease]; - [newOp setString:anOperator]; - [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; - [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; - return newOp; -} - -/** - * Update WHERE clause in Filter Table Window - * - * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields - * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the - * currently edited table cell - */ -- (void)updateFilterTableClause:(id)currentValue -{ -#ifndef SP_REFACTOR - NSMutableString *clause = [NSMutableString string]; - NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; - NSInteger numberOfCols = [[filterTableView tableColumns] count]; - NSInteger numberOfValues = 0; - NSRange opRange, defopRange; - - BOOL lookInAllFields = NO; - - NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; - NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; - NSInteger editedRow = [filterTableView editedRow]; - - - if(currentValue == filterTableSearchAllFields) { - numberOfRows = 1; - lookInAllFields = YES; - } - - [filterTableWhereClause setString:@""]; - - for(NSInteger i=0; i<numberOfRows; i++) { - numberOfValues = 0; - for(NSInteger anIndex=0; anIndex<numberOfCols; anIndex++) { - NSString *filterCell; - NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%d",anIndex]]]; - - // Take filterTableData - if(currentValue == nil) { - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:@"filter"], i); - // Take last edited value to create the OR clause - } else if(lookInAllFields) { - if(lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { - filterCell = lastEditedFilterTableValue; - } else { - [filterTableWhereClause setString:@""]; - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if([filterTableLiveSearchCheckbox state] == NSOnState) - [self filterTable:filterTableFilterButton]; - - } - // Take value from currently edited table cell - } else if([currentValue isKindOfClass:[NSString class]]){ - if(i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) - filterCell = (NSString*)currentValue; - else - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:@"filter"], i); - } - - if([filterCell length]) { - - // Recode special operators - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; - - if(numberOfValues) - [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; - - NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; - NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - opRange = [filterCell rangeOfString:@"`@`"]; - defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; - - // if cell data begins with ' or " treat it as it is - // by checking if default operator by itself contains a ' or " - if so - // remove first and if given the last ' or " - if([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { - if([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; - if([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } else { - matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; - if([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } - } else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - } - - // if cell contains the field name placeholder - else if(opRange.length || defopRange.length) { - filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - if(defopRange.length) - [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - else - [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - } - - // if cell is equal to NULL - else if([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { - [clause appendFormat:@"%@ IS NULL", fieldName]; - } - - // if cell starts with an operator - else if([filterCell isMatchedByRegex:re1]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; - if([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) - [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; - } - - // if cell consists of at least two words treat the first as operator and the rest as argument - else if([filterCell isMatchedByRegex:re2]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; - if([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) - [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; - } - - // apply the default operator - else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - - numberOfValues++; - } - } - if(numberOfValues) - [clause appendString:@"\nOR\n"]; - } - - // Remove last " OR " if any - if([clause length] > 3) - [filterTableWhereClause setString:[clause substringToIndex:([clause length]-4)]]; - else - [filterTableWhereClause setString:@""]; - - // Update syntax highlighting and uppercasing - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if([filterTableLiveSearchCheckbox state] == NSOnState) - [self filterTable:filterTableFilterButton]; -#endif -} - -/** - * Makes the content filter field have focus by making it the first responder. - */ -- (void)makeContentFilterHaveFocus -{ - - NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; - - if([filter objectForKey:@"NumberOfArguments"]) { - NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; - switch(numOfArgs) { - case 2: - [[firstBetweenField window] makeFirstResponder:firstBetweenField]; - break; - case 1: - [[argumentField window] makeFirstResponder:argumentField]; - break; - default: - [[compareField window] makeFirstResponder:compareField]; - } - } -} - - (void)setFieldEditorSelectedRange:(NSRange)aRange { [tableContentView setFieldEditorSelectedRange:aRange]; diff --git a/Source/SPTableContentDataSource.m b/Source/SPTableContentDataSource.m index 484df453..21743464 100644 --- a/Source/SPTableContentDataSource.m +++ b/Source/SPTableContentDataSource.m @@ -31,6 +31,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPTableContentDataSource.h" +#import "SPTableContentFilter.h" #import "SPDataStorage.h" #import "SPCopyTable.h" #import "SPTablesList.h" @@ -47,7 +48,7 @@ { #ifndef SP_REFACTOR if (tableView == filterTableView) { - return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:@"filter"] count]; + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; } else #endif @@ -69,10 +70,10 @@ return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; } else { - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"], [[tableColumn identifier] integerValue] - 1); + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], [[tableColumn identifier] integerValue] - 1); } else { - return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"], rowIndex); + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); } } else @@ -124,10 +125,10 @@ #ifndef SP_REFACTOR if(tableView == filterTableView) { if (filterTableIsSwapped) { - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"filter"] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; } else { - [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:@"filter"] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; } [self updateFilterTableClause:nil]; diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m index 3a0be224..9cae98d8 100644 --- a/Source/SPTableContentDelegate.m +++ b/Source/SPTableContentDelegate.m @@ -31,6 +31,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPTableContentDelegate.h" +#import "SPTableContentFilter.h" #ifndef SP_REFACTOR /* headers */ #import "SPAppController.h" #endif diff --git a/Source/SPTableContentFilter.h b/Source/SPTableContentFilter.h new file mode 100644 index 00000000..1efe8039 --- /dev/null +++ b/Source/SPTableContentFilter.h @@ -0,0 +1,43 @@ +// +// $Id$ +// +// SPTableContentFilter.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 14, 2012. +// Copyright (c) 2012 Stuart Connolly. 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. + +#import "SPTableContent.h" + +@interface SPTableContent (SPTableContentFilter) + +#ifndef SP_REFACTOR + +- (void)makeContentFilterHaveFocus; +- (void)updateFilterTableClause:(id)currentValue; +- (NSString*)escapeFilterTableDefaultOperator:(NSString*)operator; + +#endif + +@end diff --git a/Source/SPTableContentFilter.m b/Source/SPTableContentFilter.m new file mode 100644 index 00000000..8d2c80ce --- /dev/null +++ b/Source/SPTableContentFilter.m @@ -0,0 +1,249 @@ +// +// $Id$ +// +// SPTableContentFilter.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on August 14, 2012. +// Copyright (c) 2012 Stuart Connolly. 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. + +#import "SPTableContentFilter.h" +#import "RegexKitLite.h" +#import "SPCopyTable.h" + +@implementation SPTableContent (SPTableContentFilter) + +#ifndef SP_REFACTOR + +/** + * Escape passed operator for usage as filterTableDefaultOperator. + */ +- (NSString*)escapeFilterTableDefaultOperator:(NSString *)operator +{ + if (!operator) return @""; + + NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[operator length]] autorelease]; + + [newOp setString:operator]; + [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; + [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; + + return newOp; +} + +/** + * Update WHERE clause in filter table window. + * + * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields + * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the + * currently edited table cell + */ +- (void)updateFilterTableClause:(id)currentValue +{ +#ifndef SP_REFACTOR + NSMutableString *clause = [NSMutableString string]; + NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; + NSInteger numberOfCols = [[filterTableView tableColumns] count]; + NSInteger numberOfValues = 0; + NSRange opRange, defopRange; + + BOOL lookInAllFields = NO; + + NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; + NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; + + NSInteger editedRow = [filterTableView editedRow]; + + if (currentValue == filterTableSearchAllFields) { + numberOfRows = 1; + lookInAllFields = YES; + } + + [filterTableWhereClause setString:@""]; + + for (NSInteger i = 0; i < numberOfRows; i++) + { + numberOfValues = 0; + + for (NSInteger anIndex = 0; anIndex < numberOfCols; anIndex++) + { + NSString *filterCell; + NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%d", anIndex]]]; + + // Take filterTableData + if (!currentValue) { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + // Take last edited value to create the OR clause + else if (lookInAllFields) { + if (lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { + filterCell = lastEditedFilterTableValue; + } + else { + [filterTableWhereClause setString:@""]; + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } + } + } + // Take value from currently edited table cell + else if ([currentValue isKindOfClass:[NSString class]]) { + if (i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) { + filterCell = (NSString*)currentValue; + } + else { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + } + + if ([filterCell length]) { + + // Recode special operators + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; + + if (numberOfValues) { + [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; + } + + NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; + NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + opRange = [filterCell rangeOfString:@"`@`"]; + defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; + + // if cell data begins with ' or " treat it as it is + // by checking if default operator by itself contains a ' or " - if so + // remove first and if given the last ' or " + if ([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { + if ([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + else { + matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + } + } + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + } + // If cell contains the field name placeholder + else if (opRange.length || defopRange.length) { + filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + if (defopRange.length) { + [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + else { + [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + } + // If cell is equal to NULL + else if ([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { + [clause appendFormat:@"%@ IS NULL", fieldName]; + } + // If cell starts with an operator + else if ([filterCell isMatchedByRegex:re1]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; + } + } + // If cell consists of at least two words treat the first as operator and the rest as argument + else if ([filterCell isMatchedByRegex:re2]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; + } + } + // Apply the default operator + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + + numberOfValues++; + } + } + + if (numberOfValues) { + [clause appendString:@"\nOR\n"]; + } + } + + // Remove last " OR " if any + [filterTableWhereClause setString:[clause length] > 3 ? [clause substringToIndex:([clause length] - 4)] : @""]; + + // Update syntax highlighting and uppercasing + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +#endif +} + +/** + * Makes the content filter field have focus by making it the first responder. + */ +- (void)makeContentFilterHaveFocus +{ + NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; + + if ([filter objectForKey:@"NumberOfArguments"]) { + + NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; + + switch (numOfArgs) + { + case 2: + [[firstBetweenField window] makeFirstResponder:firstBetweenField]; + break; + case 1: + [[argumentField window] makeFirstResponder:argumentField]; + break; + default: + [[compareField window] makeFirstResponder:compareField]; + } + } +} + +#endif + +@end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 010f2fad..5df189d2 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 1717F9DB1558114D0065C036 /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11D44DEF118F5887002AA43C /* OCMock.framework */; }; 1717FA401558313A0065C036 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; 1717FA43155831600065C036 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC8BE0F9091DF002A3258 /* libicucore.dylib */; }; + 171B374115DA654300EBC7AB /* SPTableContentFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 171B374015DA654300EBC7AB /* SPTableContentFilter.m */; }; 17292443107AC41000B21980 /* SPXMLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17292442107AC41000B21980 /* SPXMLExporter.m */; }; 172A65110F7BED7A001E861A /* SPConsoleMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 172A65100F7BED7A001E861A /* SPConsoleMessage.m */; }; 173284EA1088FEDE0062E892 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; @@ -614,6 +615,8 @@ 1713C75E140D8D5900CFD461 /* SPQueryConsoleDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryConsoleDataSource.m; sourceTree = "<group>"; }; 17148563125F5FF500321285 /* SPDatabaseCharacterSets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseCharacterSets.h; sourceTree = "<group>"; }; 17148564125F5FF500321285 /* SPDatabaseCharacterSets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDatabaseCharacterSets.m; sourceTree = "<group>"; }; + 171B373F15DA654300EBC7AB /* SPTableContentFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentFilter.h; sourceTree = "<group>"; }; + 171B374015DA654300EBC7AB /* SPTableContentFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilter.m; sourceTree = "<group>"; }; 17292441107AC41000B21980 /* SPXMLExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPXMLExporter.h; sourceTree = "<group>"; }; 17292442107AC41000B21980 /* SPXMLExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPXMLExporter.m; sourceTree = "<group>"; }; 172A650F0F7BED7A001E861A /* SPConsoleMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConsoleMessage.h; sourceTree = "<group>"; }; @@ -1492,6 +1495,8 @@ children = ( 17E6414E0EF01EF6001BC333 /* SPTableContent.h */, 17E6414F0EF01EF6001BC333 /* SPTableContent.m */, + 171B373F15DA654300EBC7AB /* SPTableContentFilter.h */, + 171B374015DA654300EBC7AB /* SPTableContentFilter.m */, 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */, 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */, 17386E0915192526002DC206 /* SPTableContentDataSource.h */, @@ -3295,6 +3300,7 @@ 58DF9F3315AB26C2003B4330 /* SPDateAdditions.m in Sources */, 58DF9F7315AB8509003B4330 /* SPSplitView.m in Sources */, 58DFC91615CB3501003B4330 /* BGHUDButtonCell.m in Sources */, + 171B374115DA654300EBC7AB /* SPTableContentFilter.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |