2 // ESAwayStatusWindowController.m
5 // Created by Evan Schoenberg on 4/12/05.
6 // Copyright 2006 The Adium Team. All rights reserved.
9 #import "ESAwayStatusWindowController.h"
10 #import <Adium/AIAccountControllerProtocol.h>
11 #import <Adium/AIPreferenceControllerProtocol.h>
12 #import <Adium/AIStatusControllerProtocol.h>
13 #import <Adium/AIAccount.h>
14 #import <Adium/AIStatus.h>
15 #import <Adium/AIStatusIcons.h>
16 #import <Adium/AIServiceIcons.h>
17 #import <AIUtilities/AIAlternatingRowTableView.h>
18 #import <AIUtilities/AIApplicationAdditions.h>
19 #import <AIUtilities/AIArrayAdditions.h>
20 #import <AIUtilities/AIAttributedStringAdditions.h>
21 #import <AIUtilities/AIImageTextCell.h>
22 #import <AIUtilities/AITableViewAdditions.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 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon;
32 - (NSArray *)awayAccounts;
33 - (void)setupMultistatusTable;
37 * @class ESAwayStatusWindowController
38 * @brief Window controller for the status window which optionally shows when one or more accounts are away or invisible
40 @implementation ESAwayStatusWindowController
42 static ESAwayStatusWindowController *sharedInstance = nil;
43 static BOOL alwaysOnTop = NO;
44 static BOOL hideInBackground = NO;
47 * @brief Update the visibility of the status window
49 * Opens or closes the window if necessary.
51 * If shouldBeVisibile is YES and the window is already visible, updates its contents to reflect the current status.
52 * If shouldBeVisible is NO and the window is already not visibile, no action is taken.
54 + (void)updateStatusWindowWithVisibility:(BOOL)shouldBeVisible
56 if (shouldBeVisible) {
58 //Update the window's configuration
59 [sharedInstance configureStatusWindow];
61 //Create a new shared instance, which will be configured automatically once the window loads
62 sharedInstance = [[self alloc] initWithWindowNibName:AWAY_STATUS_WINDOW_NIB];
63 [sharedInstance showWindow:nil];
68 //If the window is current visible, close it
69 [sharedInstance closeWindow:nil];
74 + (void)setAlwaysOnTop:(BOOL)flag
79 //Update any open window
80 [[sharedInstance window] setLevel:(alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel)];
84 + (void)setHideInBackground:(BOOL)flag
86 hideInBackground = flag;
89 //Update any open window
90 [[sharedInstance window] setHidesOnDeactivate:hideInBackground];
95 * @brief Window size and position autosave name
97 - (NSString *)adiumFrameAutosaveName
99 return KEY_AWAY_STATUS_WINDOW_FRAME;
103 * @brief Window loaded
105 - (void)windowDidLoad
107 //Call super first so we get our placement before performing autosizing
108 [super windowDidLoad];
110 [[self window] setLevel:(alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel)];
111 [[self window] setHidesOnDeactivate:hideInBackground];
113 /* Set a more reasonable minimum size after the window is sized using our nib's specification.
114 * NSPanel behaves oddly with minimum size... it seems to increase the nib-specified minimum by 11.
116 [[self window] setMinSize:NSMakeSize([[self window] minSize].width, 80)];
118 //Setup the textviews
119 [textView_singleStatus setHorizontallyResizable:NO];
120 [textView_singleStatus setVerticallyResizable:YES];
121 [textView_singleStatus setDrawsBackground:NO];
122 [textView_singleStatus setMinSize:NSZeroSize];
123 [[textView_singleStatus enclosingScrollView] setDrawsBackground:NO];
125 [self localizeButtons];
126 [self setupMultistatusTable];
128 [self configureStatusWindow];
130 [[adium notificationCenter] addObserver:self
131 selector:@selector(statusIconSetChanged:)
132 name:AIStatusIconSetDidChangeNotification
137 * @brief Window will close
139 * Release and clear the reference to our shared instance
141 - (void)windowWillClose:(id)sender
143 [super windowWillClose:sender];
145 /* Hack of the day. The table view crashes when the window is released out from under it after it has reloaded data because
146 * it thinks it needs display. It thinks that because we are animating the window's resizing process. We could do animate:NO
147 * in configureStatusWindow, but that wouldn't be as pretty.
149 [tableView_multiStatus setDataSource:nil];
151 //Clean up and release the shared instance
152 [sharedInstance autorelease]; sharedInstance = nil;
160 [_awayAccounts release]; _awayAccounts = nil;
161 [[adium notificationCenter] removeObserver:self];
167 * @brief Configure status window for the current account status(es)
169 - (void)configureStatusWindow
171 NSWindow *window = [self window];
172 BOOL allOnlineAccountsAreUnvailable;
173 AIStatusType activeUnvailableStatusType;
174 NSString *activeUnvailableStatusName = nil;
175 NSSet *relevantStatuses;
176 NSRect frame = [window frame];
179 [window setTitle:AILocalizedString(@"Current Status",nil)];
180 [_awayAccounts release]; _awayAccounts = nil;
182 relevantStatuses = [[adium statusController] activeUnavailableStatusesAndType:&activeUnvailableStatusType
183 withName:&activeUnvailableStatusName
184 allOnlineAccountsAreUnvailable:&allOnlineAccountsAreUnvailable];
186 if (allOnlineAccountsAreUnvailable && ([relevantStatuses count] == 1)) {
187 //Show the single status tab if all online accounts are unavailable and they are all in the same status state
189 NSAttributedString *statusTitle;
191 statusIcon = [AIStatusIcons statusIconForStatusName:activeUnvailableStatusName
192 statusType:activeUnvailableStatusType
193 iconType:AIStatusIconTab
194 direction:AIIconNormal];
195 statusTitle = [self attributedStatusTitleForStatus:[relevantStatuses anyObject]
196 withIcon:statusIcon];
198 [[textView_singleStatus textStorage] setAttributedString:statusTitle];
200 newHeight = [statusTitle heightWithWidth:[textView_singleStatus frame].size.width] + 65;
201 frame.origin.y -= (newHeight - frame.size.height);
202 frame.size.height = newHeight;
204 //Select the right tab view item
205 [tabView_configuration selectTabViewItemWithIdentifier:@"singlestatus"];
207 /* Show the multistatus tableview tab if accounts are in different states, which includes the case of only one
208 * away state being in use but not all online accounts currently making use of it.
212 _awayAccounts = [[self awayAccounts] retain];
214 [tableView_multiStatus reloadData];
216 requiredHeight = (([tableView_multiStatus rowHeight] + [tableView_multiStatus intercellSpacing].height) *
217 [_awayAccounts count]);
219 newHeight = requiredHeight + 65;
220 frame.origin.y -= (newHeight - frame.size.height);
221 frame.size.height = newHeight;
223 /* Multiple statuses */
224 [tabView_configuration selectTabViewItemWithIdentifier:@"multistatus"];
227 //Perform the window resizing as needed
228 [window setFrame:frame display:YES animate:YES];
232 * @brief Return the attributed status title for a status
234 * This method puts statusIcon into an NSTextAttachment and prefixes statusState's status message or title with it.
236 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon
238 NSMutableAttributedString *statusTitle;
239 NSTextAttachment *attachment;
240 NSTextAttachmentCell *cell;
241 NSAttributedString *statusMessage;
243 if ((statusMessage = [statusState statusMessage]) &&
244 ([statusMessage length])) {
245 //Use the status message if it is set
246 statusTitle = [statusMessage mutableCopy];
247 [[statusTitle mutableString] insertString:@" "
251 //If it isn't, use the title
252 NSDictionary *attributesDict;
254 attributesDict = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:0]
255 forKey:NSFontAttributeName];
257 statusTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@",[statusState title]]
258 attributes:attributesDict];
261 //Insert the image at the beginning
262 cell = [[NSTextAttachmentCell alloc] init];
263 [cell setImage:statusIcon];
265 attachment = [[NSTextAttachment alloc] init];
266 [attachment setAttachmentCell:cell];
269 [statusTitle insertAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]
271 [attachment release];
273 return [statusTitle autorelease];
277 * @brief Return an array of all away accounts
279 - (NSArray *)awayAccounts
281 NSMutableArray *awayAccounts = [NSMutableArray array];
282 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
285 while ((account = [enumerator nextObject])) {
286 if ([account online] || [account integerStatusObjectForKey:@"Connecting"]) {
287 AIStatus *statusState = [account statusState];
288 if ([statusState statusType] != AIAvailableStatusType) {
289 [awayAccounts addObject:account];
298 * @brief Return from away
300 - (IBAction)returnFromAway:(id)sender
302 NSTabViewItem *selectedTabViewItem = [tabView_configuration selectedTabViewItem];
303 AIStatus *availableStatusState = [[adium statusController] defaultInitialStatusState];
307 if ([[selectedTabViewItem identifier] isEqualToString:@"singlestatus"]) {
308 //Put all accounts in the Available status state
309 //We can perform this on all accounts without fear of bringing them online;
310 //Those that are offline will remain offline since -setActiveStatusState considers this.
311 [[adium statusController] setActiveStatusState:availableStatusState];
314 NSArray *selectedAccounts;
316 selectedAccounts = [[tableView_multiStatus arrayOfSelectedItemsUsingSourceArray:_awayAccounts] copy];
318 if ([selectedAccounts count]) {
319 //Apply the available status state to only the selected accounts
320 [[adium statusController] applyState:availableStatusState
321 toAccounts:selectedAccounts];
323 //No selection: Put all accounts in the Available status state
324 //Like above, we can just call -setActiveStatusState and it will handle all accounts.
325 [[adium statusController] setActiveStatusState:availableStatusState];
328 [selectedAccounts release];
335 * @brief Perform initial setup for the multistatus table
337 - (void)setupMultistatusTable
339 [tableView_multiStatus setDrawsGradientSelection:YES];
340 [[tableView_multiStatus tableColumnWithIdentifier:@"status"] setDataCell:[[[AIImageTextCell alloc] init] autorelease]];
343 #pragma mark Multiservice table view datasource
345 * @brief Number of rows in the table
347 - (int)numberOfRowsInTableView:(NSTableView *)tableView
349 return [_awayAccounts count];
353 * @brief Table values
355 * Object value is the account's formatted UID
357 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
359 AIAccount *account = [_awayAccounts objectAtIndex:row];
361 return [account formattedUID];
365 * @brief Will display a cell
367 * Set the image (status icon) and substring (status title) before display. Cell is an AIImageTextCell.
369 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
371 AIAccount *account = [_awayAccounts objectAtIndex:row];
373 [cell setImage:[AIStatusIcons statusIconForListObject:account
374 type:AIServiceIconSmall
375 direction:AIIconNormal]];
376 [cell setSubString:[[account statusState] title]];
379 - (void)localizeButtons
381 [button_return setLocalizedString:AILocalizedStringFromTableInBundle(@"Return",
383 [NSBundle bundleForClass:[self class]],
384 "Button to return from away in the away status window")];
387 - (void)statusIconSetChanged:(NSNotification *)inNotification
389 [self configureStatusWindow];