aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <post@wickenrode.com>2015-10-12 01:09:23 +0200
committerMax <post@wickenrode.com>2015-10-12 01:14:33 +0200
commit319eee397f894160aa5d6132d7d07881a75a762e (patch)
tree7839b94dcc1c873e8d1d27c8006d7871f13df305
parent44af78202545ec911de052c5eec361f2296afc28 (diff)
downloadsequelpro-319eee397f894160aa5d6132d7d07881a75a762e.tar.gz
sequelpro-319eee397f894160aa5d6132d7d07881a75a762e.tar.bz2
sequelpro-319eee397f894160aa5d6132d7d07881a75a762e.zip
Change how the custom filename pattern in export dialog is handled
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
-rw-r--r--Interfaces/English.lproj/ExportDialog.xib8
-rw-r--r--Source/SPConstants.h15
-rw-r--r--Source/SPConstants.m13
-rw-r--r--Source/SPExportController.h3
-rw-r--r--Source/SPExportController.m29
-rw-r--r--Source/SPExportControllerDelegate.m253
-rw-r--r--Source/SPExportFileNameTokenObject.h6
-rw-r--r--Source/SPExportFileNameTokenObject.m26
-rw-r--r--Source/SPExportFilenameUtilities.h4
-rw-r--r--Source/SPExportFilenameUtilities.m229
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
@@ -3638,6 +3638,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
+ <reference key="source" ref="844630128"/>
+ <reference key="destination" ref="1001"/>
+ </object>
+ <string key="id">SPs-so-H4L</string>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">delegate</string>
<reference key="source" ref="160760073"/>
<reference key="destination" ref="1001"/>
</object>
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<NSCoding>
{
- 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]];
}
}