Frameworks/Adium Framework/Source/AIStatusMenu.m
author Zachary West <zacw@adium.im>
Mon Nov 23 17:02:25 2009 -0500 (2009-11-23)
changeset 2805 9c32a9b78f76
parent 1946 866f1f27b315
permissions -rw-r--r--
Revert the "never use last saved status" behavior. Re-fixes #6227.

The behavior was #ifdef'd out to appease MSN users, who wanted their status messages to propagate. Find some middle ground here: if a status message is currently set, a custom new-status window will retain it. Otherwise, it'll use the saved one. And we'll always use the last-saved status settings otherwise, so "autoreply" saving always works.
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