From 549d28e50a35d432531926a6916755dbe26d91c4 Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Wed, 21 Sep 2011 00:38:56 +0000 Subject: Rework linebreak handling in content and custom query result views, as triggered by Issue #1184: - Display table cells on a single line for preview purposes - Display gray pilcrow/reverse pilcrow placeholders instead of linebreaks - If a cell contains linebreaks, automatically trigger sheet editing mode - Handle newly displayed linebreaks in column width detection - If using the up/down arrow keys in a field editor, allow them to select the previous/next line within an editor if appropriat (instead of always moving to the previous/next row) --- Source/SPCopyTable.m | 35 ++++++++++++++++++++++--- Source/SPCustomQuery.m | 54 +++++++++++++++++++++++++-------------- Source/SPDataCellFormatter.m | 47 ++++++++++++++++++++++++++++++++-- Source/SPTableContent.m | 40 +++++++++++++++++++++-------- Source/SPTableStructureDelegate.m | 2 +- 5 files changed, 141 insertions(+), 37 deletions(-) (limited to 'Source') diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index edf0fcf9..c2cadf59 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -756,6 +756,7 @@ static const NSInteger kBlobAsImageFile = 4; NSUInteger cellWidth, maxCellWidth, i; NSRange linebreakRange; double rowStep; + unichar breakChar; #ifndef SP_REFACTOR /* patch */ NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPGlobalResultTableFont]]; #else @@ -804,10 +805,26 @@ static const NSInteger kBlobAsImageFile = 4; contentString = [contentString substringToIndex:500]; } - // If any linebreaks are present, use only the visible part of the string - linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; + // If any linebreaks are present, they are displayed as single characters; replace them with pilcrow/ + // reverse pilcrow to match display output width. + linebreakRange = [contentString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; if (linebreakRange.location != NSNotFound) { - contentString = [contentString substringToIndex:linebreakRange.location]; + NSMutableString *singleLineString = [[[NSMutableString alloc] initWithString:contentString] autorelease]; + while (linebreakRange.location != NSNotFound) { + breakChar = [singleLineString characterAtIndex:linebreakRange.location]; + switch (breakChar) { + case '\n': + [singleLineString replaceCharactersInRange:linebreakRange withString:@"¶"]; + break; + default: + [singleLineString replaceCharactersInRange:linebreakRange withString:@"⁋"]; + if (breakChar == '\r' && NSMaxRange(linebreakRange) < [singleLineString length] && [singleLineString characterAtIndex:linebreakRange.location+1] == '\n') { + [singleLineString deleteCharactersInRange:NSMakeRange(linebreakRange.location+1, 1)]; + } + } + linebreakRange = [singleLineString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; + } + contentString = singleLineString; } } @@ -1042,6 +1059,11 @@ static const NSInteger kBlobAsImageFile = 4; if([self isCellComplex]) return NO; + // Check whether the editor is multiline - if so, allow the arrow down to change selection if it's not + // on the final line + if (NSMaxRange([[textView string] lineRangeForRange:[textView selectedRange]]) < [[textView string] length]) + return NO; + NSInteger newRow = row+1; #ifndef SP_REFACTOR if (newRow>=[[self delegate] numberOfRowsInTableView:self]) return YES; //check if we're already at the end of the list @@ -1070,7 +1092,12 @@ static const NSInteger kBlobAsImageFile = 4; { // If enum field is edited ARROW key navigates through the popup list - if([self isCellComplex]) + if ([self isCellComplex]) + return NO; + + // Check whether the editor is multiline - if so, allow the arrow up to change selection if it's not + // on the first line + if ([[textView string] lineRangeForRange:[textView selectedRange]].location > 0) return NO; if (row==0) return YES; //already at the beginning of the list diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index c10d62d3..bb32eb41 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -2398,7 +2398,6 @@ if ( aTableView == customQueryView ) { NSDictionary *columnDefinition; - BOOL isBlob = NO; // Retrieve the column defintion for(id c in cqColumnDefinition) { @@ -2409,15 +2408,28 @@ } // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; + BOOL isBlob = ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] + || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]); - if ([multipleLineEditingButton state] == NSOnState || isBlob) { + // Determine whether to open the sheet for editing; do so if the multipleLineEditingButton is enabled, + // or if the column was a blob or a text, or if it contains linebreaks. + BOOL useFieldEditor = NO; +#ifndef SP_REFACTOR + if ([multipleLineEditingButton state] == NSOnState) useFieldEditor = YES; +#endif + if (!useFieldEditor && ![[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"] && isBlob) useFieldEditor = YES; + if (!useFieldEditor) { + id cellValue = [resultData cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]]; + if (![cellValue isNSNull] + && [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"] + && [cellValue rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch].location != NSNotFound) + { + useFieldEditor = YES; + } + } - if(fieldEditor) [fieldEditor release], fieldEditor = nil; + if (useFieldEditor) { + if (fieldEditor) [fieldEditor release], fieldEditor = nil; fieldEditor = [[SPFieldEditorController alloc] init]; // Remember edited row for reselecting and setting the scroll view after reload @@ -3829,7 +3841,7 @@ row = [customQueryView editedRow]; column = [customQueryView editedColumn]; - // Retrieve the column defintion + // Retrieve the column definition NSNumber *colIdentifier = [NSArrayObjectAtIndex([customQueryView tableColumns], column) identifier]; for(id c in cqColumnDefinition) { if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:colIdentifier]) { @@ -3869,19 +3881,23 @@ shouldBeginEditing = NO; } - BOOL isBlob = NO; + NSString *fieldType = [columnDefinition objectForKey:@"typegrouping"]; + isFieldEditable = shouldBeginEditing; // Check if current field is a blob - if([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"textdata"] - || [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"blobdata"]) - isBlob = YES; - else - isBlob = NO; - - isFieldEditable = shouldBeginEditing; + BOOL isBlob = ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"]); - // Check if current edited field is a blob or should be displayed in field editor sheet - if (isBlob || [multipleLineEditingButton state] == NSOnState) + // Use the field editor sheet instead of inline editing if the target field is a text, blob, or binary + // type; if it contains linebreaks; or if the force-editing button is enabled. + BOOL useFieldEditor = NO; +#ifndef SP_REFACTOR + if ([multipleLineEditingButton state] == NSOnState) useFieldEditor = YES; +#endif + if (!useFieldEditor && fieldType && ![fieldType isEqualToString:@"enum"] && isBlob) useFieldEditor = YES; + if (!useFieldEditor && [[aFieldEditor string] rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location != NSNotFound) useFieldEditor = YES; + + // Open the field editor sheet if required + if (useFieldEditor) { [customQueryView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; diff --git a/Source/SPDataCellFormatter.m b/Source/SPDataCellFormatter.m index 29b22cd7..70660223 100644 --- a/Source/SPDataCellFormatter.m +++ b/Source/SPDataCellFormatter.m @@ -34,7 +34,7 @@ - (NSString *)stringForObjectValue:(id)anObject { // Truncate the string for speed purposes if it's very long - improves table scrolling speed. - if ([(NSString *)anObject length] > 150) { + if ([anObject isKindOfClass:[NSString class]] && [(NSString *)anObject length] > 150) { return ([NSString stringWithFormat:@"%@...", [anObject substringToIndex:147]]); } @@ -53,9 +53,52 @@ return YES; } +/** + * When producing an attributed string, take the opportunity to convert to a single + * line for display, displaying placeholders for CR and LF characters. + */ - (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes { - return [[[NSAttributedString alloc] initWithString:[self stringForObjectValue:anObject] attributes:attributes] autorelease]; + + // Start with a base string which has been shortened for fast display + NSString *baseString = [self stringForObjectValue:anObject]; + + // Look for any linebreaks within the string + NSRange linebreakRange = [baseString rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch]; + + // If there's no linebreaks, return a non-mutable string + if (linebreakRange.location == NSNotFound) { + return [[[NSAttributedString alloc] initWithString:baseString attributes:attributes] autorelease]; + } + + NSMutableAttributedString *mutableString; + NSUInteger i, j, stringLength = [baseString length]; + unichar c; + + // Otherwise, prepare a mutable attributed string to alter, and walk along the string. + mutableString = [[[NSMutableAttributedString alloc] initWithString:baseString attributes:attributes] autorelease]; + for (i = linebreakRange.location, j = i; i < stringLength; i++, j++) { + c = [baseString characterAtIndex:i]; + switch (c) { + case '\n': + [mutableString replaceCharactersInRange:NSMakeRange(j, 1) withString:@"¶"]; + [mutableString addAttribute:NSForegroundColorAttributeName value:[NSColor lightGrayColor] range:NSMakeRange(j, 1)]; + break; + case '\r': + case 0x0085: + case 0x000b: + case 0x000c: + [mutableString replaceCharactersInRange:NSMakeRange(j, 1) withString:@"⁋"]; + [mutableString addAttribute:NSForegroundColorAttributeName value:[NSColor lightGrayColor] range:NSMakeRange(j, 1)]; + if (c == '\r' && i + 1 < stringLength && [baseString characterAtIndex:i+1] == '\n') { + [mutableString deleteCharactersInRange:NSMakeRange(j+1, 1)]; + i++; + } + break; + } + } + + return mutableString; } - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 425bc6b1..4f12f021 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -4429,12 +4429,25 @@ } } - // Open the sheet if the multipleLineEditingButton is enabled or the column was a blob or a text. - if (( + // Determine whether to open the sheet for editing; do so if the multipleLineEditingButton is enabled, + // or if the column was a blob or a text, or if it contains linebreaks. + BOOL useFieldEditor = NO; #ifndef SP_REFACTOR - [multipleLineEditingButton state] == NSOnState || + if ([multipleLineEditingButton state] == NSOnState) useFieldEditor = YES; #endif - isBlob) && ![[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { + if (!useFieldEditor && ![[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"] && isBlob) useFieldEditor = YES; + if (!useFieldEditor) { + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[aTableColumn identifier] integerValue]]; + if (![cellValue isNSNull] + && [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"] + && [cellValue rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:NSLiteralSearch].location != NSNotFound) + { + useFieldEditor = YES; + } + } + + // Open the sheet if required + if (useFieldEditor) { // A table is per definitionem editable isFieldEditable = YES; @@ -4769,16 +4782,21 @@ } - NSString *fieldType; + // Use the field editor sheet instead of inline editing if the target field is a text, blob, or binary + // type; if it contains linebreaks; or if the force-editing button is enabled. + BOOL useFieldEditor = NO; + NSString *fieldType = [[tableDataInstance columnWithName:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) headerCell] stringValue]] objectForKey:@"typegrouping"]; - // Check if current edited field is a blob - if ((fieldType = [[tableDataInstance columnWithName:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) headerCell] stringValue]] objectForKey:@"typegrouping"]) - && ![fieldType isEqualToString:@"enum"] && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"] #ifndef SP_REFACTOR - || [multipleLineEditingButton state] == NSOnState + if ([multipleLineEditingButton state] == NSOnState) useFieldEditor = YES; #endif - )) - { + + if (!useFieldEditor && fieldType && ![fieldType isEqualToString:@"enum"] && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"])) useFieldEditor = YES; + + if (!useFieldEditor && [[aFieldEditor string] rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location != NSNotFound) useFieldEditor = YES; + + // Open the field editor sheet if required + if (useFieldEditor) { [tableContentView setFieldEditorSelectedRange:[aFieldEditor selectedRange]]; // Cancel editing diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 13a18530..1daac497 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -327,7 +327,7 @@ else if (isTimestampType && ([[[originalRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) { [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"]; } - else if ([[originalRow objectForKey:@"default"] length]) { + else if ([(NSString *)[originalRow objectForKey:@"default"] length]) { [queryString appendFormat:@" DEFAULT '%@'", [mySQLConnection prepareString:[originalRow objectForKey:@"default"]]]; } } -- cgit v1.2.3