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 "AIStatusController.h"
13 #import <Adium/AIAccount.h>
14 #import <Adium/AIStatus.h>
15 #import <Adium/AIStatusIcons.h>
16 #import <Adium/AIServiceIcons.h>
17 #import <AIUtilities/AIArrayAdditions.h>
18 #import <AIUtilities/AIAttributedStringAdditions.h>
19 #import <AIUtilities/AIImageTextCell.h>
20 #import <AIUtilities/AITableViewAdditions.h>
21 #import <AIUtilities/AIApplicationAdditions.h>
24 #define AWAY_STATUS_WINDOW_NIB @"AwayStatusWindow"
25 #define KEY_AWAY_STATUS_WINDOW_FRAME @"Away Status Window Frame"
27 @interface ESAwayStatusWindowController (PRIVATE)
28 - (void)localizeButtons;
29 - (void)configureStatusWindow;
30 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon;
31 - (NSArray *)awayAccounts;
32 - (void)setupMultistatusTable;
36 * @class ESAwayStatusWindowController
37 * @brief Window controller for the status window which optionally shows when one or more accounts are away or invisible
39 @implementation ESAwayStatusWindowController
41 static ESAwayStatusWindowController *sharedInstance = nil;
42 static BOOL alwaysOnTop = NO;
43 static BOOL hideInBackground = NO;
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
55 if (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];
73 + (void)setAlwaysOnTop:(BOOL)flag
78 //Update any open window
79 [[sharedInstance window] setLevel:(alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel)];
83 + (void)setHideInBackground:(BOOL)flag
85 hideInBackground = flag;
88 //Update any open window
89 [[sharedInstance window] setHidesOnDeactivate:hideInBackground];
94 * @brief Window size and position autosave name
96 - (NSString *)adiumFrameAutosaveName
98 return KEY_AWAY_STATUS_WINDOW_FRAME;
102 * @brief Window loaded
104 - (void)windowDidLoad
106 //Call super first so we get our placement before performing autosizing
107 [super windowDidLoad];
109 [[self window] setLevel:(alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel)];
110 [[self window] setHidesOnDeactivate:hideInBackground];
112 /* Set a more reasonable minimum size after the window is sized using our nib's specification.
113 * NSPanel behaves oddly with minimum size... it seems to increase the nib-specified minimum by 11.
115 [[self window] setMinSize:NSMakeSize([[self window] minSize].width, 80)];
117 //Setup the textviews
118 [textView_singleStatus setHorizontallyResizable:NO];
119 [textView_singleStatus setVerticallyResizable:YES];
120 [textView_singleStatus setDrawsBackground:NO];
121 [textView_singleStatus setMinSize:NSZeroSize];
122 [scrollView_singleStatus setDrawsBackground:NO];
124 [self localizeButtons];
125 [self setupMultistatusTable];
127 [self configureStatusWindow];
129 [[adium notificationCenter] addObserver:self
130 selector:@selector(statusIconSetChanged:)
131 name:AIStatusIconSetDidChangeNotification
136 * @brief Window will close
138 * Release and clear the reference to our shared instance
140 - (void)windowWillClose:(id)sender
142 [super windowWillClose:sender];
144 /* Hack of the day. The table view crashes when the window is released out from under it after it has reloaded data because
145 * it thinks it needs display. It thinks that because we are animating the window's resizing process. We could do animate:NO
146 * in configureStatusWindow, but that wouldn't be as pretty.
148 [tableView_multiStatus setDataSource:nil];
150 //Clean up and release the shared instance
151 [sharedInstance autorelease]; sharedInstance = nil;
159 [_awayAccounts release]; _awayAccounts = nil;
160 [[adium notificationCenter] removeObserver:self];
166 * @brief Configure status window for the current account status(es)
168 - (void)configureStatusWindow
170 NSWindow *window = [self window];
171 BOOL allOnlineAccountsAreUnvailable;
172 AIStatusType activeUnvailableStatusType;
173 NSString *activeUnvailableStatusName = nil;
174 NSSet *relevantStatuses;
175 NSRect frame = [window frame];
178 [window setTitle:AILocalizedString(@"Current Status",nil)];
179 [_awayAccounts release]; _awayAccounts = nil;
181 relevantStatuses = [[adium statusController] activeUnavailableStatusesAndType:&activeUnvailableStatusType
182 withName:&activeUnvailableStatusName
183 allOnlineAccountsAreUnvailable:&allOnlineAccountsAreUnvailable];
185 if (allOnlineAccountsAreUnvailable && ([relevantStatuses count] == 1)) {
186 //Show the single status tab if all online accounts are unavailable and they are all in the same status state
188 NSAttributedString *statusTitle;
190 statusIcon = [AIStatusIcons statusIconForStatusName:activeUnvailableStatusName
191 statusType:activeUnvailableStatusType
192 iconType:AIStatusIconTab
193 direction:AIIconNormal];
194 statusTitle = [self attributedStatusTitleForStatus:[relevantStatuses anyObject]
195 withIcon:statusIcon];
197 [[textView_singleStatus textStorage] setAttributedString:statusTitle];
199 newHeight = [statusTitle heightWithWidth:[textView_singleStatus frame].size.width] + 65;
200 frame.origin.y -= (newHeight - frame.size.height);
201 frame.size.height = newHeight;
203 //Select the right tab view item
204 [tabView_configuration selectTabViewItemWithIdentifier:@"singlestatus"];
206 /* Show the multistatus tableview tab if accounts are in different states, which includes the case of only one
207 * away state being in use but not all online accounts currently making use of it.
211 _awayAccounts = [[self awayAccounts] retain];
213 [tableView_multiStatus reloadData];
215 requiredHeight = (([tableView_multiStatus rowHeight] + [tableView_multiStatus intercellSpacing].height) *
216 [_awayAccounts count]);
218 newHeight = requiredHeight + 65;
219 frame.origin.y -= (newHeight - frame.size.height);
220 frame.size.height = newHeight;
222 /* Multiple statuses */
223 [tabView_configuration selectTabViewItemWithIdentifier:@"multistatus"];
226 //Perform the window resizing as needed
227 [window setFrame:frame display:YES animate:YES];
231 * @brief Return the attributed status title for a status
233 * This method puts statusIcon into an NSTextAttachment and prefixes statusState's status message or title with it.
235 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon
237 NSMutableAttributedString *statusTitle;
238 NSTextAttachment *attachment;
239 NSTextAttachmentCell *cell;
240 NSAttributedString *statusMessage;
242 if ((statusMessage = [statusState statusMessage]) &&
243 ([statusMessage length])) {
244 //Use the status message if it is set
245 statusTitle = [statusMessage mutableCopy];
246 [[statusTitle mutableString] insertString:@" "
250 //If it isn't, use the title
251 NSDictionary *attributesDict;
253 attributesDict = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:0]
254 forKey:NSFontAttributeName];
256 statusTitle = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@",[statusState title]]
257 attributes:attributesDict];
260 //Insert the image at the beginning
261 cell = [[NSTextAttachmentCell alloc] init];
262 [cell setImage:statusIcon];
264 attachment = [[NSTextAttachment alloc] init];
265 [attachment setAttachmentCell:cell];
268 [statusTitle insertAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]
270 [attachment release];
272 return [statusTitle autorelease];
276 * @brief Return an array of all away accounts
278 - (NSArray *)awayAccounts
280 NSMutableArray *awayAccounts = [NSMutableArray array];
281 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
284 while ((account = [enumerator nextObject])) {
285 if ([account online] || [account integerStatusObjectForKey:@"Connecting"]) {
286 AIStatus *statusState = [account statusState];
287 if ([statusState statusType] != AIAvailableStatusType) {
288 [awayAccounts addObject:account];
297 * @brief Return from away
299 - (IBAction)returnFromAway:(id)sender
301 NSTabViewItem *selectedTabViewItem = [tabView_configuration selectedTabViewItem];
302 AIStatus *availableStatusState = [[adium statusController] defaultInitialStatusState];
306 if ([[selectedTabViewItem identifier] isEqualToString:@"singlestatus"]) {
307 //Put all accounts in the Available status state
308 [[adium statusController] applyState:availableStatusState
309 toAccounts:[self awayAccounts]];
313 NSArray *selectedAccounts;
315 selectedAccounts = [[tableView_multiStatus arrayOfSelectedItemsUsingSourceArray:_awayAccounts] copy];
317 if ([selectedAccounts count]) {
318 //Apply the available status state to only the selected accounts
319 [[adium statusController] applyState:availableStatusState
320 toAccounts:selectedAccounts];
322 //No selection: Put all accounts in the Available status state
323 [[adium statusController] applyState:availableStatusState
324 toAccounts:[self awayAccounts]];
327 [selectedAccounts release];
334 * @brief Perform initial setup for the multistatus table
336 - (void)setupMultistatusTable
338 AIImageTextCell *imageTextCell;
340 imageTextCell = [[AIImageTextCell alloc] init];
341 [imageTextCell setDrawsGradientHighlight:YES];
342 [[tableView_multiStatus tableColumnWithIdentifier:@"status"] setDataCell:imageTextCell];
343 [imageTextCell release];
346 #pragma mark Multiservice table view datasource
348 * @brief Number of rows in the table
350 - (int)numberOfRowsInTableView:(NSTableView *)tableView
352 return [_awayAccounts count];
356 * @brief Table values
358 * Object value is the account's formatted UID
360 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
362 AIAccount *account = [_awayAccounts objectAtIndex:row];
364 return [account formattedUID];
368 * @brief Will display a cell
370 * Set the image (status icon) and substring (status title) before display. Cell is an AIImageTextCell.
372 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
374 AIAccount *account = [_awayAccounts objectAtIndex:row];
376 [cell setImage:[AIStatusIcons statusIconForListObject:account
377 type:AIServiceIconSmall
378 direction:AIIconNormal]];
379 [cell setSubString:[[account statusState] title]];
382 - (void)localizeButtons
384 [button_return setLocalizedString:AILocalizedStringFromTableInBundle(@"Return",
386 [NSBundle bundleForClass:[self class]],
387 "Button to return from away in the away status window")];
390 - (void)statusIconSetChanged:(NSNotification *)inNotification
392 [self configureStatusWindow];