merged [21403]: Fixed an exception thrown when clearing all complete file transfers...
[adiumx.git] / Source / ESAwayStatusWindowController.m
blob31d31a7f99dd3b71c10244907e05730ef82b0f8e
1 //
2 //  ESAwayStatusWindowController.m
3 //  Adium
4 //
5 //  Created by Evan Schoenberg on 4/12/05.
6 //  Copyright 2006 The Adium Team. All rights reserved.
7 //
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;
34 @end
36 /*!
37  * @class ESAwayStatusWindowController
38  * @brief Window controller for the status window which optionally shows when one or more accounts are away or invisible
39  */
40 @implementation ESAwayStatusWindowController
42 static ESAwayStatusWindowController     *sharedInstance = nil;
43 static BOOL                                                     alwaysOnTop = NO;
44 static BOOL                                                     hideInBackground = NO;
46 /*!
47  * @brief Update the visibility of the status window
48  *
49  * Opens or closes the window if necessary.
50  *
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.
53  */
54 + (void)updateStatusWindowWithVisibility:(BOOL)shouldBeVisible
56         if (shouldBeVisible) {
57                 if (sharedInstance) {
58                         //Update the window's configuration
59                         [sharedInstance configureStatusWindow];
60                 } else {
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];
64                 }
65         
66         } else {
67                 if (sharedInstance) {
68                         //If the window is current visible, close it
69                         [sharedInstance closeWindow:nil];
70                 }
71         }
74 + (void)setAlwaysOnTop:(BOOL)flag
76         alwaysOnTop = flag;
77         
78         if (sharedInstance) {
79                 //Update any open window
80                 [[sharedInstance window] setLevel:(alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel)];
81         }
84 + (void)setHideInBackground:(BOOL)flag
86         hideInBackground = flag;
87         
88         if (sharedInstance) {
89                 //Update any open window
90                 [[sharedInstance window] setHidesOnDeactivate:hideInBackground];
91         }
92 }       
94 /*!
95  * @brief Window size and position autosave name
96  */
97 - (NSString *)adiumFrameAutosaveName
99         return KEY_AWAY_STATUS_WINDOW_FRAME;
103  * @brief Window loaded
104  */
105 - (void)windowDidLoad
107         //Call super first so we get our placement before performing autosizing
108         [super windowDidLoad];
109         
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.
115          */
116         [[self window] setMinSize:NSMakeSize([[self window] minSize].width, 80)];
117         
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];
129         
130         [[adium notificationCenter] addObserver:self
131                                                                    selector:@selector(statusIconSetChanged:)
132                                                                            name:AIStatusIconSetDidChangeNotification
133                                                                          object:nil];   
137  * @brief Window will close
139  * Release and clear the reference to our shared instance
140  */
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.
148          */
149         [tableView_multiStatus setDataSource:nil];
151     //Clean up and release the shared instance
152     [sharedInstance autorelease]; sharedInstance = nil;
156  * @brief Deallocate
157  */
158 - (void)dealloc
160         [_awayAccounts release]; _awayAccounts = nil;
161         [[adium notificationCenter] removeObserver:self];
163         [super dealloc];
167  * @brief Configure status window for the current account status(es)
168  */
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];
177         int                             newHeight;
178         
179         [window setTitle:AILocalizedString(@"Current Status",nil)];
180         [_awayAccounts release]; _awayAccounts = nil;
182         relevantStatuses = [[adium statusController] activeUnavailableStatusesAndType:&activeUnvailableStatusType 
183                                                                                                                                                  withName:&activeUnvailableStatusName
184                                                                                                    allOnlineAccountsAreUnvailable:&allOnlineAccountsAreUnvailable];
185         
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
188                 NSImage                         *statusIcon;
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];
197                 
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;
203                         
204                 //Select the right tab view item
205                 [tabView_configuration selectTabViewItemWithIdentifier:@"singlestatus"];
206         } else {
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.
209                  */
210                 int                             requiredHeight;
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"];
225         }
226         
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.
235  */
236 - (NSAttributedString *)attributedStatusTitleForStatus:(AIStatus *)statusState withIcon:(NSImage *)statusIcon
238         NSMutableAttributedString       *statusTitle;
239         NSTextAttachment                        *attachment;
240         NSTextAttachmentCell            *cell;
241         NSAttributedString                      *statusMessage;
242         
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:@" "
248                                                                                   atIndex:0];
250         } else {
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];
259         }
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];
267         [cell release];
269         [statusTitle insertAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]
270                                                                 atIndex:0];
271         [attachment release];
273         return [statusTitle autorelease];
277  * @brief Return an array of all away accounts
278  */
279 - (NSArray *)awayAccounts
280 {       
281         NSMutableArray  *awayAccounts = [NSMutableArray array]; 
282         NSEnumerator    *enumerator = [[[adium accountController] accounts] objectEnumerator];
283         AIAccount               *account;
284         
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];
290                         }
291                 }
292         }
294         return awayAccounts;
298  * @brief Return from away
299  */
300 - (IBAction)returnFromAway:(id)sender
302         NSTabViewItem   *selectedTabViewItem = [tabView_configuration selectedTabViewItem];
303         AIStatus                *availableStatusState = [[adium statusController] defaultInitialStatusState];
305         [self retain];
307         if ([[selectedTabViewItem identifier] isEqualToString:@"singlestatus"]) {
308                 //Put all accounts in the Available status state
309                 [[adium statusController] applyState:availableStatusState
310                                                                   toAccounts:[self awayAccounts]];
312         } else {
313                 //Multistatus
314                 NSArray *selectedAccounts;
315                 
316                 selectedAccounts = [[tableView_multiStatus arrayOfSelectedItemsUsingSourceArray:_awayAccounts] copy];
317                 
318                 if ([selectedAccounts count]) {
319                         //Apply the available status state to only the selected accounts
320                         [[adium statusController] applyState:availableStatusState
321                                                                           toAccounts:selectedAccounts];
322                 } else {
323                         //No selection: Put all accounts in the Available status state
324                         [[adium statusController] applyState:availableStatusState
325                                                                           toAccounts:[self awayAccounts]];
326                 }
328                 [selectedAccounts release];
329         }
330         
331         [self release];
335  * @brief Perform initial setup for the multistatus table
336  */
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
346  */
347 - (int)numberOfRowsInTableView:(NSTableView *)tableView
349         return [_awayAccounts count];
353  * @brief Table values
355  * Object value is the account's formatted UID
356  */
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.
368  */
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", 
382                                                                                                                                                  @"Buttons",
383                                                                                                                                                  [NSBundle bundleForClass:[self class]],
384                                                                                                                                                  "Button to return from away in the away status window")];
387 - (void)statusIconSetChanged:(NSNotification *)inNotification
389         [self configureStatusWindow];
392 @end