AIHyperlinks universal building
[adiumx.git] / Source / AIPreferenceWindowController.m
blobc80a294316585549cd21021404125a752738a52e
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIPreferenceWindowController.h"
18 #import "AIPreferencePane.h"
19 #import "AIPreferenceController.h"
20 #import "AIAccountController.h"
21 #import <Adium/AIModularPaneCategoryView.h>
22 #import <AIUtilities/ESImageAdditions.h>
23 #import <AIUtilities/AIImageTextCell.h>
24 #import <AIUtilities/AIAutoScrollView.h>
25 #import <AIUtilities/AIViewAdditions.h>
26 #import <AIUtilities/AIWindowAdditions.h>
28 //Preferences
29 #define KEY_PREFERENCE_SELECTED_CATEGORY                @"Preference Selected Category Name"
30 #define KEY_ADVANCED_PREFERENCE_SELECTED_ROW    @"Preference Advanced Selected Row"
32 //Other
33 #define PREFERENCE_WINDOW_NIB                                   @"PreferenceWindow"     //Filename of the preference window nib
34 #define PREFERENCE_ICON_FORMAT                                  @"pref-%@"                      //Format of the preference icon filenames
35 #define ADVANCED_PANE_HEIGHT                                    333+4                           //Fixed advanced pane height
36 #define ADVANCED_PANE_IDENTIFIER                                @"advanced"                     //Identifier of advanced tab
38 //Localized strings
39 #define PREFERENCE_WINDOW_TITLE                                 AILocalizedString(@"Preferences",nil)
41 @interface AIPreferenceWindowController (PRIVATE)
42 + (AIPreferenceWindowController *)_preferenceWindowController;
43 - (id)initWithWindowNibName:(NSString *)windowNibName;
44 - (NSArray *)_panesInCategory:(PREFERENCE_CATEGORY)inCategory;
45 - (void)_saveControlChanges;
46 - (void)_configureAdvancedPreferencesTable;
47 - (void)_configreTabViewItemLabels;
48 - (NSString *)tabView:(NSTabView *)tabView labelForTabViewItem:(NSTabViewItem *)tabViewItem;
49 @end
51 static AIPreferenceWindowController *sharedPreferenceInstance = nil;
53 /*!
54  * @class AIPreferenceWindowController
55  * @brief Adium preference window controller
56  *
57  * Implements the main preference window.  This window pulls the preference panes registered with the preference
58  * controller by plugins and places, organizing them by category.
59  */
60 @implementation AIPreferenceWindowController
62 /*!
63  * @brief Open the preference window
64  */
65 + (void)openPreferenceWindow
67         [[self _preferenceWindowController] showWindow:nil];
70 /*!
71  * @brief Open the preference window to a specific category
72  */
73 + (void)openPreferenceWindowToCategoryWithIdentifier:(NSString *)identifier
75         //Load the window first
76         [[self _preferenceWindowController] window];
77         
78         [[self _preferenceWindowController] selectCategoryWithIdentifier:identifier];
79         [[self _preferenceWindowController] showWindow:nil];
82 /*!
83  * @brief Open the preference window to a specific advanced category
84  */
85 + (void)openPreferenceWindowToAdvancedPane:(NSString *)advancedPane
87         [[self _preferenceWindowController] selectAdvancedPane:advancedPane];
88         [[self _preferenceWindowController] showWindow:nil];
91 /*!
92  * @brief Close the preference window (if it is open)
93  */
94 + (void)closePreferenceWindow
96         if(sharedPreferenceInstance) [sharedPreferenceInstance closeWindow:nil];
99 /*!
100  * @brief Returns the shared preference window controller
102  * Loads (if necessary) and returns a shared instance of AIPreferenceWindowController.
103  * This method is used by the varions openPreferenceWindow methods and shouldn't be called from
104  * outside AIPreferenceWindowController.
105  */
106 + (AIPreferenceWindowController *)_preferenceWindowController
108     if(!sharedPreferenceInstance){
109         sharedPreferenceInstance = [[self alloc] initWithWindowNibName:PREFERENCE_WINDOW_NIB];
110     }
111     
112     return(sharedPreferenceInstance);
116  * @brief Initialize
117  */
118 - (id)initWithWindowNibName:(NSString *)windowNibName
120         if((self = [super initWithWindowNibName:windowNibName])) {
121                 loadedPanes = [[NSMutableArray alloc] init];
122                 loadedAdvancedPanes = nil;
123                 _advancedCategoryArray = nil;
124                 shouldRestorePreviousSelectedPane = YES;
125         }
126         return self;    
130  * @brief Deallocate
131  */
132 - (void)dealloc
133 {       
134     [viewArray release];
135     [loadedPanes release];
136         [loadedAdvancedPanes release];
137         [_advancedCategoryArray release];
138         
139     [super dealloc];
143  * @brief Setup the window before it is displayed
144  */
145 - (void)windowDidLoad
147         [super windowDidLoad];
149         //Configure window
150         [[self window] setTitle:PREFERENCE_WINDOW_TITLE];
151         [[[self window] standardWindowButton:NSWindowToolbarButton] setFrame:NSMakeRect(0,0,0,0)];
152         [self _configureAdvancedPreferencesTable];
153         [[self window] betterCenter];
155         //Prepare our array of preference views.  We place these in an array to cut down on a ton of duplicate code.
156         viewArray = [[NSArray alloc] initWithObjects:
157                 view_Accounts,
158                 view_General,
159                 view_Appearance,
160                 view_Messages,
161                 view_Status,
162                 view_Events,
163                 view_FileTransfer,
164                 view_Advanced,
165                 nil];
169  * @brief Invoked before the window opens
170  */
171 - (IBAction)showWindow:(id)sender
173         //Ensure the window is loaded
174         [self window];
175         
176         //Make the previously selected category active if it is valid
177         if(shouldRestorePreviousSelectedPane){
178                 NSString *previouslySelectedCategory = [[adium preferenceController] preferenceForKey:KEY_PREFERENCE_SELECTED_CATEGORY
179                                                                                                                                                                                 group:PREF_GROUP_WINDOW_POSITIONS];
180                 if(!previouslySelectedCategory || [previouslySelectedCategory isEqualToString:@"loading"])
181                         previouslySelectedCategory = @"accounts";
182                 [self selectCategoryWithIdentifier:previouslySelectedCategory];
183         }
184         
185         [super showWindow:sender];
189  * @brief Invoked before the window closes
191  * We always allow closing of the preference window, so always return YES from this method.  We take this
192  * opportunity to save the state of our window and clean up before the window closes.
193  */
194 - (void)windowWillClose:(id)sender
196         [super windowWillClose:sender];
197                 
198         //Save changes
199         [self _saveControlChanges];
200         
201     //Save the selected category and advanced category
202     [[adium preferenceController] setPreference:[[tabView_category selectedTabViewItem] identifier]
203                                                                                  forKey:KEY_PREFERENCE_SELECTED_CATEGORY
204                                                                                   group:PREF_GROUP_WINDOW_POSITIONS];
205         [[adium preferenceController] setPreference:[NSNumber numberWithInt:[tableView_advanced selectedRow]]
206                                                                                  forKey:KEY_ADVANCED_PREFERENCE_SELECTED_ROW
207                                                                                   group:PREF_GROUP_WINDOW_POSITIONS];
208     
209     //Close all panes and our shared instance
210         [loadedPanes makeObjectsPerformSelector:@selector(closeView)];
211     [sharedPreferenceInstance autorelease]; sharedPreferenceInstance = nil;
215 //Panes ---------------------------------------------------------------------------------------------------------------
216 #pragma mark Panes 
218  * @brief Select a preference category
219  */
220 - (void)selectCategoryWithIdentifier:(NSString *)identifier
222         NSTabViewItem   *tabViewItem;
223         int                             index;
225         //Load the window first
226         [self window];
227         
228         index = [tabView_category indexOfTabViewItemWithIdentifier:identifier];
229         if(index != NSNotFound){
230                 tabViewItem = [tabView_category tabViewItemAtIndex:index];
231                 [self tabView:tabView_category willSelectTabViewItem:tabViewItem];
232                 [tabView_category selectTabViewItem:tabViewItem];    
233         }
235         shouldRestorePreviousSelectedPane = NO;
239  * @brief Select an advanced preference category
240  */
241 - (void)selectAdvancedPane:(NSString *)advancedPane
243         NSEnumerator            *enumerator = [[self advancedCategoryArray] objectEnumerator];
244         AIPreferencePane        *pane;
246         shouldRestorePreviousSelectedPane = NO;
247         
248         //Load the window first
249         [self window];
251     //First, select the advanced category
252     [self selectCategoryWithIdentifier:@"advanced"];
254         //Search for the advanded pane
255         while(pane = [enumerator nextObject]){
256                 if([advancedPane caseInsensitiveCompare:[pane label]] == NSOrderedSame) break;
257         }
259         //If it exists, make it active
260         if(pane){
261                 int row = [[self advancedCategoryArray] indexOfObject:pane];
262                 if([self tableView:tableView_advanced shouldSelectRow:row]){
263                         [tableView_advanced selectRow:row byExtendingSelection:NO];
264                 }               
265         }
269  * @brief Loads and returns the AIPreferencePanes in the specified category
270  */
271 - (NSArray *)_panesInCategory:(PREFERENCE_CATEGORY)inCategory
273     NSMutableArray              *paneArray = [NSMutableArray array];
274     NSEnumerator                *enumerator = [[[adium preferenceController] paneArray] objectEnumerator];
275     AIPreferencePane    *pane;
276     
277     //Get the panes for this category
278     while(pane = [enumerator nextObject]){
279         if([pane category] == inCategory){
280             [paneArray addObject:pane];
281             [loadedPanes addObject:pane];
282         }
283     }
285     //Alphabetize them
286     [paneArray sortUsingSelector:@selector(caseInsensitiveCompare:)];
287     
288     return(paneArray);
292  * @brief Save any preference changes
294  * This takes focus away from any controls to ensure that any changes in the current pane are saved.
295  * This isn't a problem for most controls, but can cause issues with text fields if the user switches panes
296  * with a text field focused.
297  */
298 - (void)_saveControlChanges
300         [[self window] makeFirstResponder:tabView_category];
303 - (NSDictionary *)identifierToLabelDict
305         static NSDictionary     *_identifierToLabelDict = nil;
306         if(!_identifierToLabelDict){
307                 _identifierToLabelDict = [[NSDictionary alloc] initWithObjectsAndKeys:
308                         ACCOUNTS_TITLE,@"accounts",
309                         AILocalizedString(@"General",nil),@"general",
310                         AILocalizedString(@"Appearance",nil),@"appearance",
311                         AILocalizedString(@"Messages",nil),@"messages",
312                         AILocalizedString(@"Status",nil),@"status",
313                         AILocalizedString(@"Events",nil),@"events",
314                         AILocalizedString(@"File Transfer",nil),@"ft",
315                         AILocalizedString(@"Advanced",nil),@"advanced",
316                         AILocalizedString(@"Loading",nil),@"loading",
317                         nil];
318         }
320         return _identifierToLabelDict;
323 //Toolbar tab view -----------------------------------------------------------------------------------------------------
324 #pragma mark Toolbar tab view
326  * @brief Tabview will select a new pane; load the views for that pane.
327  */
328 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
330     if(tabView == tabView_category && 
331            ![[tabViewItem identifier] isEqualToString:@"loading"]){
332                 
333                 int selectedIndex = [tabView indexOfTabViewItem:tabViewItem];
335                 //Save changes
336                 [self _saveControlChanges];
337                 
338                 //Load the pane if it isn't already loaded
339                 if(![[tabViewItem identifier] isEqualToString:ADVANCED_PANE_IDENTIFIER]){
340                         AIModularPaneCategoryView *view = [viewArray objectAtIndex:selectedIndex];
341                         if([view isEmpty]) [view setPanes:[self _panesInCategory:selectedIndex]];
342                 }
343                 
344                 //Update the window title
345                 [[self window] setTitle:[NSString stringWithFormat:@"%@ : %@",
346                         PREFERENCE_WINDOW_TITLE,
347                         [self tabView:tabView labelForTabViewItem:tabViewItem]]];
348    }
352  * @brief Tabview will select a new pane; should it immediately show the loading indicator?
354  * We only immediately show the loading inidicator if the view is empty.
355  */
356 - (BOOL)immediatelyShowLoadingIndicatorForTabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
358         if(tabView == tabView_category){
359                 AIModularPaneCategoryView *view = [viewArray objectAtIndex:[tabView indexOfTabViewItem:tabViewItem]];
360                 if([view isEmpty]) return YES;
361         }
363         return NO;
367  * @brief Returns the preference image associated with the tab view item
368  */
369 - (NSImage *)tabView:(NSTabView *)tabView imageForTabViewItem:(NSTabViewItem *)tabViewItem
371         return([NSImage imageNamed:[NSString stringWithFormat:PREFERENCE_ICON_FORMAT, [tabViewItem identifier]] forClass:[self class]]);
375  * @brief Returns the localized label for the tab view item
376  */
377 - (NSString *)tabView:(NSTabView *)tabView labelForTabViewItem:(NSTabViewItem *)tabViewItem
379         if(tabView == tabView_category){
380                 NSString        *identifier;
381                 if(identifier = [tabViewItem identifier]){
382                         return [[self identifierToLabelDict] objectForKey:identifier];
383                 }
384         }
385         
386         return nil;
390  * @brief Returns the desired height for the tab view item
391  */
392 - (int)tabView:(NSTabView *)tabView heightForTabViewItem:(NSTabViewItem *)tabViewItem
394         if(![[tabViewItem identifier] isEqualToString:ADVANCED_PANE_IDENTIFIER]){
395                 return([[viewArray objectAtIndex:[tabView indexOfTabViewItem:tabViewItem]] desiredHeight]);
396         }else{
397                 return(ADVANCED_PANE_HEIGHT);
398         }
402 //Advanced Preferences -------------------------------------------------------------------------------------------------
403 #pragma mark Advanced Preferences
405  * @brief Displays the passed AIPreferencePane in the advanced preferences tab of our window
406  */
407 - (void)configureAdvancedPreferencesForPane:(AIPreferencePane *)preferencePane
409         NSEnumerator            *enumerator;
410         AIPreferencePane        *pane;
411         
412         //Close open panes
413         enumerator = [loadedAdvancedPanes objectEnumerator];
414         while(pane = [enumerator nextObject]){
415                 [pane closeView];
416         }
417         [view_Advanced removeAllSubviews];
418         [loadedAdvancedPanes release]; loadedAdvancedPanes = nil;
419         
420         //Load new panes
421         if(preferencePane){
422                 loadedAdvancedPanes = [[NSArray arrayWithObject:preferencePane] retain];
423                 [view_Advanced setPanes:loadedAdvancedPanes];
424         }
426         //Disable the "Restore Defaults" button if there's nothing to restore
427         [button_restoreDefaults setEnabled:([preferencePane restorablePreferences] != nil)];
431  * @brief Returns an array containing all the available advanced preference views
432  */
433 - (NSArray *)advancedCategoryArray
435     if(!_advancedCategoryArray){
436         _advancedCategoryArray = [[self _panesInCategory:AIPref_Advanced] retain];
437     }
438     
439     return(_advancedCategoryArray);
443  * @brief Restores all preferences on the currently active advanced pane to their defaults
444  */
445 - (IBAction)restoreDefaults:(id)sender
447         int     selectedRow = [tableView_advanced selectedRow];
448         [[adium preferenceController] resetPreferencesInPane:[[self advancedCategoryArray] objectAtIndex:selectedRow]];
452 //Advanced Preferences (Outline View) ----------------------------------------------------------------------------------
453 #pragma mark Advanced Preferences (Outline View)
455  * @brief Configure the advanced preference category table view
456  */
457 - (void)_configureAdvancedPreferencesTable
459     AIImageTextCell                     *cell;
460         
461     //Configure our tableView
462     cell = [[AIImageTextCell alloc] init];
463     [cell setFont:[NSFont systemFontOfSize:12]];
464         [cell setDrawsGradientHighlight:YES];
465     [[tableView_advanced tableColumnWithIdentifier:@"description"] setDataCell:cell];
466         [cell release];
467         
468     [scrollView_advanced setAutoHideScrollBar:YES];
469         
470         //Select the previously selected row
471         int row = [[[adium preferenceController] preferenceForKey:KEY_ADVANCED_PREFERENCE_SELECTED_ROW
472                                                                                                                 group:PREF_GROUP_WINDOW_POSITIONS] intValue];
473         if(row < 0 || row >= [tableView_advanced numberOfRows]) row = 1;
474         
475         if([self tableView:tableView_advanced shouldSelectRow:row]){
476                 [tableView_advanced selectRow:row byExtendingSelection:NO];
477         }
481  * @brief Return the number of accounts
482  */
483 - (int)numberOfRowsInTableView:(NSTableView *)tableView
485         return([[self advancedCategoryArray] count]);
489  * @brief Return the account description or image
490  */
491 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
493         return([[[self advancedCategoryArray] objectAtIndex:row] label]);
497  * @brief Set the category image before the cell is displayed
498  */
499 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
501         [cell setImage:[[[self advancedCategoryArray] objectAtIndex:row] image]];
502         [cell setSubString:nil];
506  * @brief Update our advanced preferences for the selected pane
507  */
508 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row
510         if(row >= 0 && row < [[self advancedCategoryArray] count]){             
511                 [self configureAdvancedPreferencesForPane:[[self advancedCategoryArray] objectAtIndex:row]];
512                 return(YES);
513     }else{
514                 return(NO);
515         }
518 @end