Add "show hidden files" checkbox to save dialog
[MacVim.git] / src / MacVim / MMPreferenceController.m
blobbfba36f8ac0fe6148242e331f4330043ffdc2db2
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
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
25 // with other apps.
27 #import <dlfcn.h>
29 NSString* nsImageNamePreferencesGeneral = nil;
30 NSString* nsImageNamePreferencesAdvanced = nil;
32 static void loadSymbols()
34     // use dlfcn() instead of the deprecated NSModule api.
35     void *ptr;
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)
84 // Integration pane
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;
92 @end
94 @implementation MMPreferenceController
96 - (id)initWithWindow:(NSWindow *)window
98     self = [super initWithWindow:window];
99     if (self == nil)
100         return nil;
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,
112         nil];
113     return self;
116 - (void)dealloc
118     [supportedOdbEditors release]; supportedOdbEditors = nil;
119     [super dealloc];
122 - (void)awakeFromNib
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];
129     NSString *key;
130     while ((key = [enumerator nextObject]) != nil) {
131         NSString *identifier = [supportedOdbEditors objectForKey:key];
133         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key
134                                 action:@selector(odbEditorChanged:)
135                                 keyEquivalent:@""];
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];
146             }
147         }
148         [editorsMenu addItem:item];
149         [item release];
150     }
152     [self updateIntegrationPane];
155 - (void)setupToolbar
157     loadSymbols();
159     if (nsImageNamePreferencesGeneral != NULL) {
160         [self addView:generalPreferences
161                 label:@"General"
162                 image:[NSImage imageNamed:nsImageNamePreferencesGeneral]];
163     } else {
164         [self addView:generalPreferences label:@"General"];
165     }
167     [self addView:integrationPreferences label:@"Integration"];
169     if (nsImageNamePreferencesAdvanced != NULL) {
170         [self addView:advancedPreferences
171                 label:@"Advanced"
172                 image:[NSImage imageNamed:nsImageNamePreferencesAdvanced]];
173     } else {
174         [self addView:advancedPreferences label:@"Advanced"];
175     }
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)
200             return NO;
201         if ([identifier isEqualToString:kOdbEditorIdentifierNone])
202             return YES;
203         return [[NSWorkspace sharedWorkspace]
204             absolutePathForAppBundleWithIdentifier:identifier] != nil;
205     }
206     return YES;
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];
218 #pragma mark -
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
226     // dialog)
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];
252             break;
253         case NSOrderedSame:
254             versionString = [NSString stringWithFormat:
255                 @"Latest version is %@. You have the latest version.",
256                 installVersion];
257             [installOdbButton setEnabled:NO];
258             break;
259         case NSOrderedDescending:
260             versionString = [NSString stringWithFormat:
261                 @"Latest version is %@, you have %@.",
262                 installVersion, installedVersion];
263             [installOdbButton setEnabled:NO];
264             break;
265         }
266     } else {
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]];
274     }
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);
293     } else {
294         CFPreferencesSetAppValue(ODB_BUNDLE_IDENTIFIER, NULL, ODBEDITOR);
295         CFPreferencesSetAppValue(ODB_EDITOR_NAME, NULL, ODBEDITOR);
296     }
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
324 // at bundlePath.
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,
357             nil],
358         [NSDictionary dictionaryWithObjectsAndKeys:
359             @"/bin/mkdir", MMCommand,
360             [NSArray arrayWithObjects:@"-p", ODBEDITOR_DIR, nil], MMArguments,
361             nil],
362         [NSDictionary dictionaryWithObjectsAndKeys:
363             @"/bin/cp", MMCommand,
364             [NSArray arrayWithObjects: @"-R",
365                 source, @"/Library/InputManagers", nil], MMArguments,
366             nil],
367         [NSDictionary dictionaryWithObjectsAndKeys:
368             @"/usr/sbin/chown", MMCommand,
369             [NSArray arrayWithObjects: @"-R",
370                 @"root:admin", @"/Library/InputManagers", nil], MMArguments,
371             nil],
372         nil
373         ];
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];
384         }
385     } else {
386         NSLog(@"Failed to install input manager, error is %d", err);
387     }
388     [au release];
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,
399             nil]];
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);
406     [au release];
408     [self updateIntegrationPane];
411 @end