aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPFieldEditorController.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/SPFieldEditorController.m')
-rw-r--r--Source/SPFieldEditorController.m569
1 files changed, 569 insertions, 0 deletions
diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m
new file mode 100644
index 00000000..ed2f941e
--- /dev/null
+++ b/Source/SPFieldEditorController.m
@@ -0,0 +1,569 @@
+//
+// $Id: SPFieldEditorController.m 802 2009-06-03 20:46:57Z bibiko $
+//
+// SPFieldEditorController.m
+// sequel-pro
+//
+// Created by Hans-Jörg Bibiko on July 16, 2009
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+#import "SPFieldEditorController.h"
+#import "SPStringAdditions.h"
+#import "SPArrayAdditions.h"
+#import "SPTextViewAdditions.h"
+#import "SPDataAdditions.h"
+#import "QLPreviewPanel.h"
+
+
+@implementation SPFieldEditorController
+
+- (id) init
+{
+ if ((self = [super initWithWindowNibName:@"FieldEditorSheet"])) {
+ // force the nib to be loaded
+ (void) [self window];
+ }
+ return self;
+
+}
+
+- (void) dealloc
+{
+ [super dealloc];
+}
+
+- (id)editWithObject:(id)data usingEncoding:(NSStringEncoding)anEncoding isObjectBlob:(BOOL)isFieldBlob withWindow:(NSWindow *)tableWindow
+{
+
+ [self clean];
+ // hide all views in editSheet
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+
+ editSheetWillBeInitialized = YES;
+
+ encoding = anEncoding;
+
+ isBlob = isFieldBlob;
+
+ // sheetEditData = data;
+ sheetEditData = [data copy];
+
+ // hide all views in editSheet
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+
+ // Hide QuickLook button and text/iamge/hex control for text data
+ [editSheetQuickLookButton setHidden:(!isBlob)];
+ [editSheetSegmentControl setHidden:(!isBlob)];
+
+ [NSApp beginSheet:editSheet modalForWindow:tableWindow modalDelegate:self didEndSelector:nil contextInfo:nil];
+
+ [editSheetProgressBar startAnimation:self];
+
+ NSImage *image = nil;
+ if ( [sheetEditData isKindOfClass:[NSData class]] ) {
+ image = [[[NSImage alloc] initWithData:sheetEditData] autorelease];
+
+ // Set hex view to "" - load on demand only
+ [hexTextView setString:@""];
+
+ stringValue = [[NSString alloc] initWithData:sheetEditData encoding:encoding];
+ if (stringValue == nil)
+ stringValue = [[NSString alloc] initWithData:sheetEditData encoding:NSASCIIStringEncoding];
+
+ [hexTextView setHidden:NO];
+ [hexTextScrollView setHidden:NO];
+ [editImage setHidden:YES];
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+ [editSheetSegmentControl setSelectedSegment:2];
+ } else {
+ stringValue = [sheetEditData retain];
+
+ [hexTextView setString:@""];
+
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [editTextView setHidden:NO];
+ [editTextScrollView setHidden:NO];
+ [editSheetSegmentControl setSelectedSegment:0];
+ }
+
+ if (image) {
+ [editImage setImage:image];
+
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:NO];
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+ [editSheetSegmentControl setSelectedSegment:1];
+ } else {
+ [editImage setImage:nil];
+ }
+ if (stringValue) {
+ [editTextView setString:stringValue];
+
+ if(image == nil) {
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [editTextView setHidden:NO];
+ [editTextScrollView setHidden:NO];
+ [editSheetSegmentControl setSelectedSegment:0];
+ }
+
+ // Locate the caret in editTextView
+ // (to select all takes a bit time for large data)
+ [editTextView setSelectedRange:NSMakeRange(0,0)];
+
+ // Set focus
+ if(image == nil)
+ [editSheet makeFirstResponder:editTextView];
+ else
+ [editSheet makeFirstResponder:editImage];
+
+ [stringValue release];
+ }
+
+
+
+ editSheetWillBeInitialized = NO;
+
+ [editSheetProgressBar stopAnimation:self];
+
+ // wait for editSheet
+ int code = [NSApp runModalForWindow:editSheet];
+
+ [NSApp endSheet:editSheet];
+ [editSheet orderOut:nil];
+
+
+ // For safety reasons inform QuickLook to quit
+ quickLookCloseMarker = 1;
+
+ if ( code ) return [sheetEditData autorelease];
+
+ [self clean];
+
+ return nil;
+}
+
+- (void)clean
+{
+ [hexTextView setString:@""];
+ [editTextView setString:@""];
+ [editImage setImage:nil];
+ if ( sheetEditData ) {
+ [sheetEditData release];
+ }
+
+}
+
+- (IBAction)closeEditSheet:(id)sender
+{
+ [NSApp stopModalWithCode:[sender tag]];
+}
+
+- (IBAction)openEditSheet:(id)sender
+{
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+
+ if ( [panel runModal] == NSOKButton ) {
+ NSString *fileName = [panel filename];
+ NSString *contents = nil;
+
+ editSheetWillBeInitialized = YES;
+
+ [editSheetProgressBar startAnimation:self];
+
+ // free old data
+ if ( sheetEditData != nil ) {
+ [sheetEditData release];
+ }
+
+ // load new data/images
+ sheetEditData = [[NSData alloc] initWithContentsOfFile:fileName];
+
+ NSImage *image = [[NSImage alloc] initWithData:sheetEditData];
+ contents = [[NSString alloc] initWithData:sheetEditData encoding:encoding];
+ if (contents == nil)
+ contents = [[NSString alloc] initWithData:sheetEditData encoding:NSASCIIStringEncoding];
+
+ // set the image preview, string contents and hex representation
+ [editImage setImage:image];
+
+
+ if(contents)
+ [editTextView setString:contents];
+ else
+ [editTextView setString:@""];
+
+ // Load hex data only if user has already displayed them
+ if(![[hexTextView string] isEqualToString:@""])
+ [hexTextView setString:[sheetEditData dataToFormattedHexString]];
+
+ // If the image cell now contains a valid image, select the image view
+ if (image) {
+ [editSheetSegmentControl setSelectedSegment:1];
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:NO];
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+
+ // Otherwise deselect the image view
+ } else {
+ [editSheetSegmentControl setSelectedSegment:0];
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [editTextView setHidden:NO];
+ [editTextScrollView setHidden:NO];
+ }
+
+ [image release];
+ if(contents)
+ [contents release];
+ [editSheetProgressBar stopAnimation:self];
+ editSheetWillBeInitialized = NO;
+ }
+}
+
+/*
+ * Segement controller for text/image/hex buttons in editSheet
+ */
+- (IBAction)segmentControllerChanged:(id)sender
+{
+ switch([sender selectedSegment]){
+ case 0: // text
+ [editTextView setHidden:NO];
+ [editTextScrollView setHidden:NO];
+ [editImage setHidden:YES];
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ // [self makeFirstResponder:editTextView];
+ break;
+ case 1: // image
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+ [editImage setHidden:NO];
+ [hexTextView setHidden:YES];
+ [hexTextScrollView setHidden:YES];
+ // [self makeFirstResponder:editImage];
+ break;
+ case 2: // hex - load on demand
+ if([sheetEditData length] && [[hexTextView string] isEqualToString:@""]) {
+ [editSheetProgressBar startAnimation:self];
+ [hexTextView setString:[sheetEditData dataToFormattedHexString]];
+ [editSheetProgressBar stopAnimation:self];
+ }
+ [editTextView setHidden:YES];
+ [editTextScrollView setHidden:YES];
+ [editImage setHidden:YES];
+ [hexTextView setHidden:NO];
+ [hexTextScrollView setHidden:NO];
+ // [self makeFirstResponder:hexTextView];
+ break;
+ }
+}
+
+/*
+ * Saves a file containing the content of the editSheet
+ */
+- (IBAction)saveEditSheet:(id)sender
+{
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ if ( [panel runModal] == NSOKButton ) {
+
+ [editSheetProgressBar startAnimation:self];
+
+ NSString *fileName = [panel filename];
+
+ // Write binary field types directly to the file
+ //// || [editSheetBinaryButton state] == NSOnState
+ if ( [sheetEditData isKindOfClass:[NSData class]] ) {
+ [sheetEditData writeToFile:fileName atomically:YES];
+
+ // Write other field types' representations to the file via the current encoding
+ } else {
+ [[sheetEditData description] writeToFile:fileName
+ atomically:YES
+ encoding:encoding
+ error:NULL];
+ }
+
+ [editSheetProgressBar stopAnimation:self];
+
+ }
+}
+
+
+#pragma mark -
+#pragma mark QuickLook
+
+- (IBAction)quickLookFormatButton:(id)sender
+{
+ switch([sender tag]) {
+ case 0: [self invokeQuickLookOfType:@"pict" treatAsText:NO]; break;
+ case 1: [self invokeQuickLookOfType:@"m4a" treatAsText:NO]; break;
+ case 2: [self invokeQuickLookOfType:@"mp3" treatAsText:NO]; break;
+ case 3: [self invokeQuickLookOfType:@"wav" treatAsText:NO]; break;
+ case 4: [self invokeQuickLookOfType:@"mov" treatAsText:NO]; break;
+ case 5: [self invokeQuickLookOfType:@"pdf" treatAsText:NO]; break;
+ case 6: [self invokeQuickLookOfType:@"html" treatAsText:YES]; break;
+ case 7: [self invokeQuickLookOfType:@"doc" treatAsText:NO]; break;
+ case 8: [self invokeQuickLookOfType:@"rtf" treatAsText:YES]; break;
+ }
+}
+
+/*
+ * Opens QuickLook for current data if QuickLook is available
+ */
+- (void)invokeQuickLookOfType:(NSString *)type treatAsText:(BOOL)isText
+{
+
+ // Load private framework
+ if([[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/QuickLookUI.framework"] load]) {
+
+ [editSheetProgressBar startAnimation:self];
+
+ // Create a temporary file name to store the data as file
+ // since QuickLook only works on files.
+ NSString *tmpFileName = [NSString stringWithFormat:@"/tmp/SequelProQuickLook.%@", type];
+
+ // if data are binary
+ if ( [sheetEditData isKindOfClass:[NSData class]] || !isText) {
+ [sheetEditData writeToFile:tmpFileName atomically:YES];
+
+ // write other field types' representations to the file via the current encoding
+ } else {
+ [[sheetEditData description] writeToFile:tmpFileName
+ atomically:YES
+ encoding:encoding
+ error:NULL];
+ }
+
+ id ql = [NSClassFromString(@"QLPreviewPanel") sharedPreviewPanel];
+
+ // Init QuickLook
+ [[ql delegate] setDelegate:self];
+ [ql setURLs:[NSArray arrayWithObject:
+ [NSURL fileURLWithPath:tmpFileName]] currentIndex:0 preservingDisplayState:YES];
+ // TODO: No interaction with iChat and iPhoto due to .scriptSuite warning:
+ // for superclass of class 'MainController' in suite 'Sequel Pro': 'NSCoreSuite.NSAbstractObject' is not a valid class name.
+ [ql setShowsAddToiPhotoButton:NO];
+ [ql setShowsiChatTheaterButton:NO];
+ // Since we are inside of editSheet we have to avoid full-screen zooming
+ // otherwise QuickLook hangs
+ [ql setShowsFullscreenButton:NO];
+ [ql setEnableDragNDrop:NO];
+ // Order out QuickLook with animation effect according to self:previewPanel:frameForURL:
+ [ql makeKeyAndOrderFrontWithEffect:2]; // 1 = fade in
+
+ // quickLookCloseMarker == 1 break the modal session
+ quickLookCloseMarker = 0;
+
+ [editSheetProgressBar stopAnimation:self];
+
+ // Run QuickLook in its own modal seesion for event handling
+ NSModalSession session = [NSApp beginModalSessionForWindow:ql];
+ for (;;) {
+ // Conditions for closing QuickLook
+ if ([NSApp runModalSession:session] != NSRunContinuesResponse
+ || quickLookCloseMarker == 1
+ || ![ql isVisible])
+ break;
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
+
+ }
+ [NSApp endModalSession:session];
+
+ // Remove temp file after closing the sheet to allow double-click event at the QuickLook preview.
+ // The afterDelay: time is a kind of dummy, because after double-clicking the model session loop
+ // will break (ql not visible) and returns the event handling back to the editSheet which by itself
+ // blocks the execution of removeQuickLooksTempFile: until the editSheet is closed.
+ [self performSelector:@selector(removeQuickLooksTempFile:) withObject:tmpFileName afterDelay:2];
+
+ // [[NSFileManager defaultManager] removeItemAtPath:tmpFileName error:NULL];
+
+ }
+
+}
+
+- (void)removeQuickLooksTempFile:(NSString*)aPath
+{
+ [[NSFileManager defaultManager] removeItemAtPath:aPath error:NULL];
+}
+
+// This is the delegate method
+// It should return the frame for the item represented by the URL
+// If an empty frame is returned then the panel will fade in/out instead
+- (NSRect)previewPanel:(NSPanel*)panel frameForURL:(NSURL*)URL
+{
+
+ // Close modal session defined in invokeQuickLookOfType:
+ // if user closes the QuickLook view
+ quickLookCloseMarker = 1;
+
+ // Return the App's middle point
+ NSRect mwf = [[NSApp mainWindow] frame];
+ return NSMakeRect(
+ mwf.origin.x+mwf.size.width/2,
+ mwf.origin.y+mwf.size.height/2,
+ 5, 5);
+
+}
+
+-(void)processPasteImageData
+{
+ editSheetWillBeInitialized = YES;
+
+ NSImage *image = nil;
+
+ image = [[[NSImage alloc] initWithPasteboard:[NSPasteboard generalPasteboard]] autorelease];
+ if (image) {
+
+ if (nil != sheetEditData) [sheetEditData release];
+
+ [editImage setImage:image];
+
+ sheetEditData = [[NSData alloc] initWithData:[image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:1]];
+
+ NSString *contents = [[NSString alloc] initWithData:sheetEditData encoding:encoding];
+ if (contents == nil)
+ contents = [[NSString alloc] initWithData:sheetEditData encoding:NSASCIIStringEncoding];
+
+ // Set the string contents and hex representation
+ if(contents)
+ [editTextView setString:contents];
+ if(![[hexTextView string] isEqualToString:@""])
+ [hexTextView setString:[sheetEditData dataToFormattedHexString]];
+
+ [contents release];
+
+ }
+
+ editSheetWillBeInitialized = NO;
+}
+/*
+ * Invoked when the imageView in the connection sheet has the contents deleted
+ * or a file dragged and dropped onto it.
+ */
+- (void)processUpdatedImageData:(NSData *)data
+{
+
+ editSheetWillBeInitialized = YES;
+
+ if (nil != sheetEditData) [sheetEditData release];
+
+ // If the image was not processed, set a blank string as the contents of the edit and hex views.
+ if ( data == nil ) {
+ sheetEditData = [[NSData alloc] init];
+ [editTextView setString:@""];
+ [hexTextView setString:@""];
+ editSheetWillBeInitialized = NO;
+ return;
+ }
+
+ // Process the provided image
+ sheetEditData = [[NSData alloc] initWithData:data];
+ NSString *contents = [[NSString alloc] initWithData:data encoding:encoding];
+ if (contents == nil)
+ contents = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
+
+ // Set the string contents and hex representation
+ if(contents)
+ [editTextView setString:contents];
+ if(![[hexTextView string] isEqualToString:@""])
+ [hexTextView setString:[sheetEditData dataToFormattedHexString]];
+
+ [contents release];
+ editSheetWillBeInitialized = NO;
+}
+
+- (IBAction)dropImage:(id)sender
+{
+
+ // If the image was deleted, set a blank string as the contents of the edit and hex views.
+ // The actual dropped image processing is handled by processUpdatedImageData:.
+ if ( [editImage image] == nil ) {
+ if (nil != sheetEditData) [sheetEditData release];
+ sheetEditData = [[NSData alloc] init];
+ [editTextView setString:@""];
+ [hexTextView setString:@""];
+ return;
+ }
+}
+
+- (void)textViewDidChangeSelection:(NSNotification *)notification
+/*
+ invoked when the user changes the string in the editSheet
+ */
+{
+
+ // Do nothing if user really didn't changed text (e.g. for font size changing return)
+ if(editSheetWillBeInitialized || ([[[notification object] textStorage] changeInLength]==0))
+ return;
+
+ // clear the image and hex (since i doubt someone can "type" a gif)
+ [editImage setImage:nil];
+ [hexTextView setString:@""];
+
+ // free old data
+ if ( sheetEditData != nil ) {
+ [sheetEditData release];
+ }
+
+ // set edit data to text
+ sheetEditData = [[editTextView string] retain];
+
+}
+
+// TextView delegate methods
+
+/**
+ * Traps enter and return key and closes editSheet instead of inserting a linebreak when user hits return.
+ */
+- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
+{
+ if ( aTextView == editTextView ) {
+ if ( [aTextView methodForSelector:aSelector] == [aTextView methodForSelector:@selector(insertNewline:)] &&
+ [[[NSApp currentEvent] characters] isEqualToString:@"\003"] )
+ {
+ [NSApp stopModalWithCode:1];
+ return YES;
+ }
+ else
+ return NO;
+ }
+ return NO;
+}
+
+
+@end