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,
120 [supportedOdbEditors release]; supportedOdbEditors = nil;
126 // fill list of editors in integration pane
127 NSArray *keys = [[supportedOdbEditors allKeys]
128 sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
129 NSMenu *editorsMenu = [editors menu];
130 NSEnumerator *enumerator = [keys objectEnumerator];
132 while ((key = [enumerator nextObject]) != nil) {
133 NSString *identifier = [supportedOdbEditors objectForKey:key];
135 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key
136 action:@selector(odbEditorChanged:)
138 [item setTarget:self];
139 if (![identifier isEqualToString:kOdbEditorIdentifierNone]) {
140 NSString *appPath = [[NSWorkspace sharedWorkspace]
141 absolutePathForAppBundleWithIdentifier:identifier];
142 [item setEnabled:appPath != nil];
143 if (appPath != nil) {
144 NSImage *icon = [[NSWorkspace sharedWorkspace]
145 iconForFile:appPath];
146 [icon setSize:NSMakeSize(16, 16)]; // XXX: make res independent
147 [item setImage:icon];
150 [editorsMenu addItem:item];
154 [self updateIntegrationPane];
161 if (nsImageNamePreferencesGeneral != NULL) {
162 [self addView:generalPreferences
164 image:[NSImage imageNamed:nsImageNamePreferencesGeneral]];
166 [self addView:generalPreferences label:@"General"];
169 [self addView:integrationPreferences label:@"Integration"];
171 if (nsImageNamePreferencesAdvanced != NULL) {
172 [self addView:advancedPreferences
174 image:[NSImage imageNamed:nsImageNamePreferencesAdvanced]];
176 [self addView:advancedPreferences label:@"Advanced"];
182 - (NSString *)currentPaneIdentifier
184 // We override this to persist the current pane.
185 return [[NSUserDefaults standardUserDefaults]
186 stringForKey:MMCurrentPreferencePaneKey];
189 - (void)setCurrentPaneIdentifier:(NSString *)identifier
191 // We override this to persist the current pane.
192 [[NSUserDefaults standardUserDefaults]
193 setObject:identifier forKey:MMCurrentPreferencePaneKey];
197 - (BOOL)validateMenuItem:(NSMenuItem *)item
199 if ([item action] == @selector(odbEditorChanged:)) {
200 NSString *identifier = [supportedOdbEditors objectForKey:[item title]];
201 if (identifier == nil)
203 if ([identifier isEqualToString:kOdbEditorIdentifierNone])
205 return [[NSWorkspace sharedWorkspace]
206 absolutePathForAppBundleWithIdentifier:identifier] != nil;
211 - (IBAction)openInCurrentWindowSelectionChanged:(id)sender
213 BOOL openInCurrentWindowSelected = ([[sender selectedCell] tag] != 0);
214 BOOL useWindowsLayout =
215 ([[layoutPopUpButton selectedItem] tag] == MMLayoutWindows);
216 if (openInCurrentWindowSelected && useWindowsLayout)
217 [layoutPopUpButton selectItemWithTag:MMLayoutTabs];
221 #pragma mark Integration pane
223 - (void)updateIntegrationPane
225 // XXX: check validation api.
226 // XXX: call this each time the dialog becomes active (so that if the
227 // user changes settings in terminal, the changes are reflected in the
230 NSString *versionString = @"";
232 // Check if ODB path exists before calling isFilePackageAtPath: otherwise
233 // an error is output to stderr on Tiger.
234 BOOL odbIsInstalled =
235 [[NSFileManager defaultManager] fileExistsAtPath:ODBEDITOR_PATH]
236 && [[NSWorkspace sharedWorkspace] isFilePackageAtPath:ODBEDITOR_PATH];
238 // enable/disable buttons
239 [installOdbButton setTitle:@"Install"];
240 if (odbIsInstalled) {
241 [uninstallOdbButton setEnabled:YES];
242 [editors setEnabled:YES];
244 NSString *installVersion = [self odbBundleInstallVersion];
245 NSString *installedVersion = [self odbBundleInstalledVersion];
246 switch ([installedVersion compare:installVersion
247 options:NSNumericSearch]) {
248 case NSOrderedAscending:
249 versionString = [NSString stringWithFormat:
250 @"Latest version is %@, you have %@.",
251 installVersion, installedVersion];
252 [installOdbButton setTitle:@"Update"];
253 [installOdbButton setEnabled:YES];
256 versionString = [NSString stringWithFormat:
257 @"Latest version is %@. You have the latest version.",
259 [installOdbButton setEnabled:NO];
261 case NSOrderedDescending:
262 versionString = [NSString stringWithFormat:
263 @"Latest version is %@, you have %@.",
264 installVersion, installedVersion];
265 [installOdbButton setEnabled:NO];
269 [installOdbButton setEnabled:YES];
270 [uninstallOdbButton setEnabled:NO];
271 [editors setEnabled:NO];
273 versionString = [NSString
274 stringWithFormat:@"Latest version is %@. It is not installed.",
275 [self odbBundleInstallVersion]];
278 [obdBundleVersionLabel setStringValue:versionString];
280 // make sure the right editor is selected on the popup button
281 NSString *selectedTitle = kOdbEditorNameNone;
282 NSArray* keys = [supportedOdbEditors
283 allKeysForObject:[self odbEditorBundleIdentifier]];
284 if ([keys count] > 0)
285 selectedTitle = [keys objectAtIndex:0];
286 [editors selectItemWithTitle:selectedTitle];
289 - (void)setOdbEditorByName:(NSString *)name
291 NSString *identifier = [supportedOdbEditors objectForKey:name];
292 if (identifier != kOdbEditorIdentifierNone) {
293 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, identifier, ODBEDITOR);
294 CFPreferencesSetAppValue(ODB_EDITOR_NAME, name, ODBEDITOR);
296 CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, NULL, ODBEDITOR);
297 CFPreferencesSetAppValue(ODB_EDITOR_NAME, NULL, ODBEDITOR);
299 CFPreferencesAppSynchronize(ODBEDITOR);
302 // Note that you can't compare the result of this function with ==, you have
303 // to use isStringEqual: (since this returns a new copy of the string).
304 - (NSString *)odbEditorBundleIdentifier
306 // reading the defaults of a different app is easier with carbon
307 NSString *bundleIdentifier = (NSString*)CFPreferencesCopyAppValue(
308 ODB_BUNDLE_IDENTIFIER, ODBEDITOR);
309 if (bundleIdentifier == nil)
310 return kOdbEditorIdentifierNone;
311 return [bundleIdentifier autorelease];
314 - (void)odbEditorChanged:(id)sender
316 [self setOdbEditorByName:[sender title]];
319 - (NSString *)odbBundleSourceDir
321 return [[[NSBundle mainBundle] resourcePath]
322 stringByAppendingString:@"/Edit in ODBEditor"];
325 // Returns the CFBundleVersion of a bundle. This assumes a bundle exists
327 - (NSString *)versionOfBundle:(NSString *)bundlePath
329 // -[NSBundle initWithPath:] caches a bundle, so if the bundle is replaced
330 // with a new bundle on disk, we get the old version. So we can't use it :-(
332 NSString *infoPath = [bundlePath
333 stringByAppendingString:@"/Contents/Info.plist"];
334 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:infoPath];
335 return [info objectForKey:@"CFBundleVersion"];
338 - (NSString *)odbBundleInstalledVersion
340 return [self versionOfBundle:ODBEDITOR_PATH];
343 - (NSString *)odbBundleInstallVersion
345 return [self versionOfBundle:[[self odbBundleSourceDir]
346 stringByAppendingString:@"/Edit in ODBEditor.bundle"]];
349 - (IBAction)installOdb:(id)sender
351 NSString *source = [self odbBundleSourceDir];
353 // It doesn't hurt to rm -rf the InputManager even if it's not there,
354 // the code is simpler that way.
355 NSArray *cmd = [NSArray arrayWithObjects:
356 [NSDictionary dictionaryWithObjectsAndKeys:
357 @"/bin/rm", MMCommand,
358 [NSArray arrayWithObjects:@"-rf", ODBEDITOR_DIR, nil], MMArguments,
360 [NSDictionary dictionaryWithObjectsAndKeys:
361 @"/bin/mkdir", MMCommand,
362 [NSArray arrayWithObjects:@"-p", ODBEDITOR_DIR, nil], MMArguments,
364 [NSDictionary dictionaryWithObjectsAndKeys:
365 @"/bin/cp", MMCommand,
366 [NSArray arrayWithObjects: @"-R",
367 source, @"/Library/InputManagers", nil], MMArguments,
369 [NSDictionary dictionaryWithObjectsAndKeys:
370 @"/usr/sbin/chown", MMCommand,
371 [NSArray arrayWithObjects: @"-R",
372 @"root:admin", @"/Library/InputManagers", nil], MMArguments,
377 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
378 initWithCommands:cmd];
379 OSStatus err = [au run];
380 if (err == errAuthorizationSuccess) {
381 // If the user just installed the input manager and no editor was
382 // selected before, chances are he wants to use MacVim as editor
383 if ([[self odbEditorBundleIdentifier]
384 isEqualToString:kOdbEditorIdentifierNone]) {
385 [self setOdbEditorByName:kOdbEditorNameMacVim];
388 ASLogErr(@"Failed to install input manager, error is %d", err);
392 [self updateIntegrationPane];
395 - (IBAction)uninstallOdb:(id)sender
397 NSArray *cmd = [NSArray arrayWithObject:
398 [NSDictionary dictionaryWithObjectsAndKeys:
399 @"/bin/rm", MMCommand,
400 [NSArray arrayWithObjects: @"-rf", ODBEDITOR_DIR, nil], MMArguments,
403 AuthorizedShellCommand *au = [[AuthorizedShellCommand alloc]
404 initWithCommands:cmd];
405 OSStatus err = [au run];
406 if (err != errAuthorizationSuccess)
407 ASLogErr(@"Failed to uninstall input manager, error is %d", err);
410 [self updateIntegrationPane];