From 319eee397f894160aa5d6132d7d07881a75a762e Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Oct 2015 01:09:23 +0200 Subject: Change how the custom filename pattern in export dialog is handled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Namely: * They were previously stored in the users locale. Now they are stored using a language independent id * Just typing a token in your language will no longer work. If you want to manually type a token use: {host}, {database}, and so on… (all in English) * Copy & Paste of tokens will use the new form, too (so a user running SP in English can simply share a custom pattern with a user running SP in German) * The localized token names can now contain spaces --- Interfaces/English.lproj/ExportDialog.xib | 8 + Source/SPConstants.h | 15 +- Source/SPConstants.m | 13 ++ Source/SPExportController.h | 3 + Source/SPExportController.m | 29 +++- Source/SPExportControllerDelegate.m | 253 +++++++++++++++++++++++------- Source/SPExportFileNameTokenObject.h | 6 +- Source/SPExportFileNameTokenObject.m | 26 ++- Source/SPExportFilenameUtilities.h | 4 +- Source/SPExportFilenameUtilities.m | 229 ++++++++------------------- 10 files changed, 351 insertions(+), 235 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 2f39243b..5cea2358 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -3635,6 +3635,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 1239 + + + delegate + + + + SPs-so-H4L + delegate diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 80df6ba4..e53d09b4 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -259,6 +259,7 @@ extern NSString *SPFavoritesPasteboardDragType; extern NSString *SPContentFilterPasteboardDragType; extern NSString *SPNavigatorPasteboardDragType; extern NSString *SPNavigatorTableDataPasteboardDragType; +extern NSString *SPExportCustomFileNameTokenPlistType; // File extensions extern NSString *SPFileExtensionDefault; @@ -398,7 +399,19 @@ extern NSString *SPImportClipboardTempFileNamePrefix; extern NSString *SPSQLExportUseCompression; extern NSString *SPNoBOMforSQLdumpFile; extern NSString *SPExportLastDirectory; -extern NSString *SPExportFilenameFormat; +extern NSString *SPExportFilenameFormat; // legacy +extern NSString *SPExportFilenameFormatIntl; // new, user language independent version + +// Export filename tokens +extern NSString *SPFileNameDatabaseTokenName; +extern NSString *SPFileNameHostTokenName; +extern NSString *SPFileNameDateTokenName; +extern NSString *SPFileNameYearTokenName; +extern NSString *SPFileNameMonthTokenName; +extern NSString *SPFileNameDayTokenName; +extern NSString *SPFileNameTimeTokenName; +extern NSString *SPFileNameFavoriteTokenName; +extern NSString *SPFileNameTableTokenName; // Misc extern NSString *SPContentFilters; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index ca8164e3..893c725f 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -47,6 +47,7 @@ NSString *SPFavoritesPasteboardDragType = @"SPFavoritesPasteboard"; NSString *SPContentFilterPasteboardDragType = @"SPContentFilterPasteboard"; NSString *SPNavigatorPasteboardDragType = @"SPNavigatorPasteboardDragType"; NSString *SPNavigatorTableDataPasteboardDragType = @"SPNavigatorTableDataPasteboardDragType"; +NSString *SPExportCustomFileNameTokenPlistType = @"SPExportCustomFileNameTokenPlist"; // File extensions NSString *SPFileExtensionDefault = @"spf"; @@ -199,6 +200,18 @@ NSString *SPSQLExportUseCompression = @"SQLExportUseCompression"; NSString *SPNoBOMforSQLdumpFile = @"NoBOMforSQLdumpFile"; NSString *SPExportLastDirectory = @"SPExportLastDirectory"; NSString *SPExportFilenameFormat = @"SPExportFilenameFormat"; +NSString *SPExportFilenameFormatIntl = @"CustomExportFilenameFormat"; + +// Export filename tokens +NSString *SPFileNameDatabaseTokenName = @"database"; +NSString *SPFileNameHostTokenName = @"host"; +NSString *SPFileNameDateTokenName = @"date"; +NSString *SPFileNameYearTokenName = @"year"; +NSString *SPFileNameMonthTokenName = @"month"; +NSString *SPFileNameDayTokenName = @"day"; +NSString *SPFileNameTimeTokenName = @"time"; +NSString *SPFileNameFavoriteTokenName = @"favorite"; +NSString *SPFileNameTableTokenName = @"table"; // Misc NSString *SPContentFilters = @"ContentFilters"; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index bc706f46..d36c8f81 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -230,6 +230,9 @@ NSInteger heightOffset2; NSUInteger windowMinWidth; NSUInteger windowMinHeigth; + + NSDictionary *localizedTokenNames; + } /** diff --git a/Source/SPExportController.m b/Source/SPExportController.m index b249505a..bbbec9bd 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -124,6 +124,18 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; windowMinHeigth = [[self window] minSize].height; prefs = [NSUserDefaults standardUserDefaults]; + + localizedTokenNames = [@{ + SPFileNameHostTokenName: NSLocalizedString(@"Host", @"export filename host token"), + SPFileNameDatabaseTokenName: NSLocalizedString(@"Database", @"export filename database token"), + SPFileNameTableTokenName: NSLocalizedString(@"Table", @"table"), + SPFileNameDateTokenName: NSLocalizedString(@"Date", @"export filename date token"), + SPFileNameYearTokenName: NSLocalizedString(@"Year", @"export filename date token"), + SPFileNameMonthTokenName: NSLocalizedString(@"Month", @"export filename date token"), + SPFileNameDayTokenName: NSLocalizedString(@"Day", @"export filename date token"), + SPFileNameTimeTokenName: NSLocalizedString(@"Time", @"export filename time token"), + SPFileNameFavoriteTokenName: NSLocalizedString(@"Favorite", @"export filename favorite name token") + } retain]; } return self; @@ -680,7 +692,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Check whether to save the export filename. Save it if it's not blank and contains at least one // token - this suggests it's not a one-off filename if ([[exportCustomFilenameTokenField stringValue] length] < 1) { - [prefs removeObjectForKey:SPExportFilenameFormat]; + [prefs removeObjectForKey:SPExportFilenameFormatIntl]; } else { BOOL saveFilename = NO; @@ -692,7 +704,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; if ([aToken isKindOfClass:[SPExportFileNameTokenObject class]]) saveFilename = YES; } - if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormat]; + if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormatIntl]; } // If we are about to perform a table export, cache the current number of tables within the list, @@ -950,14 +962,15 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; */ - (void)_setPreviousExportFilenameAndPath { + id o; // Restore the export filename if it exists, and update the display - if ([prefs objectForKey:SPExportFilenameFormat]) { - [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPExportFilenameFormat]]]; + if ((o = [prefs objectForKey:SPExportFilenameFormatIntl])) { + [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:o]]; } // If a directory has previously been selected, reselect it - if ([prefs objectForKey:SPExportLastDirectory]) { - [exportPathField setStringValue:[prefs objectForKey:SPExportLastDirectory]]; + if ((o = [prefs objectForKey:SPExportLastDirectory])) { + [exportPathField setStringValue:o]; } else { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); @@ -1071,8 +1084,8 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; SPClear(exportFiles); SPClear(operationQueue); SPClear(exportFilename); - - if (previousConnectionEncoding) SPClear(previousConnectionEncoding); + SPClear(localizedTokenNames); + SPClear(previousConnectionEncoding); [super dealloc]; } diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index 43b1804f..748fec1d 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -32,6 +32,9 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" +#define IS_TOKEN(x) [x isKindOfClass:[SPExportFileNameTokenObject class]] +#define IS_STRING(x) [x isKindOfClass:[NSString class]] + // Defined to suppress warnings @interface SPExportController (SPExportControllerPrivateAPI) @@ -39,6 +42,8 @@ - (void)_toggleSQLExportTableNameTokenAvailability; - (void)_updateExportFormatInformation; - (void)_switchTab; +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens; +- (void)_tokenizeCustomFilenameTokenField; @end @@ -97,78 +102,80 @@ */ - (NSTokenStyle)tokenField:(NSTokenField *)tokenField styleForRepresentedObject:(id)representedObject { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) return NSDefaultTokenStyle; + if (IS_TOKEN(representedObject)) return NSDefaultTokenStyle; return NSPlainTextTokenStyle; } -/** - * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and - * split into many shorter tokens, using non-alphanumeric characters as (preserved) breaks. This preserves - * all supplied characters and allows tokens to be typed. - */ -- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index +- (BOOL)tokenField:(NSTokenField *)tokenField writeRepresentedObjects:(NSArray *)objects toPasteboard:(NSPasteboard *)pboard { - NSUInteger i, j; - NSInteger k; - NSMutableArray *processedTokens = [NSMutableArray array]; - NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - id groupToken; - - for (NSString *inputToken in tokens) - { - j = 0; - - for (i = 0; i < [inputToken length]; i++) - { - if (![alphanumericSet characterIsMember:[inputToken characterAtIndex:i]]) { - if (i > j) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; - } - - [processedTokens addObject:[inputToken substringWithRange:NSMakeRange(i, 1)]]; - - j = i + 1; - } + NSMutableArray *mixed = [NSMutableArray arrayWithCapacity:[objects count]]; + NSMutableString *flatted = [NSMutableString string]; + + for(id item in objects) { + if(IS_TOKEN(item)) { + [mixed addObject:@{@"tokenId": [item tokenId]}]; + [flatted appendFormat:@"{%@}",[item tokenId]]; } - - if (j < i) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; + else if(IS_STRING(item)) { + [mixed addObject:item]; + [flatted appendString:item]; } - } - - // Check to see whether unprocessed strings can be combined to form tokens - for (i = 1; i < [processedTokens count]; i++) { - - // If this is a token object, skip - if ([[processedTokens objectAtIndex:i] isKindOfClass:[SPExportFileNameTokenObject class]]) { - continue; + else { + [NSException raise:NSInternalInconsistencyException format:@"tokenField %@ contains unexpected object %@",tokenField,item]; } + } + + [pboard setString:flatted forType:NSPasteboardTypeString]; + [pboard setPropertyList:mixed forType:SPExportCustomFileNameTokenPlistType]; + return YES; +} - for (k = i - 1; k >= 0; k--) { - - // If this is a token object, stop processing - if ([[processedTokens objectAtIndex:k] isKindOfClass:[SPExportFileNameTokenObject class]]) { - break; +- (NSArray *)tokenField:(NSTokenField *)tokenField readFromPasteboard:(NSPasteboard *)pboard +{ + NSArray *items = [pboard propertyListForType:SPExportCustomFileNameTokenPlistType]; + // if we have our preferred object type use it + if(items) { + NSMutableArray *res = [NSMutableArray arrayWithCapacity:[items count]]; + for (id item in items) { + if (IS_STRING(item)) { + [res addObject:item]; } - - // Check whether the group of items make up a token - groupToken = [self tokenObjectForString:[[processedTokens subarrayWithRange:NSMakeRange(k, 1 + i - k)] componentsJoinedByString:@""]]; - if ([groupToken isKindOfClass:[SPExportFileNameTokenObject class]]) { - [processedTokens replaceObjectsInRange:NSMakeRange(k, 1 + i - k) withObjectsFromArray:@[groupToken]]; - i = k + 1; - break; + else if([item isKindOfClass:[NSDictionary class]]) { + NSString *name = [item objectForKey:@"tokenId"]; + if(name) { + SPExportFileNameTokenObject *tok = [SPExportFileNameTokenObject tokenWithId:name]; + [res addObject:tok]; + } + } + else { + [NSException raise:NSInternalInconsistencyException format:@"pasteboard %@ contains unexpected object %@",pboard,item]; } } + return res; } + // if the string came from another app, paste it literal, tokenfield will take care of any conversions + NSString *raw = [pboard stringForType:NSPasteboardTypeString]; + if(raw) { + return @[raw]; + } + + return nil; +} - return processedTokens; +/** + * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and + * split/recombine strings that contain tokens. This preserves all supplied characters and allows tokens to be typed. + */ +- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index +{ + return [self _updateTokensForMixedContent:tokens]; } - (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - return [(SPExportFileNameTokenObject *)representedObject tokenContent]; + if (IS_TOKEN(representedObject)) { + return [localizedTokenNames objectForKey:[(SPExportFileNameTokenObject *)representedObject tokenId]]; } return representedObject; @@ -188,10 +195,16 @@ */ - (void)controlTextDidChange:(NSNotification *)notification { + // this method can either be called by typing, or by copy&paste. + // In the latter case tokenization will already be done by now. if ([notification object] == exportCustomFilenameTokenField) { [self updateDisplayedExportFilename]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tokenizeCustomFilenameTokenField) object:nil]; - [self performSelector:@selector(tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_tokenizeCustomFilenameTokenField) object:nil]; + // do not queue a call if the key causing this change was the return key. + // This is to prevent a loop with _tokenizeCustomFilenameTokenField. + if([[NSApp currentEvent] type] != NSKeyDown || [[NSApp currentEvent] keyCode] != 0x24) { + [self performSelector:@selector(_tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; + } } } @@ -205,4 +218,132 @@ } } +#pragma mark - + +/** + * Takes a mixed array of strings and tokens and converts + * any valid tokens inside the strings into real tokens + */ +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens +{ + //if two consecutive tokens are strings, merge them + NSMutableArray *mergedTokens = [NSMutableArray array]; + for (id inputToken in tokens) + { + if(IS_TOKEN(inputToken)) { + [mergedTokens addObject:inputToken]; + } + else if(IS_STRING(inputToken)) { + id prev = [mergedTokens lastObject]; + if(IS_STRING(prev)) { + [mergedTokens removeLastObject]; + [mergedTokens addObject:[prev stringByAppendingString:inputToken]]; + } + else { + [mergedTokens addObject:inputToken]; + } + } + } + + // create a mapping dict of tokenId => token + NSMutableDictionary *replacement = [NSMutableDictionary dictionary]; + for (SPExportFileNameTokenObject *realToken in [exportCustomFilenameTokenPool objectValue]) { + NSString *serializedName = [NSString stringWithFormat:@"{%@}",[realToken tokenId]]; + [replacement setObject:realToken forKey:serializedName]; + } + + //now we can look for real tokens to convert inside the strings + NSMutableArray *processedTokens = [NSMutableArray array]; + for (id token in mergedTokens) { + if(IS_TOKEN(token)) { + [processedTokens addObject:token]; + continue; + } + + NSString *remainder = token; + while(true) { + NSRange openCurl = [remainder rangeOfString:@"{"]; + if(openCurl.location == NSNotFound) { + break; + } + NSString *before = [remainder substringToIndex:openCurl.location]; + if([before length]) { + [processedTokens addObject:before]; + } + remainder = [remainder substringFromIndex:openCurl.location]; + NSRange closeCurl = [remainder rangeOfString:@"}"]; + if(closeCurl.location == NSNotFound) { + break; //we've hit an unterminated token + } + NSString *tokenString = [remainder substringToIndex:closeCurl.location+1]; + SPExportFileNameTokenObject *tokenObject = [replacement objectForKey:tokenString]; + if(tokenObject) { + [processedTokens addObject:tokenObject]; + } + else { + [processedTokens addObject:tokenString]; // no token with this name, add it as string + } + remainder = [remainder substringFromIndex:closeCurl.location+1]; + } + if([remainder length]) { + [processedTokens addObject:remainder]; + } + } + + return processedTokens; +} + +- (void)_tokenizeCustomFilenameTokenField +{ + // if we are currently inside or at the end of a string segment we can + // call for tokenization to happen by simulating a return press + + if ([exportCustomFilenameTokenField currentEditor] == nil) return; + + NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; + + if (selectedRange.location == NSNotFound) return; + if (selectedRange.location == 0) return; // the beginning of the field is not valid for tokenization + if (selectedRange.length > 0) return; + + NSUInteger start = 0; + for(id obj in [exportCustomFilenameTokenField objectValue]) { + NSUInteger length; + BOOL isText = NO; + if(IS_STRING(obj)) { + length = [obj length]; + isText = YES; + } + else if(IS_TOKEN(obj)) { + length = 1; // tokens are seen as one char by the textview + } + else { + [NSException raise:NSInternalInconsistencyException format:@"Unknown object type in token field: %@",obj]; + } + NSUInteger end = start+length; + if(selectedRange.location >= start && selectedRange.location <= end) { + if(!isText) return; // cursor is at the end of a token + break; + } + start += length; + } + + // All conditions met - synthesize the return key to trigger tokenization. + NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:[[exportCustomFilenameTokenField window] windowNumber] + context:[NSGraphicsContext currentContext] + characters:nil + charactersIgnoringModifiers:nil + isARepeat:NO + keyCode:0x24]; + + [NSApp postEvent:tokenizingEvent atStart:NO]; +} + @end + +#undef IS_TOKEN +#undef IS_STRING diff --git a/Source/SPExportFileNameTokenObject.h b/Source/SPExportFileNameTokenObject.h index 5a63b56b..8c9b8a80 100644 --- a/Source/SPExportFileNameTokenObject.h +++ b/Source/SPExportFileNameTokenObject.h @@ -30,9 +30,11 @@ @interface SPExportFileNameTokenObject : NSObject { - NSString *tokenContent; + NSString *tokenId; } -@property (retain) NSString *tokenContent; ++ (id)tokenWithId:(NSString *)token; + +@property (nonatomic,copy) NSString *tokenId; @end diff --git a/Source/SPExportFileNameTokenObject.m b/Source/SPExportFileNameTokenObject.m index 5e91049b..9c6338f3 100644 --- a/Source/SPExportFileNameTokenObject.m +++ b/Source/SPExportFileNameTokenObject.m @@ -32,7 +32,27 @@ @implementation SPExportFileNameTokenObject -@synthesize tokenContent; +@synthesize tokenId; + ++ (id)tokenWithId:(NSString *)token +{ + SPExportFileNameTokenObject *obj = [[SPExportFileNameTokenObject alloc] init]; + [obj setTokenId:token]; + return [obj autorelease]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%p {%@}>",self,[self tokenId]]; +} + +- (BOOL)isEqual:(id)object +{ + if([object isKindOfClass:[SPExportFileNameTokenObject class]]) { + return [[self tokenId] isEqualToString:[object tokenId]]; + } + return [super isEqual:object]; +} #pragma mark - #pragma mark NSCoding compatibility @@ -40,7 +60,7 @@ - (id)initWithCoder:(NSCoder *)decoder { if ((self = [super init])) { - [self setTokenContent:[decoder decodeObjectForKey:@"TokenContent"]]; + [self setTokenId:[decoder decodeObjectForKey:@"tokenId"]]; } return self; @@ -48,7 +68,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:[self tokenContent] forKey:@"TokenContent"]; + [encoder encodeObject:[self tokenId] forKey:@"tokenId"]; } @end diff --git a/Source/SPExportFilenameUtilities.h b/Source/SPExportFilenameUtilities.h index 7483316e..978dfc2c 100644 --- a/Source/SPExportFilenameUtilities.h +++ b/Source/SPExportFilenameUtilities.h @@ -41,10 +41,10 @@ - (void)updateDisplayedExportFilename; - (void)updateAvailableExportFilenameTokens; -- (id)tokenObjectForString:(NSString *)stringToTokenize; -- (void)tokenizeCustomFilenameTokenField; - (NSString *)generateDefaultExportFilename; - (NSString *)currentDefaultExportFileExtension; - (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; +- (NSString *)customFilenamePathExtension; +- (BOOL)isTableTokenAllowed; @end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index d8f1198f..e71ede3b 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -50,7 +50,8 @@ //note that there will be no tableName if the export is done from a query result without a database selected (or empty). filename = [self expandCustomFilenameFormatUsingTableName:[[tablesListInstance tables] objectOrNilAtIndex:1]]; - if (![[filename pathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; + + if (![[self customFilenamePathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; } else { filename = [self generateDefaultExportFilename]; @@ -59,11 +60,29 @@ [exportCustomFilenameViewLabelButton setTitle:[NSString stringWithFormat:NSLocalizedString(@"Customize Filename (%@)", @"customize file name label"), filename]]; } -/** - * Updates the available export filename tokens based on the currently selected options. - */ -- (void)updateAvailableExportFilenameTokens -{ +- (NSString *)customFilenamePathExtension +{ + NSMutableString *flatted = [NSMutableString string]; + + // This time we replace every token with "/a". This has the following effect: + // "{host}.{database}" -> "/a./a" -> extension="" + // "{database}_{date}.sql" -> "/a_/a.sql" -> extension="sql" + // That seems to be the easiest way to let NSString correctly determine if an extension is present + for (id filenamePart in [exportCustomFilenameTokenField objectValue]) + { + if([filenamePart isKindOfClass:[NSString class]]) + [flatted appendString:filenamePart]; + else if([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) + [flatted appendString:@"/a"]; + else + [NSException raise:NSInternalInconsistencyException format:@"unknown object in token list: %@",filenamePart]; + } + + return [flatted pathExtension]; +} + +- (BOOL)isTableTokenAllowed +{ NSUInteger i = 0; BOOL removeTable = NO; @@ -71,19 +90,7 @@ BOOL isCSV = exportType == SPCSVExport; BOOL isDot = exportType == SPDotExport; BOOL isXML = exportType == SPXMLExport; - - NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: - NSLocalizedString(@"host", @"export filename host token"), - NSLocalizedString(@"database", @"export filename database token"), - NSLocalizedString(@"table", @"table"), - NSLocalizedString(@"date", @"export filename date token"), - NSLocalizedString(@"year", @"export filename date token"), - NSLocalizedString(@"month", @"export filename date token"), - NSLocalizedString(@"day", @"export filename date token"), - NSLocalizedString(@"time", @"export filename time token"), - NSLocalizedString(@"favorite", @"export filename favorite name token"), - nil]; - + // Determine whether to remove the table from the tokens list if (exportSource == SPQueryExport || isDot) { removeTable = YES; @@ -102,150 +109,46 @@ } } - if (removeTable) { - [exportTokens removeObject:NSLocalizedString(@"table", @"table")]; - NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; - - for (id token in [exportCustomFilenameTokenField objectValue]) - { - if ([token isKindOfClass:[SPExportFileNameTokenObject class]]) { - if ([[token tokenContent] isEqualToString:NSLocalizedString(@"table", @"table")]) { - NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; - - [newTokens removeObjectAtIndex:[tokenParts indexOfObject:token]]; - - [exportCustomFilenameTokenField setObjectValue:newTokens]; - break; - } - } - } - } - - [exportCustomFilenameTokenPool setStringValue:[exportTokens componentsJoinedByString:@","]]; + return (removeTable == NO); } /** - * Take a supplied string and return the token for it - a SPExportFileNameTokenObject if the token - * has been recognized, or the supplied NSString if unmatched. - */ -- (id)tokenObjectForString:(NSString *)stringToTokenize -{ - if ([[exportCustomFilenameTokenPool objectValue] containsObject:stringToTokenize]) { - SPExportFileNameTokenObject *newToken = [[SPExportFileNameTokenObject alloc] init]; - - [newToken setTokenContent:stringToTokenize]; - - return [newToken autorelease]; - } - - return stringToTokenize; -} - -/** - * Tokenize the filename field. - * - * This is called on a delay after text entry to update the tokens during text entry. - * There's no API to perform tokenizing, but the same result can be achieved by using the return key; - * however, this only works if the cursor is after text, not after a token. + * Updates the available export filename tokens based on the currently selected options. */ -- (void)tokenizeCustomFilenameTokenField -{ - NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - - if ([exportCustomFilenameTokenField currentEditor] == nil) return; - - NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; +- (void)updateAvailableExportFilenameTokens +{ + SPExportFileNameTokenObject *tableObject; + NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: + [SPExportFileNameTokenObject tokenWithId:SPFileNameDatabaseTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameHostTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDateTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameYearTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameMonthTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDayTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameTimeTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameFavoriteTokenName], + (tableObject = [SPExportFileNameTokenObject tokenWithId:SPFileNameTableTokenName]), + nil + ]; - if (selectedRange.location == NSNotFound) return; - if (selectedRange.length > 0) return; - - // Retrieve the object value of the token field. This consists of plain text and recognised tokens interspersed. - NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - - // Walk through the strings - not the tokens - and determine whether any need tokenizing, - // including scanning for groups of strings which make up a single token - BOOL tokenizingRequired = NO; - NSUInteger i, j; - NSInteger k; - id tokenCheck; - NSMutableArray *tokenParts = [NSMutableArray array]; - - // Add all tokens, words, and separators to the array to process - for (id eachObject in representedObjects) { - if ([eachObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - [tokenParts addObject:eachObject]; - } else { - for (i = 0, j = 0; i < [(NSString *)eachObject length]; i++) { - if ([alphanumericSet characterIsMember:[eachObject characterAtIndex:i]]) { - continue; - } - if (i > j) { - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(j, i - j)]]; - } - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(i, 1)]]; - j = i + 1; - } - if (j < i) { - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(j, i - j)]]; - } - } - } - - // Walk through the array to process, scanning it for words or groups which are tokens - for (i = 0; i < [tokenParts count]; i++) { - for (k = i; k >= 0; k--) { - - // Don't process existing token objects - if ([[tokenParts objectAtIndex:k] isKindOfClass:[SPExportFileNameTokenObject class]]) { + if (![self isTableTokenAllowed]) { + [exportTokens removeObject:tableObject]; + NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; + + for (id token in tokenParts) + { + if([token isEqual:tableObject]) { + NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; + + [newTokens removeObject:tableObject]; //removes all occurances + + [exportCustomFilenameTokenField setObjectValue:newTokens]; break; } - - // Check whether this item, or group of adjacent items, make up a token - tokenCheck = [self tokenObjectForString:[[tokenParts subarrayWithRange:NSMakeRange(k, 1 + i - k)] componentsJoinedByString:@""]]; - if ([tokenCheck isKindOfClass:[SPExportFileNameTokenObject class]]) { - tokenizingRequired = YES; - } } } - // If no tokenizing is required, don't process any further. - if (!tokenizingRequired) return; - - // Detect where the cursor is currently located. If it's at the end of a token, also return - - // or the enter key would result in closing the sheet. - NSUInteger stringPosition = 0; - - for (id representedObject in representedObjects) - { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - stringPosition++; - } - else { - stringPosition += [(NSString *)representedObject length]; - } - - if (selectedRange.location <= stringPosition) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) return; - break; - } - } - - // All conditions met - synthesize the return key to trigger tokenization. - NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:[[exportCustomFilenameTokenField window] windowNumber] - context:[NSGraphicsContext currentContext] - characters:nil - charactersIgnoringModifiers:nil - isARepeat:NO - keyCode:0x24]; - - [NSApp postEvent:tokenizingEvent atStart:NO]; - - // Update the filename preview - [self updateDisplayedExportFilename]; + [exportCustomFilenameTokenPool setObjectValue:exportTokens]; } /** @@ -343,43 +246,43 @@ for (id filenamePart in representedFilenameParts) { if ([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) { - NSString *tokenContent = [filenamePart tokenContent]; + NSString *tokenContent = [filenamePart tokenId]; - if ([tokenContent isEqualToString:NSLocalizedString(@"host", @"export filename host token")]) { + if ([tokenContent isEqualToString:SPFileNameHostTokenName]) { [string appendStringOrNil:[tableDocumentInstance host]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"database", @"export filename database token")]) { + else if ([tokenContent isEqualToString:SPFileNameDatabaseTokenName]) { [string appendStringOrNil:[tableDocumentInstance database]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"table", @"table")]) { + else if ([tokenContent isEqualToString:SPFileNameTableTokenName]) { [string appendStringOrNil:table]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"date", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameDateTokenName]) { [dateFormatter setDateStyle:NSDateFormatterShortStyle]; [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"year", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameYearTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%Y" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"month", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameMonthTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%m" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"day", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameDayTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%d" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"time", @"export filename time token")]) { + else if ([tokenContent isEqualToString:SPFileNameTimeTokenName]) { [dateFormatter setDateStyle:NSDateFormatterNoStyle]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"favorite", @"export filename favorite name token")]) { + else if ([tokenContent isEqualToString:SPFileNameFavoriteTokenName]) { [string appendStringOrNil:[tableDocumentInstance name]]; } } -- cgit v1.2.3