1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
11 #import "MMPreferenceController.h"
14 #import "AuthorizedShellCommand.h"
16 // On Leopard, we want to use the images provided by the OS for some of the
17 // toolbar images (NSImageNamePreferencesGeneral and friends). We need to jump
18 // through some hoops to do that in a way that MacVim still _compiles_ on Tiger
19 // (life would be easier if we'd require Leopard for building). See
20 // http://developer.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
21 // and http://developer.apple.com/technotes/tn2002/tn2064.html
22 // for how you'd do it with a Leopard build system, and see
23 // http://lists.cairographics.org/archives/cairo-bugs/2007-December/001818.html
24 // for why this doesn't work here.
25 // Using the system images gives us resolution independence and consistency
30 NSString* nsImageNamePreferencesGeneral = nil;
31 NSString* nsImageNamePreferencesAdvanced = nil;
33 static void loadSymbols()
35 // use dlfcn() instead of the deprecated NSModule api.
37 if ((ptr = dlsym(RTLD_DEFAULT, "NSImageNamePreferencesGeneral")) != NULL)
38 nsImageNamePreferencesGeneral = *(NSString**)ptr;
39 if ((ptr = dlsym(RTLD_DEFAULT, "NSImageNamePreferencesAdvanced")) != NULL)
40 nsImageNamePreferencesAdvanced = *(NSString**)ptr;
44 static CFStringRef ODBEDITOR = CFSTR("org.slashpunt.edit_in_odbeditor");
45 static CFStringRef ODB_BUNDLE_IDENTIFIER = CFSTR("ODBEditorBundleIdentifier");
46 static CFStringRef ODB_EDITOR_NAME = CFSTR("ODBEditorName");
47 static NSString *ODBEDITOR_DIR =
48 @"/Library/InputManagers/Edit in ODBEditor";
49 static NSString *ODBEDITOR_PATH =
50 @"/Library/InputManagers/Edit in ODBEditor/Edit in ODBEditor.bundle";
53 NSString *kOdbEditorNameNone = @"(None)";
54 NSString *kOdbEditorIdentifierNone = @"";
56 NSString *kOdbEditorNameBBEdit = @"BBEdit";
57 NSString *kOdbEditorIdentifierBBEdit = @"com.barebones.bbedit";
59 NSString *kOdbEditorNameCSSEdit = @"CSSEdit";
60 NSString *kOdbEditorIdentifierCSSEdit = @"com.macrabbit.cssedit";
62 NSString *kOdbEditorNameMacVim = @"MacVim";
63 NSString *kOdbEditorIdentifierMacVim = @"org.vim.MacVim";
65 NSString *kOdbEditorNameSmultron = @"Smultron";
66 NSString *kOdbEditorIdentifierSmultron = @"org.smultron.Smultron";
68 NSString *kOdbEditorNameSubEthaEdit = @"SubEthaEdit";
69 NSString *kOdbEditorIdentifierSubEthaEdit = @"de.codingmonkeys.SubEthaEdit";
71 NSString *kOdbEditorNameTextMate = @"TextMate";
72 NSString *kOdbEditorIdentifierTextMate = @"com.macromates.textmate";
74 NSString *kOdbEditorNameTextWrangler = @"TextWrangler";
75 NSString *kOdbEditorIdentifierTextWrangler = @"com.barebones.textwrangler";
77 NSString *kOdbEditorNameWriteRoom = @"WriteRoom";
78 NSString *kOdbEditorIdentifierWriteRoom = @"com.hogbaysoftware.WriteRoom";
81 @interface MMPreferenceController (Private)
83 - (void)updateIntegrationPane;
84 - (void)setOdbEditorByName:(NSString *)name;
85 - (NSString *)odbEditorBundleIdentifier;
86 - (NSString *)odbBundleSourceDir;
87 - (NSString *)versionOfBundle:(NSString *)bundlePath;
88 - (NSString *)odbBundleInstalledVersion;
89 - (NSString *)odbBundleInstallVersion;
92 @implementation MMPreferenceController
94 - (id)initWithWindow:(NSWindow *)window
96 self = [super initWithWindow:window];
99 // taken from Cyberduck. Thanks :-)
100 supportedOdbEditors = [[NSDictionary alloc] initWithObjectsAndKeys:
101 kOdbEditorIdentifierNone, kOdbEditorNameNone,
102 kOdbEditorIdentifierBBEdit, kOdbEditorNameBBEdit,
103 kOdbEditorIdentifierCSSEdit, kOdbEditorNameCSSEdit,
104 kOdbEditorIdentifierMacVim, kOdbEditorNameMacVim,
105 kOdbEditorIdentifierSmultron, kOdbEditorNameSmultron,
106 kOdbEditorIdentifierSubEthaEdit, kOdbEditorNameSubEthaEdit,
107 kOdbEditorIdentifierTextMate, kOdbEditorNameTextMate,
108 kOdbEditorIdentifierTextWrangler, kOdbEditorNameTextWrangler,
109 kOdbEditorIdentifierWriteRoom, kOdbEditorNameWriteRoom,
116 [supportedOdbEditors release]; supportedOdbEditors = nil;
122 // fill list of editors in integration pane
123 NSArray *keys = [[supportedOdbEditors allKeys]
124 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
125 NSMenu *editorsMenu = [editors menu];
126 NSEnumerator *enumerator = [keys objectEnumerator];
128 while ((key = [enumerator nextObject]) != nil) {
129 NSString *identifier = [supportedOdbEditors objectForKey:key];
131 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key
132 action:@selector(odbEditorChanged:)
134 [item setTarget:self];
135 if (![identifier isEqualToString:kOdbEditorIdentifierNone]) {
136 NSString *appPath = [[NSWorkspace sharedWorkspace]
137 absolutePathForAppBundleWithIdentifier:identifier];
138 [item setEnabled:appPath != nil];
139 if (appPath != nil) {
140 NSImage *icon = [[NSWorkspace sharedWorkspace]
141 iconForFile:appPath];
142 [icon setSize:NSMakeSize(16, 16)]; // XXX: make res independent
143 [item setImage:icon];
146 [editorsMenu addItem:item];
150 [self updateIntegrationPane];
157 if (nsImageNamePreferencesGeneral != NULL) {
158 [self addView:generalPreferences
160 image:[NSImage imageNamed:nsImageNamePreferencesGeneral]];
162 [self addView:generalPreferences label:@"General"];
165 [self addView:integrationPreferences label:@"Integration"];
169 - (NSString *)currentPaneIdentifier
171 // We override this to persist the current pane.
172 return [[NSUserDefaults standardUserDefaults]
173 stringForKey:MMCurrentPreferencePaneKey];
176 - (void)setCurrentPaneIdentifier:(NSString *)identifier
178 // We override this to persist the current pane.
179 [[NSUserDefaults standardUserDefaults]
180 setObject:identifier forKey:MMCurrentPreferencePaneKey];
184 - (BOOL)validateMenuItem:(NSMenuItem *)item
186 if ([item action] == @selector(odbEditorChanged:)) {
187 NSString *identifier = [supportedOdbEditors objectForKey:[item title]];
188 if (identifier == nil)
190 if ([identifier isEqualToString:kOdbEditorIdentifierNone])
192 return [[NSWorkspace sharedWorkspace]
193 absolutePathForAppBundleWithIdentifier:identifier] != nil;
199 #pragma mark Integration pane
201 - (void)updateIntegrationPane
203 // XXX: check validation api.
204 // XXX: call this each time the dialog becomes active (so that if the
205 // user changes settings in terminal, the changes are reflected in the
208 NSString *versionString;
210 // Check if ODB path exists before calling isFilePackageAtPath: otherwise
211 // an error is output to stderr on Tiger.
212 BOOL odbIsInstalled =
213 [[NSFileManager defaultManager] fileExistsAtPath:ODBEDITOR_PATH]
214 && [[NSWorkspace sharedWorkspace] isFilePackageAtPath:ODBEDITOR_PATH];
216 // enable/disable buttons
217 [installOdbButton setTitle:@"Install"];
218 if (odbIsInstalled) {
219 [uninstallOdbButton setEnabled:YES];
220 [editors setEnabled:YES];
222 NSString *installVersion = [self odbBundleInstallVersion];
223 NSString *installedVersion = [self odbBundleInstalledVersion];
224 switch ([installedVersion compare:installVersion
225 options:NSNumericSearch]) {
226 case NSOrderedAscending:
227 versionString = [NSString stringWithFormat:
228 @"Latest version is %@, you have %@.",
229 installVersion, installedVersion];
230 [installOdbButton setTitle:@"Update"];
231 [installOdbButton setEnabled:YES];
234 versionString = [NSString stringWithFormat:
235 @"Latest version is %@. You have the latest version.",
237 [installOdbButton setEnabled:NO];
239 case NSOrderedDescending:
240 versionString = [NSString stringWithFormat:
241 @"Latest version is %@, you have %@.",
242 installVersion, installedVersion];
243 [installOdbButton setEnabled:NO];
247 [installOdbButton setEnabled:YES];
248 [uninstallOdbButton setEnabled:NO];
249 [editors setEnabled:NO];
251 versionString = [NSString
252 stringWithFormat:@"Latest version is %@. It is not installed.",
253 [self odbBundleInstallVersion]];
256 [obdBundleVersionLabel setStringValue:versionString];
258 // make sure the right editor is selected on the popup button
259 NSString *selectedTitle = kOdbEditorNameNone;
260 NSArray* keys = [supportedOdbEditors
261 allKeysForObject:[self odbEditorBundleIdentifier]];
262 if ([keys count] > 0)
263 selectedTitle = [keys objectAtIndex:0];
264 [editors selectItemWithTitle:selectedTitle];
267 - (void)setOdbEditorByName:(NSString *)name
269 NSString *identifier = [supportedOdbEditors objectForKey:name];
270 if (identifier != kOdbEditorIdentifierNone) {
271 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, identifier, ODBEDITOR);
272 CFPreferencesSetAppValue(ODB_EDITOR_NAME, name, ODBEDITOR);
274 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, NULL, ODBEDITOR);
275 CFPreferencesSetAppValue(ODB_EDITOR_NAME, NULL, ODBEDITOR);
277 CFPreferencesAppSynchronize(ODBEDITOR);
280 // Note that you can't compare the result of this function with ==, you have
281 // to use isStringEqual: (since this returns a new copy of the string).
282 - (NSString *)odbEditorBundleIdentifier
284 // reading the defaults of a different app is easier with carbon
285 NSString *bundleIdentifier = (NSString*)CFPreferencesCopyAppValue(
286 ODB_BUNDLE_IDENTIFIER, ODBEDITOR);
287 if (bundleIdentifier == nil)
288 return kOdbEditorIdentifierNone;
289 return [bundleIdentifier autorelease];
292 - (void)odbEditorChanged:(id)sender
294 [self setOdbEditorByName:[sender title]];
297 - (NSString *)odbBundleSourceDir
299 return [[[NSBundle mainBundle] resourcePath]
300 stringByAppendingString:@"/Edit in ODBEditor"];
303 // Returns the CFBundleVersion of a bundle. This assumes a bundle exists
305 - (NSString *)versionOfBundle:(NSString *)bundlePath
307 // -[NSBundle initWithPath:] caches a bundle, so if the bundle is replaced
308 // with a new bundle on disk, we get the old version. So we can't use it :-(
310 NSString *infoPath = [bundlePath
311 stringByAppendingString:@"/Contents/Info.plist"];
312 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:infoPath];
313 return [info objectForKey:@"CFBundleVersion"];
316 - (NSString *)odbBundleInstalledVersion
318 return [self versionOfBundle:ODBEDITOR_PATH];
321 - (NSString *)odbBundleInstallVersion
323 return [self versionOfBundle:[[self odbBundleSourceDir]
324 stringByAppendingString:@"/Edit in ODBEditor.bundle"]];
327 - (IBAction)installOdb:(id)sender
329 NSString *source = [self odbBundleSourceDir];
331 // It doesn't hurt to rm -rf the InputManager even if it's not there,
332 // the code is simpler that way.
333 NSArray *cmd = [NSArray arrayWithObjects:
334 [NSDictionary dictionaryWithObjectsAndKeys:
335 @"/bin/rm", MMCommand,
336 [NSArray arrayWithObjects:@"-rf", ODBEDITOR_DIR, nil], MMArguments,
338 [NSDictionary dictionaryWithObjectsAndKeys:
339 @"/bin/mkdir", MMCommand,
340 [NSArray arrayWithObjects:@"-p", ODBEDITOR_DIR, nil], MMArguments,
342 [NSDictionary dictionaryWithObjectsAndKeys:
343 @"/bin/cp", MMCommand,
344 [NSArray arrayWithObjects: @"-R",
345 source, @"/Library/InputManagers", nil], MMArguments,
347 [NSDictionary dictionaryWithObjectsAndKeys:
348 @"/usr/sbin/chown", MMCommand,
349 [NSArray arrayWithObjects: @"-R",
350 @"root:admin", @"/Library/InputManagers", nil], MMArguments,
355 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
356 initWithCommands:cmd];
357 OSStatus err = [au run];
358 if (err == errAuthorizationSuccess) {
359 // If the user just installed the input manager and no editor was
360 // selected before, chances are he wants to use MacVim as editor
361 if ([[self odbEditorBundleIdentifier]
362 isEqualToString:kOdbEditorIdentifierNone]) {
363 [self setOdbEditorByName:kOdbEditorNameMacVim];
366 NSLog(@"Failed to install input manager, error is %d", err);
370 [self updateIntegrationPane];
373 - (IBAction)uninstallOdb:(id)sender
375 NSArray *cmd = [NSArray arrayWithObject:
376 [NSDictionary dictionaryWithObjectsAndKeys:
377 @"/bin/rm", MMCommand,
378 [NSArray arrayWithObjects: @"-rf", ODBEDITOR_DIR, nil], MMArguments,
381 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
382 initWithCommands:cmd];
383 OSStatus err = [au run];
384 if (err != errAuthorizationSuccess)
385 NSLog(@"Failed to uninstall input manager, error is %d", err);
388 [self updateIntegrationPane];