diff options
Diffstat (limited to 'Source/SPBundleCommandTextView.m')
-rw-r--r-- | Source/SPBundleCommandTextView.m | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/Source/SPBundleCommandTextView.m b/Source/SPBundleCommandTextView.m new file mode 100644 index 00000000..a840101a --- /dev/null +++ b/Source/SPBundleCommandTextView.m @@ -0,0 +1,360 @@ +// +// $Id$ +// +// SPBundleCommandTextView.m +// sequel-pro +// +// Created by Hans-Jörg Bibiko on Nov, 19 2010 +// +// 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 "SPBundleCommandTextView.h" +#import "SPTextViewAdditions.h" +#import "SPBundleEditorController.h" + +@implementation SPBundleCommandTextView + +- (id)init +{ + if(self = [super init]) + { + ; + } + return self; +} + +- (IBAction)undo:(id)sender +{ + textWasChanged = NO; + [[self undoManager] undo]; + // Due to the undoManager implementation it could happen that + // an action will be recoreded which actually didn't change the + // text buffer. That's why repeat undo. + if(!textWasChanged) [[self undoManager] undo]; + if(!textWasChanged) [[self undoManager] undo]; +} + +- (IBAction)redo:(id)sender +{ + textWasChanged = NO; + [[self undoManager] redo]; + // Due to the undoManager implementation it could happen that + // an action will be recoreded which actually didn't change the + // text buffer. That's why repeat redo. + if(!textWasChanged) [[self undoManager] redo]; + if(!textWasChanged) [[self undoManager] redo]; +} + +- (IBAction)paste:(id)sender +{ + // Try to create an undo group + if([[self delegate] respondsToSelector:@selector(setWasCutPaste)]) + [[self delegate] setWasCutPaste]; + [super paste:sender]; +} + +- (IBAction)cut:(id)sender +{ + // Try to create an undo group + if([[self delegate] respondsToSelector:@selector(setWasCutPaste)]) + [[self delegate] setWasCutPaste]; + [super cut:sender]; +} + +/** + * Validate undo and redo menu items + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + + if ([menuItem action] == @selector(undo:)) { + return ([[self undoManager] canUndo]); + } + if ([menuItem action] == @selector(redo:)) { + return ([[self undoManager] canRedo]); + } + return YES; +} + +- (void)textDidChange:(NSNotification *)aNotification +{ + textWasChanged = YES; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + + long allFlags = (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask); + + // Check if user pressed ⌥ to allow composing of accented characters. + // e.g. for US keyboard "⌥u a" to insert ä + // or for non-US keyboards to allow to enter dead keys + // e.g. for German keyboard ` is a dead key, press space to enter ` + if (([theEvent modifierFlags] & allFlags) == NSAlternateKeyMask || [[theEvent characters] length] == 0) + { + [super keyDown: theEvent]; + return; + } + + NSString *charactersIgnMod = [theEvent charactersIgnoringModifiers]; + long curFlags = ([theEvent modifierFlags] & allFlags); + + if(curFlags & NSCommandKeyMask) { + if([charactersIgnMod isEqualToString:@"+"] || [charactersIgnMod isEqualToString:@"="]) // increase text size by 1; ⌘+ and numpad + + { + [self makeTextSizeLarger]; + [self saveChangedFontInUserDefaults]; + return; + } + if([charactersIgnMod isEqualToString:@"-"]) // decrease text size by 1; ⌘- and numpad - + { + [self makeTextSizeSmaller]; + [self saveChangedFontInUserDefaults]; + return; + } + } + + // Allow undo grouping if user typed a ' ' (for word level undo) + // or a RETURN but not for each char due to writing speed + if([charactersIgnMod isEqualToString:@" "] + || [theEvent keyCode] == 36 + || [theEvent modifierFlags] & (NSCommandKeyMask|NSControlKeyMask|NSAlternateKeyMask) + ) { + [[self delegate] setDoGroupDueToChars]; + } + + + if([[[[self delegate] class] description] isEqualToString:@"SPBundleEditorController"]) { + [super keyDown: theEvent]; + return; + } + + // Check for assign key equivalents inside user-defined bundle commands + NSDictionary *keyEquivalents = [[NSApp delegate] bundleKeyEquivalentsForScope:SPBundleScopeInputField]; + if([keyEquivalents count]) { + for(NSString* key in [keyEquivalents allKeys]) { + NSArray *keyData = [keyEquivalents objectForKey:key]; + if([[keyData objectAtIndex:0] isEqualToString:charactersIgnMod] && [[[keyEquivalents objectForKey:key] objectAtIndex:1] intValue] == curFlags) { + NSMenuItem *item = [[[NSMenuItem alloc] init] autorelease]; + [item setToolTip:[[keyEquivalents objectForKey:key] objectAtIndex:2]]; + [item setTag:0]; + [self executeBundleItemForInputField:item]; + return; + } + } + } + + [super keyDown: theEvent]; + +} + +/* + * Insert the content of a dragged file path or if ⌘ is pressed + * while dragging insert the file path + */ +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSFilenamesPboardType] && [[pboard types] containsObject:@"CorePasteboardFlavorType 0x54455854"]) + return [super performDragOperation:sender]; + + + if ( [[pboard types] containsObject:NSFilenamesPboardType] ) { + NSArray *files = [pboard propertyListForType:NSFilenamesPboardType]; + + // Only one file path is allowed + if([files count] > 1) { + NSLog(@"%@", NSLocalizedString(@"Only one dragged item allowed.",@"Only one dragged item allowed.")); + return YES; + } + + NSString *filepath = [[pboard propertyListForType:NSFilenamesPboardType] objectAtIndex:0]; + + // Set the new insertion point + NSPoint draggingLocation = [sender draggingLocation]; + draggingLocation = [self convertPoint:draggingLocation fromView:nil]; + NSUInteger characterIndex = [self characterIndexOfPoint:draggingLocation]; + [self setSelectedRange:NSMakeRange(characterIndex,0)]; + + // Check if user pressed ⌘ while dragging for inserting only the file path + if([sender draggingSourceOperationMask] == 4) + { + [self insertText:filepath]; + return YES; + } + + // Check size and NSFileType + NSDictionary *attr = [[NSFileManager defaultManager] fileAttributesAtPath:filepath traverseLink:YES]; + if(attr) + { + NSNumber *filesize = [attr objectForKey:NSFileSize]; + NSString *filetype = [attr objectForKey:NSFileType]; + if(filetype == NSFileTypeRegular && filesize) + { + // Ask for confirmation if file content is larger than 1MB + if([filesize unsignedLongValue] > 1000000) + { + NSAlert *alert = [[NSAlert alloc] init]; + [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you really want to proceed with %@ of data?", @"message of panel asking for confirmation for inserting large text from dragging action"), + [NSString stringForByteSize:[filesize longLongValue]]]]; + [alert setHelpAnchor:filepath]; + [alert setMessageText:NSLocalizedString(@"Warning", @"warning")]; + [alert setAlertStyle:NSWarningAlertStyle]; + [alert beginSheetModalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(dragAlertSheetDidEnd:returnCode:contextInfo:) + contextInfo:nil]; + [alert release]; + + } else + [self insertFileContentOfFile:filepath]; + } + } + return YES; + } + + return [super performDragOperation:sender]; +} + +/* + * Confirmation sheetDidEnd method + */ +- (void)dragAlertSheetDidEnd:(NSAlert *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + + [[sheet window] orderOut:nil]; + if ( returnCode == NSAlertFirstButtonReturn ) + [self insertFileContentOfFile:[sheet helpAnchor]]; + +} +/* + * Convert a NSPoint, usually the mouse location, to + * a character index of the text view. + */ +- (NSUInteger)characterIndexOfPoint:(NSPoint)aPoint +{ + NSUInteger glyphIndex; + NSLayoutManager *layoutManager = [self layoutManager]; + CGFloat fraction; + NSRange range; + + range = [layoutManager glyphRangeForTextContainer:[self textContainer]]; + glyphIndex = [layoutManager glyphIndexForPoint:aPoint + inTextContainer:[self textContainer] + fractionOfDistanceThroughGlyph:&fraction]; + if( fraction > 0.5 ) glyphIndex++; + + if( glyphIndex == NSMaxRange(range) ) + return [[self textStorage] length]; + else + return [layoutManager characterIndexForGlyphAtIndex:glyphIndex]; + +} + +/* + * Insert content of a plain text file for a given path. + * In addition it tries to figure out the file's text encoding heuristically. + */ +- (void)insertFileContentOfFile:(NSString *)aPath +{ + + NSError *err = nil; + NSStringEncoding enc; + NSString *content = nil; + + // Make usage of the UNIX command "file" to get an info + // about file type and encoding. + NSTask *task=[[NSTask alloc] init]; + NSPipe *pipe=[[NSPipe alloc] init]; + NSFileHandle *handle; + NSString *result; + [task setLaunchPath:@"/usr/bin/file"]; + [task setArguments:[NSArray arrayWithObjects:aPath, @"-Ib", nil]]; + [task setStandardOutput:pipe]; + handle=[pipe fileHandleForReading]; + [task launch]; + result=[[NSString alloc] initWithData:[handle readDataToEndOfFile] + encoding:NSASCIIStringEncoding]; + + [pipe release]; + [task release]; + + // UTF16/32 files are detected as application/octet-stream resp. audio/mpeg + if( [result hasPrefix:@"text/plain"] + || [[[aPath pathExtension] lowercaseString] isEqualToString:SPFileExtensionSQL] + || [[[aPath pathExtension] lowercaseString] isEqualToString:@"txt"] + || [result hasPrefix:@"audio/mpeg"] + || [result hasPrefix:@"application/octet-stream"] + ) + { + // if UTF16/32 cocoa will try to find the correct encoding + if([result hasPrefix:@"application/octet-stream"] || [result hasPrefix:@"audio/mpeg"] || [result rangeOfString:@"utf-16"].length) + enc = 0; + else if([result rangeOfString:@"utf-8"].length) + enc = NSUTF8StringEncoding; + else if([result rangeOfString:@"iso-8859-1"].length) + enc = NSISOLatin1StringEncoding; + else if([result rangeOfString:@"us-ascii"].length) + enc = NSASCIIStringEncoding; + else + enc = 0; + + if(enc == 0) // cocoa tries to detect the encoding + content = [NSString stringWithContentsOfFile:aPath usedEncoding:&enc error:&err]; + else + content = [NSString stringWithContentsOfFile:aPath encoding:enc error:&err]; + + if(content) + { + [self insertText:content]; + [result release]; + return; + } + // If UNIX "file" failed try cocoa's encoding detection + content = [NSString stringWithContentsOfFile:aPath encoding:enc error:&err]; + if(content) + { + [self insertText:content]; + [result release]; + return; + } + } + + [result release]; + + NSLog(@"%@ ‘%@’.", NSLocalizedString(@"Couldn't read the file content of", @"Couldn't read the file content of"), aPath); +} + +// Store the font in the prefs for selected delegates only +- (void)saveChangedFontInUserDefaults +{ + if([[[[self delegate] class] description] isEqualToString:@"SPBundleEditorController"]) + [[NSUserDefaults standardUserDefaults] setObject:[NSArchiver archivedDataWithRootObject:[self font]] forKey:@"FieldEditorSheetFont"]; +} + +// Action receiver for a font change in the font panel +- (void)changeFont:(id)sender +{ + NSFont *nf = [[NSFontPanel sharedFontPanel] panelConvertFont:[self font]]; + [self setFont:nf]; + [self saveChangedFontInUserDefaults]; +} + +@end |