diff options
author | Max <dmoagx@users.noreply.github.com> | 2017-12-28 03:24:44 +0100 |
---|---|---|
committer | Max <dmoagx@users.noreply.github.com> | 2018-01-20 02:42:36 +0100 |
commit | 69cc436627f16434b8c72bd6b115cd39769ff38f (patch) | |
tree | b57fd4833dc386c314c1949e06baccafd65c1b54 | |
parent | d0bf2ec8a55bb64555d5d0bc77a770a938b68be7 (diff) | |
download | sequelpro-69cc436627f16434b8c72bd6b115cd39769ff38f.tar.gz sequelpro-69cc436627f16434b8c72bd6b115cd39769ff38f.tar.bz2 sequelpro-69cc436627f16434b8c72bd6b115cd39769ff38f.zip |
merge SPTableContent (part of #2789)
-rw-r--r-- | Source/SPCustomQuery.m | 2 | ||||
-rw-r--r-- | Source/SPDatabaseDocument.m | 1 | ||||
-rw-r--r-- | Source/SPExportFileUtilities.m | 1 | ||||
-rw-r--r-- | Source/SPTableContent.h | 12 | ||||
-rw-r--r-- | Source/SPTableContent.m | 1209 | ||||
-rw-r--r-- | Source/SPTableContentDataSource.h | 37 | ||||
-rw-r--r-- | Source/SPTableContentDataSource.m | 255 | ||||
-rw-r--r-- | Source/SPTableContentDelegate.h | 35 | ||||
-rw-r--r-- | Source/SPTableContentDelegate.m | 845 | ||||
-rw-r--r-- | Source/SPTableContentFilter.h | 37 | ||||
-rw-r--r-- | Source/SPTableContentFilter.m | 246 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 18 |
12 files changed, 1213 insertions, 1485 deletions
diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 319b766c..a1e14a1f 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -4012,7 +4012,7 @@ */ - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview; { -#warning duplicate code with SPTableContentDataSource.m tableView:objectValueForTableColumn:… +#warning duplicate code with SPTableContent.m tableView:objectValueForTableColumn:… id value = nil; // While the table is being loaded, additional validation is required - data diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 69e5cb65..cdef75a1 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -36,7 +36,6 @@ #import "SPFileHandle.h" #import "SPKeychain.h" #import "SPTableContent.h" -#import "SPTableContentFilter.h" #import "SPCustomQuery.h" #import "SPDataImport.h" #import "ImageAndTextCell.h" diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m index 23fb2f4a..f9a6639e 100644 --- a/Source/SPExportFileUtilities.m +++ b/Source/SPExportFileUtilities.m @@ -35,7 +35,6 @@ #import "SPDatabaseDocument.h" #import "SPCustomQuery.h" #import "SPTableContent.h" -#import "SPTableContentDelegate.h" #import "SPExportController+SharedPrivateAPI.h" #import <SPMySQL/SPMySQL.h> diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 74329e20..4c6627de 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -52,7 +52,7 @@ #import "SPDatabaseContentViewDelegate.h" -@interface SPTableContent : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate> +@interface SPTableContent : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate, SPDatabaseContentViewDelegate> { IBOutlet SPDatabaseDocument *tableDocumentInstance; IBOutlet id tablesListInstance; @@ -309,4 +309,14 @@ - (NSArray *)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; +#pragma mark - SPTableContentDataSource + +- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex; + +#pragma mark - SPTableContentFilter + +- (void)makeContentFilterHaveFocus; +- (void)updateFilterTableClause:(id)currentValue; +- (NSString*)escapeFilterTableDefaultOperator:(NSString*)op; + @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 3735c04c..87e996c7 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -30,8 +30,6 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPTableContent.h" -#import "SPTableContentFilter.h" -#import "SPTableContentDataSource.h" #import "SPDatabaseDocument.h" #import "SPTableStructure.h" #import "SPTableInfo.h" @@ -72,17 +70,15 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; #endif -@interface SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; - -@end - @interface SPTableContent () - (BOOL)cancelRowEditing; - (void)documentWillClose:(NSNotification *)notification; +#pragma mark - SPTableContentDataSource_Private_API + +- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; + @end @implementation SPTableContent @@ -4238,6 +4234,1203 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper return [tableContentView fieldEditorSelectedRange]; } +#pragma mark - +#pragma mark TableView datasource methods + +- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; + } +#endif + if (tableView == tableContentView) { + return tableRowsCount; + } + + return 0; +} + +- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; +#ifndef SP_CODA + if (tableView == filterTableView) { + if (filterTableIsSwapped) { + // First column shows the field names + if (columnIndex == 0) { + return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); + } +#endif + if (tableView == tableContentView) { + + id value = nil; + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Return "..." to indicate loading in these + // cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!value) return @"..."; + } + else { + if ([tableView editedColumn] == (NSInteger)columnIndex && [tableView editedRow] == rowIndex) { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:NO]; + } + else { + value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; + } + } + + if ([value isKindOfClass:[SPMySQLGeometryData class]]) { + return [value wktString]; + } + + if ([value isNSNull]) { + return [prefs objectForKey:SPNullValue]; + } + + if ([value isKindOfClass:[NSData class]]) { + + if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + if ([(NSData *)value length] > 255) { + return [NSString stringWithFormat:@"0x%@…", [[(NSData *)value subdataWithRange:NSMakeRange(0, 255)] dataToHexString]]; + } + return [NSString stringWithFormat:@"0x%@", [(NSData *)value dataToHexString]]; + } + + pthread_mutex_t *fieldEditorCheckLock = NULL; + if (isWorking) { + fieldEditorCheckLock = &tableValuesLock; + } + + // Unless we're editing, always retrieve the short string representation, truncating the value where necessary + if ([tableView editedColumn] == (NSInteger)columnIndex || [tableView editedRow] == rowIndex) { + return [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } else { + return [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; + } + } + + if ([value isSPNotLoaded]) { + return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); + } + + return value; + } + + return nil; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if(tableView == filterTableView) { + if (filterTableIsSwapped) { + [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + } + else { + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + } + + [self updateFilterTableClause:nil]; + + return; + } +#endif + if (tableView == tableContentView) { + NSInteger columnIndex = [[tableColumn identifier] integerValue]; + // If the current cell should have been edited in a sheet, do nothing - field closing will have already + // updated the field. + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:columnIndex checkWithLock:NULL]) { + return; + } + + // If table data comes from a view, save back to the view + if ([tablesListInstance tableType] == SPTableTypeView) { + [self saveViewCellValue:object forTableColumn:tableColumn row:rowIndex]; + return; + } + + // Catch editing events in the row and if the row isn't currently being edited, + // start an edit. This allows edits including enum changes to save correctly. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow) { + [self saveRowOnDeselect]; + } + + if (!isEditingRow) { + [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; + + isEditingRow = YES; + currentlyEditingRow = rowIndex; + } + + NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); + + if (object) { + // Restore NULLs if necessary + if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + object = [NSNull null]; + } + else if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + // This is a binary object being edited as a hex string. + // Convert the string back to binary. + // Error checking is done in -control:textShouldEndEditing: + NSData *data = [NSData dataWithHexString:object]; + if (!data) { + NSBeep(); + return; + } + object = data; + } + + [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:object]; + } + else { + [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:@""]; + } + } +} + +- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex +{ + if (![prefs boolForKey:SPDisplayBinaryDataAsHex]) { + return NO; + } + + NSDictionary *columnDefinition = [[(id <SPDatabaseContentViewDelegate>)[tableContentView delegate] dataColumnDefinitions] objectAtIndex:columnIndex]; + NSString *typeGrouping = columnDefinition[@"typegrouping"]; + + if ([typeGrouping isEqual:@"binary"]) { + return YES; + } + + if ([typeGrouping isEqual:@"blobdata"]) { + return YES; + } + + + return NO; +} + +#pragma mark - SPTableContentDataSource_Private_API + +- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview +{ + if (asPreview) { + return SPDataStoragePreviewAtRowAndColumn(tableValues, rowIndex, columnIndex, 150); + } + + return SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); +} + +#pragma mark - SPTableContentFilter + +#ifndef SP_CODA + +/** + * Escape passed operator for usage as filterTableDefaultOperator. + */ +- (NSString*)escapeFilterTableDefaultOperator:(NSString *)op +{ + if (!op) return @""; + + NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[op length]] autorelease]; + + [newOp setString:op]; + [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 +{ + 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 = nil; + NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)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]; + } +} + +/** + * 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 + +#pragma mark - +#pragma mark TableView delegate methods + +/** + * Sorts the tableView by the clicked column. If clicked twice, order is altered to descending. + * Performs the task in a new thread if necessary. + */ +- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn +{ + if ([selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView) return; + + // Prevent sorting while the table is still loading + if ([tableDocumentInstance isWorking]) return; + + // Start the task + [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; + + if ([NSThread isMainThread]) { + [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent table sort task", tableDocumentInstance) target:self selector:@selector(sortTableTaskWithColumn:) object:tableColumn]; + } + else { + [self sortTableTaskWithColumn:tableColumn]; + } +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + // Check our notification object is our table content view + if ([aNotification object] != tableContentView) return; + + isFirstChangeInView = YES; + + [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; + + // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. + if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; + + if (![tableDocumentInstance isWorking]) { + // Update the row selection count + // and update the status of the delete/duplicate buttons + if([tablesListInstance tableType] == SPTableTypeTable) { + if ([tableContentView numberOfSelectedRows] > 0) { + [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; + [removeButton setEnabled:YES]; + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + else { + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + } + } + + [self updateCountText]; + +#ifndef SP_CODA /* triggered commands */ + NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; + + for (NSString *cmdPath in triggeredCommands) + { + NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; + NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; + + [aMenuItem setTag:0]; + [aMenuItem setToolTip:[data objectAtIndex:0]]; + + // For HTML output check if corresponding window already exists + BOOL stopTrigger = NO; + + if ([(NSString *)[data objectAtIndex:2] length]) { + BOOL correspondingWindowFound = NO; + NSString *uuid = [data objectAtIndex:2]; + + for (id win in [NSApp windows]) + { + if ([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { + if ([[[win delegate] windowUUID] isEqualToString:uuid]) { + correspondingWindowFound = YES; + break; + } + } + } + + if (!correspondingWindowFound) stopTrigger = YES; + } + if (!stopTrigger) { + id firstResponder = [[NSApp keyWindow] firstResponder]; + if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + } + else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if ([firstResponder isKindOfClass:[NSTextView class]]) { + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; + } + } + } + } +#endif +} + +/** + * Saves the new column size in the preferences. + */ +- (void)tableViewColumnDidResize:(NSNotification *)notification +{ + // Check our notification object is our table content view + if ([notification object] != tableContentView) return; + + // Sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item + if (![[[notification userInfo] objectForKey:@"NSTableColumn"] identifier]) return; + + NSMutableDictionary *tableColumnWidths; + NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *table = [tablesListInstance tableName]; + + // Get tableColumnWidths object +#ifndef SP_CODA + if ([prefs objectForKey:SPTableColumnWidths] != nil ) { + tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + } + else { +#endif + tableColumnWidths = [NSMutableDictionary dictionary]; +#ifndef SP_CODA + } +#endif + + // Get the database object + if ([tableColumnWidths objectForKey:database] == nil) { + [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; + } + else { + [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; + } + + // Get the table object + if ([[tableColumnWidths objectForKey:database] objectForKey:table] == nil) { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; + } + else { + [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; + } + + // Save column size + [[[tableColumnWidths objectForKey:database] objectForKey:table] + setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[notification userInfo] objectForKey:@"NSTableColumn"] width]] + forKey:[[[[notification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; +#ifndef SP_CODA + [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; +#endif +} + +/** + * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in + * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if ([tableDocumentInstance isWorking]) return NO; + +#ifndef SP_CODA + if (tableView == filterTableView) { + return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; + } + else +#endif + if (tableView == tableContentView) { + + // Nothing is editable while the field editor is running. + // This guards against a special case where accessibility services might + // check if a table field is editable while the sheet is running. + if (fieldEditor) return NO; + + // Ensure that row is editable since it could contain "(not loaded)" columns together with + // issue that the table has no primary key + NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; + + if (![wherePart length]) return NO; + + // If the selected cell hasn't been loaded, load it. + if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { + + // Only get the data for the selected column, not all of them + NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; + + SPMySQLResult *tempResult = [mySQLConnection queryString:query]; + + if (![tempResult numberOfRows]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") + ); + return NO; + } + + NSArray *tempRow = [tempResult getRowAsArray]; + + [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; + [tableContentView reloadData]; + } + + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; + + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { + + BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; + + // A table is per definition editable + BOOL isFieldEditable = YES; + + // Check for Views if field is editable + if ([tablesListInstance tableType] == SPTableTypeView) { + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[tableColumn identifier] integerValue]]; + isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; + } + + NSUInteger fieldLength = 0; + NSString *fieldEncoding = nil; + BOOL allowNULL = YES; + + NSString *fieldType = [columnDefinition objectForKey:@"type"]; + + if ([columnDefinition objectForKey:@"char_length"]) { + fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; + } + + if ([columnDefinition objectForKey:@"null"]) { + allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); + } + + if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { + fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; + } + + fieldEditor = [[SPFieldEditorController alloc] init]; + + [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [[tableColumn headerCell] stringValue], @"colName", + [self usedQuery], @"usedQuery", + @"content", @"tableSource", + nil]]; + + [fieldEditor setTextMaxLength:fieldLength]; + [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; + [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; + [fieldEditor setAllowNULL:allowNULL]; + + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; + + if ([cellValue isNSNull]) { + cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; + } + + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; + isFieldEditable = NO; + } + + NSInteger editedColumn = 0; + + for (NSTableColumn* col in [tableContentView tableColumns]) + { + if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; + + editedColumn++; + } + + [fieldEditor editWithObject:cellValue + fieldName:[[tableColumn headerCell] stringValue] + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable + withWindow:[tableDocumentInstance parentWindow] + sender:self + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", + [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", + nil]]; + + return NO; + } + + return YES; + } + + return YES; +} + +/** + * Enable drag from tableview + */ +- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard +{ + if (tableView == tableContentView) { + NSString *tmp; + + // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS + // otherwise \t delimited lines + if ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) { + tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; + } + else { + tmp = [tableContentView draggedRowsAsTabString]; + } + + if (tmp && [tmp length]) + { + [pboard declareTypes:@[NSTabularTextPboardType, NSStringPboardType] owner:nil]; + + [pboard setString:tmp forType:NSStringPboardType]; + [pboard setString:tmp forType:NSTabularTextPboardType]; + + return YES; + } + } + + return NO; +} + +/** + * Disable row selection while the document is working. + */ +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + return YES; + } + else +#endif + return tableView == tableContentView ? tableRowsSelectable : YES; +} + +/** + * Resize a column when it's double-clicked (10.6+ only). + */ +- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex +{ + NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; + NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; + + // Get the column width + NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; + +#ifndef SP_CODA + // Clear any saved widths for the column + NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; + NSString *tableKey = [tablesListInstance tableName]; + NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; + NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; + NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; + + if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { + [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; + + if ([tableDict count]) { + [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; + } + else { + [dbDict removeObjectForKey:tableKey]; + } + + if ([dbDict count]) { + [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; + } + else { + [savedWidths removeObjectForKey:dbKey]; + } + + [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; + } +#endif + + // Return the width, while the delegate is empty to prevent column resize notifications + [tableContentView setDelegate:nil]; + [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; + + return targetWidth; +} + +/** + * This function changes the text color of text/blob fields which are null or not yet loaded to gray + */ +- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ +#ifndef SP_CODA + if (tableView == filterTableView) { + if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { + [cell setDrawsBackground:YES]; + [cell setBackgroundColor:lightGrayColor]; + } + else { + [cell setDrawsBackground:NO]; + } + + return; + } + else +#endif + if (tableView == tableContentView) { + + if (![cell respondsToSelector:@selector(setTextColor:)]) return; + + BOOL cellIsNullOrUnloaded = NO; + BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; + + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + // If user wants to edit 'cell' set text color to black and return to avoid + // writing in gray if value was NULL + if ([tableView editedColumn] != -1 + && [tableView editedRow] == rowIndex + && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { + [cell setTextColor:blackColor]; + if (cellIsLinkCell) [cell setLinkActive:NO]; + return; + } + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Use gray to indicate loading in these cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); + + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + } + + pthread_mutex_unlock(&tableValuesLock); + } + else { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + } + + if (cellIsNullOrUnloaded) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; + } + else { + [cell setTextColor:blackColor]; + + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; + } + } + + // Disable link arrows for the currently editing row and for any NULL or unloaded cells + if (cellIsLinkCell) { + if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { + [cell setLinkActive:NO]; + } + else { + [cell setLinkActive:YES]; + } + } + } +} + +#ifndef SP_CODA +/** + * Show the table cell content as tooltip + * + * - for text displays line breaks and tabs as well + * - if blob data can be interpret as image data display the image as transparent thumbnail + * (up to now using base64 encoded HTML data). + */ +- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation +{ + if (tableView == filterTableView) { + return nil; + } + else if (tableView == tableContentView) { + + if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; + + // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command + // TODO has to be improved + for (id win in [NSApp orderedWindows]) + { + if ([[[[win contentView] class] description] isEqualToString:@"WebView"]) return nil; + } + + NSImage *image; + + NSPoint pos = [NSEvent mouseLocation]; + pos.y -= 20; + + id theValue = nil; + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Return "..." to indicate loading in these + // cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); + + if (row < (NSInteger)tableRowsCount && [[tableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { + theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]) copy] autorelease]; + } + + pthread_mutex_unlock(&tableValuesLock); + + if (!theValue) theValue = @"..."; + } + else { + theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]); + } + + if (theValue == nil) return nil; + + if ([theValue isKindOfClass:[NSData class]]) { + image = [[[NSImage alloc] initWithData:theValue] autorelease]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + return nil; + } + } + else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { + SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; + image = [v thumbnailImage]; + + if (image) { + [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; + [v release]; + return nil; + } + + [v release]; + } + + // Show the cell string value as tooltip (including line breaks and tabs) + // by using the cell's font + [SPTooltip showWithObject:[aCell stringValue] + atLocation:pos + ofType:@"text" + displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: + [[aCell font] familyName], @"fontname", + [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", + nil]]; + + return nil; + } + + return nil; +} +#endif + +#ifndef SP_CODA /* SplitView delegate methods */ + +#pragma mark - +#pragma mark SplitView delegate methods + +- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview +{ + return NO; +} + +/** + * Set a minimum size for the filter text area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset +{ + return proposedMax - 180; +} + +/** + * Set a minimum size for the field list and action area. + */ +- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset +{ + return proposedMin + 225; +} + +/** + * Improve default resizing and resize only the filter text area by default. + */ +- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize +{ + NSSize newSize = [sender frame].size; + NSView *leftView = [[sender subviews] objectAtIndex:0]; + NSView *rightView = [[sender subviews] objectAtIndex:1]; + float dividerThickness = [sender dividerThickness]; + NSRect leftFrame = [leftView frame]; + NSRect rightFrame = [rightView frame]; + + // Resize height of both views + leftFrame.size.height = newSize.height; + rightFrame.size.height = newSize.height; + + // Only resize the right view's width - unless the constraint has been reached + if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { + rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; + } + else { + leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; + } + + rightFrame.origin.x = leftFrame.size.width + dividerThickness; + + [leftView setFrame:leftFrame]; + [rightView setFrame:rightFrame]; +} + +#endif + +#pragma mark - +#pragma mark Control delegate methods + +- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)editor +{ + // Validate hex input + // We do this here because the textfield will still be selected with the pending changes if we bail out here + if(control == tableContentView) { + NSInteger columnIndex = [tableContentView editedColumn]; + if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { + // special case: the "NULL" string + NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); + if ([[editor string] isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { + return YES; + } + // This is a binary object being edited as a hex string. + // Convert the string back to binary, checking for errors. + NSData *data = [NSData dataWithHexString:[editor string]]; + if (!data) { + SPOnewayAlertSheet( + NSLocalizedString(@"Invalid hexadecimal value", @"table content : editing : error message title when parsing as hex string failed"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"A valid hex string may only contain the numbers 0-9 and letters A-F (a-f). It can optionally begin with „0x“ and spaces will be ignored.\nAlternatively the syntax X'val' is supported, too.", @"table content : editing : error message description when parsing as hex string failed") + ); + return NO; + } + } + } + return YES; +} + +- (void)controlTextDidChange:(NSNotification *)notification +{ +#ifndef SP_CODA + if ([notification object] == filterTableView) { + + NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; + + if (string && [string length]) { + if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; + + lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; + } + + [self updateFilterTableClause:string]; + } +#endif +} + +/** + * If the user selected a table cell which is a blob field and tried to edit it + * cancel the inline edit, display the field editor sheet instead for editing + * and re-enable inline editing after closing the sheet. + */ +- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor +{ + if (control != tableContentView) return YES; + + NSUInteger row, column; + BOOL shouldBeginEditing = YES; + + row = [tableContentView editedRow]; + column = [tableContentView editedColumn]; + + // If cell editing mode and editing request comes + // from the keyboard show an error tooltip + // or bypass if numberOfPossibleUpdateRows == 1 + if ([tableContentView isCellEditingMode]) { + + NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier] integerValue]]; + NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; + NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[tableContentView convertPoint:[tableContentView frameOfCellAtColumn:column row:row].origin toView:nil]]; + + pos.y -= 20; + + switch (numberOfPossibleUpdateRows) + { + case -1: + [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 0: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + break; + case 1: + shouldBeginEditing = YES; + break; + default: + [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows] + atLocation:pos + ofType:@"text"]; + shouldBeginEditing = NO; + } + + } + + // Open the field editor sheet if required + if ([tableContentView shouldUseFieldEditorForRow:row column:column checkWithLock:NULL]) + { + [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; + + // Cancel editing + [control abortEditing]; + + NSAssert(fieldEditor == nil, @"Method should not to be called while a field editor sheet is open!"); + // Call the field editor sheet + [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; + + // send current event to field editor sheet + if ([NSApp currentEvent]) { + [NSApp sendEvent:[NSApp currentEvent]]; + } + + return NO; + } + + return shouldBeginEditing; +} + +/** + * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, + * only within the current row. + */ +- (BOOL)control:(NSControl<NSControlTextEditingDelegate> *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + // Check firstly if SPCopyTable can handle command + if ([control control:control textView:textView doCommandBySelector:command]) + return YES; + + // Trap the escape key + if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { + // Abort editing + [control abortEditing]; + + if ((SPCopyTable*)control == tableContentView) { + [self cancelRowEditing]; + } + + return YES; + } + + return NO; +} + +#pragma mark - +#pragma mark Database content view delegate methods + +- (NSString *)usedQuery +{ + return usedQuery; +} + +/** + * Retrieve the data column definitions + */ +- (NSArray *)dataColumnDefinitions +{ + return dataColumns; +} #pragma mark - diff --git a/Source/SPTableContentDataSource.h b/Source/SPTableContentDataSource.h deleted file mode 100644 index 19864a80..00000000 --- a/Source/SPTableContentDataSource.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SPTableContentDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 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. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContent.h" - -@interface SPTableContent (SPTableContentDataSource) - -- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex; - -@end diff --git a/Source/SPTableContentDataSource.m b/Source/SPTableContentDataSource.m deleted file mode 100644 index a623f83b..00000000 --- a/Source/SPTableContentDataSource.m +++ /dev/null @@ -1,255 +0,0 @@ -// -// SPTableContentDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 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. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContentDataSource.h" -#import "SPTableContentFilter.h" -#import "SPDataStorage.h" -#import "SPCopyTable.h" -#import "SPTablesList.h" -#import "SPAlertSheets.h" - -#import <pthread.h> -#import <SPMySQL/SPMySQL.h> - -@interface SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; - -@end - -@implementation SPTableContent (SPTableContentDataSource) - -#pragma mark - -#pragma mark TableView datasource methods - -- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; - } -#endif - if (tableView == tableContentView) { - return tableRowsCount; - } - - return 0; -} - -- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - NSUInteger columnIndex = [[tableColumn identifier] integerValue]; -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped) { - // First column shows the field names - if (columnIndex == 0) { - return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); - } -#endif - if (tableView == tableContentView) { - - id value = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Return "..." to indicate loading in these - // cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; - } - - pthread_mutex_unlock(&tableValuesLock); - - if (!value) return @"..."; - } - else { - if ([tableView editedColumn] == (NSInteger)columnIndex && [tableView editedRow] == rowIndex) { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:NO]; - } - else { - value = [self _contentValueForTableColumn:columnIndex row:rowIndex asPreview:YES]; - } - } - - if ([value isKindOfClass:[SPMySQLGeometryData class]]) { - return [value wktString]; - } - - if ([value isNSNull]) { - return [prefs objectForKey:SPNullValue]; - } - - if ([value isKindOfClass:[NSData class]]) { - - if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - if ([(NSData *)value length] > 255) { - return [NSString stringWithFormat:@"0x%@…", [[(NSData *)value subdataWithRange:NSMakeRange(0, 255)] dataToHexString]]; - } - return [NSString stringWithFormat:@"0x%@", [(NSData *)value dataToHexString]]; - } - - pthread_mutex_t *fieldEditorCheckLock = NULL; - if (isWorking) { - fieldEditorCheckLock = &tableValuesLock; - } - - // Unless we're editing, always retrieve the short string representation, truncating the value where necessary - if ([tableView editedColumn] == (NSInteger)columnIndex || [tableView editedRow] == rowIndex) { - return [value stringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - } else { - return [value shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; - } - } - - if ([value isSPNotLoaded]) { - return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); - } - - return value; - } - - return nil; -} - -- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if(tableView == filterTableView) { - if (filterTableIsSwapped) { - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; - } - else { - [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; - } - - [self updateFilterTableClause:nil]; - - return; - } -#endif - if (tableView == tableContentView) { - NSInteger columnIndex = [[tableColumn identifier] integerValue]; - // If the current cell should have been edited in a sheet, do nothing - field closing will have already - // updated the field. - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:columnIndex checkWithLock:NULL]) { - return; - } - - // If table data comes from a view, save back to the view - if ([tablesListInstance tableType] == SPTableTypeView) { - [self saveViewCellValue:object forTableColumn:tableColumn row:rowIndex]; - return; - } - - // Catch editing events in the row and if the row isn't currently being edited, - // start an edit. This allows edits including enum changes to save correctly. - if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow) { - [self saveRowOnDeselect]; - } - - if (!isEditingRow) { - [oldRow setArray:[tableValues rowContentsAtIndex:rowIndex]]; - - isEditingRow = YES; - currentlyEditingRow = rowIndex; - } - - NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); - - if (object) { - // Restore NULLs if necessary - if ([object isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { - object = [NSNull null]; - } - else if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - // This is a binary object being edited as a hex string. - // Convert the string back to binary. - // Error checking is done in -control:textShouldEndEditing: - NSData *data = [NSData dataWithHexString:object]; - if (!data) { - NSBeep(); - return; - } - object = data; - } - - [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:object]; - } - else { - [tableValues replaceObjectInRow:rowIndex column:columnIndex withObject:@""]; - } - } -} - -- (BOOL)cellValueIsDisplayedAsHexForColumn:(NSUInteger)columnIndex -{ - if (![prefs boolForKey:SPDisplayBinaryDataAsHex]) { - return NO; - } - - NSDictionary *columnDefinition = [[(id <SPDatabaseContentViewDelegate>)[tableContentView delegate] dataColumnDefinitions] objectAtIndex:columnIndex]; - NSString *typeGrouping = columnDefinition[@"typegrouping"]; - - if ([typeGrouping isEqual:@"binary"]) { - return YES; - } - - if ([typeGrouping isEqual:@"blobdata"]) { - return YES; - } - - - return NO; -} - -@end - -@implementation SPTableContent (SPTableContentDataSource_Private_API) - -- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview -{ - if (asPreview) { - return SPDataStoragePreviewAtRowAndColumn(tableValues, rowIndex, columnIndex, 150); - } - - return SPDataStorageObjectAtRowAndColumn(tableValues, rowIndex, columnIndex); -} - -@end diff --git a/Source/SPTableContentDelegate.h b/Source/SPTableContentDelegate.h deleted file mode 100644 index 2925fa36..00000000 --- a/Source/SPTableContentDelegate.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// SPTableContentDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 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. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContent.h" - -@interface SPTableContent (SPTableContentDelegate) <SPDatabaseContentViewDelegate> - -@end diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m deleted file mode 100644 index 0a80b602..00000000 --- a/Source/SPTableContentDelegate.m +++ /dev/null @@ -1,845 +0,0 @@ -// -// SPTableContentDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on March 20, 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. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContentDelegate.h" -#import "SPTableContentFilter.h" -#import "SPTableContentDataSource.h" -#ifndef SP_CODA /* headers */ -#import "SPAppController.h" -#endif -#import "SPDatabaseDocument.h" -#import "SPDataStorage.h" -#import "SPGeometryDataView.h" -#import "SPTooltip.h" -#import "SPTablesList.h" -#ifndef SP_CODA /* headers */ -#import "SPBundleHTMLOutputController.h" -#endif -#import "SPCopyTable.h" -#import "SPAlertSheets.h" -#import "SPTableData.h" -#import "SPFieldEditorController.h" -#import "SPThreadAdditions.h" -#import "SPTextAndLinkCell.h" - -#import <pthread.h> -#import <SPMySQL/SPMySQL.h> - -@interface SPTableContent (SPDeclaredAPI) - -- (BOOL)cancelRowEditing; - -@end - -@implementation SPTableContent (SPTableContentDelegate) - -#pragma mark - -#pragma mark TableView delegate methods - -/** - * Sorts the tableView by the clicked column. If clicked twice, order is altered to descending. - * Performs the task in a new thread if necessary. - */ -- (void)tableView:(NSTableView*)tableView didClickTableColumn:(NSTableColumn *)tableColumn -{ - if ([selectedTable isEqualToString:@""] || !selectedTable || tableView != tableContentView) return; - - // Prevent sorting while the table is still loading - if ([tableDocumentInstance isWorking]) return; - - // Start the task - [tableDocumentInstance startTaskWithDescription:NSLocalizedString(@"Sorting table...", @"Sorting table task description")]; - - if ([NSThread isMainThread]) { - [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent table sort task", tableDocumentInstance) target:self selector:@selector(sortTableTaskWithColumn:) object:tableColumn]; - } - else { - [self sortTableTaskWithColumn:tableColumn]; - } -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ - // Check our notification object is our table content view - if ([aNotification object] != tableContentView) return; - - isFirstChangeInView = YES; - - [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; - - // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. - if (isEditingRow && [tableContentView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; - - if (![tableDocumentInstance isWorking]) { - // Update the row selection count - // and update the status of the delete/duplicate buttons - if([tablesListInstance tableType] == SPTableTypeTable) { - if ([tableContentView numberOfSelectedRows] > 0) { - [duplicateButton setEnabled:([tableContentView numberOfSelectedRows] == 1)]; - [removeButton setEnabled:YES]; - } - else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } - else { - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - } - } - - [self updateCountText]; - -#ifndef SP_CODA /* triggered commands */ - NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableRowChanged]; - - for (NSString *cmdPath in triggeredCommands) - { - NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; - NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; - - [aMenuItem setTag:0]; - [aMenuItem setToolTip:[data objectAtIndex:0]]; - - // For HTML output check if corresponding window already exists - BOOL stopTrigger = NO; - - if ([(NSString *)[data objectAtIndex:2] length]) { - BOOL correspondingWindowFound = NO; - NSString *uuid = [data objectAtIndex:2]; - - for (id win in [NSApp windows]) - { - if ([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { - if ([[[win delegate] windowUUID] isEqualToString:uuid]) { - correspondingWindowFound = YES; - break; - } - } - } - - if (!correspondingWindowFound) stopTrigger = YES; - } - if (!stopTrigger) { - id firstResponder = [[NSApp keyWindow] firstResponder]; - if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { - [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - } - else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if ([firstResponder isKindOfClass:[NSTextView class]]) { - [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; - } - } - } - } -#endif -} - -/** - * Saves the new column size in the preferences. - */ -- (void)tableViewColumnDidResize:(NSNotification *)notification -{ - // Check our notification object is our table content view - if ([notification object] != tableContentView) return; - - // Sometimes the column has no identifier. I can't figure out what is causing it, so we just skip over this item - if (![[[notification userInfo] objectForKey:@"NSTableColumn"] identifier]) return; - - NSMutableDictionary *tableColumnWidths; - NSString *database = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *table = [tablesListInstance tableName]; - - // Get tableColumnWidths object -#ifndef SP_CODA - if ([prefs objectForKey:SPTableColumnWidths] != nil ) { - tableColumnWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - } - else { -#endif - tableColumnWidths = [NSMutableDictionary dictionary]; -#ifndef SP_CODA - } -#endif - - // Get the database object - if ([tableColumnWidths objectForKey:database] == nil) { - [tableColumnWidths setObject:[NSMutableDictionary dictionary] forKey:database]; - } - else { - [tableColumnWidths setObject:[NSMutableDictionary dictionaryWithDictionary:[tableColumnWidths objectForKey:database]] forKey:database]; - } - - // Get the table object - if ([[tableColumnWidths objectForKey:database] objectForKey:table] == nil) { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionary] forKey:table]; - } - else { - [[tableColumnWidths objectForKey:database] setObject:[NSMutableDictionary dictionaryWithDictionary:[[tableColumnWidths objectForKey:database] objectForKey:table]] forKey:table]; - } - - // Save column size - [[[tableColumnWidths objectForKey:database] objectForKey:table] - setObject:[NSNumber numberWithDouble:[(NSTableColumn *)[[notification userInfo] objectForKey:@"NSTableColumn"] width]] - forKey:[[[[notification userInfo] objectForKey:@"NSTableColumn"] headerCell] stringValue]]; -#ifndef SP_CODA - [prefs setObject:tableColumnWidths forKey:SPTableColumnWidths]; -#endif -} - -/** - * Confirm whether to allow editing of a row. Returns YES by default, unless the multipleLineEditingButton is in - * the ON state, or for blob or text fields - in those cases opens a sheet for editing instead and returns NO. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ - if ([tableDocumentInstance isWorking]) return NO; - -#ifndef SP_CODA - if (tableView == filterTableView) { - return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; - } - else -#endif - if (tableView == tableContentView) { - - // Nothing is editable while the field editor is running. - // This guards against a special case where accessibility services might - // check if a table field is editable while the sheet is running. - if (fieldEditor) return NO; - - // Ensure that row is editable since it could contain "(not loaded)" columns together with - // issue that the table has no primary key - NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; - - if (![wherePart length]) return NO; - - // If the selected cell hasn't been loaded, load it. - if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { - - // Only get the data for the selected column, not all of them - NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; - - SPMySQLResult *tempResult = [mySQLConnection queryString:query]; - - if (![tempResult numberOfRows]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") - ); - return NO; - } - - NSArray *tempRow = [tempResult getRowAsArray]; - - [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; - [tableContentView reloadData]; - } - - // Retrieve the column definition - NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; - - // Open the editing sheet if required - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { - - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; - - // A table is per definition editable - BOOL isFieldEditable = YES; - - // Check for Views if field is editable - if ([tablesListInstance tableType] == SPTableTypeView) { - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[tableColumn identifier] integerValue]]; - isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; - } - - NSUInteger fieldLength = 0; - NSString *fieldEncoding = nil; - BOOL allowNULL = YES; - - NSString *fieldType = [columnDefinition objectForKey:@"type"]; - - if ([columnDefinition objectForKey:@"char_length"]) { - fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; - } - - if ([columnDefinition objectForKey:@"null"]) { - allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); - } - - if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { - fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; - } - - fieldEditor = [[SPFieldEditorController alloc] init]; - - [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [[tableColumn headerCell] stringValue], @"colName", - [self usedQuery], @"usedQuery", - @"content", @"tableSource", - nil]]; - - [fieldEditor setTextMaxLength:fieldLength]; - [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; - [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; - [fieldEditor setAllowNULL:allowNULL]; - - id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; - - if ([cellValue isNSNull]) { - cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; - } - - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; - isFieldEditable = NO; - } - - NSInteger editedColumn = 0; - - for (NSTableColumn* col in [tableContentView tableColumns]) - { - if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; - - editedColumn++; - } - - [fieldEditor editWithObject:cellValue - fieldName:[[tableColumn headerCell] stringValue] - usingEncoding:[mySQLConnection stringEncoding] - isObjectBlob:isBlob - isEditable:isFieldEditable - withWindow:[tableDocumentInstance parentWindow] - sender:self - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"rowIndex", - [NSNumber numberWithInteger:editedColumn], @"columnIndex", - [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", - nil]]; - - return NO; - } - - return YES; - } - - return YES; -} - -/** - * Enable drag from tableview - */ -- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard -{ - if (tableView == tableContentView) { - NSString *tmp; - - // By holding ⌘, ⇧, or/and ⌥ copies selected rows as SQL INSERTS - // otherwise \t delimited lines - if ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSShiftKeyMask|NSAlternateKeyMask)) { - tmp = [tableContentView rowsAsSqlInsertsOnlySelectedRows:YES]; - } - else { - tmp = [tableContentView draggedRowsAsTabString]; - } - - if (tmp && [tmp length]) - { - [pboard declareTypes:@[NSTabularTextPboardType, NSStringPboardType] owner:nil]; - - [pboard setString:tmp forType:NSStringPboardType]; - [pboard setString:tmp forType:NSTabularTextPboardType]; - - return YES; - } - } - - return NO; -} - -/** - * Disable row selection while the document is working. - */ -- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - return YES; - } - else -#endif - return tableView == tableContentView ? tableRowsSelectable : YES; -} - -/** - * Resize a column when it's double-clicked (10.6+ only). - */ -- (CGFloat)tableView:(NSTableView *)tableView sizeToFitWidthOfColumn:(NSInteger)columnIndex -{ - NSTableColumn *theColumn = [[tableView tableColumns] objectAtIndex:columnIndex]; - NSDictionary *columnDefinition = [dataColumns objectAtIndex:[[theColumn identifier] integerValue]]; - - // Get the column width - NSUInteger targetWidth = [tableContentView autodetectWidthForColumnDefinition:columnDefinition maxRows:500]; - -#ifndef SP_CODA - // Clear any saved widths for the column - NSString *dbKey = [NSString stringWithFormat:@"%@@%@", [tableDocumentInstance database], [tableDocumentInstance host]]; - NSString *tableKey = [tablesListInstance tableName]; - NSMutableDictionary *savedWidths = [NSMutableDictionary dictionaryWithDictionary:[prefs objectForKey:SPTableColumnWidths]]; - NSMutableDictionary *dbDict = [NSMutableDictionary dictionaryWithDictionary:[savedWidths objectForKey:dbKey]]; - NSMutableDictionary *tableDict = [NSMutableDictionary dictionaryWithDictionary:[dbDict objectForKey:tableKey]]; - - if ([tableDict objectForKey:[columnDefinition objectForKey:@"name"]]) { - [tableDict removeObjectForKey:[columnDefinition objectForKey:@"name"]]; - - if ([tableDict count]) { - [dbDict setObject:[NSDictionary dictionaryWithDictionary:tableDict] forKey:tableKey]; - } - else { - [dbDict removeObjectForKey:tableKey]; - } - - if ([dbDict count]) { - [savedWidths setObject:[NSDictionary dictionaryWithDictionary:dbDict] forKey:dbKey]; - } - else { - [savedWidths removeObjectForKey:dbKey]; - } - - [prefs setObject:[NSDictionary dictionaryWithDictionary:savedWidths] forKey:SPTableColumnWidths]; - } -#endif - - // Return the width, while the delegate is empty to prevent column resize notifications - [tableContentView setDelegate:nil]; - [tableContentView performSelector:@selector(setDelegate:) withObject:self afterDelay:0.1]; - - return targetWidth; -} - -/** - * This function changes the text color of text/blob fields which are null or not yet loaded to gray - */ -- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex -{ -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:lightGrayColor]; - } - else { - [cell setDrawsBackground:NO]; - } - - return; - } - else -#endif - if (tableView == tableContentView) { - - if (![cell respondsToSelector:@selector(setTextColor:)]) return; - - BOOL cellIsNullOrUnloaded = NO; - BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; - - NSUInteger columnIndex = [[tableColumn identifier] integerValue]; - - // If user wants to edit 'cell' set text color to black and return to avoid - // writing in gray if value was NULL - if ([tableView editedColumn] != -1 - && [tableView editedRow] == rowIndex - && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { - [cell setTextColor:blackColor]; - if (cellIsLinkCell) [cell setLinkActive:NO]; - return; - } - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Use gray to indicate loading in these cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; - } - - pthread_mutex_unlock(&tableValuesLock); - } - else { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; - } - - if (cellIsNullOrUnloaded) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; - } - else { - [cell setTextColor:blackColor]; - - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; - } - } - - // Disable link arrows for the currently editing row and for any NULL or unloaded cells - if (cellIsLinkCell) { - if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { - [cell setLinkActive:NO]; - } - else { - [cell setLinkActive:YES]; - } - } - } -} - -#ifndef SP_CODA -/** - * Show the table cell content as tooltip - * - * - for text displays line breaks and tabs as well - * - if blob data can be interpret as image data display the image as transparent thumbnail - * (up to now using base64 encoded HTML data). - */ -- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation -{ - if (tableView == filterTableView) { - return nil; - } - else if (tableView == tableContentView) { - - if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; - - // Suppress tooltip if another toolip is already visible, mainly displayed by a Bundle command - // TODO has to be improved - for (id win in [NSApp orderedWindows]) - { - if ([[[[win contentView] class] description] isEqualToString:@"WebView"]) return nil; - } - - NSImage *image; - - NSPoint pos = [NSEvent mouseLocation]; - pos.y -= 20; - - id theValue = nil; - - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Return "..." to indicate loading in these - // cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); - - if (row < (NSInteger)tableRowsCount && [[tableColumn identifier] integerValue] < (NSInteger)[tableValues columnCount]) { - theValue = [[SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]) copy] autorelease]; - } - - pthread_mutex_unlock(&tableValuesLock); - - if (!theValue) theValue = @"..."; - } - else { - theValue = SPDataStorageObjectAtRowAndColumn(tableValues, row, [[tableColumn identifier] integerValue]); - } - - if (theValue == nil) return nil; - - if ([theValue isKindOfClass:[NSData class]]) { - image = [[[NSImage alloc] initWithData:theValue] autorelease]; - - if (image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - return nil; - } - } - else if ([theValue isKindOfClass:[SPMySQLGeometryData class]]) { - SPGeometryDataView *v = [[SPGeometryDataView alloc] initWithCoordinates:[theValue coordinates]]; - image = [v thumbnailImage]; - - if (image) { - [SPTooltip showWithObject:image atLocation:pos ofType:@"image"]; - [v release]; - return nil; - } - - [v release]; - } - - // Show the cell string value as tooltip (including line breaks and tabs) - // by using the cell's font - [SPTooltip showWithObject:[aCell stringValue] - atLocation:pos - ofType:@"text" - displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: - [[aCell font] familyName], @"fontname", - [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", - nil]]; - - return nil; - } - - return nil; -} -#endif - -#ifndef SP_CODA /* SplitView delegate methods */ - -#pragma mark - -#pragma mark SplitView delegate methods - -- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview -{ - return NO; -} - -/** - * Set a minimum size for the filter text area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return proposedMax - 180; -} - -/** - * Set a minimum size for the field list and action area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return proposedMin + 225; -} - -/** - * Improve default resizing and resize only the filter text area by default. - */ -- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize -{ - NSSize newSize = [sender frame].size; - NSView *leftView = [[sender subviews] objectAtIndex:0]; - NSView *rightView = [[sender subviews] objectAtIndex:1]; - float dividerThickness = [sender dividerThickness]; - NSRect leftFrame = [leftView frame]; - NSRect rightFrame = [rightView frame]; - - // Resize height of both views - leftFrame.size.height = newSize.height; - rightFrame.size.height = newSize.height; - - // Only resize the right view's width - unless the constraint has been reached - if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { - rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; - } - else { - leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; - } - - rightFrame.origin.x = leftFrame.size.width + dividerThickness; - - [leftView setFrame:leftFrame]; - [rightView setFrame:rightFrame]; -} - -#endif - -#pragma mark - -#pragma mark Control delegate methods - -- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)editor -{ - // Validate hex input - // We do this here because the textfield will still be selected with the pending changes if we bail out here - if(control == tableContentView) { - NSInteger columnIndex = [tableContentView editedColumn]; - if ([self cellValueIsDisplayedAsHexForColumn:columnIndex]) { - // special case: the "NULL" string - NSDictionary *column = NSArrayObjectAtIndex(dataColumns, columnIndex); - if ([[editor string] isEqualToString:[prefs objectForKey:SPNullValue]] && [[column objectForKey:@"null"] boolValue]) { - return YES; - } - // This is a binary object being edited as a hex string. - // Convert the string back to binary, checking for errors. - NSData *data = [NSData dataWithHexString:[editor string]]; - if (!data) { - SPOnewayAlertSheet( - NSLocalizedString(@"Invalid hexadecimal value", @"table content : editing : error message title when parsing as hex string failed"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"A valid hex string may only contain the numbers 0-9 and letters A-F (a-f). It can optionally begin with „0x“ and spaces will be ignored.\nAlternatively the syntax X'val' is supported, too.", @"table content : editing : error message description when parsing as hex string failed") - ); - return NO; - } - } - } - return YES; -} - -- (void)controlTextDidChange:(NSNotification *)notification -{ -#ifndef SP_CODA - if ([notification object] == filterTableView) { - - NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; - - if (string && [string length]) { - if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; - - lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; - } - - [self updateFilterTableClause:string]; - } -#endif -} - -/** - * If the user selected a table cell which is a blob field and tried to edit it - * cancel the inline edit, display the field editor sheet instead for editing - * and re-enable inline editing after closing the sheet. - */ -- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor -{ - if (control != tableContentView) return YES; - - NSUInteger row, column; - BOOL shouldBeginEditing = YES; - - row = [tableContentView editedRow]; - column = [tableContentView editedColumn]; - - // If cell editing mode and editing request comes - // from the keyboard show an error tooltip - // or bypass if numberOfPossibleUpdateRows == 1 - if ([tableContentView isCellEditingMode]) { - - NSArray *editStatus = [self fieldEditStatusForRow:row andColumn:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) identifier] integerValue]]; - NSInteger numberOfPossibleUpdateRows = [[editStatus objectAtIndex:0] integerValue]; - NSPoint pos = [[tableDocumentInstance parentWindow] convertBaseToScreen:[tableContentView convertPoint:[tableContentView frameOfCellAtColumn:column row:row].origin toView:nil]]; - - pos.y -= 20; - - switch (numberOfPossibleUpdateRows) - { - case -1: - [SPTooltip showWithObject:kCellEditorErrorNoMultiTabDb - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - case 0: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorNoMatch, selectedTable] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - break; - case 1: - shouldBeginEditing = YES; - break; - default: - [SPTooltip showWithObject:[NSString stringWithFormat:kCellEditorErrorTooManyMatches, (long)numberOfPossibleUpdateRows] - atLocation:pos - ofType:@"text"]; - shouldBeginEditing = NO; - } - - } - - // Open the field editor sheet if required - if ([tableContentView shouldUseFieldEditorForRow:row column:column checkWithLock:NULL]) - { - [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; - - // Cancel editing - [control abortEditing]; - - NSAssert(fieldEditor == nil, @"Method should not to be called while a field editor sheet is open!"); - // Call the field editor sheet - [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; - - // send current event to field editor sheet - if ([NSApp currentEvent]) { - [NSApp sendEvent:[NSApp currentEvent]]; - } - - return NO; - } - - return shouldBeginEditing; -} - -/** - * Trap the enter, escape, tab and arrow keys, overriding default behaviour and continuing/ending editing, - * only within the current row. - */ -- (BOOL)control:(NSControl<NSControlTextEditingDelegate> *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command -{ - // Check firstly if SPCopyTable can handle command - if ([control control:control textView:textView doCommandBySelector:command]) - return YES; - - // Trap the escape key - if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { - // Abort editing - [control abortEditing]; - - if ((SPCopyTable*)control == tableContentView) { - [self cancelRowEditing]; - } - - return YES; - } - - return NO; -} - -#pragma mark - -#pragma mark Database content view delegate methods - -- (NSString *)usedQuery -{ - return usedQuery; -} - -/** - * Retrieve the data column definitions - */ -- (NSArray *)dataColumnDefinitions -{ - return dataColumns; -} - -@end diff --git a/Source/SPTableContentFilter.h b/Source/SPTableContentFilter.h deleted file mode 100644 index 2380f34c..00000000 --- a/Source/SPTableContentFilter.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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) - -- (void)makeContentFilterHaveFocus; -- (void)updateFilterTableClause:(id)currentValue; -- (NSString*)escapeFilterTableDefaultOperator:(NSString*)operator; - -@end diff --git a/Source/SPTableContentFilter.m b/Source/SPTableContentFilter.m deleted file mode 100644 index b4651e50..00000000 --- a/Source/SPTableContentFilter.m +++ /dev/null @@ -1,246 +0,0 @@ -// -// 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" -#import "SPTextView.h" - -@implementation SPTableContent (SPTableContentFilter) - -#ifndef SP_CODA - -/** - * 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 -{ - 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 = nil; - NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)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]; - } -} - -/** - * 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 013f4762..9df83175 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -37,14 +37,11 @@ 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 */; }; 1734696B11C1167000AB3D16 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; }; 17381856151FB34E0078FFE2 /* SPUserManagerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17381855151FB34E0078FFE2 /* SPUserManagerDelegate.m */; }; - 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0A15192526002DC206 /* SPTableContentDataSource.m */; }; - 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */; }; 173C4362104455CA001F3A30 /* QueryFavoriteManager.xib in Resources */ = {isa = PBXBuildFile; fileRef = 173C4360104455CA001F3A30 /* QueryFavoriteManager.xib */; }; 173C4366104455E0001F3A30 /* SPQueryFavoriteManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */; }; 173C44D81044A6B0001F3A30 /* SPOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 173C44D71044A6B0001F3A30 /* SPOutlineView.m */; }; @@ -672,8 +669,6 @@ 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>"; }; 171C398D16BD634600209EC6 /* SPDatabaseContentViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDatabaseContentViewDelegate.h; 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>"; }; @@ -683,10 +678,6 @@ 173284E91088FEDE0062E892 /* SPConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConstants.m; sourceTree = "<group>"; }; 17381854151FB34E0078FFE2 /* SPUserManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserManagerDelegate.h; sourceTree = "<group>"; }; 17381855151FB34E0078FFE2 /* SPUserManagerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserManagerDelegate.m; sourceTree = "<group>"; }; - 17386E0915192526002DC206 /* SPTableContentDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDataSource.h; sourceTree = "<group>"; }; - 17386E0A15192526002DC206 /* SPTableContentDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDataSource.m; sourceTree = "<group>"; }; - 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentDelegate.h; sourceTree = "<group>"; }; - 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentDelegate.m; sourceTree = "<group>"; }; 173C4361104455CA001F3A30 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/QueryFavoriteManager.xib; sourceTree = "<group>"; }; 173C4364104455E0001F3A30 /* SPQueryFavoriteManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPQueryFavoriteManager.h; sourceTree = "<group>"; }; 173C4365104455E0001F3A30 /* SPQueryFavoriteManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPQueryFavoriteManager.m; sourceTree = "<group>"; }; @@ -1552,12 +1543,6 @@ children = ( 17E6414E0EF01EF6001BC333 /* SPTableContent.h */, 17E6414F0EF01EF6001BC333 /* SPTableContent.m */, - 171B373F15DA654300EBC7AB /* SPTableContentFilter.h */, - 171B374015DA654300EBC7AB /* SPTableContentFilter.m */, - 17386E0C1519257E002DC206 /* SPTableContentDelegate.h */, - 17386E0D1519257E002DC206 /* SPTableContentDelegate.m */, - 17386E0915192526002DC206 /* SPTableContentDataSource.h */, - 17386E0A15192526002DC206 /* SPTableContentDataSource.m */, ); name = "Table Content"; sourceTree = "<group>"; @@ -3407,8 +3392,6 @@ 584D87921514101E00F24774 /* SPDatabaseStructure.m in Sources */, 584D88A91515034200F24774 /* NSNotificationCenterThreadingAdditions.m in Sources */, 584D899D15162CBE00F24774 /* SPDataBase64EncodingAdditions.m in Sources */, - 17386E0B15192526002DC206 /* SPTableContentDataSource.m in Sources */, - 17386E0E1519257E002DC206 /* SPTableContentDelegate.m in Sources */, 17381856151FB34E0078FFE2 /* SPUserManagerDelegate.m in Sources */, 17BA2A3215275D8600389803 /* SPExportInterfaceController.m in Sources */, 1798F1871550175B004B0AB8 /* SPFavoritesExporter.m in Sources */, @@ -3423,7 +3406,6 @@ 58DF9F3315AB26C2003B4330 /* SPDateAdditions.m in Sources */, 58DF9F7315AB8509003B4330 /* SPSplitView.m in Sources */, 58DFC91615CB3501003B4330 /* BGHUDButtonCell.m in Sources */, - 171B374115DA654300EBC7AB /* SPTableContentFilter.m in Sources */, 5843E247162B555B00EAA6D1 /* SPThreadAdditions.m in Sources */, 17005CB316D6CF0000AF81F4 /* SPTableTriggersDelegate.m in Sources */, 58D29FE016E96306002EB401 /* SPUserManagerDataSource.m in Sources */, |