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 "AuthorizedShellCommand.h"
12 #import "MMPreferenceController.h"
13 #import "Miscellaneous.h"
15 // On Leopard, we want to use the images provided by the OS for some of the
16 // toolbar images (NSImageNamePreferencesGeneral and friends). We need to jump
17 // through some hoops to do that in a way that MacVim still _compiles_ on Tiger
18 // (life would be easier if we'd require Leopard for building). See
19 // http://developer.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
20 // and http://developer.apple.com/technotes/tn2002/tn2064.html
21 // for how you'd do it with a Leopard build system, and see
22 // http://lists.cairographics.org/archives/cairo-bugs/2007-December/001818.html
23 // for why this doesn't work here.
24 // Using the system images gives us resolution independence and consistency
29 NSString* nsImageNamePreferencesGeneral = nil;
30 NSString* nsImageNamePreferencesAdvanced = nil;
32 static void loadSymbols()
34 // use dlfcn() instead of the deprecated NSModule api.
36 if ((ptr = dlsym(RTLD_DEFAULT, "NSImageNamePreferencesGeneral")) != NULL)
37 nsImageNamePreferencesGeneral = *(NSString**)ptr;
38 if ((ptr = dlsym(RTLD_DEFAULT, "NSImageNamePreferencesAdvanced")) != NULL)
39 nsImageNamePreferencesAdvanced = *(NSString**)ptr;
43 // The compiler on OS X 10.4 balks at using CFSTR() for globals so we get
44 // around with this some ugly type casting.
45 static CFStringRef ODBEDITOR = (CFStringRef)@"org.slashpunt.edit_in_odbeditor";
46 static CFStringRef ODB_BUNDLE_IDENTIFIER =
47 (CFStringRef)@"ODBEditorBundleIdentifier";
48 static CFStringRef ODB_EDITOR_NAME = (CFStringRef)@"ODBEditorName";
49 static NSString *ODBEDITOR_DIR =
50 @"/Library/InputManagers/Edit in ODBEditor";
51 static NSString *ODBEDITOR_PATH =
52 @"/Library/InputManagers/Edit in ODBEditor/Edit in ODBEditor.bundle";
55 NSString *kOdbEditorNameNone = @"(None)";
56 NSString *kOdbEditorIdentifierNone = @"";
58 NSString *kOdbEditorNameBBEdit = @"BBEdit";
59 NSString *kOdbEditorIdentifierBBEdit = @"com.barebones.bbedit";
61 NSString *kOdbEditorNameCSSEdit = @"CSSEdit";
62 NSString *kOdbEditorIdentifierCSSEdit = @"com.macrabbit.cssedit";
64 NSString *kOdbEditorNameMacVim = @"MacVim";
65 NSString *kOdbEditorIdentifierMacVim = @"org.vim.MacVim";
67 NSString *kOdbEditorNameSmultron = @"Smultron";
68 NSString *kOdbEditorIdentifierSmultron = @"org.smultron.Smultron";
70 NSString *kOdbEditorNameSubEthaEdit = @"SubEthaEdit";
71 NSString *kOdbEditorIdentifierSubEthaEdit = @"de.codingmonkeys.SubEthaEdit";
73 NSString *kOdbEditorNameTextMate = @"TextMate";
74 NSString *kOdbEditorIdentifierTextMate = @"com.macromates.textmate";
76 NSString *kOdbEditorNameTextWrangler = @"TextWrangler";
77 NSString *kOdbEditorIdentifierTextWrangler = @"com.barebones.textwrangler";
79 NSString *kOdbEditorNameWriteRoom = @"WriteRoom";
80 NSString *kOdbEditorIdentifierWriteRoom = @"com.hogbaysoftware.WriteRoom";
83 @interface MMPreferenceController (Private)
85 - (void)updateIntegrationPane;
86 - (void)setOdbEditorByName:(NSString *)name;
87 - (NSString *)odbEditorBundleIdentifier;
88 - (NSString *)odbBundleSourceDir;
89 - (NSString *)versionOfBundle:(NSString *)bundlePath;
90 - (NSString *)odbBundleInstalledVersion;
91 - (NSString *)odbBundleInstallVersion;
94 @implementation MMPreferenceController
96 - (id)initWithWindow:(NSWindow *)window
98 self = [super initWithWindow:window];
101 // taken from Cyberduck. Thanks :-)
102 supportedOdbEditors = [[NSDictionary alloc] initWithObjectsAndKeys:
103 kOdbEditorIdentifierNone, kOdbEditorNameNone,
104 kOdbEditorIdentifierBBEdit, kOdbEditorNameBBEdit,
105 kOdbEditorIdentifierCSSEdit, kOdbEditorNameCSSEdit,
106 kOdbEditorIdentifierMacVim, kOdbEditorNameMacVim,
107 kOdbEditorIdentifierSmultron, kOdbEditorNameSmultron,
108 kOdbEditorIdentifierSubEthaEdit, kOdbEditorNameSubEthaEdit,
109 kOdbEditorIdentifierTextMate, kOdbEditorNameTextMate,
110 kOdbEditorIdentifierTextWrangler, kOdbEditorNameTextWrangler,
111 kOdbEditorIdentifierWriteRoom, kOdbEditorNameWriteRoom,
118 [supportedOdbEditors release]; supportedOdbEditors = nil;
124 // fill list of editors in integration pane
125 NSArray *keys = [[supportedOdbEditors allKeys]
126 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
127 NSMenu *editorsMenu = [editors menu];
128 NSEnumerator *enumerator = [keys objectEnumerator];
130 while ((key = [enumerator nextObject]) != nil) {
131 NSString *identifier = [supportedOdbEditors objectForKey:key];
133 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key
134 action:@selector(odbEditorChanged:)
136 [item setTarget:self];
137 if (![identifier isEqualToString:kOdbEditorIdentifierNone]) {
138 NSString *appPath = [[NSWorkspace sharedWorkspace]
139 absolutePathForAppBundleWithIdentifier:identifier];
140 [item setEnabled:appPath != nil];
141 if (appPath != nil) {
142 NSImage *icon = [[NSWorkspace sharedWorkspace]
143 iconForFile:appPath];
144 [icon setSize:NSMakeSize(16, 16)]; // XXX: make res independent
145 [item setImage:icon];
148 [editorsMenu addItem:item];
152 [self updateIntegrationPane];
159 if (nsImageNamePreferencesGeneral != NULL) {
160 [self addView:generalPreferences
162 image:[NSImage imageNamed:nsImageNamePreferencesGeneral]];
164 [self addView:generalPreferences label:@"General"];
167 [self addView:integrationPreferences label:@"Integration"];
169 if (nsImageNamePreferencesAdvanced != NULL) {
170 [self addView:advancedPreferences
172 image:[NSImage imageNamed:nsImageNamePreferencesAdvanced]];
174 [self addView:advancedPreferences label:@"Advanced"];
180 - (NSString *)currentPaneIdentifier
182 // We override this to persist the current pane.
183 return [[NSUserDefaults standardUserDefaults]
184 stringForKey:MMCurrentPreferencePaneKey];
187 - (void)setCurrentPaneIdentifier:(NSString *)identifier
189 // We override this to persist the current pane.
190 [[NSUserDefaults standardUserDefaults]
191 setObject:identifier forKey:MMCurrentPreferencePaneKey];
195 - (BOOL)validateMenuItem:(NSMenuItem *)item
197 if ([item action] == @selector(odbEditorChanged:)) {
198 NSString *identifier = [supportedOdbEditors objectForKey:[item title]];
199 if (identifier == nil)
201 if ([identifier isEqualToString:kOdbEditorIdentifierNone])
203 return [[NSWorkspace sharedWorkspace]
204 absolutePathForAppBundleWithIdentifier:identifier] != nil;
209 - (IBAction)openInCurrentWindowSelectionChanged:(id)sender
211 BOOL openInCurrentWindowSelected = ([[sender selectedCell] tag] != 0);
212 BOOL useWindowsLayout =
213 ([[layoutPopUpButton selectedItem] tag] == MMLayoutWindows);
214 if (openInCurrentWindowSelected && useWindowsLayout)
215 [layoutPopUpButton selectItemWithTag:MMLayoutTabs];
219 #pragma mark Integration pane
221 - (void)updateIntegrationPane
223 // XXX: check validation api.
224 // XXX: call this each time the dialog becomes active (so that if the
225 // user changes settings in terminal, the changes are reflected in the
228 NSString *versionString;
230 // Check if ODB path exists before calling isFilePackageAtPath: otherwise
231 // an error is output to stderr on Tiger.
232 BOOL odbIsInstalled =
233 [[NSFileManager defaultManager] fileExistsAtPath:ODBEDITOR_PATH]
234 && [[NSWorkspace sharedWorkspace] isFilePackageAtPath:ODBEDITOR_PATH];
236 // enable/disable buttons
237 [installOdbButton setTitle:@"Install"];
238 if (odbIsInstalled) {
239 [uninstallOdbButton setEnabled:YES];
240 [editors setEnabled:YES];
242 NSString *installVersion = [self odbBundleInstallVersion];
243 NSString *installedVersion = [self odbBundleInstalledVersion];
244 switch ([installedVersion compare:installVersion
245 options:NSNumericSearch]) {
246 case NSOrderedAscending:
247 versionString = [NSString stringWithFormat:
248 @"Latest version is %@, you have %@.",
249 installVersion, installedVersion];
250 [installOdbButton setTitle:@"Update"];
251 [installOdbButton setEnabled:YES];
254 versionString = [NSString stringWithFormat:
255 @"Latest version is %@. You have the latest version.",
257 [installOdbButton setEnabled:NO];
259 case NSOrderedDescending:
260 versionString = [NSString stringWithFormat:
261 @"Latest version is %@, you have %@.",
262 installVersion, installedVersion];
263 [installOdbButton setEnabled:NO];
267 [installOdbButton setEnabled:YES];
268 [uninstallOdbButton setEnabled:NO];
269 [editors setEnabled:NO];
271 versionString = [NSString
272 stringWithFormat:@"Latest version is %@. It is not installed.",
273 [self odbBundleInstallVersion]];
276 [obdBundleVersionLabel setStringValue:versionString];
278 // make sure the right editor is selected on the popup button
279 NSString *selectedTitle = kOdbEditorNameNone;
280 NSArray* keys = [supportedOdbEditors
281 allKeysForObject:[self odbEditorBundleIdentifier]];
282 if ([keys count] > 0)
283 selectedTitle = [keys objectAtIndex:0];
284 [editors selectItemWithTitle:selectedTitle];
287 - (void)setOdbEditorByName:(NSString *)name
289 NSString *identifier = [supportedOdbEditors objectForKey:name];
290 if (identifier != kOdbEditorIdentifierNone) {
291 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, identifier, ODBEDITOR);
292 CFPreferencesSetAppValue(ODB_EDITOR_NAME, name, ODBEDITOR);
294 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, NULL, ODBEDITOR);
295 CFPreferencesSetAppValue(ODB_EDITOR_NAME, NULL, ODBEDITOR);
297 CFPreferencesAppSynchronize(ODBEDITOR);
300 // Note that you can't compare the result of this function with ==, you have
301 // to use isStringEqual: (since this returns a new copy of the string).
302 - (NSString *)odbEditorBundleIdentifier
304 // reading the defaults of a different app is easier with carbon
305 NSString *bundleIdentifier = (NSString*)CFPreferencesCopyAppValue(
306 ODB_BUNDLE_IDENTIFIER, ODBEDITOR);
307 if (bundleIdentifier == nil)
308 return kOdbEditorIdentifierNone;
309 return [bundleIdentifier autorelease];
312 - (void)odbEditorChanged:(id)sender
314 [self setOdbEditorByName:[sender title]];
317 - (NSString *)odbBundleSourceDir
319 return [[[NSBundle mainBundle] resourcePath]
320 stringByAppendingString:@"/Edit in ODBEditor"];
323 // Returns the CFBundleVersion of a bundle. This assumes a bundle exists
325 - (NSString *)versionOfBundle:(NSString *)bundlePath
327 // -[NSBundle initWithPath:] caches a bundle, so if the bundle is replaced
328 // with a new bundle on disk, we get the old version. So we can't use it :-(
330 NSString *infoPath = [bundlePath
331 stringByAppendingString:@"/Contents/Info.plist"];
332 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:infoPath];
333 return [info objectForKey:@"CFBundleVersion"];
336 - (NSString *)odbBundleInstalledVersion
338 return [self versionOfBundle:ODBEDITOR_PATH];
341 - (NSString *)odbBundleInstallVersion
343 return [self versionOfBundle:[[self odbBundleSourceDir]
344 stringByAppendingString:@"/Edit in ODBEditor.bundle"]];
347 - (IBAction)installOdb:(id)sender
349 NSString *source = [self odbBundleSourceDir];
351 // It doesn't hurt to rm -rf the InputManager even if it's not there,
352 // the code is simpler that way.
353 NSArray *cmd = [NSArray arrayWithObjects:
354 [NSDictionary dictionaryWithObjectsAndKeys:
355 @"/bin/rm", MMCommand,
356 [NSArray arrayWithObjects:@"-rf", ODBEDITOR_DIR, nil], MMArguments,
358 [NSDictionary dictionaryWithObjectsAndKeys:
359 @"/bin/mkdir", MMCommand,
360 [NSArray arrayWithObjects:@"-p", ODBEDITOR_DIR, nil], MMArguments,
362 [NSDictionary dictionaryWithObjectsAndKeys:
363 @"/bin/cp", MMCommand,
364 [NSArray arrayWithObjects: @"-R",
365 source, @"/Library/InputManagers", nil], MMArguments,
367 [NSDictionary dictionaryWithObjectsAndKeys:
368 @"/usr/sbin/chown", MMCommand,
369 [NSArray arrayWithObjects: @"-R",
370 @"root:admin", @"/Library/InputManagers", nil], MMArguments,
375 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
376 initWithCommands:cmd];
377 OSStatus err = [au run];
378 if (err == errAuthorizationSuccess) {
379 // If the user just installed the input manager and no editor was
380 // selected before, chances are he wants to use MacVim as editor
381 if ([[self odbEditorBundleIdentifier]
382 isEqualToString:kOdbEditorIdentifierNone]) {
383 [self setOdbEditorByName:kOdbEditorNameMacVim];
386 NSLog(@"Failed to install input manager, error is %d", err);
390 [self updateIntegrationPane];
393 - (IBAction)uninstallOdb:(id)sender
395 NSArray *cmd = [NSArray arrayWithObject:
396 [NSDictionary dictionaryWithObjectsAndKeys:
397 @"/bin/rm", MMCommand,
398 [NSArray arrayWithObjects: @"-rf", ODBEDITOR_DIR, nil], MMArguments,
401 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
402 initWithCommands:cmd];
403 OSStatus err = [au run];
404 if (err != errAuthorizationSuccess)
405 NSLog(@"Failed to uninstall input manager, error is %d", err);
408 [self updateIntegrationPane];