2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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>
29 #define KEY_PREFERENCE_SELECTED_CATEGORY @"Preference Selected Category Name"
30 #define KEY_ADVANCED_PREFERENCE_SELECTED_ROW @"Preference Advanced Selected Row"
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
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;
51 static AIPreferenceWindowController *sharedPreferenceInstance = nil;
54 * @class AIPreferenceWindowController
55 * @brief Adium preference window controller
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.
60 @implementation AIPreferenceWindowController
63 * @brief Open the preference window
65 + (void)openPreferenceWindow
67 [[self _preferenceWindowController] showWindow:nil];
71 * @brief Open the preference window to a specific category
73 + (void)openPreferenceWindowToCategoryWithIdentifier:(NSString *)identifier
75 //Load the window first
76 [[self _preferenceWindowController] window];
78 [[self _preferenceWindowController] selectCategoryWithIdentifier:identifier];
79 [[self _preferenceWindowController] showWindow:nil];
83 * @brief Open the preference window to a specific advanced category
85 + (void)openPreferenceWindowToAdvancedPane:(NSString *)advancedPane
87 [[self _preferenceWindowController] selectAdvancedPane:advancedPane];
88 [[self _preferenceWindowController] showWindow:nil];
92 * @brief Close the preference window (if it is open)
94 + (void)closePreferenceWindow
96 if(sharedPreferenceInstance) [sharedPreferenceInstance closeWindow:nil];
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.
106 + (AIPreferenceWindowController *)_preferenceWindowController
108 if(!sharedPreferenceInstance){
109 sharedPreferenceInstance = [[self alloc] initWithWindowNibName:PREFERENCE_WINDOW_NIB];
112 return(sharedPreferenceInstance);
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;
135 [loadedPanes release];
136 [loadedAdvancedPanes release];
137 [_advancedCategoryArray release];
143 * @brief Setup the window before it is displayed
145 - (void)windowDidLoad
147 [super windowDidLoad];
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:
169 * @brief Invoked before the window opens
171 - (IBAction)showWindow:(id)sender
173 //Ensure the window is loaded
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];
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.
194 - (void)windowWillClose:(id)sender
196 [super windowWillClose:sender];
199 [self _saveControlChanges];
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];
209 //Close all panes and our shared instance
210 [loadedPanes makeObjectsPerformSelector:@selector(closeView)];
211 [sharedPreferenceInstance autorelease]; sharedPreferenceInstance = nil;
215 //Panes ---------------------------------------------------------------------------------------------------------------
218 * @brief Select a preference category
220 - (void)selectCategoryWithIdentifier:(NSString *)identifier
222 NSTabViewItem *tabViewItem;
225 //Load the window first
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];
235 shouldRestorePreviousSelectedPane = NO;
239 * @brief Select an advanced preference category
241 - (void)selectAdvancedPane:(NSString *)advancedPane
243 NSEnumerator *enumerator = [[self advancedCategoryArray] objectEnumerator];
244 AIPreferencePane *pane;
246 shouldRestorePreviousSelectedPane = NO;
248 //Load the window first
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;
259 //If it exists, make it active
261 int row = [[self advancedCategoryArray] indexOfObject:pane];
262 if([self tableView:tableView_advanced shouldSelectRow:row]){
263 [tableView_advanced selectRow:row byExtendingSelection:NO];
269 * @brief Loads and returns the AIPreferencePanes in the specified category
271 - (NSArray *)_panesInCategory:(PREFERENCE_CATEGORY)inCategory
273 NSMutableArray *paneArray = [NSMutableArray array];
274 NSEnumerator *enumerator = [[[adium preferenceController] paneArray] objectEnumerator];
275 AIPreferencePane *pane;
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];
286 [paneArray sortUsingSelector:@selector(caseInsensitiveCompare:)];
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.
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",
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.
328 - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
330 if(tabView == tabView_category &&
331 ![[tabViewItem identifier] isEqualToString:@"loading"]){
333 int selectedIndex = [tabView indexOfTabViewItem:tabViewItem];
336 [self _saveControlChanges];
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]];
344 //Update the window title
345 [[self window] setTitle:[NSString stringWithFormat:@"%@ : %@",
346 PREFERENCE_WINDOW_TITLE,
347 [self tabView:tabView labelForTabViewItem:tabViewItem]]];
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.
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;
367 * @brief Returns the preference image associated with the tab view item
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
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];
390 * @brief Returns the desired height for the tab view item
392 - (int)tabView:(NSTabView *)tabView heightForTabViewItem:(NSTabViewItem *)tabViewItem
394 if(![[tabViewItem identifier] isEqualToString:ADVANCED_PANE_IDENTIFIER]){
395 return([[viewArray objectAtIndex:[tabView indexOfTabViewItem:tabViewItem]] desiredHeight]);
397 return(ADVANCED_PANE_HEIGHT);
402 //Advanced Preferences -------------------------------------------------------------------------------------------------
403 #pragma mark Advanced Preferences
405 * @brief Displays the passed AIPreferencePane in the advanced preferences tab of our window
407 - (void)configureAdvancedPreferencesForPane:(AIPreferencePane *)preferencePane
409 NSEnumerator *enumerator;
410 AIPreferencePane *pane;
413 enumerator = [loadedAdvancedPanes objectEnumerator];
414 while(pane = [enumerator nextObject]){
417 [view_Advanced removeAllSubviews];
418 [loadedAdvancedPanes release]; loadedAdvancedPanes = nil;
422 loadedAdvancedPanes = [[NSArray arrayWithObject:preferencePane] retain];
423 [view_Advanced setPanes:loadedAdvancedPanes];
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
433 - (NSArray *)advancedCategoryArray
435 if(!_advancedCategoryArray){
436 _advancedCategoryArray = [[self _panesInCategory:AIPref_Advanced] retain];
439 return(_advancedCategoryArray);
443 * @brief Restores all preferences on the currently active advanced pane to their defaults
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
457 - (void)_configureAdvancedPreferencesTable
459 AIImageTextCell *cell;
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];
468 [scrollView_advanced setAutoHideScrollBar:YES];
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;
475 if([self tableView:tableView_advanced shouldSelectRow:row]){
476 [tableView_advanced selectRow:row byExtendingSelection:NO];
481 * @brief Return the number of accounts
483 - (int)numberOfRowsInTableView:(NSTableView *)tableView
485 return([[self advancedCategoryArray] count]);
489 * @brief Return the account description or image
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
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
508 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row
510 if(row >= 0 && row < [[self advancedCategoryArray] count]){
511 [self configureAdvancedPreferencesForPane:[[self advancedCategoryArray] objectAtIndex:row]];