// // $Id$ // // SPFieldEditorController.m // sequel-pro // // Created by Hans-Jörg Bibiko on July 16, 2009. // Copyright (c) 2009 Hans-Jörg Bibiko. 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 #import "SPFieldEditorController.h" #ifndef SP_REFACTOR #import "QLPreviewPanel.h" #endif #import "RegexKitLite.h" #import "SPTooltip.h" #import "SPGeometryDataView.h" #import "SPCopyTable.h" #import "SPWindow.h" #include #import "SPCustomQuery.h" #import "SPTableContent.h" #import @interface SPFieldEditorController (SPFieldEditorControllerDelegate) - (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo; @end #ifdef SP_REFACTOR /* Suppress deprecation warning for beginSheetForDirectory: until Sequel Pro team can migrate */ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif @implementation SPFieldEditorController @synthesize editedFieldInfo; /** * Initialise an instance of SPFieldEditorController using the XIB “FieldEditorSheet.xib”. Init the available Quciklook format by reading * EditorQuickLookTypes.plist and if given user-defined format store in the Preferences for key (SPQuickLookTypes). */ - (id)init { #ifndef SP_REFACTOR if ((self = [super initWithWindowNibName:@"FieldEditorSheet"])) #else if ((self = [super initWithWindowNibName:@"SQLFieldEditorSheet"])) #endif { // force the nib to be loaded (void) [self window]; counter = 0; maxTextLength = 0; stringValue = nil; _isEditable = NO; _isBlob = NO; _allowNULL = YES; _isGeometry = NO; contextInfo = nil; callerInstance = nil; doGroupDueToChars = NO; prefs = [NSUserDefaults standardUserDefaults]; // Used for max text length recognition if last typed char is a non-space char editTextViewWasChanged = NO; // Allow the user to enter cmd+return to close the edit sheet in addition to fn+return [editSheetOkButton setKeyEquivalentModifierMask:NSCommandKeyMask]; // Permit the field edit sheet to become main if necessary; this allows fields within the sheet to // support full interactivity, for example use of the NSFindPanel inside NSTextViews. [editSheet setIsSheetWhichCanBecomeMain:YES]; allowUndo = NO; selectionChanged = NO; tmpDirPath = [NSTemporaryDirectory() retain]; tmpFileName = nil; NSMenu *menu = [editSheetQuickLookButton menu]; [menu setAutoenablesItems:NO]; NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Interpret data as:", @"Interpret data as:") action:NULL keyEquivalent:@""]; [menuItem setTag:1]; [menuItem setEnabled:NO]; [menu addItem:menuItem]; [menuItem release]; #ifndef SP_REFACTOR NSUInteger tag = 2; // Load default QL types NSMutableArray *qlTypesItems = [[NSMutableArray alloc] init]; NSError *readError = nil; NSString *convError = nil; NSPropertyListFormat format; NSData *defaultTypeData = [NSData dataWithContentsOfFile:[NSBundle pathForResource:@"EditorQuickLookTypes.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]] options:NSMappedRead error:&readError]; NSDictionary *defaultQLTypes = [NSPropertyListSerialization propertyListFromData:defaultTypeData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError]; if(defaultQLTypes == nil || readError != nil || convError != nil) NSLog(@"Error while reading 'EditorQuickLookTypes.plist':\n%@\n%@", [readError localizedDescription], convError); if(defaultQLTypes != nil && [defaultQLTypes objectForKey:@"QuickLookTypes"]) { for(id type in [defaultQLTypes objectForKey:@"QuickLookTypes"]) { NSMenuItem *aMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithString:[type objectForKey:@"MenuLabel"]] action:NULL keyEquivalent:@""]; [aMenuItem setTag:tag]; [aMenuItem setAction:@selector(quickLookFormatButton:)]; [menu addItem:aMenuItem]; [aMenuItem release]; tag++; [qlTypesItems addObject:type]; } } // Load user-defined QL types if([prefs objectForKey:SPQuickLookTypes]) { for(id type in [prefs objectForKey:SPQuickLookTypes]) { NSMenuItem *aMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithString:[type objectForKey:@"MenuLabel"]] action:NULL keyEquivalent:@""]; [aMenuItem setTag:tag]; [aMenuItem setAction:@selector(quickLookFormatButton:)]; [menu addItem:aMenuItem]; [aMenuItem release]; tag++; [qlTypesItems addObject:type]; } } qlTypes = [[NSDictionary dictionaryWithObject:qlTypesItems forKey:SPQuickLookTypes] retain]; [qlTypesItems release]; #endif fieldType = @""; fieldEncoding = @""; } return self; } /** * Dealloc SPFieldEditorController and closes Quicklook window if visible. */ - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self]; #ifndef SP_REFACTOR // On Mac OSX 10.6 QuickLook runs non-modal thus order out the panel // if still visible if ([[NSClassFromString(@"QLPreviewPanel") sharedPreviewPanel] isVisible]) { [[NSClassFromString(@"QLPreviewPanel") sharedPreviewPanel] orderOut:nil]; } #endif if ( sheetEditData ) [sheetEditData release]; #ifndef SP_REFACTOR if ( qlTypes ) [qlTypes release]; #endif if ( tmpFileName ) [tmpFileName release]; if ( tmpDirPath ) [tmpDirPath release]; if ( esUndoManager ) [esUndoManager release]; if ( contextInfo ) [contextInfo release]; [super dealloc]; } #pragma mark - /** * Main method for editing data. It will validate several settings and display a modal sheet for theWindow whioch waits until the user closes the sheet. * * @param data The to be edited table field data. * @param fieldName The name of the currently edited table field. * @param anEncoding The used encoding while editing. * @param isFieldBlob If YES the underlying table field is a TEXT/BLOB field. This setting handles several controls which are offered in the sheet to the user. * @param isEditable If YES the underlying table field is editable, if NO the field is not editable and the SPFieldEditorController sheet do not show a "OK" button for saving. * @param theWindow The window for displaying the sheet. * @param sender The calling instance. * @param contextInfo context info for processing the edited data in sender. */ - (void)editWithObject:(id)data fieldName:(NSString*)fieldName usingEncoding:(NSStringEncoding)anEncoding isObjectBlob:(BOOL)isFieldBlob isEditable:(BOOL)isEditable withWindow:(NSWindow *)theWindow sender:(id)sender contextInfo:(NSDictionary*)theContextInfo { usedSheet = nil; _isEditable = isEditable; contextInfo = [theContextInfo retain]; callerInstance = sender; _isGeometry = ([[fieldType uppercaseString] isEqualToString:@"GEOMETRY"]) ? YES : NO; // Set field label NSMutableString *label = [NSMutableString string]; [label appendFormat:@"“%@”", fieldName]; if([fieldType length] || maxTextLength > 0 || [fieldEncoding length] || !_allowNULL) [label appendString:@" – "]; if([fieldType length]) [label appendString:fieldType]; if(maxTextLength > 0) [label appendFormat:@"(%ld) ", maxTextLength]; if(!_allowNULL) [label appendString:@"NOT NULL "]; if([fieldEncoding length]) [label appendString:fieldEncoding]; if([fieldType length] && [[fieldType uppercaseString] isEqualToString:@"BIT"]) { sheetEditData = [(NSString*)data retain]; [bitSheetNULLButton setEnabled:_allowNULL]; // Check for NULL if([sheetEditData isEqualToString:[prefs objectForKey:SPNullValue]]) { [bitSheetNULLButton setState:NSOnState]; [self setToNull:bitSheetNULLButton]; } else { [bitSheetNULLButton setState:NSOffState]; } [bitSheetFieldName setStringValue:label]; // Init according bit check boxes NSUInteger i = 0; NSUInteger maxBit = (NSUInteger)((maxTextLength > 64) ? 64 : maxTextLength); if([bitSheetNULLButton state] == NSOffState && maxBit <= [(NSString*)sheetEditData length]) for( i = 0; i