2 // ESAwayStatusWindowController.m
5 // Created by Evan Schoenberg on 4/12/05.
6 // Copyright 2005 The Adium Team. All rights reserved.
9 #import "ESAwayStatusWindowController.h"
10 #import "AIAccountController.h"
11 #import "AIPreferenceController.h"
12 #import "AISoundController.h"
13 #import "AIStatusController.h"
14 #import <Adium/AIAccount.h>
15 #import <Adium/AIStatus.h>
16 #import <Adium/AIStatusIcons.h>
17 #import <Adium/AIServiceIcons.h>
18 #import <AIUtilities/AIArrayAdditions.h>
19 #import <AIUtilities/AIAttributedStringAdditions.h>
20 #import <AIUtilities/AIImageTextCell.h>
21 #import <AIUtilities/AITableViewAdditions.h>
22 #import <AIUtilities/CBApplicationAdditions.h>
25 #define AWAY_STATUS_WINDOW_NIB @"AwayStatusWindow"
26 #define KEY_AWAY_STATUS_WINDOW_FRAME @"Away Status Window Frame"
28 @interface ESAwayStatusWindowController (PRIVATE)
29 - (void)localizeButtons;
30 - (void)configureStatusWindow;
31 - (void)configureMuteWhileAway;
32 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon;
33 - (NSArray *)awayAccounts;
34 - (void)setupMultistatusTable;
38 * @class ESAwayStatusWindowController
39 * @brief Window controller for the status window which optionally shows when one or more accounts are away or invisible
41 @implementation ESAwayStatusWindowController
43 static ESAwayStatusWindowController *sharedInstance = nil;
46 * @brief Update the visibility of the status window
48 * Opens or closes the window if necessary.
50 * If shouldBeVisibile is YES and the window is already visible, updates its contents to reflect the current status.
51 * If shouldBeVisible is NO and the window is already not visibile, no action is taken.
53 + (void)updateStatusWindowWithVisibility:(BOOL)shouldBeVisible
57 //Update the window's configuration
58 [sharedInstance configureStatusWindow];
60 //Create a new shared instance, which will be configured automatically once the window loads
61 sharedInstance = [[self alloc] initWithWindowNibName:AWAY_STATUS_WINDOW_NIB];
62 [sharedInstance showWindow:nil];
67 //If the window is current visible, close it
68 [sharedInstance closeWindow:nil];
74 * @brief Window size and position autosave name
76 - (NSString *)adiumFrameAutosaveName
78 return(KEY_AWAY_STATUS_WINDOW_FRAME);
82 * @brief Window loaded
86 //Call super first so we get our placement before performing autosizing
87 [super windowDidLoad];
90 [textView_singleStatus setHorizontallyResizable:NO];
91 [textView_singleStatus setVerticallyResizable:YES];
92 [textView_singleStatus setDrawsBackground:NO];
93 [textView_singleStatus setMinSize:NSZeroSize];
94 [scrollView_singleStatus setDrawsBackground:NO];
96 [self localizeButtons];
97 [self setupMultistatusTable];
98 [self configureMuteWhileAway];
100 [self configureStatusWindow];
104 * @brief Window will close
106 * Release and clear the reference to our shared instance
108 - (void)windowWillClose:(id)sender
110 //If we are muting while this window is open, remove the mute before closing
111 if([button_muteWhileAway state]){
112 [[adium preferenceController] setPreference:nil
113 forKey:KEY_SOUND_STATUS_MUTE
114 group:PREF_GROUP_SOUNDS];
117 [super windowWillClose:sender];
119 //Clean up and release the shared instance
120 [sharedInstance autorelease]; sharedInstance = nil;
128 [_awayAccounts release];
134 * @brief Configure status window for the current account status(es)
136 - (void)configureStatusWindow
138 NSWindow *window = [self window];
139 BOOL allOnlineAccountsAreUnvailable;
140 AIStatusType activeUnvailableStatusType;
141 NSString *activeUnvailableStatusName = nil;
142 NSSet *relevantStatuses;
143 NSRect frame = [window frame];
146 [window setTitle:AILocalizedString(@"Current Status",nil)];
147 [_awayAccounts release]; _awayAccounts = nil;
149 relevantStatuses = [[adium statusController] activeUnavailableStatusesAndType:&activeUnvailableStatusType
150 withName:&activeUnvailableStatusName
151 allOnlineAccountsAreUnvailable:&allOnlineAccountsAreUnvailable];
153 if(allOnlineAccountsAreUnvailable && ([relevantStatuses count] == 1)){
154 //Show the single status tab if all online accounts are unavailable and they are all in the same status state
156 NSAttributedString *statusTitle;
158 statusIcon = [AIStatusIcons statusIconForStatusName:activeUnvailableStatusName
159 statusType:activeUnvailableStatusType
160 iconType:AIStatusIconTab
161 direction:AIIconNormal];
162 statusTitle = [self attributedStatusTitleForStatus:[relevantStatuses anyObject]
163 withIcon:statusIcon];
165 [[textView_singleStatus textStorage] setAttributedString:statusTitle];
167 newHeight = [statusTitle heightWithWidth:[textView_singleStatus frame].size.width] + 65;
168 frame.origin.y -= (newHeight - frame.size.height);
169 frame.size.height = newHeight;
171 //Select the right tab view item
172 [tabView_configuration selectTabViewItemWithIdentifier:@"singlestatus"];
175 /* Show the multistatus tableview tab if accounts are in different states, which includes the case of only one
176 * away state being in use but not all online accounts currently making use of it.
180 _awayAccounts = [[self awayAccounts] retain];
182 [tableView_multiStatus reloadData];
184 requiredHeight = (([tableView_multiStatus rowHeight] + [tableView_multiStatus intercellSpacing].height) *
185 [_awayAccounts count]);
187 newHeight = requiredHeight + 65;
188 frame.origin.y -= (newHeight - frame.size.height);
189 frame.size.height = newHeight;
191 /* Multiple statuses */
192 [tabView_configuration selectTabViewItemWithIdentifier:@"multistatus"];
195 //Perform the window resizing as needed
196 if ([NSApp isOnPantherOrBetter]){
197 [window setFrame:frame display:YES animate:YES];
199 [window setFrame:frame display:YES]; //animate:YES can crash in 10.2
204 * @brief Return the attributed status title for a status
206 * This method puts statusIcon into an NSTextAttachment and prefixes statusState's status message or title with it.
208 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon
210 NSMutableAttributedString *statusTitle;
211 NSTextAttachment *attachment;
212 NSTextAttachmentCell *cell;
213 NSAttributedString *statusMessage;
215 if((statusMessage = [statusState statusMessage]) &&
216 ([statusMessage length])){
217 //Use the status message if it is set
218 statusTitle = [statusMessage mutableCopy];
219 [[statusTitle mutableString] insertString:@" "
223 //If it isn't, use the title
224 NSDictionary *attributesDict;
226 attributesDict = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:0]
227 forKey:NSFontAttributeName];
229 statusTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@",[statusState title]]
230 attributes:attributesDict];
233 //Insert the image at the beginning
234 cell = [[NSTextAttachmentCell alloc] init];
235 [cell setImage:statusIcon];
237 attachment = [[NSTextAttachment alloc] init];
238 [attachment setAttachmentCell:cell];
241 [statusTitle insertAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]
243 [attachment release];
245 return [statusTitle autorelease];
249 * @brief Return an array of all away accounts
251 - (NSArray *)awayAccounts
253 NSMutableArray *awayAccounts = [NSMutableArray array];
254 NSEnumerator *enumerator = [[[adium accountController] accountArray] objectEnumerator];
257 while(account = [enumerator nextObject]){
258 if([account online] || [account integerStatusObjectForKey:@"Connecting"]){
259 AIStatus *statusState = [account statusState];
260 if([statusState statusType] != AIAvailableStatusType){
261 [awayAccounts addObject:account];
270 * @brief Return from away
272 - (IBAction)returnFromAway:(id)sender
274 NSTabViewItem *selectedTabViewItem = [tabView_configuration selectedTabViewItem];
275 AIStatus *availableStatusState = [[adium statusController] defaultInitialStatusState];
277 if([[selectedTabViewItem identifier] isEqualToString:@"singlestatus"]){
278 //Put all accounts in the Available status state
279 [[adium statusController] setActiveStatusState:availableStatusState];
283 NSArray *selectedAccounts;
285 selectedAccounts = [[tableView_multiStatus arrayOfSelectedItemsUsingSourceArray:_awayAccounts] copy];
287 if([selectedAccounts count]){
288 //Apply the available status state to only the selected accounts
289 [[adium statusController] applyState:availableStatusState
290 toAccounts:selectedAccounts];
292 //No selection: Put all accounts in the Available status state
293 [[adium statusController] setActiveStatusState:availableStatusState];
296 [selectedAccounts release];
301 * @brief Perform initial setup for the multistatus table
303 - (void)setupMultistatusTable
305 AIImageTextCell *imageTextCell;
307 imageTextCell = [[AIImageTextCell alloc] init];
308 [imageTextCell setDrawsGradientHighlight:YES];
309 [[tableView_multiStatus tableColumnWithIdentifier:@"status"] setDataCell:imageTextCell];
310 [imageTextCell release];
313 #pragma mark Multiservice table view datasource
315 * @brief Number of rows in the table
317 - (int)numberOfRowsInTableView:(NSTableView *)tableView
319 return([_awayAccounts count]);
323 * @brief Table values
325 * Object value is the account's formatted UID
327 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
329 AIAccount *account = [_awayAccounts objectAtIndex:row];
331 return([account formattedUID]);
335 * @brief Will display a cell
337 * Set the image (status icon) and substring (status title) before display. Cell is an AIImageTextCell.
339 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
341 AIAccount *account = [_awayAccounts objectAtIndex:row];
343 [cell setImage:[AIStatusIcons statusIconForListObject:account
344 type:AIServiceIconSmall
345 direction:AIIconNormal]];
346 [cell setSubString:[[account statusState] title]];
349 - (void)configureMuteWhileAway
351 NSNumber *shouldMuteWhileWindowIsOpen = [[adium preferenceController] preferenceForKey:@"Mute While Away Status Window is Open"
352 group:PREF_GROUP_STATUS_PREFERENCES];
353 //Apply the mute to the sound controller by setting a preference for it
354 [[adium preferenceController] setPreference:shouldMuteWhileWindowIsOpen
355 forKey:KEY_SOUND_STATUS_MUTE
356 group:PREF_GROUP_SOUNDS];
357 [button_muteWhileAway setState:([shouldMuteWhileWindowIsOpen boolValue] ? NSOnState : NSOffState)];
360 - (IBAction)toggleMuteWhileAway:(id)sender
362 NSNumber *shouldMuteWhileWindowIsOpen;
364 shouldMuteWhileWindowIsOpen = ([sender state] ?
365 [NSNumber numberWithBool:YES] :
367 //Store for restoring here
368 [[adium preferenceController] setPreference:shouldMuteWhileWindowIsOpen
369 forKey:@"Mute While Away Status Window is Open"
370 group:PREF_GROUP_STATUS_PREFERENCES];
371 //And apply the mute to the sound controller by setting a preference for it
372 [[adium preferenceController] setPreference:shouldMuteWhileWindowIsOpen
373 forKey:KEY_SOUND_STATUS_MUTE
374 group:PREF_GROUP_SOUNDS];
377 - (void)localizeButtons
379 [button_return setLocalizedString:AILocalizedStringFromTableInBundle(@"Return",
381 [NSBundle bundleForClass:[self class]],
382 "Button to return from away in the away status window")];
383 [button_muteWhileAway setLocalizedString:AILocalizedString(@"Mute While Away", "Mute sounds while away. Found in the away status window.")];