|
David@0
|
1 |
// |
|
David@0
|
2 |
// AIStatusMenu.m |
|
David@0
|
3 |
// Adium |
|
David@0
|
4 |
// |
|
David@0
|
5 |
// Created by Evan Schoenberg on 11/23/05. |
|
David@0
|
6 |
// |
|
David@0
|
7 |
|
|
David@0
|
8 |
#import <Adium/AIStatusMenu.h> |
|
David@0
|
9 |
#import <Adium/AIStatus.h> |
|
David@0
|
10 |
#import <Adium/AIStatusGroup.h> |
|
David@0
|
11 |
#import <Adium/AIAccount.h> |
|
David@0
|
12 |
#import <Adium/AIStatusControllerProtocol.h> |
|
David@0
|
13 |
#import <Adium/AIEditStateWindowController.h> |
|
David@0
|
14 |
#import <Adium/AIStatusIcons.h> |
|
David@0
|
15 |
#import <Adium/AISocialNetworkingStatusMenu.h> |
|
David@0
|
16 |
#import <Adium/AIAccountControllerProtocol.h> |
|
David@0
|
17 |
#import <Adium/AIMenuControllerProtocol.h> |
|
David@0
|
18 |
#import <AIUtilities/AIArrayAdditions.h> |
|
David@0
|
19 |
#import <AIUtilities/AIEventAdditions.h> |
|
David@0
|
20 |
#import <AIUtilities/AIMenuAdditions.h> |
|
David@0
|
21 |
#import <AIUtilities/AIStringAdditions.h> |
|
David@0
|
22 |
|
|
David@0
|
23 |
#define STATUS_TITLE_CUSTOM [AILocalizedString(@"Custom", nil) stringByAppendingEllipsis] |
|
David@0
|
24 |
#define STATE_TITLE_MENU_LENGTH 30 |
|
David@0
|
25 |
|
|
David@84
|
26 |
@interface AIStatusMenu () |
|
David@759
|
27 |
- (id)initWithDelegate:(id<AIStatusMenuDelegate>)inDelegate; |
|
David@0
|
28 |
@end |
|
David@0
|
29 |
|
|
David@0
|
30 |
@implementation AIStatusMenu |
|
David@0
|
31 |
|
|
David@759
|
32 |
+ (id)statusMenuWithDelegate:(id<AIStatusMenuDelegate>)inDelegate |
|
David@0
|
33 |
{ |
|
David@0
|
34 |
return [[[self alloc] initWithDelegate:inDelegate] autorelease]; |
|
David@0
|
35 |
} |
|
David@0
|
36 |
|
|
David@759
|
37 |
- (id)initWithDelegate:(id<AIStatusMenuDelegate>)inDelegate |
|
David@0
|
38 |
{ |
|
David@0
|
39 |
if ((self = [super init])) { |
|
David@513
|
40 |
self.delegate = inDelegate; |
|
David@0
|
41 |
|
|
David@0
|
42 |
NSParameterAssert([delegate respondsToSelector:@selector(statusMenu:didRebuildStatusMenuItems:)]); |
|
David@0
|
43 |
|
|
David@0
|
44 |
menuItemArray = [[NSMutableArray alloc] init]; |
|
David@0
|
45 |
stateMenuItemsAlreadyValidated = [[NSMutableSet alloc] init]; |
|
David@0
|
46 |
|
|
David@0
|
47 |
[self rebuildMenu]; |
|
David@0
|
48 |
|
|
David@1109
|
49 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
50 |
selector:@selector(stateArrayChanged:) |
|
David@0
|
51 |
name:AIStatusStateArrayChangedNotification |
|
David@0
|
52 |
object:nil]; |
|
David@1109
|
53 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
54 |
selector:@selector(activeStatusStateChanged:) |
|
David@0
|
55 |
name:AIStatusActiveStateChangedNotification |
|
David@0
|
56 |
object:nil]; |
|
David@0
|
57 |
|
|
David@0
|
58 |
//Update our state menus when the state array or status icon set changes |
|
David@1109
|
59 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
60 |
selector:@selector(statusIconSetChanged:) |
|
David@0
|
61 |
name:AIStatusIconSetDidChangeNotification |
|
David@0
|
62 |
object:nil]; |
|
David@0
|
63 |
} |
|
David@0
|
64 |
|
|
David@0
|
65 |
return self; |
|
David@0
|
66 |
} |
|
David@0
|
67 |
|
|
David@0
|
68 |
- (void)dealloc |
|
David@0
|
69 |
{ |
|
David@1109
|
70 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
David@0
|
71 |
[stateMenuItemsAlreadyValidated release]; |
|
David@0
|
72 |
[menuItemArray release]; |
|
David@0
|
73 |
|
|
David@513
|
74 |
self.delegate = nil; |
|
David@0
|
75 |
|
|
David@0
|
76 |
[super dealloc]; |
|
David@0
|
77 |
} |
|
David@0
|
78 |
|
|
David@513
|
79 |
@synthesize delegate; |
|
David@0
|
80 |
|
|
David@0
|
81 |
/*! |
|
David@0
|
82 |
* @brief The delegate is just too good for the menu items we've created; it will create all of the ones it wants on its own |
|
David@0
|
83 |
*/ |
|
David@0
|
84 |
- (void)delegateWillReplaceAllMenuItems |
|
David@0
|
85 |
{ |
|
David@0
|
86 |
//Remove the menu items from needing update |
|
David@0
|
87 |
[stateMenuItemsAlreadyValidated removeAllObjects]; |
|
David@0
|
88 |
|
|
David@0
|
89 |
//Clear the array itself |
|
David@0
|
90 |
[menuItemArray removeAllObjects]; |
|
David@0
|
91 |
} |
|
David@0
|
92 |
|
|
David@0
|
93 |
/*! |
|
David@0
|
94 |
* @brief The delegate created its own menu items it wants us to track and update |
|
David@0
|
95 |
*/ |
|
David@0
|
96 |
- (void)delegateCreatedMenuItems:(NSArray *)addedMenuItems |
|
David@0
|
97 |
{ |
|
David@0
|
98 |
//Now add the items we were given |
|
David@0
|
99 |
[menuItemArray addObjectsFromArray:addedMenuItems]; |
|
David@0
|
100 |
} |
|
David@0
|
101 |
|
|
David@0
|
102 |
- (void)stateArrayChanged:(NSNotification *)notification |
|
David@0
|
103 |
{ |
|
David@0
|
104 |
[self rebuildMenu]; |
|
David@0
|
105 |
} |
|
David@0
|
106 |
|
|
David@0
|
107 |
- (void)activeStatusStateChanged:(NSNotification *)notification |
|
David@0
|
108 |
{ |
|
David@0
|
109 |
[stateMenuItemsAlreadyValidated removeAllObjects]; |
|
David@0
|
110 |
} |
|
David@0
|
111 |
|
|
David@0
|
112 |
- (void)statusIconSetChanged:(NSNotification *)notification |
|
David@0
|
113 |
{ |
|
David@0
|
114 |
[self rebuildMenu]; |
|
David@0
|
115 |
} |
|
David@0
|
116 |
|
|
David@0
|
117 |
/*! |
|
David@0
|
118 |
* @brief Generate the custom menu item for a status type |
|
David@0
|
119 |
*/ |
|
David@0
|
120 |
- (NSMenuItem *)customMenuItemForStatusType:(AIStatusType)statusType |
|
David@0
|
121 |
{ |
|
David@0
|
122 |
NSMenuItem *menuItem; |
|
David@0
|
123 |
|
|
David@0
|
124 |
menuItem = [[NSMenuItem alloc] initWithTitle:STATUS_TITLE_CUSTOM |
|
David@0
|
125 |
target:self |
|
David@0
|
126 |
action:@selector(selectCustomState:) |
|
David@0
|
127 |
keyEquivalent:@""]; |
|
David@0
|
128 |
|
|
David@0
|
129 |
[menuItem setImage:[AIStatusIcons statusIconForStatusName:nil |
|
David@0
|
130 |
statusType:statusType |
|
David@0
|
131 |
iconType:AIStatusIconMenu |
|
David@0
|
132 |
direction:AIIconNormal]]; |
|
David@0
|
133 |
[menuItem setTag:statusType]; |
|
David@0
|
134 |
|
|
David@0
|
135 |
return [menuItem autorelease]; |
|
David@0
|
136 |
} |
|
David@0
|
137 |
|
|
David@0
|
138 |
/*! |
|
David@0
|
139 |
* @brief Rebuild the menu |
|
David@0
|
140 |
*/ |
|
David@0
|
141 |
- (void)rebuildMenu |
|
David@0
|
142 |
{ |
|
David@0
|
143 |
NSEnumerator *enumerator; |
|
David@0
|
144 |
NSMenuItem *menuItem; |
|
David@0
|
145 |
AIStatus *statusState; |
|
David@0
|
146 |
AIStatusType currentStatusType = AIAvailableStatusType; |
|
David@0
|
147 |
AIStatusMutabilityType currentStatusMutabilityType = AILockedStatusState; |
|
David@0
|
148 |
|
|
David@100
|
149 |
[adium.menuController delayMenuItemPostProcessing]; |
|
David@0
|
150 |
|
|
David@0
|
151 |
if ([delegate respondsToSelector:@selector(statusMenu:willRemoveStatusMenuItems:)]) { |
|
David@0
|
152 |
[delegate statusMenu:self willRemoveStatusMenuItems:menuItemArray]; |
|
David@0
|
153 |
} |
|
David@0
|
154 |
|
|
David@0
|
155 |
[menuItemArray removeAllObjects]; |
|
David@0
|
156 |
[stateMenuItemsAlreadyValidated removeAllObjects]; |
|
David@0
|
157 |
|
|
David@0
|
158 |
/* Create a menu item for each state. States must first be sorted such that states of the same AIStatusType |
|
David@0
|
159 |
* are grouped together. |
|
David@0
|
160 |
*/ |
|
David@100
|
161 |
enumerator = [[adium.statusController sortedFullStateArray] objectEnumerator]; |
|
David@0
|
162 |
while ((statusState = [enumerator nextObject])) { |
|
David@0
|
163 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|
David@837
|
164 |
AIStatusType thisStatusType = statusState.statusType; |
|
David@0
|
165 |
AIStatusType thisStatusMutabilityType = [statusState mutabilityType]; |
|
David@0
|
166 |
|
|
David@0
|
167 |
if ((currentStatusMutabilityType != AISecondaryLockedStatusState) && |
|
David@0
|
168 |
(thisStatusMutabilityType == AISecondaryLockedStatusState)) { |
|
David@0
|
169 |
//Add the custom item, as we are ending this group |
|
David@0
|
170 |
[menuItemArray addObject:[self customMenuItemForStatusType:currentStatusType]]; |
|
David@0
|
171 |
|
|
David@0
|
172 |
//Add a divider when we switch to a secondary locked group |
|
David@0
|
173 |
[menuItemArray addObject:[NSMenuItem separatorItem]]; |
|
David@0
|
174 |
} |
|
David@0
|
175 |
|
|
David@0
|
176 |
//We treat Invisible statuses as being the same as Away for purposes of the menu |
|
David@0
|
177 |
if (thisStatusType == AIInvisibleStatusType) thisStatusType = AIAwayStatusType; |
|
David@0
|
178 |
|
|
David@0
|
179 |
/* Add the "Custom..." state option and a separatorItem before beginning to add items for a new statusType |
|
David@0
|
180 |
* Sorting the menu items before enumerating means that we know our statuses are sorted first by statusType |
|
David@0
|
181 |
*/ |
|
David@0
|
182 |
if ((currentStatusType != thisStatusType) && |
|
David@0
|
183 |
(currentStatusType != AIOfflineStatusType)) { |
|
David@0
|
184 |
|
|
David@0
|
185 |
//Don't include a Custom item after the secondary locked group, as it was already included |
|
David@0
|
186 |
if ((currentStatusMutabilityType != AISecondaryLockedStatusState)) { |
|
David@0
|
187 |
[menuItemArray addObject:[self customMenuItemForStatusType:currentStatusType]]; |
|
David@0
|
188 |
} |
|
David@0
|
189 |
|
|
David@0
|
190 |
//Add a divider |
|
David@0
|
191 |
[menuItemArray addObject:[NSMenuItem separatorItem]]; |
|
David@0
|
192 |
|
|
David@0
|
193 |
currentStatusType = thisStatusType; |
|
David@0
|
194 |
} |
|
David@0
|
195 |
|
|
David@0
|
196 |
menuItem = [[NSMenuItem alloc] initWithTitle:[AIStatusMenu titleForMenuDisplayOfState:statusState] |
|
David@0
|
197 |
target:self |
|
David@0
|
198 |
action:@selector(selectState:) |
|
David@0
|
199 |
keyEquivalent:@""]; |
|
David@0
|
200 |
|
|
David@0
|
201 |
if ([statusState isKindOfClass:[AIStatus class]]) { |
|
David@0
|
202 |
[menuItem setToolTip:[statusState statusMessageTooltipString]]; |
|
David@0
|
203 |
|
|
David@0
|
204 |
} else { |
|
David@0
|
205 |
/* AIStatusGroup */ |
|
David@0
|
206 |
[menuItem setSubmenu:[(AIStatusGroup *)statusState statusSubmenuNotifyingTarget:self |
|
David@0
|
207 |
action:@selector(selectState:)]]; |
|
David@0
|
208 |
} |
|
David@0
|
209 |
[menuItem setRepresentedObject:[NSDictionary dictionaryWithObject:statusState |
|
David@0
|
210 |
forKey:@"AIStatus"]]; |
|
David@0
|
211 |
[menuItem setTag:currentStatusType]; |
|
David@0
|
212 |
[menuItem setImage:[statusState menuIcon]]; |
|
David@0
|
213 |
[menuItemArray addObject:menuItem]; |
|
David@0
|
214 |
[menuItem release]; |
|
David@0
|
215 |
|
|
David@0
|
216 |
currentStatusMutabilityType = thisStatusMutabilityType; |
|
David@0
|
217 |
[pool release]; |
|
David@0
|
218 |
} |
|
David@0
|
219 |
|
|
David@0
|
220 |
if (currentStatusType != AIOfflineStatusType) { |
|
David@0
|
221 |
/* Add the last "Custom..." state option for the last statusType we handled, |
|
David@0
|
222 |
* which didn't get a "Custom..." item yet. At present, our last status type should always be |
|
David@0
|
223 |
* our AIOfflineStatusType, so this will never be executed and just exists for completeness. |
|
David@0
|
224 |
*/ |
|
David@0
|
225 |
[menuItemArray addObject:[self customMenuItemForStatusType:currentStatusType]]; |
|
David@0
|
226 |
} |
|
David@0
|
227 |
|
|
David@0
|
228 |
//Now that we are done creating the menu items, tell the plugin about them |
|
David@0
|
229 |
[delegate statusMenu:self didRebuildStatusMenuItems:menuItemArray]; |
|
David@0
|
230 |
|
|
David@100
|
231 |
[adium.menuController endDelayMenuItemPostProcessing]; |
|
David@0
|
232 |
} |
|
David@0
|
233 |
|
|
David@0
|
234 |
/*! |
|
David@0
|
235 |
* @brief Menu validation |
|
David@0
|
236 |
* |
|
David@0
|
237 |
* Our state menu items should always be active, so always return YES for validation. |
|
David@0
|
238 |
* |
|
David@0
|
239 |
* Here we lazily set the state of our menu items if our stateMenuItemsAlreadyValidated set indicates it is needed. |
|
David@0
|
240 |
* |
|
David@0
|
241 |
* Random note: stateMenuItemsAlreadyValidated will almost never have a count of 0 because separatorItems |
|
David@0
|
242 |
* get included but never get validated. |
|
David@0
|
243 |
*/ |
|
David@0
|
244 |
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem |
|
David@0
|
245 |
{ |
|
David@0
|
246 |
if (![stateMenuItemsAlreadyValidated containsObject:menuItem]) { |
|
David@0
|
247 |
NSDictionary *dict = [menuItem representedObject]; |
|
David@0
|
248 |
AIAccount *account = [dict objectForKey:@"AIAccount"]; |
|
David@0
|
249 |
AIStatus *menuItemStatusState = [dict objectForKey:@"AIStatus"]; |
|
David@0
|
250 |
|
|
David@0
|
251 |
if (account) { |
|
David@0
|
252 |
/* Account-specific menu items */ |
|
David@419
|
253 |
AIStatus *appropriateActiveStatusState = account.statusState; |
|
David@0
|
254 |
|
|
David@0
|
255 |
/* Our "Custom..." menu choice has a nil represented object. If the appropriate active search state is |
|
David@0
|
256 |
* in our array of states from which we made menu items, we'll be searching to match it. If it isn't, |
|
David@0
|
257 |
* we have a custom state and will be searching for the custom item of the right type, switching all other |
|
David@0
|
258 |
* menu items to NSOffState. |
|
David@0
|
259 |
*/ |
|
David@419
|
260 |
if ([adium.statusController.flatStatusSet containsObject:appropriateActiveStatusState]) { |
|
David@0
|
261 |
//If the search state is in the array so is a saved state, search for the match |
|
David@419
|
262 |
if ((menuItemStatusState == appropriateActiveStatusState) || |
|
David@0
|
263 |
([menuItemStatusState isKindOfClass:[AIStatusGroup class]] && |
|
David@419
|
264 |
[(AIStatusGroup *)menuItemStatusState enclosesStatusState:appropriateActiveStatusState])) { |
|
David@0
|
265 |
if ([menuItem state] != NSOnState) [menuItem setState:NSOnState]; |
|
David@0
|
266 |
} else { |
|
David@0
|
267 |
if ([menuItem state] != NSOffState) [menuItem setState:NSOffState]; |
|
David@0
|
268 |
} |
|
David@0
|
269 |
} else { |
|
David@0
|
270 |
//If there is not a status state, we are in a Custom state. Search for the correct Custom item. |
|
David@0
|
271 |
if (menuItemStatusState) { |
|
David@0
|
272 |
//If the menu item has an associated state, it's always off. |
|
David@0
|
273 |
if ([menuItem state] != NSOffState) [menuItem setState:NSOffState]; |
|
David@0
|
274 |
} else { |
|
David@0
|
275 |
//If it doesn't, check the tag to see if it should be on or off. |
|
David@837
|
276 |
if ([menuItem tag] == appropriateActiveStatusState.statusType) { |
|
David@0
|
277 |
if ([menuItem state] != NSOnState) [menuItem setState:NSOnState]; |
|
David@0
|
278 |
} else { |
|
David@0
|
279 |
if ([menuItem state] != NSOffState) [menuItem setState:NSOffState]; |
|
David@0
|
280 |
} |
|
David@0
|
281 |
} |
|
David@0
|
282 |
} |
|
David@0
|
283 |
} else { |
|
David@0
|
284 |
/* General menu items */ |
|
David@100
|
285 |
NSSet *allActiveStatusStates = [adium.statusController allActiveStatusStates]; |
|
David@0
|
286 |
int onState = (([allActiveStatusStates count] == 1) ? NSOnState : NSMixedState); |
|
David@0
|
287 |
|
|
David@0
|
288 |
if (menuItemStatusState) { |
|
David@0
|
289 |
//If this menu item has a status state, set it to the right on state if that state is active |
|
David@0
|
290 |
if ([allActiveStatusStates containsObject:menuItemStatusState] || |
|
David@0
|
291 |
([menuItemStatusState isKindOfClass:[AIStatusGroup class]] && |
|
David@0
|
292 |
[(AIStatusGroup *)menuItemStatusState enclosesStatusStateInSet:allActiveStatusStates])) { |
|
David@0
|
293 |
if ([menuItem state] != onState) [menuItem setState:onState]; |
|
David@0
|
294 |
} else { |
|
David@0
|
295 |
if ([menuItem state] != NSOffState) [menuItem setState:NSOffState]; |
|
David@0
|
296 |
} |
|
David@0
|
297 |
} else { |
|
David@0
|
298 |
//If it doesn't, check the tag to see if it should be on or off by looking for a matching custom state |
|
David@0
|
299 |
NSEnumerator *activeStatusStatesEnumerator = [allActiveStatusStates objectEnumerator]; |
|
David@418
|
300 |
NSSet *flatStatusSet = adium.statusController.flatStatusSet; |
|
David@0
|
301 |
AIStatus *statusState; |
|
David@0
|
302 |
BOOL foundCorrectStatusState = NO; |
|
David@0
|
303 |
|
|
David@0
|
304 |
while (!foundCorrectStatusState && (statusState = [activeStatusStatesEnumerator nextObject])) { |
|
David@0
|
305 |
/* We found a custom match if our array of menu item states doesn't contain this state and |
|
David@0
|
306 |
* its statusType matches the menuItem's tag. |
|
David@0
|
307 |
*/ |
|
David@0
|
308 |
foundCorrectStatusState = (![flatStatusSet containsObject:statusState] && |
|
David@837
|
309 |
([menuItem tag] == statusState.statusType)); |
|
David@0
|
310 |
} |
|
David@0
|
311 |
|
|
David@0
|
312 |
if (foundCorrectStatusState) { |
|
David@0
|
313 |
if ([menuItem state] != NSOnState) [menuItem setState:onState]; |
|
David@0
|
314 |
} else { |
|
David@0
|
315 |
if ([menuItem state] != NSOffState) [menuItem setState:NSOffState]; |
|
David@0
|
316 |
} |
|
David@0
|
317 |
} |
|
David@0
|
318 |
} |
|
David@0
|
319 |
|
|
David@0
|
320 |
[stateMenuItemsAlreadyValidated addObject:menuItem]; |
|
David@0
|
321 |
} |
|
David@0
|
322 |
|
|
David@0
|
323 |
return YES; |
|
David@0
|
324 |
} |
|
David@0
|
325 |
|
|
David@0
|
326 |
/*! |
|
David@0
|
327 |
* @brief Select a state menu item |
|
David@0
|
328 |
* |
|
David@0
|
329 |
* Invoked by a state menu item, sets the state corresponding to the menu item as the active state. |
|
David@0
|
330 |
* |
|
David@0
|
331 |
* If the representedObject NSDictionary has an @"AIAccount" object, set the state just for the appropriate AIAccount. |
|
David@0
|
332 |
* Otherwise, set the state globally. |
|
David@0
|
333 |
*/ |
|
David@0
|
334 |
- (void)selectState:(id)sender |
|
David@0
|
335 |
{ |
|
David@0
|
336 |
NSDictionary *dict = [sender representedObject]; |
|
David@0
|
337 |
AIStatusItem *statusItem = [dict objectForKey:@"AIStatus"]; |
|
David@0
|
338 |
AIAccount *account = [dict objectForKey:@"AIAccount"]; |
|
David@0
|
339 |
|
|
David@0
|
340 |
if ([statusItem isKindOfClass:[AIStatusGroup class]]) { |
|
David@0
|
341 |
statusItem = [(AIStatusGroup *)statusItem anyContainedStatus]; |
|
David@0
|
342 |
} |
|
David@0
|
343 |
|
|
David@0
|
344 |
/* Random undocumented feature of the moment... hold option and select a state to bring up the custom status window |
|
David@0
|
345 |
* for modifying and then setting it. Alternately, select an active status (one in the on state) to do the same. |
|
David@0
|
346 |
* Selecting a mixed state item should still select it to switch to full-on (all accounts). |
|
Evan@632
|
347 |
*/ |
|
Evan@632
|
348 |
NSEventType eventType = [[NSApp currentEvent] type]; |
|
Evan@632
|
349 |
BOOL keyEvent = (eventType == NSKeyDown || eventType == NSKeyUp); |
|
Evan@632
|
350 |
BOOL isOptionClick = [NSEvent optionKey] && !keyEvent; |
|
Evan@632
|
351 |
if (isOptionClick || |
|
David@837
|
352 |
(([sender state] == NSOnState) && (statusItem.statusType != AIOfflineStatusType))) { |
|
David@0
|
353 |
[AIEditStateWindowController editCustomState:(AIStatus *)statusItem |
|
David@837
|
354 |
forType:statusItem.statusType |
|
David@0
|
355 |
andAccount:account |
|
David@0
|
356 |
withSaveOption:YES |
|
David@0
|
357 |
onWindow:nil |
|
David@100
|
358 |
notifyingTarget:adium.statusController]; |
|
David@0
|
359 |
|
|
David@0
|
360 |
} else { |
|
David@0
|
361 |
if (account) { |
|
David@0
|
362 |
BOOL shouldRebuild; |
|
David@0
|
363 |
|
|
David@837
|
364 |
shouldRebuild = [adium.statusController removeIfNecessaryTemporaryStatusState:account.statusState]; |
|
David@0
|
365 |
[account setStatusState:(AIStatus *)statusItem]; |
|
David@0
|
366 |
|
|
David@0
|
367 |
//Enable the account if it isn't currently enabled |
|
David@837
|
368 |
if (!account.enabled && statusItem.statusType != AIOfflineStatusType) { |
|
David@0
|
369 |
[account setEnabled:YES]; |
|
David@0
|
370 |
} |
|
David@0
|
371 |
|
|
David@0
|
372 |
if (shouldRebuild) { |
|
David@0
|
373 |
//Rebuild our menus if there was a change |
|
David@1109
|
374 |
[[NSNotificationCenter defaultCenter] postNotificationName:AIStatusStateArrayChangedNotification object:nil]; |
|
David@0
|
375 |
} |
|
David@0
|
376 |
|
|
David@0
|
377 |
} else { |
|
David@100
|
378 |
[adium.statusController setActiveStatusState:(AIStatus *)statusItem]; |
|
David@0
|
379 |
} |
|
David@0
|
380 |
} |
|
David@0
|
381 |
} |
|
David@0
|
382 |
|
|
David@0
|
383 |
/*! |
|
David@0
|
384 |
* @brief Select the custom state menu item |
|
David@0
|
385 |
* |
|
David@0
|
386 |
* Invoked by the custom state menu item, opens a custom state window. |
|
David@0
|
387 |
* If the representedObject NSDictionary has an @"AIAccount" object, configure just for the appropriate AIAccount. |
|
David@0
|
388 |
* Otherwise, configure globally. |
|
David@0
|
389 |
*/ |
|
David@0
|
390 |
- (IBAction)selectCustomState:(id)sender |
|
David@0
|
391 |
{ |
|
David@0
|
392 |
NSDictionary *dict = [sender representedObject]; |
|
David@0
|
393 |
AIAccount *account = [dict objectForKey:@"AIAccount"]; |
|
David@0
|
394 |
AIStatusType statusType = [sender tag]; |
|
David@0
|
395 |
AIStatus *baseStatusState; |
|
David@0
|
396 |
|
|
David@0
|
397 |
if (account) { |
|
David@837
|
398 |
baseStatusState = account.statusState; |
|
David@0
|
399 |
} else { |
|
catfish@1829
|
400 |
baseStatusState = adium.statusController.activeStatusState; |
|
David@0
|
401 |
} |
|
David@0
|
402 |
|
|
David@0
|
403 |
/* If we are going to a custom state of a different type, we don't want to prefill with baseStatusState as it stands. |
|
David@0
|
404 |
* Instead, we load the last used status of that type. |
|
David@0
|
405 |
*/ |
|
David@837
|
406 |
if ((baseStatusState.statusType != statusType)) { |
|
David@95
|
407 |
NSDictionary *lastStatusStates = [adium.preferenceController preferenceForKey:@"LastStatusStates" |
|
David@0
|
408 |
group:PREF_GROUP_STATUS_PREFERENCES]; |
|
David@0
|
409 |
NSData *lastStatusStateData = [lastStatusStates objectForKey:[[NSNumber numberWithInt:statusType] stringValue]]; |
|
David@0
|
410 |
AIStatus *lastStatusStateOfThisType = (lastStatusStateData ? |
|
David@0
|
411 |
[NSKeyedUnarchiver unarchiveObjectWithData:lastStatusStateData] : |
|
David@0
|
412 |
nil); |
|
zacw@2805
|
413 |
if (lastStatusStateOfThisType) { |
|
zacw@2805
|
414 |
// Restore the current status message into this last-saved variety, since users tend want to keep them. |
|
zacw@2805
|
415 |
// If it doesn't exist, use the last-saved status message. |
|
zacw@2805
|
416 |
if (baseStatusState.statusMessage.length) { |
|
zacw@2805
|
417 |
lastStatusStateOfThisType.statusMessage = baseStatusState.statusMessage; |
|
zacw@2805
|
418 |
} |
|
zacw@2805
|
419 |
|
|
zacw@2805
|
420 |
baseStatusState = [[lastStatusStateOfThisType retain] autorelease]; |
|
zacw@2805
|
421 |
} |
|
David@0
|
422 |
} |
|
Evan@295
|
423 |
|
|
David@0
|
424 |
[AIEditStateWindowController editCustomState:baseStatusState |
|
David@0
|
425 |
forType:statusType |
|
David@0
|
426 |
andAccount:account |
|
David@0
|
427 |
withSaveOption:YES |
|
David@0
|
428 |
onWindow:nil |
|
David@100
|
429 |
notifyingTarget:adium.statusController]; |
|
David@0
|
430 |
} |
|
David@0
|
431 |
|
|
David@0
|
432 |
#pragma mark - |
|
David@0
|
433 |
#pragma mark Class methods |
|
David@0
|
434 |
+ (NSMenu *)staticStatusStatesMenuNotifyingTarget:(id)target selector:(SEL)selector |
|
David@0
|
435 |
{ |
|
David@0
|
436 |
NSMenu *statusStatesMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init]; |
|
David@0
|
437 |
NSEnumerator *enumerator; |
|
David@0
|
438 |
AIStatus *statusState; |
|
David@0
|
439 |
AIStatusType currentStatusType = AIAvailableStatusType; |
|
David@0
|
440 |
NSMenuItem *menuItem; |
|
David@0
|
441 |
|
|
David@0
|
442 |
[statusStatesMenu setMenuChangedMessagesEnabled:NO]; |
|
David@0
|
443 |
[statusStatesMenu setAutoenablesItems:NO]; |
|
David@0
|
444 |
|
|
David@0
|
445 |
if (!target && !selector) { |
|
David@0
|
446 |
//Need to set a target and action for items with submenus (AIStatusGroups) to be selectable... so if we're not given one, set one. |
|
David@0
|
447 |
target = self; |
|
David@0
|
448 |
selector = @selector(dummyAction:); |
|
David@0
|
449 |
} |
|
David@0
|
450 |
|
|
David@0
|
451 |
/* Create a menu item for each state. States must first be sorted such that states of the same AIStatusType |
|
David@0
|
452 |
* are grouped together. |
|
David@0
|
453 |
*/ |
|
David@100
|
454 |
enumerator = [[adium.statusController sortedFullStateArray] objectEnumerator]; |
|
David@0
|
455 |
while ((statusState = [enumerator nextObject])) { |
|
David@837
|
456 |
AIStatusType thisStatusType = statusState.statusType; |
|
David@0
|
457 |
|
|
David@0
|
458 |
//We treat Invisible statuses as being the same as Away for purposes of the menu |
|
David@0
|
459 |
if (thisStatusType == AIInvisibleStatusType) thisStatusType = AIAwayStatusType; |
|
David@0
|
460 |
|
|
David@0
|
461 |
if (currentStatusType != thisStatusType) { |
|
David@0
|
462 |
//Add a divider between each type of status |
|
David@0
|
463 |
[statusStatesMenu addItem:[NSMenuItem separatorItem]]; |
|
David@0
|
464 |
currentStatusType = thisStatusType; |
|
David@0
|
465 |
} |
|
David@0
|
466 |
|
|
David@0
|
467 |
menuItem = [[NSMenuItem alloc] initWithTitle:[AIStatusMenu titleForMenuDisplayOfState:statusState] |
|
David@0
|
468 |
target:target |
|
David@0
|
469 |
action:selector |
|
David@0
|
470 |
keyEquivalent:@""]; |
|
David@0
|
471 |
|
|
David@0
|
472 |
[menuItem setImage:[statusState menuIcon]]; |
|
David@837
|
473 |
[menuItem setTag:statusState.statusType]; |
|
David@0
|
474 |
[menuItem setRepresentedObject:[NSDictionary dictionaryWithObject:statusState |
|
David@0
|
475 |
forKey:@"AIStatus"]]; |
|
David@0
|
476 |
if ([statusState isKindOfClass:[AIStatus class]]) { |
|
David@0
|
477 |
[menuItem setToolTip:[statusState statusMessageTooltipString]]; |
|
David@0
|
478 |
|
|
David@0
|
479 |
} else { |
|
David@0
|
480 |
/* AIStatusGroup */ |
|
David@0
|
481 |
[menuItem setSubmenu:[(AIStatusGroup *)statusState statusSubmenuNotifyingTarget:target |
|
David@0
|
482 |
action:selector]]; |
|
David@0
|
483 |
} |
|
David@0
|
484 |
|
|
David@0
|
485 |
[statusStatesMenu addItem:menuItem]; |
|
David@0
|
486 |
[menuItem release]; |
|
David@0
|
487 |
} |
|
David@0
|
488 |
|
|
David@0
|
489 |
[statusStatesMenu setMenuChangedMessagesEnabled:YES]; |
|
David@0
|
490 |
|
|
David@0
|
491 |
return [statusStatesMenu autorelease]; |
|
David@0
|
492 |
} |
|
David@0
|
493 |
|
|
David@0
|
494 |
/*! |
|
David@0
|
495 |
* @brief Determine a string to use as a menu title |
|
David@0
|
496 |
* |
|
David@0
|
497 |
* This method truncates a state title string for display as a menu item. |
|
David@0
|
498 |
* Wide menus aren't pretty and may cause crashing in certain versions of OS X, so all state |
|
David@0
|
499 |
* titles should be run through this method before being used as menu item titles. |
|
David@0
|
500 |
* |
|
David@0
|
501 |
* @param statusState The state for which we want a title |
|
David@0
|
502 |
* |
|
David@0
|
503 |
* @result An appropriate NSString title |
|
David@0
|
504 |
*/ |
|
David@0
|
505 |
+ (NSString *)titleForMenuDisplayOfState:(AIStatusItem *)statusState |
|
David@0
|
506 |
{ |
|
David@0
|
507 |
NSString *title = [statusState title]; |
|
David@0
|
508 |
|
|
David@0
|
509 |
/* Why plus 3? Say STATE_TITLE_MENU_LENGTH was 7, and the title is @"ABCDEFGHIJ". |
|
David@0
|
510 |
* The shortened title will be @"ABCDEFG..." which looks to be just as long - even |
|
David@0
|
511 |
* if the ellipsis is an ellipsis character and therefore technically two characters |
|
David@0
|
512 |
* shorter. Better to just use the full string, which appears as being the same length. |
|
David@0
|
513 |
*/ |
|
David@0
|
514 |
if ([title length] > (STATE_TITLE_MENU_LENGTH + 3)) { |
|
David@0
|
515 |
title = [title stringWithEllipsisByTruncatingToLength:STATE_TITLE_MENU_LENGTH]; |
|
David@0
|
516 |
} |
|
David@0
|
517 |
|
|
David@0
|
518 |
return title; |
|
David@0
|
519 |
} |
|
David@0
|
520 |
|
|
David@0
|
521 |
+ (void)dummyAction:(id)sender {}; |
|
David@0
|
522 |
|
|
David@0
|
523 |
@end |