Permalink
Browse files

support for a configurable menu of external editors, with a designate…

…d default editor and a preset list of known ODB-protocol-capable ones
  • Loading branch information...
1 parent 1a6bf20 commit 62eb1b7c03833480d8a3c3f70b3097e24a1ef95b @scrod committed Mar 31, 2011
Showing with 592 additions and 25 deletions.
  1. +1 −0 AppController.h
  2. +25 −8 AppController.m
  3. +77 −0 ExternalEditorListController.h
  4. +442 −0 ExternalEditorListController.m
  5. +8 −7 NoteObject.m
  6. +16 −10 NotesTableView.m
  7. +4 −0 PrefsWindowController.h
  8. +19 −0 PrefsWindowController.m
View
1 AppController.h
@@ -90,6 +90,7 @@ void outletObjectAwoke(id sender);
- (IBAction)copyNoteLink:(id)sender;
- (IBAction)exportNote:(id)sender;
- (IBAction)revealNote:(id)sender;
+- (IBAction)editNoteExternally:(id)sender;
- (IBAction)printNote:(id)sender;
- (IBAction)tagNote:(id)sender;
- (IBAction)importNotes:(id)sender;
View
33 AppController.m
@@ -30,6 +30,7 @@
#import "NSFileManager_NV.h"
#import "EncodingsManager.h"
#import "ExporterManager.h"
+#import "ExternalEditorListController.h"
#import "NSData_transformations.h"
#import "BufferUtils.h"
#import "LinkingEditor.h"
@@ -415,19 +416,15 @@ - (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
} else if (selector == @selector(fixFileEncoding:)) {
return (currentNote != nil && storageFormatOfNote(currentNote) == PlainTextFormat && ![currentNote contentsWere7Bit]);
+ } else if (selector == @selector(editNoteExternally:)) {
+
+ return (numberSelected > 0) && [[menuItem representedObject] canEditAllNotes:
+ [notationController notesAtIndexes:[notesTableView selectedRowIndexes]]];
}
return YES;
}
-/*
- - (void)menuNeedsUpdate:(NSMenu *)menu {
- NSLog(@"mama needs update: %@", [menu title]);
-
- NSArray *selectedNotes = [notationController notesAtIndexes:[notesTableView selectedRowIndexes]];
- [selectedNotes setURLsInNotesForMenu:menu];
- }*/
-
- (void)updateNoteMenus {
NSMenu *notesMenu = [[[NSApp mainMenu] itemWithTag:NOTES_MENU_ID] submenu];
@@ -439,6 +436,8 @@ - (void)updateNoteMenus {
NSLocalizedString(@"Delete", nil), trailingQualifier]];
}
+ [notesMenu setSubmenu:[[ExternalEditorListController sharedInstance] addEditNotesMenu] forItem:[notesMenu itemWithTag:88]];
+
NSMenu *viewMenu = [[[NSApp mainMenu] itemWithTag:VIEW_MENU_ID] submenu];
menuIndex = [viewMenu indexOfItemWithTarget:notesTableView andAction:@selector(toggleNoteBodyPreviews:)];
@@ -592,6 +591,24 @@ - (IBAction)revealNote:(id)sender {
[[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""];
}
+- (IBAction)editNoteExternally:(id)sender {
+ ExternalEditor *ed = [sender representedObject];
+ if ([ed isKindOfClass:[ExternalEditor class]]) {
+ NSIndexSet *indexes = [notesTableView selectedRowIndexes];
+
+ if (kCGEventFlagMaskAlternate == (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & NSDeviceIndependentModifierFlagsMask)) {
+ //allow changing the default editor directly from Notes menu
+ [[ExternalEditorListController sharedInstance] setDefaultEditor:ed];
+ }
+ //force-write any queued changes to disk in case notes are being stored as separate files which might be opened directly by the method below
+ [notationController synchronizeNoteChanges:nil];
+
+ [[notationController notesAtIndexes:indexes] makeObjectsPerformSelector:@selector(editExternallyUsingEditor:) withObject:ed];
+ } else {
+ NSBeep();
+ }
+}
+
- (IBAction)printNote:(id)sender {
NSIndexSet *indexes = [notesTableView selectedRowIndexes];
View
77 ExternalEditorListController.h
@@ -0,0 +1,77 @@
+//
+// ExternalEditorListController.h
+// Notation
+//
+// Created by Zachary Schneirov on 3/14/11.
+
+/*Copyright (c) 2010, Zachary Schneirov. All rights reserved.
+ This file is part of Notational Velocity.
+
+ Notational Velocity 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 3 of the License, or
+ (at your option) any later version.
+
+ Notational Velocity 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 Notational Velocity. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#import <Cocoa/Cocoa.h>
+
+extern NSString *ExternalEditorsChangedNotification;
+
+@class NoteObject;
+
+@interface ExternalEditor : NSObject {
+
+ BOOL installCheckFailed;
+ NSImage *iconImg;
+ NSString *bundleIdentifier;
+ NSURL *resolvedURL;
+ NSString *displayName;
+ NSMutableDictionary *knownPathExtensions;
+}
+
+- (id)initWithBundleID:(NSString*)aBundleIdentifier resolvedURL:(NSURL*)aURL;
+- (BOOL)canEditNoteDirectly:(NoteObject*)aNote;
+- (BOOL)canEditAllNotes:(NSArray*)notes;
+- (NSImage*)iconImage;
+- (NSURL*)resolvedURL;
+- (NSString*)displayName;
+- (BOOL)isInstalled;
+- (BOOL)isODBEditor;
+- (NSString*)bundleIdentifier;
+
+@end
+
+@interface ExternalEditorListController : NSObject {
+
+ NSMutableArray *userEditorList;
+ NSArray *ODBEditorList;
+ ExternalEditor *defaultEditor;
+
+ NSMutableSet *editNotesMenus, *editorPrefsMenus;
+
+ NSMutableArray *_installedODBEditors;
+}
+- (id)initWithUserDefaults;
++ (ExternalEditorListController*)sharedInstance;
+- (void)addUserEditorFromDialog:(id)sender;
+- (void)resetUserEditors:(id)sender;
+- (void)_initDefaults;
+- (NSArray*)_installedODBEditors;
+- (BOOL)editorIsMember:(ExternalEditor*)anEditor;
++ (NSSet*)ODBAppIdentifiers;
+- (NSArray*)userEditorIdentifiers;
+- (NSMenu*)addEditorPrefsMenu;
+- (NSMenu*)addEditNotesMenu;
+- (void)menusChanged;
+- (void)_updateMenu:(NSMenu*)theMenu;
+- (ExternalEditor*)defaultExternalEditor;
+- (void)setDefaultEditor:(id)anEditor;
+@end
View
442 ExternalEditorListController.m
@@ -0,0 +1,442 @@
+//
+// ExternalEditorListController.m
+// Notation
+//
+// Created by Zachary Schneirov on 3/14/11.
+
+/*Copyright (c) 2010, Zachary Schneirov. All rights reserved.
+ This file is part of Notational Velocity.
+
+ Notational Velocity 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 3 of the License, or
+ (at your option) any later version.
+
+ Notational Velocity 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 Notational Velocity. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#import "ExternalEditorListController.h"
+#import "NoteObject.h"
+#import "NotationController.h"
+#import "NotationPrefs.h"
+#import "NSBezierPath_NV.h"
+
+static NSString *UserEEIdentifiersKey = @"UserEEIdentifiers";
+static NSString *DefaultEEIdentifierKey = @"DefaultEEIdentifier";
+NSString *ExternalEditorsChangedNotification = @"ExternalEditorsChanged";
+
+@implementation ExternalEditor
+
+- (id)initWithBundleID:(NSString*)aBundleIdentifier resolvedURL:(NSURL*)aURL {
+ if ([self init]) {
+ bundleIdentifier = [aBundleIdentifier retain];
+ resolvedURL = [aURL retain];
+
+ NSAssert(resolvedURL || bundleIdentifier, @"the bundle identifier and URL cannot both be nil");
+ if (!bundleIdentifier) {
+ if (!(bundleIdentifier = [[[NSBundle bundleWithPath:[aURL path]] bundleIdentifier] copy])) {
+ NSLog(@"initWithBundleID:resolvedURL: URL does not seem to point to a valid bundle");
+ return nil;
+ }
+ }
+ }
+ return self;
+}
+
+- (BOOL)canEditNoteDirectly:(NoteObject*)aNote {
+ NSAssert(aNote != nil, @"aNote is nil");
+
+ //for determining whether this potentially non-ODB-editor can open a non-plain-text file
+ //process: does pathExtension key exist in knownPathExtensions dict?
+ //if not, check this path extension w/ launch services
+ //then add a corresponding YES/NO NSNumber value to the knownPathExtensions dict
+
+ //but first, this editor can't handle any path if it's not actually installed
+ if (![self isInstalled]) return NO;
+
+ //and if this note isn't actually stored in a separate file, then obviously it can't be opened directly
+ if ([[aNote delegate] currentNoteStorageFormat] == SingleDatabaseFormat) return NO;
+
+ //and if aNote is in plaintext format and this editor is ODB-capable, then it should also be a general-purpose texteditor
+ //conversely ODB editors should never be allowed to open non-plain-text documents; for some reason LSCanURLAcceptURL claims they can do that
+ //one exception known: writeroom can edit rich-text documents
+ if ([self isODBEditor] && ![bundleIdentifier hasPrefix:@"com.hogbaysoftware.WriteRoom"]) {
+ return storageFormatOfNote(aNote) == PlainTextFormat;
+ }
+
+ if (!knownPathExtensions) knownPathExtensions = [NSMutableDictionary new];
+ NSString *extension = [[filenameOfNote(aNote) pathExtension] lowercaseString];
+ NSNumber *canHandleNumber = [knownPathExtensions objectForKey:extension];
+
+ if (!canHandleNumber) {
+ NSString *path = [aNote noteFilePath];
+
+ Boolean canAccept = false;
+ OSStatus err = LSCanURLAcceptURL((CFURLRef)[NSURL fileURLWithPath:path], (CFURLRef)[self resolvedURL], kLSRolesEditor, kLSAcceptAllowLoginUI, &canAccept);
+ if (noErr != err) {
+ NSLog(@"LSCanURLAcceptURL '%@' err: %d", path, err);
+ }
+ [knownPathExtensions setObject:[NSNumber numberWithBool:(BOOL)canAccept] forKey:extension];
+
+ return (BOOL)canAccept;
+ }
+
+ return [canHandleNumber boolValue];
+}
+
+- (BOOL)canEditAllNotes:(NSArray*)notes {
+ NSUInteger i = 0;
+ for (i=0; i<[notes count]; i++) {
+ if (![self isODBEditor] && ![self canEditNoteDirectly:[notes objectAtIndex:i]])
+ return NO;
+ }
+ return YES;
+}
+
+- (NSImage*)iconImage {
+ if (!iconImg) {
+ FSRef appRef;
+ if (CFURLGetFSRef((CFURLRef)[self resolvedURL], &appRef))
+ iconImg = [[NSImage smallIconForFSRef:&appRef] retain];
+ }
+ return iconImg;
+}
+
+- (NSString*)displayName {
+ if (!displayName) {
+ LSCopyDisplayNameForURL((CFURLRef)[self resolvedURL], (CFStringRef*)&displayName);
+ }
+ return displayName;
+}
+
+- (NSURL*)resolvedURL {
+ if (!resolvedURL && !installCheckFailed) {
+
+ OSStatus err = LSFindApplicationForInfo(kLSUnknownCreator, (CFStringRef)bundleIdentifier, NULL, NULL, (CFURLRef*)&resolvedURL);
+
+ if (kLSApplicationNotFoundErr == err) {
+ installCheckFailed = YES;
+ } else if (noErr != err) {
+ NSLog(@"LSFindApplicationForInfo error for bundle identifier '%@': %d", bundleIdentifier, err);
+ }
+ }
+ return resolvedURL;
+}
+
+- (BOOL)isInstalled {
+ return [self resolvedURL] != nil;
+}
+
+- (BOOL)isODBEditor {
+ return [[ExternalEditorListController ODBAppIdentifiers] containsObject:bundleIdentifier];
+}
+
+- (NSString*)bundleIdentifier {
+ return bundleIdentifier;
+}
+
+- (NSString*)description {
+ return [bundleIdentifier stringByAppendingFormat:@" (URL: %@)", resolvedURL];
+}
+
+- (NSUInteger)hash {
+ return [bundleIdentifier hash];
+}
+- (BOOL)isEqual:(id)otherEntry {
+ return [[otherEntry bundleIdentifier] isEqualToString:bundleIdentifier];
+}
+- (NSComparisonResult)compareDisplayName:(ExternalEditor *)otherEd {
+ return [[self displayName] caseInsensitiveCompare:[otherEd displayName]];
+}
+
+
+- (void)dealloc {
+ [knownPathExtensions release];
+ [bundleIdentifier release];
+ [displayName release];
+ [resolvedURL release];
+ [iconImg release];
+ [super dealloc];
+}
+
+
+@end
+
+@implementation ExternalEditorListController
+
+static ExternalEditorListController* sharedInstance = nil;
+
++ (ExternalEditorListController*)sharedInstance {
+ if (sharedInstance == nil)
+ sharedInstance = [[ExternalEditorListController alloc] initWithUserDefaults];
+ return sharedInstance;
+}
+
++ (id)allocWithZone:(NSZone *)zone {
+ if (sharedInstance == nil) {
+ sharedInstance = [super allocWithZone:zone];
+ return sharedInstance; // assignment and return on first allocation
+ }
+ return nil; // on subsequent allocation attempts return nil
+}
+
+- (id)initWithUserDefaults {
+ if ([self init]) {
+ //TextEdit is not an ODB editor, but can be used to open files directly
+ [[NSUserDefaults standardUserDefaults] registerDefaults:
+ [NSDictionary dictionaryWithObject:[NSArray arrayWithObject:@"com.apple.TextEdit"] forKey:UserEEIdentifiersKey]];
+
+ [self _initDefaults];
+ }
+ return self;
+}
+
+- (id)init {
+ if ([super init]) {
+
+ userEditorList = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)_initDefaults {
+ NSArray *userIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:UserEEIdentifiersKey];
+
+ NSUInteger i = 0;
+ for (i=0; i<[userIdentifiers count]; i++) {
+ ExternalEditor *ed = [[ExternalEditor alloc] initWithBundleID:[userIdentifiers objectAtIndex:i] resolvedURL:nil];
+ [userEditorList addObject:ed];
+ [ed release];
+ }
+
+ //initialize the default editor if one has not already been set or if the identifier was somehow lost from the list
+ if (![self editorIsMember:[self defaultExternalEditor]] || ![[self defaultExternalEditor] isInstalled]) {
+ if ([[self _installedODBEditors] count]) {
+ [self setDefaultEditor:[[self _installedODBEditors] lastObject]];
+ }
+ }
+}
+
+- (NSArray*)_installedODBEditors {
+ if (!_installedODBEditors) {
+ _installedODBEditors = [[NSMutableArray alloc] initWithCapacity:5];
+
+ NSArray *ODBApps = [[[self class] ODBAppIdentifiers] allObjects];
+ NSUInteger i = 0;
+ for (i=0; i<[ODBApps count]; i++) {
+ ExternalEditor *ed = [[ExternalEditor alloc] initWithBundleID:[ODBApps objectAtIndex:i] resolvedURL:nil];
+ if ([ed isInstalled]) {
+ [_installedODBEditors addObject:ed];
+ }
+ [ed release];
+ }
+ [_installedODBEditors sortUsingSelector:@selector(compareDisplayName:)];
+ }
+ return _installedODBEditors;
+}
+
++ (NSSet*)ODBAppIdentifiers {
+ static NSSet *_ODBAppIdentifiers = nil;
+ if (!_ODBAppIdentifiers)
+ _ODBAppIdentifiers = [[NSSet alloc] initWithObjects:
+ @"de.codingmonkeys.SubEthaEdit", @"com.barebones.bbedit", @"com.barebones.textwrangler",
+ @"com.macromates.textmate", @"com.transtex.texeditplus", @"jp.co.artman21.JeditX", @"org.gnu.Aquamacs",
+ @"org.smultron.Smultron", @"com.peterborgapps.Smultron", @"org.fraise.Fraise", @"com.aynimac.CotEditor", @"com.macrabbit.cssedit",
+ @"com.talacia.Tag", @"org.skti.skEdit", @"com.cgerdes.ji", @"com.optima.PageSpinner", @"com.hogbaysoftware.WriteRoom",
+ @"com.hogbaysoftware.WriteRoom.mac", @"org.vim.MacVim", @"com.forgedit.ForgEdit", @"com.tacosw.TacoHTMLEdit", @"com.macrabbit.espresso", nil];
+ return _ODBAppIdentifiers;
+}
+
+- (void)addUserEditorFromDialog:(id)sender {
+
+ //always send menuChanged notification because this class is the target of its menus,
+ //so the notification is the only way to maintain a consistent selected item in PrefsWindowController
+ [self performSelector:@selector(menusChanged) withObject:nil afterDelay:0.0];
+
+ NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+ [openPanel setResolvesAliases:YES];
+ [openPanel setAllowsMultipleSelection:NO];
+
+ if ([openPanel runModalForDirectory:@"/Applications" file:nil types:[NSArray arrayWithObject:@"app"]] == NSOKButton) {
+ if (![openPanel filename]) goto errorReturn;
+ NSURL *appURL = [NSURL fileURLWithPath:[openPanel filename]];
+ if (!appURL) goto errorReturn;
+
+ ExternalEditor *ed = [[ExternalEditor alloc] initWithBundleID:nil resolvedURL:appURL];
+ if (!ed) goto errorReturn;
+
+ //check against lists of all known editors, installed or not
+ if (![self editorIsMember:ed]) {
+ [userEditorList addObject:ed];
+ [[NSUserDefaults standardUserDefaults] setObject:[self userEditorIdentifiers] forKey:UserEEIdentifiersKey];
+ }
+
+ [self setDefaultEditor:ed];
+ }
+ return;
+errorReturn:
+ NSBeep();
+ NSLog(@"Unable to add external editor");
+}
+
+- (void)resetUserEditors:(id)sender {
+ [userEditorList removeAllObjects];
+
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:UserEEIdentifiersKey];
+
+ [self _initDefaults];
+
+ [self menusChanged];
+}
+
+- (NSArray*)userEditorIdentifiers {
+ //for storing in nsuserdefaults
+ //extract bundle identifiers
+
+ NSMutableArray *array = [NSMutableArray arrayWithCapacity:[userEditorList count]];
+ NSUInteger i = 0;
+ for (i=0; i<[userEditorList count]; i++) {
+ [array addObject:[[userEditorList objectAtIndex:i] bundleIdentifier]];
+ }
+
+ return array;
+}
+
+
+- (BOOL)editorIsMember:(ExternalEditor*)anEditor {
+ //does the editor exist in any of the lists?
+ return [userEditorList containsObject:anEditor] || [[ExternalEditorListController ODBAppIdentifiers] containsObject:[anEditor bundleIdentifier]];
+}
+
+- (NSMenu*)addEditorPrefsMenu {
+ if (!editorPrefsMenus) editorPrefsMenus = [NSMutableSet new];
+ NSMenu *aMenu = [[NSMenu alloc] initWithTitle:@"External Editors Menu"];
+ [aMenu setAutoenablesItems:NO];
+ [aMenu setDelegate:self];
+ [editorPrefsMenus addObject:[aMenu autorelease]];
+ [self _updateMenu:aMenu];
+ return aMenu;
+}
+
+- (NSMenu*)addEditNotesMenu {
+ if (!editNotesMenus) editNotesMenus = [NSMutableSet new];
+ NSMenu *aMenu = [[NSMenu alloc] initWithTitle:@"Edit Note Menu"];
+ [aMenu setAutoenablesItems:YES];
+ [aMenu setDelegate:self];
+ [editNotesMenus addObject:[aMenu autorelease]];
+ [self _updateMenu:aMenu];
+ return aMenu;
+}
+
+- (void)menusChanged {
+
+ [editNotesMenus makeObjectsPerformSelector:@selector(_updateMenuForEEListController:) withObject:self];
+ [editorPrefsMenus makeObjectsPerformSelector:@selector(_updateMenuForEEListController:) withObject:self];
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:ExternalEditorsChangedNotification object:self]];
+}
+
+- (void)_updateMenu:(NSMenu*)theMenu {
+ //for allowing the user to configure external editors in the preferences window
+
+ if (IsSnowLeopardOrLater) {
+ [theMenu performSelector:@selector(removeAllItems)];
+ } else {
+ while ([theMenu numberOfItems])
+ [theMenu removeItemAtIndex:0];
+ }
+
+ BOOL isPrefsMenu = [editorPrefsMenus containsObject:theMenu];
+ BOOL didAddItem = NO;
+ NSMutableArray *editors = [NSMutableArray arrayWithArray:[self _installedODBEditors]];
+ [editors addObjectsFromArray:[userEditorList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isInstalled == YES"]]];
+ [editors sortUsingSelector:@selector(compareDisplayName:)];
+
+ NSUInteger i = 0;
+ for (i=0; i<[editors count]; i++) {
+ ExternalEditor *ed = [editors objectAtIndex:i];
+
+ //change action SEL based on whether this is coming from Notes menu or preferences window
+ NSMenuItem *theMenuItem = isPrefsMenu ?
+ [[[NSMenuItem alloc] initWithTitle:[ed displayName] action:@selector(setDefaultEditor:) keyEquivalent:@""] autorelease] :
+ [[[NSMenuItem alloc] initWithTitle:[ed displayName] action:@selector(editNoteExternally:) keyEquivalent:@""] autorelease];
+
+ if (!isPrefsMenu && [[self defaultExternalEditor] isEqual:ed]) {
+ [theMenuItem setKeyEquivalent:@"E"];
+ [theMenuItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSShiftKeyMask];
+ }
+ //PrefsWindowController maintains default-editor selection by updating on ExternalEditorsChangedNotification
+
+ [theMenuItem setTarget: isPrefsMenu ? self : [NSApp delegate]];
+
+ [theMenuItem setRepresentedObject:ed];
+
+ if ([ed iconImage])
+ [theMenuItem setImage:[ed iconImage]];
+
+ [theMenu addItem:theMenuItem];
+ didAddItem = YES;
+ }
+
+ if (!didAddItem) {
+ //disabled placeholder menu item; will probably not be displayed, but would be necessary for preferences list
+ NSMenuItem *theMenuItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"(None)", @"description for no key combination") action:NULL keyEquivalent:@""] autorelease];
+ [theMenuItem setEnabled:NO];
+ [theMenu addItem:theMenuItem];
+ }
+ if ([userEditorList count] > 1 && isPrefsMenu) {
+ //if the user added at least one editor (in addition to the default TextEdit item), then allow items to be reset to their default
+ [theMenu addItem:[NSMenuItem separatorItem]];
+
+ NSMenuItem *theMenuItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reset", @"menu command to clear out custom external editors")
+ action:@selector(resetUserEditors:) keyEquivalent:@""] autorelease];
+ [theMenuItem setTarget:self];
+ [theMenu addItem:theMenuItem];
+ }
+ [theMenu addItem:[NSMenuItem separatorItem]];
+
+ NSMenuItem *theMenuItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Other...", @"title of menu item for selecting a different notes folder")
+ action:@selector(addUserEditorFromDialog:) keyEquivalent:@""] autorelease];
+ [theMenuItem setTarget:self];
+ [theMenu addItem:theMenuItem];
+}
+
+- (ExternalEditor*)defaultExternalEditor {
+ if (!defaultEditor) {
+ NSString *defaultIdentifier = [[NSUserDefaults standardUserDefaults] stringForKey:DefaultEEIdentifierKey];
+ if (defaultIdentifier)
+ defaultEditor = [[ExternalEditor alloc] initWithBundleID:defaultIdentifier resolvedURL:nil];
+ }
+ return defaultEditor;
+}
+
+- (void)setDefaultEditor:(id)anEditor {
+ if ((anEditor = ([anEditor isKindOfClass:[NSMenuItem class]] ? [anEditor representedObject] : anEditor))) {
+ [defaultEditor release];
+ defaultEditor = [anEditor retain];
+
+ [[NSUserDefaults standardUserDefaults] setObject:[defaultEditor bundleIdentifier] forKey:DefaultEEIdentifierKey];
+
+ [self menusChanged];
+ }
+}
+
+@end
+
+
+//this category exists because I want to use -makeObjectsPerformSelector: in -menusChanged
+
+@interface NSMenu (ExternalEditorListMenu)
+- (void)_updateMenuForEEListController:(ExternalEditorListController*)controller;
+@end
+
+@implementation NSMenu (ExternalEditorListMenu)
+- (void)_updateMenuForEEListController:(ExternalEditorListController*)controller {
+ [controller _updateMenu:self];
+}
+@end
View
15 NoteObject.m
@@ -35,12 +35,13 @@
#import "NotationSyncServiceManager.h"
#import "SyncServiceSessionProtocol.h"
#import "SyncSessionController.h"
+#import "ExternalEditorListController.h"
#import "NSData_transformations.h"
#import "NSCollection_utils.h"
#import "NotesTableView.h"
#import "UnifiedCell.h"
#import "LabelColumnCell.h"
-#import "NSBezierPath_NV.h"
+#import "ODBEditor.h"
#if __LP64__
// Needed for compatability with data created by 32bit app
@@ -1425,7 +1426,7 @@ - (BOOL)updateFromFile {
return NO;
}
- if ([self updateFromData:data]) {
+ if ([self updateFromData:data inFormat:currentFormatID]) {
FSCatalogInfo info;
if ([delegate fileInNotesDirectory:noteFileRefInit(self) isOwnedByUs:NULL hasCatalogInfo:&info] == noErr) {
fileModifiedDate = info.contentModDate;
@@ -1448,7 +1449,7 @@ - (BOOL)updateFromCatalogEntry:(NoteCatalogEntry*)catEntry {
return NO;
}
- if (![self updateFromData:data])
+ if (![self updateFromData:data inFormat:currentFormatID])
return NO;
[self setFilename:(NSString*)catEntry->filename withExternalTrigger:YES];
@@ -1501,7 +1502,7 @@ - (BOOL)updateFromCatalogEntry:(NoteCatalogEntry*)catEntry {
return YES;
}
-- (BOOL)updateFromData:(NSMutableData*)data {
+- (BOOL)updateFromData:(NSMutableData*)data inFormat:(int)fmt {
if (!data) {
NSLog(@"%@: Data is nil!", NSStringFromSelector(_cmd));
@@ -1511,7 +1512,7 @@ - (BOOL)updateFromData:(NSMutableData*)data {
NSMutableString *stringFromData = nil;
NSMutableAttributedString *attributedStringFromData = nil;
//interpret based on format; text, rtf, html, etc...
- switch (currentFormatID) {
+ switch (fmt) {
case SingleDatabaseFormat:
//hmmmmm
NSAssert(NO, @"Warning! Tried to update data from a note in single-db format!");
@@ -1539,11 +1540,11 @@ - (BOOL)updateFromData:(NSMutableData*)data {
break;
default:
- NSLog(@"%@: Unknown format: %d", NSStringFromSelector(_cmd), currentFormatID);
+ NSLog(@"%@: Unknown format: %d", NSStringFromSelector(_cmd), fmt);
}
if (!attributedStringFromData) {
- NSLog(@"Couldn't make string out of data for note %@ with format %d", titleString, currentFormatID);
+ NSLog(@"Couldn't make string out of data for note %@ with format %d", titleString, fmt);
return NO;
}
View
26 NotesTableView.m
@@ -19,6 +19,7 @@
#import "AppController_Importing.h"
#import "FastListDataSource.h"
#import "NoteAttributeColumn.h"
+#import "ExternalEditorListController.h"
#import "GlobalPrefs.h"
#import "NotationPrefs.h"
#import "NoteObject.h"
@@ -34,7 +35,7 @@
#define SYNTHETIC_TAGS_COLUMN_INDEX 200
-static void _CopyItemWithSelectorFromMenu(NSMenu *destMenu, NSMenu *sourceMenu, SEL aSel, id target);
+static void _CopyItemWithSelectorFromMenu(NSMenu *destMenu, NSMenu *sourceMenu, SEL aSel, id target, NSInteger tag);
@implementation NotesTableView
@@ -658,18 +659,20 @@ - (NSMenu *)menuForEvent:(NSEvent *)theEvent {
return [self defaultNoteCommandsMenuWithTarget:[NSApp delegate]];
}
-static void _CopyItemWithSelectorFromMenu(NSMenu *destMenu, NSMenu *sourceMenu, SEL aSel, id target) {
- int menuIndex = [sourceMenu indexOfItemWithTarget:target andAction:aSel];
- if (menuIndex > -1) [destMenu addItem:[[(NSMenuItem*)[sourceMenu itemAtIndex:menuIndex] copy] autorelease]];
+static void _CopyItemWithSelectorFromMenu(NSMenu *destMenu, NSMenu *sourceMenu, SEL aSel, id target, NSInteger tag) {
+ NSInteger idx = [sourceMenu indexOfItemWithTag:tag];
+ if (idx > -1 || (idx = [sourceMenu indexOfItemWithTarget:target andAction:aSel]) > -1) {
+ [destMenu addItem:[[(NSMenuItem*)[sourceMenu itemAtIndex:idx] copy] autorelease]];
+ }
}
- (NSMenu *)defaultNoteCommandsMenuWithTarget:(id)target {
NSMenu *theMenu = [[[NSMenu alloc] initWithTitle:@"Contextual Note Commands Menu"] autorelease];
NSMenu *notesMenu = [[[NSApp mainMenu] itemWithTag:NOTES_MENU_ID] submenu];
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(renameNote:), target);
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(tagNote:), target);
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(deleteNote:), target);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(renameNote:), target, -1);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(tagNote:), target, -1);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(deleteNote:), target, -1);
[theMenu addItem:[NSMenuItem separatorItem]];
@@ -679,12 +682,15 @@ - (NSMenu *)defaultNoteCommandsMenuWithTarget:(id)target {
[noteLinkItem setTarget:target];
[theMenu addItem:[noteLinkItem autorelease]];
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(exportNote:), target);
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(revealNote:), target);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(exportNote:), target, -1);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(revealNote:), target, -1);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, NULL, target, 88);
+
+ [theMenu setSubmenu:[[ExternalEditorListController sharedInstance] addEditNotesMenu] forItem:[theMenu itemAtIndex:[theMenu numberOfItems] - 1]];
[theMenu addItem:[NSMenuItem separatorItem]];
- _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(printNote:), target);
+ _CopyItemWithSelectorFromMenu(theMenu, notesMenu, @selector(printNote:), target, -1);
NSArray *notes = [(FastListDataSource*)[self dataSource] objectsAtFilteredIndexes:[self selectedRowIndexes]];
[notes addMenuItemsForURLsInNotes:theMenu];
View
4 PrefsWindowController.h
@@ -31,6 +31,7 @@
IBOutlet NSTextField *bodyTextFontField;
IBOutlet NSMatrix *tabKeyRadioMatrix;
IBOutlet NSPopUpButton *tableTextMenuButton;
+ IBOutlet NSPopUpButton *externalEditorMenuButton;
IBOutlet NSTextField *tableTextSizeField;
IBOutlet NSTextField *appShortcutField;
IBOutlet NSButton *completeNoteTitlesButton;
@@ -74,9 +75,12 @@
- (IBAction)changedSpellChecking:(id)sender;
- (IBAction)changedTabBehavior:(id)sender;
- (IBAction)changedTableText:(id)sender;
+- (IBAction)changedExternalEditorsMenu:(id)sender;
- (IBAction)changedTitleCompletion:(id)sender;
- (IBAction)changedSoftTabs:(id)sender;
+- (void)_selectDefaultExternalEditor;
+
- (NSMenu*)directorySelectionMenu;
- (void)changeDefaultDirectory;
- (BOOL)getNewNotesRefFromOpenPanel:(FSRef*)notesDirectoryRef returnedPath:(NSString**)path;
View
19 PrefsWindowController.m
@@ -19,6 +19,7 @@
#import "PTKeyComboPanel.h"
#import "PTKeyCombo.h"
#import "NotationPrefsViewController.h"
+#import "ExternalEditorListController.h"
#import "NSData_transformations.h"
#import "NSString_NV.h"
#import "NSFileManager_NV.h"
@@ -192,6 +193,19 @@ - (IBAction)changedTabBehavior:(id)sender {
[prefsController setTabIndenting:[[tabKeyRadioMatrix cellAtRow:0 column:0] state] sender:self];
}
+- (IBAction)changedExternalEditorsMenu:(id)sender {
+ //not currently called as an action in practice
+ [self _selectDefaultExternalEditor];
+}
+
+- (void)_selectDefaultExternalEditor {
+ ExternalEditor *ed = [[ExternalEditorListController sharedInstance] defaultExternalEditor];
+ NSInteger idx = ed ? [externalEditorMenuButton indexOfItemWithRepresentedObject:ed] : 0;
+ if (idx > -1) {
+ [externalEditorMenuButton selectItemAtIndex:idx];
+ }
+}
+
- (IBAction)changedTableText:(id)sender {
if (sender == tableTextMenuButton) {
if ([tableTextSizeField selectedTag] != 3) [tableTextSizeField setFloatValue:[prefsController tableFontSize]];
@@ -387,6 +401,11 @@ - (void)awakeFromNib {
[tableTextSizeField setFloatValue:fontSize];
[tableTextSizeField setHidden:(fontButtonIndex != 3)];
+ [externalEditorMenuButton setMenu:[[ExternalEditorListController sharedInstance] addEditorPrefsMenu]];
+ [self _selectDefaultExternalEditor];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changedExternalEditorsMenu:)
+ name:ExternalEditorsChangedNotification object:nil];
+
[completeNoteTitlesButton setState:[prefsController autoCompleteSearches]];
[checkSpellingButton setState:[prefsController checkSpellingAsYouType]];
[confirmDeletionButton setState:[prefsController confirmNoteDeletion]];

0 comments on commit 62eb1b7

Please sign in to comment.