2 // ESStatusPreferences.m
5 // Created by Evan Schoenberg on 2/26/05.
6 // Copyright 2005 The Adium Team. All rights reserved.
9 #import "ESStatusPreferences.h"
10 #import "AIStatusController.h"
11 #import "AIAccountController.h"
12 #import <Adium/AIAccount.h>
13 #import <Adium/AIEditStateWindowController.h>
14 #import <AIUtilities/AIImageTextCell.h>
15 #import <AIUtilities/AIAutoScrollView.h>
16 #import <AIUtilities/AIVerticallyCenteredTextCell.h>
17 #import "KFTypeSelectTableView.h"
19 #define STATE_DRAG_TYPE @"AIState"
21 @interface ESStatusPreferences (PRIVATE)
22 - (void)configureOtherControls;
23 - (void)configureAutoAwayStatusStatePopUp;
24 - (void)saveTimeValues;
25 - (void)_selectStatusWithUniqueID:(NSNumber *)uniqueID inPopUpButton:(NSPopUpButton *)inPopUpButton;
28 @implementation ESStatusPreferences
33 - (PREFERENCE_CATEGORY)category{
34 return(AIPref_Status);
40 return(AILocalizedString(@"Status",nil));
46 - (NSString *)nibName{
47 return(@"StatusPreferences");
51 * @brief Configure the preference view
55 //Configure the controls
56 [self configureStateList];
57 [button_editState setTitle:AILocalizedString(@"Edit",nil)];
59 /* Register as an observer of state array changes so we can refresh our list
60 * in response to changes. */
61 [[adium notificationCenter] addObserver:self
62 selector:@selector(stateArrayChanged:)
63 name:AIStatusStateArrayChangedNotification
65 [self stateArrayChanged:nil];
67 [self configureOtherControls];
71 * @brief Preference view is closing
75 [self saveTimeValues];
76 [[adium notificationCenter] removeObserver:self];
89 #pragma mark Status state list and controls
91 * @brief Configure the state list
93 * Configure the state list table view, setting up the custom table cells, padding, scroll view settings and other
94 * state list interface related setup.
96 - (void)configureStateList
98 AIImageTextCell *cell;
100 //Configure the table view
101 [tableView_stateList setTarget:self];
102 [tableView_stateList setDoubleAction:@selector(editState:)];
103 [tableView_stateList setIntercellSpacing:NSMakeSize(4,4)];
104 [scrollView_stateList setAutoHideScrollBar:YES];
106 //Enable dragging of states
107 [tableView_stateList registerForDraggedTypes:[NSArray arrayWithObject:STATE_DRAG_TYPE]];
109 //Custom vertically-centered text cell for status state names
110 cell = [[AIVerticallyCenteredTextCell alloc] init];
111 [cell setFont:[NSFont systemFontOfSize:13]];
112 [[tableView_stateList tableColumnWithIdentifier:@"name"] setDataCell:cell];
117 * @brief Update table control availability
119 * Updates table control availability based on the current state selection. If no states are selected this method dims the
120 * edit and delete buttons since they require a selection to function. The edit and delete buttons are also
121 * dimmed if the selected state is a built-in state.
123 - (void)updateTableControlAvailability
125 int selectedRow = [tableView_stateList selectedRow];
128 shouldEnable = ((selectedRow != -1) &&
129 ([[stateArray objectAtIndex:selectedRow] mutabilityType] == AIEditableStatusState));
131 [button_editState setEnabled:shouldEnable];
132 [button_deleteState setEnabled:shouldEnable];
136 * @brief Invoked when the state array changes
138 * This method is invoked when the state array changes. In response, we hold onto the new array and refresh our state
141 - (void)stateArrayChanged:(NSNotification *)notification
143 [stateArray release];
144 stateArray = [[[adium statusController] stateArray] retain];
146 [tableView_stateList reloadData];
147 [self updateTableControlAvailability];
149 //Update the auto away status pop up as necessary
150 [self configureAutoAwayStatusStatePopUp];
153 //State Editing --------------------------------------------------------------------------------------------------------
154 #pragma mark State Editing
156 * @brief Edit the selected state
158 * Opens an edit state sheet for the selected state. If the sheet is closed with success our
159 * customStatusState:changedTo: method will be invoked and we can save the changes
161 - (IBAction)editState:(id)sender
163 int selectedIndex = [tableView_stateList selectedRow];
165 if(selectedIndex >= 0 && selectedIndex < [stateArray count]){
166 AIStatus *statusState = [stateArray objectAtIndex:selectedIndex];
167 [AIEditStateWindowController editCustomState:statusState
168 forType:[statusState statusType]
171 onWindow:[[self view] window]
172 notifyingTarget:self];
177 * @brief State edited callback
179 * Invoked when the user successfully edits a state. This method adds the new or updated state to Adium's state array.
181 - (void)customStatusState:(AIStatus *)originalState changedTo:(AIStatus *)newState forAccount:(AIAccount *)account
184 /* As far the user was concerned, this was an edit. The unique status ID should remain the same so that anything
185 * depending upon this status will update to using it. Furthermore, since this may be a copy of originalState
186 * rather than the exact same object, we should update all accounts which are using this state to use the new copy
188 [newState setUniqueStatusID:[originalState uniqueStatusID]];
190 NSEnumerator *enumerator;
193 enumerator = [[[adium accountController] accountArray] objectEnumerator];
194 while(account = [enumerator nextObject]){
195 if([account statusState] == originalState){
196 [account setStatusStateAndRemainOffline:newState];
198 [account notifyOfChangedStatusSilently:YES];
202 [[adium statusController] replaceExistingStatusState:originalState withStatusState:newState];
204 [originalState setUniqueStatusID:nil];
207 [[adium statusController] addStatusState:newState];
212 * @brief Delete the selected state
214 * Deletes the selected state from Adium's state array.
216 - (IBAction)deleteState:(id)sender
218 int selectedIndex = [tableView_stateList selectedRow];
220 if(selectedIndex >= 0 && selectedIndex < [stateArray count]){
221 [[adium statusController] removeStatusState:[stateArray objectAtIndex:selectedIndex]];
226 * @brief Add a new state
228 * Creates a new state. This is done by invoking an edit window without passing it a base state. When the edit window
229 * returns successfully, it will invoke our customStatusState:changedTo: which adds the new state to Adium's state
232 - (IBAction)newState:(id)sender
234 [AIEditStateWindowController editCustomState:nil
235 forType:AIAwayStatusType
238 onWindow:[[self view] window]
239 notifyingTarget:self];
243 //State List Table Delegate --------------------------------------------------------------------------------------------
244 #pragma mark State List (Table Delegate)
246 * @brief Number of rows
248 - (int)numberOfRowsInTableView:(NSTableView *)tableView
250 return([stateArray count]);
254 * @brief Table values
256 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
258 NSString *identifier = [tableColumn identifier];
259 AIStatus *statusState = [stateArray objectAtIndex:row];
261 if([identifier isEqualToString:@"icon"]){
262 return([statusState icon]);
264 }else if([identifier isEqualToString:@"name"]){
265 return([statusState title]);
273 * @brief Delete the selected row
275 - (void)tableViewDeleteSelectedRows:(NSTableView *)tableView
277 [self deleteState:nil];
281 * @brief Selection change
283 - (void)tableViewSelectionDidChange:(NSNotification *)notification
285 [self updateTableControlAvailability];
291 - (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
293 tempDragState = [stateArray objectAtIndex:[[rows objectAtIndex:0] intValue]];
295 [pboard declareTypes:[NSArray arrayWithObject:STATE_DRAG_TYPE] owner:self];
296 [pboard setString:@"State" forType:STATE_DRAG_TYPE]; //Arbitrary state
302 * @brief Drag validate
304 - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
306 if(op == NSTableViewDropAbove && row != -1){
307 return(NSDragOperationPrivate);
309 return(NSDragOperationNone);
314 * @brief Drag complete
316 - (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op
318 NSString *avaliableType = [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:STATE_DRAG_TYPE]];
320 if([avaliableType isEqualToString:STATE_DRAG_TYPE]){
323 //Move the state and select it in the new location
324 newIndex = [[adium statusController] moveStatusState:tempDragState toIndex:row];
325 [tableView_stateList selectRow:newIndex byExtendingSelection:NO];
334 * @brief Set up KFTypeSelectTableView
336 * Only search the "name" column.
338 - (void)configureTypeSelectTableView:(KFTypeSelectTableView *)tableView
340 [tableView setSearchColumnIdentifiers:[NSSet setWithObject:@"name"]];
343 #pragma mark Other status-related controls
346 * @brief Configure initial values for idle, auto-away, etc., preferences.
349 - (void)configureOtherControls
351 NSDictionary *prefDict = [[adium preferenceController] preferencesForGroup:PREF_GROUP_STATUS_PREFERENCES];
353 [checkBox_idle setState:[[prefDict objectForKey:KEY_STATUS_REPORT_IDLE] boolValue]];
354 [textField_idleMinutes setDoubleValue:([[prefDict objectForKey:KEY_STATUS_REPORT_IDLE_INTERVAL] doubleValue] / 60.0)];
356 [checkBox_autoAway setState:[[prefDict objectForKey:KEY_STATUS_AUTO_AWAY] boolValue]];
357 [textField_autoAwayMinutes setDoubleValue:([[prefDict objectForKey:KEY_STATUS_AUTO_AWAY_INTERVAL] doubleValue] / 60.0)];
359 [checkBox_fastUserSwitching setState:[[prefDict objectForKey:KEY_STATUS_FUS] boolValue]];
361 [checkBox_showStatusWindow setState:[[prefDict objectForKey:KEY_STATUS_SHOW_STATUS_WINDOW] boolValue]];
363 [self configureControlDimming];
367 * @brief Configure the pop up of states for autoAway.
369 * Should be called by stateArrayChanged: both for initial set up and for updating when the states change.
371 - (void)configureAutoAwayStatusStatePopUp
373 NSMenu *statusStatesMenu;
374 NSNumber *targetUniqueStatusIDNumber;
376 statusStatesMenu = [[adium statusController] statusStatesMenu];
377 [popUp_autoAwayStatusState setMenu:statusStatesMenu];
378 [popUp_fastUserSwitchingStatusState setMenu:[[statusStatesMenu copy] autorelease]];
380 //Now select the proper state, or deselect all items if there is no chosen state or the chosen state doesn't exist
381 targetUniqueStatusIDNumber = [[adium preferenceController] preferenceForKey:KEY_STATUS_ATUO_AWAY_STATUS_STATE_ID
382 group:PREF_GROUP_STATUS_PREFERENCES];
383 [self _selectStatusWithUniqueID:targetUniqueStatusIDNumber inPopUpButton:popUp_autoAwayStatusState];
385 targetUniqueStatusIDNumber = [[adium preferenceController] preferenceForKey:KEY_STATUS_FUS_STATUS_STATE_ID
386 group:PREF_GROUP_STATUS_PREFERENCES];
387 [self _selectStatusWithUniqueID:targetUniqueStatusIDNumber inPopUpButton:popUp_fastUserSwitchingStatusState];
390 - (void)_selectStatusWithUniqueID:(NSNumber *)uniqueID inPopUpButton:(NSPopUpButton *)inPopUpButton
396 int targetUniqueStatusID;
397 unsigned itemArrayCount, i;
399 targetUniqueStatusID = [uniqueID intValue];
401 itemArray = [inPopUpButton itemArray];
402 itemArrayCount = [itemArray count];
403 for(i = 0; i < itemArrayCount; i++){
404 NSMenuItem *menuItem = [itemArray objectAtIndex:i];
405 AIStatus *statusState = [[menuItem representedObject] objectForKey:@"AIStatus"];
407 //Found the right status by matching its status ID to our preferred one
408 if([statusState preexistingUniqueStatusID] == targetUniqueStatusID){
415 [inPopUpButton selectItemAtIndex:index];
419 * @brief Configure control dimming for idle, auto-away, etc., preferences.
421 - (void)configureControlDimming
423 BOOL idleControlsEnabled, autoAwayControlsEnabled;
425 idleControlsEnabled = ([checkBox_idle state] == NSOnState);
426 [textField_idleMinutes setEnabled:idleControlsEnabled];
427 [stepper_idleMinutes setEnabled:idleControlsEnabled];
429 autoAwayControlsEnabled = ([checkBox_autoAway state] == NSOnState);
430 [popUp_autoAwayStatusState setEnabled:autoAwayControlsEnabled];
431 [textField_autoAwayMinutes setEnabled:autoAwayControlsEnabled];
432 [stepper_autoAwayMinutes setEnabled:autoAwayControlsEnabled];
434 [popUp_fastUserSwitchingStatusState setEnabled:([checkBox_fastUserSwitching state] == NSOnState)];
438 * @brief Change preference
440 * Sent when controls are clicked
442 - (void)changePreference:(id)sender
444 if(sender == checkBox_idle){
445 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
446 forKey:KEY_STATUS_REPORT_IDLE
447 group:PREF_GROUP_STATUS_PREFERENCES];
448 [self configureControlDimming];
450 }else if(sender == checkBox_autoAway){
451 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
452 forKey:KEY_STATUS_AUTO_AWAY
453 group:PREF_GROUP_STATUS_PREFERENCES];
454 [self configureControlDimming];
456 }else if(sender == checkBox_showStatusWindow){
457 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
458 forKey:KEY_STATUS_SHOW_STATUS_WINDOW
459 group:PREF_GROUP_STATUS_PREFERENCES];
461 }else if(sender == checkBox_fastUserSwitching){
462 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
463 forKey:KEY_STATUS_FUS
464 group:PREF_GROUP_STATUS_PREFERENCES];
465 [self configureControlDimming];
467 }else if(sender == popUp_autoAwayStatusState){
468 AIStatus *statusState = [[[sender selectedItem] representedObject] objectForKey:@"AIStatus"];
470 [[adium preferenceController] setPreference:[statusState uniqueStatusID]
471 forKey:KEY_STATUS_ATUO_AWAY_STATUS_STATE_ID
472 group:PREF_GROUP_STATUS_PREFERENCES];
474 }else if(sender == popUp_fastUserSwitchingStatusState){
475 AIStatus *statusState = [[[sender selectedItem] representedObject] objectForKey:@"AIStatus"];
477 [[adium preferenceController] setPreference:[statusState uniqueStatusID]
478 forKey:KEY_STATUS_FUS_STATUS_STATE_ID
479 group:PREF_GROUP_STATUS_PREFERENCES];
484 * @brief Control text did end editing
486 * In an attempt to get closer to a live-apply of preferences, save the preference when the
487 * text field loses focus. See saveTimeValues for more information.
489 - (void)controlTextDidEndEditing:(NSNotification *)notification
491 [self saveTimeValues];
495 * @brief Save time text field values
497 * We can't get notified when the associated NSStepper is clicked, so we just save as requested.
498 * This method should be called before the view closes.
500 - (void)saveTimeValues
502 [[adium preferenceController] setPreference:[NSNumber numberWithDouble:([textField_idleMinutes doubleValue]*60.0)]
503 forKey:KEY_STATUS_REPORT_IDLE_INTERVAL
504 group:PREF_GROUP_STATUS_PREFERENCES];
506 [[adium preferenceController] setPreference:[NSNumber numberWithDouble:([textField_autoAwayMinutes doubleValue]*60.0)]
507 forKey:KEY_STATUS_AUTO_AWAY_INTERVAL
508 group:PREF_GROUP_STATUS_PREFERENCES];