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 static CFStringRef ODBEDITOR = CFSTR("org.slashpunt.edit_in_odbeditor");
44 static CFStringRef ODB_BUNDLE_IDENTIFIER = CFSTR("ODBEditorBundleIdentifier");
45 static CFStringRef ODB_EDITOR_NAME = CFSTR("ODBEditorName");
46 static NSString *ODBEDITOR_DIR =
47 @"/Library/InputManagers/Edit in ODBEditor";
48 static NSString *ODBEDITOR_PATH =
49 @"/Library/InputManagers/Edit in ODBEditor/Edit in ODBEditor.bundle";
52 NSString *kOdbEditorNameNone = @"(None)";
53 NSString *kOdbEditorIdentifierNone = @"";
55 NSString *kOdbEditorNameBBEdit = @"BBEdit";
56 NSString *kOdbEditorIdentifierBBEdit = @"com.barebones.bbedit";
58 NSString *kOdbEditorNameCSSEdit = @"CSSEdit";
59 NSString *kOdbEditorIdentifierCSSEdit = @"com.macrabbit.cssedit";
61 NSString *kOdbEditorNameMacVim = @"MacVim";
62 NSString *kOdbEditorIdentifierMacVim = @"org.vim.MacVim";
64 NSString *kOdbEditorNameSmultron = @"Smultron";
65 NSString *kOdbEditorIdentifierSmultron = @"org.smultron.Smultron";
67 NSString *kOdbEditorNameSubEthaEdit = @"SubEthaEdit";
68 NSString *kOdbEditorIdentifierSubEthaEdit = @"de.codingmonkeys.SubEthaEdit";
70 NSString *kOdbEditorNameTextMate = @"TextMate";
71 NSString *kOdbEditorIdentifierTextMate = @"com.macromates.textmate";
73 NSString *kOdbEditorNameTextWrangler = @"TextWrangler";
74 NSString *kOdbEditorIdentifierTextWrangler = @"com.barebones.textwrangler";
76 NSString *kOdbEditorNameWriteRoom = @"WriteRoom";
77 NSString *kOdbEditorIdentifierWriteRoom = @"com.hogbaysoftware.WriteRoom";
80 @interface MMPreferenceController (Private)
82 - (void)updateIntegrationPane;
83 - (void)setOdbEditorByName:(NSString *)name;
84 - (NSString *)odbEditorBundleIdentifier;
85 - (NSString *)odbBundleSourceDir;
86 - (NSString *)versionOfBundle:(NSString *)bundlePath;
87 - (NSString *)odbBundleInstalledVersion;
88 - (NSString *)odbBundleInstallVersion;
91 @implementation MMPreferenceController
93 - (id)initWithWindow:(NSWindow *)window
95 self = [super initWithWindow:window];
98 // taken from Cyberduck. Thanks :-)
99 supportedOdbEditors = [[NSDictionary alloc] initWithObjectsAndKeys:
100 kOdbEditorIdentifierNone, kOdbEditorNameNone,
101 kOdbEditorIdentifierBBEdit, kOdbEditorNameBBEdit,
102 kOdbEditorIdentifierCSSEdit, kOdbEditorNameCSSEdit,
103 kOdbEditorIdentifierMacVim, kOdbEditorNameMacVim,
104 kOdbEditorIdentifierSmultron, kOdbEditorNameSmultron,
105 kOdbEditorIdentifierSubEthaEdit, kOdbEditorNameSubEthaEdit,
106 kOdbEditorIdentifierTextMate, kOdbEditorNameTextMate,
107 kOdbEditorIdentifierTextWrangler, kOdbEditorNameTextWrangler,
108 kOdbEditorIdentifierWriteRoom, kOdbEditorNameWriteRoom,
115 [supportedOdbEditors release]; supportedOdbEditors = nil;
121 // fill list of editors in integration pane
122 NSArray *keys = [[supportedOdbEditors allKeys]
123 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
124 NSMenu *editorsMenu = [editors menu];
125 NSEnumerator *enumerator = [keys objectEnumerator];
127 while ((key = [enumerator nextObject]) != nil) {
128 NSString *identifier = [supportedOdbEditors objectForKey:key];
130 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key
131 action:@selector(odbEditorChanged:)
133 [item setTarget:self];
134 if (![identifier isEqualToString:kOdbEditorIdentifierNone]) {
135 NSString *appPath = [[NSWorkspace sharedWorkspace]
136 absolutePathForAppBundleWithIdentifier:identifier];
137 [item setEnabled:appPath != nil];
138 if (appPath != nil) {
139 NSImage *icon = [[NSWorkspace sharedWorkspace]
140 iconForFile:appPath];
141 [icon setSize:NSMakeSize(16, 16)]; // XXX: make res independent
142 [item setImage:icon];
145 [editorsMenu addItem:item];
149 [self updateIntegrationPane];
156 if (nsImageNamePreferencesGeneral != NULL) {
157 [self addView:generalPreferences
159 image:[NSImage imageNamed:nsImageNamePreferencesGeneral]];
161 [self addView:generalPreferences label:@"General"];
164 [self addView:integrationPreferences label:@"Integration"];
166 if (nsImageNamePreferencesAdvanced != NULL) {
167 [self addView:advancedPreferences
169 image:[NSImage imageNamed:nsImageNamePreferencesAdvanced]];
171 [self addView:advancedPreferences label:@"Advanced"];
177 - (NSString *)currentPaneIdentifier
179 // We override this to persist the current pane.
180 return [[NSUserDefaults standardUserDefaults]
181 stringForKey:MMCurrentPreferencePaneKey];
184 - (void)setCurrentPaneIdentifier:(NSString *)identifier
186 // We override this to persist the current pane.
187 [[NSUserDefaults standardUserDefaults]
188 setObject:identifier forKey:MMCurrentPreferencePaneKey];
192 - (BOOL)validateMenuItem:(NSMenuItem *)item
194 if ([item action] == @selector(odbEditorChanged:)) {
195 NSString *identifier = [supportedOdbEditors objectForKey:[item title]];
196 if (identifier == nil)
198 if ([identifier isEqualToString:kOdbEditorIdentifierNone])
200 return [[NSWorkspace sharedWorkspace]
201 absolutePathForAppBundleWithIdentifier:identifier] != nil;
206 - (IBAction)openInCurrentWindowSelectionChanged:(id)sender
208 BOOL openInCurrentWindowSelected = ([[sender selectedCell] tag] != 0);
209 BOOL useWindowsLayout =
210 ([[layoutPopUpButton selectedItem] tag] == MMLayoutWindows);
211 if (openInCurrentWindowSelected && useWindowsLayout)
212 [layoutPopUpButton selectItemWithTag:MMLayoutTabs];
216 #pragma mark Integration pane
218 - (void)updateIntegrationPane
220 // XXX: check validation api.
221 // XXX: call this each time the dialog becomes active (so that if the
222 // user changes settings in terminal, the changes are reflected in the
225 NSString *versionString;
227 // Check if ODB path exists before calling isFilePackageAtPath: otherwise
228 // an error is output to stderr on Tiger.
229 BOOL odbIsInstalled =
230 [[NSFileManager defaultManager] fileExistsAtPath:ODBEDITOR_PATH]
231 && [[NSWorkspace sharedWorkspace] isFilePackageAtPath:ODBEDITOR_PATH];
233 // enable/disable buttons
234 [installOdbButton setTitle:@"Install"];
235 if (odbIsInstalled) {
236 [uninstallOdbButton setEnabled:YES];
237 [editors setEnabled:YES];
239 NSString *installVersion = [self odbBundleInstallVersion];
240 NSString *installedVersion = [self odbBundleInstalledVersion];
241 switch ([installedVersion compare:installVersion
242 options:NSNumericSearch]) {
243 case NSOrderedAscending:
244 versionString = [NSString stringWithFormat:
245 @"Latest version is %@, you have %@.",
246 installVersion, installedVersion];
247 [installOdbButton setTitle:@"Update"];
248 [installOdbButton setEnabled:YES];
251 versionString = [NSString stringWithFormat:
252 @"Latest version is %@. You have the latest version.",
254 [installOdbButton setEnabled:NO];
256 case NSOrderedDescending:
257 versionString = [NSString stringWithFormat:
258 @"Latest version is %@, you have %@.",
259 installVersion, installedVersion];
260 [installOdbButton setEnabled:NO];
264 [installOdbButton setEnabled:YES];
265 [uninstallOdbButton setEnabled:NO];
266 [editors setEnabled:NO];
268 versionString = [NSString
269 stringWithFormat:@"Latest version is %@. It is not installed.",
270 [self odbBundleInstallVersion]];
273 [obdBundleVersionLabel setStringValue:versionString];
275 // make sure the right editor is selected on the popup button
276 NSString *selectedTitle = kOdbEditorNameNone;
277 NSArray* keys = [supportedOdbEditors
278 allKeysForObject:[self odbEditorBundleIdentifier]];
279 if ([keys count] > 0)
280 selectedTitle = [keys objectAtIndex:0];
281 [editors selectItemWithTitle:selectedTitle];
284 - (void)setOdbEditorByName:(NSString *)name
286 NSString *identifier = [supportedOdbEditors objectForKey:name];
287 if (identifier != kOdbEditorIdentifierNone) {
288 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, identifier, ODBEDITOR);
289 CFPreferencesSetAppValue(ODB_EDITOR_NAME, name, ODBEDITOR);
291 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, NULL, ODBEDITOR);
292 CFPreferencesSetAppValue(ODB_EDITOR_NAME, NULL, ODBEDITOR);
294 CFPreferencesAppSynchronize(ODBEDITOR);
297 // Note that you can't compare the result of this function with ==, you have
298 // to use isStringEqual: (since this returns a new copy of the string).
299 - (NSString *)odbEditorBundleIdentifier
301 // reading the defaults of a different app is easier with carbon
302 NSString *bundleIdentifier = (NSString*)CFPreferencesCopyAppValue(
303 ODB_BUNDLE_IDENTIFIER, ODBEDITOR);
304 if (bundleIdentifier == nil)
305 return kOdbEditorIdentifierNone;
306 return [bundleIdentifier autorelease];
309 - (void)odbEditorChanged:(id)sender
311 [self setOdbEditorByName:[sender title]];
314 - (NSString *)odbBundleSourceDir
316 return [[[NSBundle mainBundle] resourcePath]
317 stringByAppendingString:@"/Edit in ODBEditor"];
320 // Returns the CFBundleVersion of a bundle. This assumes a bundle exists
322 - (NSString *)versionOfBundle:(NSString *)bundlePath
324 // -[NSBundle initWithPath:] caches a bundle, so if the bundle is replaced
325 // with a new bundle on disk, we get the old version. So we can't use it :-(
327 NSString *infoPath = [bundlePath
328 stringByAppendingString:@"/Contents/Info.plist"];
329 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:infoPath];
330 return [info objectForKey:@"CFBundleVersion"];
333 - (NSString *)odbBundleInstalledVersion
335 return [self versionOfBundle:ODBEDITOR_PATH];
338 - (NSString *)odbBundleInstallVersion
340 return [self versionOfBundle:[[self odbBundleSourceDir]
341 stringByAppendingString:@"/Edit in ODBEditor.bundle"]];
344 - (IBAction)installOdb:(id)sender
346 NSString *source = [self odbBundleSourceDir];
348 // It doesn't hurt to rm -rf the InputManager even if it's not there,
349 // the code is simpler that way.
350 NSArray *cmd = [NSArray arrayWithObjects:
351 [NSDictionary dictionaryWithObjectsAndKeys:
352 @"/bin/rm", MMCommand,
353 [NSArray arrayWithObjects:@"-rf", ODBEDITOR_DIR, nil], MMArguments,
355 [NSDictionary dictionaryWithObjectsAndKeys:
356 @"/bin/mkdir", MMCommand,
357 [NSArray arrayWithObjects:@"-p", ODBEDITOR_DIR, nil], MMArguments,
359 [NSDictionary dictionaryWithObjectsAndKeys:
360 @"/bin/cp", MMCommand,
361 [NSArray arrayWithObjects: @"-R",
362 source, @"/Library/InputManagers", nil], MMArguments,
364 [NSDictionary dictionaryWithObjectsAndKeys:
365 @"/usr/sbin/chown", MMCommand,
366 [NSArray arrayWithObjects: @"-R",
367 @"root:admin", @"/Library/InputManagers", nil], MMArguments,
372 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
373 initWithCommands:cmd];
374 OSStatus err = [au run];
375 if (err == errAuthorizationSuccess) {
376 // If the user just installed the input manager and no editor was
377 // selected before, chances are he wants to use MacVim as editor
378 if ([[self odbEditorBundleIdentifier]
379 isEqualToString:kOdbEditorIdentifierNone]) {
380 [self setOdbEditorByName:kOdbEditorNameMacVim];
383 NSLog(@"Failed to install input manager, error is %d", err);
387 [self updateIntegrationPane];
390 - (IBAction)uninstallOdb:(id)sender
392 NSArray *cmd = [NSArray arrayWithObject:
393 [NSDictionary dictionaryWithObjectsAndKeys:
394 @"/bin/rm", MMCommand,
395 [NSArray arrayWithObjects: @"-rf", ODBEDITOR_DIR, nil], MMArguments,
398 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
399 initWithCommands:cmd];
400 OSStatus err = [au run];
401 if (err != errAuthorizationSuccess)
402 NSLog(@"Failed to uninstall input manager, error is %d", err);
405 [self updateIntegrationPane];