2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 #import "AIMenuController.h"
20 #import <Adium/AIAccount.h>
21 #import <Adium/AIChat.h>
22 #import <Adium/AIListContact.h>
23 #import <Adium/AIListObject.h>
24 #import <Adium/AIMetaContact.h>
25 #import <AIUtilities/AIMenuAdditions.h>
26 #import <AIUtilities/AIStringAdditions.h>
28 @interface AIMenuController (PRIVATE)
29 - (void)localizeMenuTitles;
30 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray usingMenu:(NSMenu *)inMenu;
31 - (void)addMenuItemsForContact:(AIListContact *)inContact toMenu:(NSMenu *)workingMenu separatorItem:(BOOL *)separatorItem;
34 @implementation AIMenuController
38 if ((self = [super init])) {
39 //Set up our contextual menu stuff
40 contextualMenu = [[NSMenu alloc] init];
41 [contextualMenu setDelegate:self];
43 contextualMenuItemDict = [[NSMutableDictionary alloc] init];
44 currentContextMenuObject = nil;
46 textViewContextualMenu = [[NSMenu alloc] init];
47 [textViewContextualMenu setDelegate:self];
49 contextualMenu_TextView = nil;
57 //Build the array of menu locations
58 locationArray = [[NSMutableArray alloc] initWithObjects:menu_Adium_About, menu_Adium_Preferences, menu_Adium_Other,
59 menu_File_New, menu_File_Close, menu_File_Save, menu_File_Accounts, menu_File_Additions,
60 menu_Edit_Bottom, menu_Edit_Links, menu_Edit_Additions,
61 menu_View_General, menu_View_Sorting, menu_View_Toggles, menu_View_Appearance_Toggles, menu_View_Additions,
62 menu_Contact_Manage, menu_Contact_Info, menu_Contact_Action, menu_Contact_NegativeAction, menu_Contact_Additions,
63 menu_Status_State, menu_Status_Accounts, menu_Status_Additions,
64 menu_Format_Styles, menu_Format_Palettes, menu_Format_Additions,
65 menu_Window_Top, menu_Window_Commands, menu_Window_Auxiliary, menu_Window_Fixed,
66 menu_Help_Local, menu_Help_Web, menu_Help_Additions,
67 menu_Dock_Status, nil];
70 - (void)controllerDidLoad
72 [self localizeMenuTitles];
76 - (void)controllerWillClose
78 //There's no need to remove the menu items, the system will take them out for us.
82 - (void)addMenuItem:(NSMenuItem *)newItem toLocation:(AIMenuLocation)location
85 NSMenu *targetMenu = nil;
89 //Find the menu item (or the closest one above it)
90 destination = location;
91 menuItem = [locationArray objectAtIndex:destination];
92 while ((menuItem == nilMenuItem) && (destination > 0)) {
94 menuItem = [locationArray objectAtIndex:destination];
97 if ([menuItem isKindOfClass:[NSMenuItem class]]) {
98 //If attached to a menu item, insert below that item
99 targetMenu = [menuItem menu];
100 targetIndex = [targetMenu indexOfItem:menuItem];
102 //If the next item is its alternate, skip over it
103 if ((targetIndex < [targetMenu numberOfItems]-1) && [[targetMenu itemAtIndex:targetIndex+1] isAlternate]) {
107 //If it's attached to an NSMenu (and not an NSMenuItem), insert at the top of the menu
108 targetMenu = (NSMenu *)menuItem;
112 //Insert the new item and a divider (if necessary)
113 if (location != destination) {
114 [targetMenu insertItem:[NSMenuItem separatorItem] atIndex:++targetIndex];
117 [targetMenu insertItem:newItem atIndex:targetIndex+1];
119 //update the location array
120 [locationArray replaceObjectAtIndex:location withObject:newItem];
122 [[adium notificationCenter] postNotificationName:AIMenuDidChnge object:[newItem menu] userInfo:nil];
126 - (void)removeMenuItem:(NSMenuItem *)targetItem
128 NSMenu *targetMenu = [targetItem menu];
129 if (!targetMenu) return;
131 int targetIndex = [targetMenu indexOfItem:targetItem];
132 unsigned loop, maxLoop;
134 //Fix the pointer if this is one
135 for (loop = 0, maxLoop = [locationArray count]; loop < maxLoop; loop++) {
136 NSMenuItem *menuItem = [locationArray objectAtIndex:loop];
138 //Move to the item above it, nil if a divider
139 if (menuItem == targetItem) {
140 if (targetIndex != 0) {
141 NSMenuItem *previousItem = [targetMenu itemAtIndex:(targetIndex - 1)];
143 if ([previousItem isSeparatorItem]) {
144 [locationArray replaceObjectAtIndex:loop withObject:nilMenuItem];
146 [locationArray replaceObjectAtIndex:loop withObject:previousItem];
149 //If there are no more items, attach to the menu
150 [locationArray replaceObjectAtIndex:loop withObject:targetMenu];
156 [targetMenu removeItem:targetItem];
158 if (!menuItemProcessingDelays) {
159 //Remove any double dividers by removing the upper divier. Also, remove dividers at the top or bottom of the menu
160 for (loop = 0; loop < [targetMenu numberOfItems]; loop++) {
161 if (([[targetMenu itemAtIndex:loop] isSeparatorItem]) &&
162 ((loop == [targetMenu numberOfItems] - 1) || (loop == 0) || ([[targetMenu itemAtIndex:loop-1] isSeparatorItem]))) {
163 [targetMenu removeItemAtIndex:loop];
164 loop--;//re-search the location
168 /* XXX Note that this notification isn't being posted if triggerred while in menuItemProcessingDelays.
169 * It's not currently needed in that situation so this is a very small performance hack... it could move outside the
170 * conditional if necessary. -evands
172 [[adium notificationCenter] postNotificationName:AIMenuDidChnge object:targetMenu userInfo:nil];
176 - (void)delayMenuItemPostProcessing
178 menuItemProcessingDelays++;
181 - (void)endDelayMenuItemPostProcessing
183 menuItemProcessingDelays--;
186 #pragma mark Contextual menu
189 * @brief Register a menu item for inclusion in contextual menus
191 * @param newItem The NSMenuItem to add
192 * @param location The AIContextMenuLocation associated with this menu item. Ordering within a given location is not defined.
194 - (void)addContextualMenuItem:(NSMenuItem *)newItem toLocation:(AIContextMenuLocation)location
197 NSMutableArray *itemArray;
199 //Search for an existing item array for menu items in this location
200 key = [NSNumber numberWithInt:location];
201 itemArray = [contextualMenuItemDict objectForKey:key];
203 //If one is not found, create it
205 itemArray = [[NSMutableArray alloc] init];
206 [contextualMenuItemDict setObject:itemArray forKey:key];
209 //Add the passed menu item to the array
210 [itemArray addObject:newItem];
214 * @brief Obtain an NSMenu of contextual menu items for a list object
216 * @param inLocationArray An NSArray of NSNumbers whose intValues are AIContextMenuLocation. The menu will be returned with these locations' items in order, separated by NSMenuItemSeparators.
217 * @param inObject The object for which menu items should be generated
219 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray forListObject:(AIListObject *)inObject
224 //Remember what our menu is configured for
225 [currentContextMenuObject release];
226 currentContextMenuObject = [inObject retain];
228 //Get the pre-created contextual menu items
229 workingMenu = [self contextualMenuWithLocations:inLocationArray usingMenu:contextualMenu];
231 //Add any account-specific menu items
233 if ([inObject isKindOfClass:[AIMetaContact class]]) {
234 NSEnumerator *enumerator;
235 AIListContact *aListContact;
236 enumerator = [[(AIMetaContact *)inObject listContacts] objectEnumerator];
238 while ((aListContact = [enumerator nextObject])) {
239 [self addMenuItemsForContact:aListContact
241 separatorItem:&separatorItem];
244 } else if ([inObject isKindOfClass:[AIListContact class]]) {
245 [self addMenuItemsForContact:(AIListContact *)inObject
247 separatorItem:&separatorItem];
253 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray forListObject:(AIListObject *)inObject inChat:(AIChat *)inChat
255 [currentContextMenuChat release];
256 currentContextMenuChat = [inChat retain];
258 return [self contextualMenuWithLocations:inLocationArray forListObject:inObject];
262 * @brief Add account-specific menuItems for a passed contact to a menu.
263 * @param inContact The contact
264 * @param workingMenu The NSMenu, which must not be nil
265 * @param seperatorItem Pointer to a BOOL which can be YES to indicate that a separator item should be inserted before the menu items if any are added. It will then be set to NO.
267 - (void)addMenuItemsForContact:(AIListContact *)inContact toMenu:(NSMenu *)workingMenu separatorItem:(BOOL *)separatorItem
269 NSArray *itemArray = [[inContact account] menuItemsForContact:inContact];
271 if (itemArray && [itemArray count]) {
272 NSEnumerator *enumerator;
273 NSMenuItem *menuItem;
275 if (*separatorItem == YES) {
276 [workingMenu addItem:[NSMenuItem separatorItem]];
280 enumerator = [itemArray objectEnumerator];
281 while ((menuItem = [enumerator nextObject])) {
282 [workingMenu addItem:menuItem];
287 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray forTextView:(NSTextView *)inTextView
289 //remember menu config
290 [contextualMenu_TextView release];
291 contextualMenu_TextView = [inTextView retain];
293 return [self contextualMenuWithLocations:inLocationArray usingMenu:textViewContextualMenu];
296 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray usingMenu:(NSMenu *)inMenu
298 NSEnumerator *enumerator;
300 NSMenuItem *menuItem;
301 BOOL itemsAbove = NO;
303 //Remove all items from the existing menu
304 [inMenu removeAllItems];
306 //Process each specified location
307 enumerator = [inLocationArray objectEnumerator];
308 while ((location = [enumerator nextObject])) {
309 NSArray *menuItems = [contextualMenuItemDict objectForKey:location];
310 NSEnumerator *itemEnumerator;
313 if (itemsAbove && [menuItems count]) {
314 [inMenu addItem:[NSMenuItem separatorItem]];
318 //Add each menu item in the location
319 itemEnumerator = [menuItems objectEnumerator];
320 while ((menuItem = [itemEnumerator nextObject])) {
322 [inMenu addItem:menuItem];
330 - (AIListObject *)currentContextMenuObject
332 return currentContextMenuObject;
335 - (AIChat *)currentContextMenuChat
337 return currentContextMenuChat;
340 - (NSTextView *)contextualMenuTextView
342 return contextualMenu_TextView;
347 - (void)removeItalicsKeyEquivalent
349 [menuItem_Format_Italics setKeyEquivalent:@""];
352 - (void)restoreItalicsKeyEquivalent
354 [menuItem_Format_Italics setKeyEquivalent:@"i"];
357 #pragma mark Localization
359 - (void)localizeMenuTitles
361 //Menu items in MainMenu.nib for localization purposes
362 [menuItem_file setTitle:AILocalizedString(@"File","Title of the File menu")];
363 [menuItem_edit setTitle:AILocalizedString(@"Edit","Title of the Edit menu")];
364 [menuItem_view setTitle:AILocalizedString(@"View","Title of the View menu")];
365 [menuItem_status setTitle:AILocalizedString(@"Status","Title of the Status menu")];
366 [menuItem_contact setTitle:AILocalizedString(@"Contact","Title of the Contact menu")];
367 [menuItem_format setTitle:AILocalizedString(@"Format","Title of the Format menu")];
368 [menuItem_window setTitle:AILocalizedString(@"Window","Title of the Window menu")];
369 [menuItem_help setTitle:AILocalizedString(@"Help","Title of the Help menu")];
371 //Also set the title of their submenus (Leopard requires this)
372 [[menuItem_file submenu] setTitle:AILocalizedString(@"File","Title of the File menu")];
373 [[menuItem_edit submenu] setTitle:AILocalizedString(@"Edit","Title of the Edit menu")];
374 [[menuItem_view submenu] setTitle:AILocalizedString(@"View","Title of the View menu")];
375 [[menuItem_status submenu] setTitle:AILocalizedString(@"Status","Title of the Status menu")];
376 [[menuItem_contact submenu] setTitle:AILocalizedString(@"Contact","Title of the Contact menu")];
377 [[menuItem_format submenu] setTitle:AILocalizedString(@"Format","Title of the Format menu")];
378 [[menuItem_window submenu] setTitle:AILocalizedString(@"Window","Title of the Window menu")];
379 [[menuItem_help submenu] setTitle:AILocalizedString(@"Help","Title of the Help menu")];
382 [menuItem_aboutAdium setTitle:AILocalizedString(@"About Adium",nil)];
383 [menuItem_adiumXtras setTitle:AILocalizedString(@"Xtras Manager",nil)];
384 [menuItem_checkForUpdates setTitle:[AILocalizedString(@"Check For Updates",nil) stringByAppendingEllipsis]];
385 [menuItem_preferences setTitle:[AILocalizedString(@"Preferences",nil) stringByAppendingEllipsis]];
386 [menuItem_donate setTitle:AILocalizedString(@"Donate",nil)];
387 [menuItem_helpOut setTitle:AILocalizedString(@"Contributing to Adium",nil)];
389 [menuItem_services setTitle:AILocalizedString(@"Services","Services menu item in the Adium menu")];
390 [menuItem_hideAdium setTitle:AILocalizedString(@"Hide Adium",nil)];
391 [menuItem_hideOthers setTitle:AILocalizedString(@"Hide Others",nil)];
392 [menuItem_showAll setTitle:AILocalizedString(@"Show All",nil)];
393 [menuItem_quitAdium setTitle:AILocalizedString(@"Quit Adium",nil)];
396 [menuItem_close setTitle:AILocalizedString(@"Close","Title for the close menu item")];
397 [menuItem_closeChat setTitle:AILocalizedString(@"Close Chat","Title for the close chat menu item")];
398 [menuItem_closeAllChats setTitle:AILocalizedString(@"Close All Chats","Title for the close all chats menu item")];
399 [menuItem_saveAs setTitle:[AILocalizedString(@"Save As",nil) stringByAppendingEllipsis]];
400 [menuItem_pageSetup setTitle:[AILocalizedString(@"Page Setup",nil) stringByAppendingEllipsis]];
401 [menuItem_print setTitle:[AILocalizedString(@"Print",nil) stringByAppendingEllipsis]];
404 [menuItem_cut setTitle:AILocalizedString(@"Cut",nil)];
405 [menuItem_copy setTitle:AILocalizedString(@"Copy",nil)];
406 [menuItem_paste setTitle:AILocalizedString(@"Paste",nil)];
407 [menuItem_pasteWithImagesAndColors setTitle:AILocalizedString(@"Paste with Images and Colors",nil)];
408 [menuItem_pasteAndMatchStyle setTitle:AILocalizedString(@"Paste and Match Style",nil)];
409 [menuItem_clear setTitle:AILocalizedString(@"Clear",nil)];
410 [menuItem_selectAll setTitle:AILocalizedString(@"Select All",nil)];
412 #define TITLE_FIND AILocalizedString(@"Find",nil)
413 [menuItem_find setTitle:TITLE_FIND];
414 [menuItem_findCommand setTitle:[TITLE_FIND stringByAppendingEllipsis]];
415 [menuItem_findNext setTitle:AILocalizedString(@"Find Next",nil)];
416 [menuItem_findPrevious setTitle:AILocalizedString(@"Find Previous",nil)];
417 [menuItem_findUseSelectionForFind setTitle:AILocalizedString(@"Use Selection for Find",nil)];
418 [menuItem_findJumpToSelection setTitle:AILocalizedString(@"Jump to Selection",nil)];
420 #define TITLE_SPELLING AILocalizedString(@"Spelling",nil)
421 [menuItem_spelling setTitle:TITLE_SPELLING];
422 [menuItem_spellingCommand setTitle:[TITLE_SPELLING stringByAppendingEllipsis]];
423 [menuItem_spellingCheckSpelling setTitle:AILocalizedString(@"Check Spelling",nil)];
424 [menuItem_spellingCheckSpellingAsYouType setTitle:AILocalizedString(@"Check Spelling As You Type",nil)];
426 [menuItem_speech setTitle:AILocalizedString(@"Speech",nil)];
427 [menuItem_startSpeaking setTitle:AILocalizedString(@"Start Speaking",nil)];
428 [menuItem_stopSpeaking setTitle:AILocalizedString(@"Stop Speaking",nil)];
431 [menuItem_customizeToolbar setTitle:[AILocalizedString(@"Customize Toolbar",nil) stringByAppendingEllipsis]];
434 [menuItem_bold setTitle:AILocalizedString(@"Bold",nil)];
435 [menuItem_italic setTitle:AILocalizedString(@"Italic",nil)];
436 [menuItem_underline setTitle:AILocalizedString(@"Underline",nil)];
437 [menuItem_showFonts setTitle:AILocalizedString(@"Show Fonts",nil)];
438 [menuItem_showColors setTitle:AILocalizedString(@"Show Colors",nil)];
439 [menuItem_bigger setTitle:AILocalizedString(@"Bigger", "Menu item title for making the font size bigger")];
440 [menuItem_smaller setTitle:AILocalizedString(@"Smaller", "Menu item title for making the font size smaller")];
441 [menuItem_copyStyle setTitle:AILocalizedString(@"Copy Style",nil)];
442 [menuItem_pasteStyle setTitle:AILocalizedString(@"Paste Style",nil)];
443 [menuItem_writingDirection setTitle:AILocalizedString(@"Writing Direction",nil)];
444 [menuItem_rightToLeft setTitle:AILocalizedString(@"Right to Left", "Menu item in a submenu under 'writing direction' for writing which goes from right to left")];
447 [menuItem_minimize setTitle:AILocalizedString(@"Minimize", "Minimize menu item title int he Wndow menu")];
448 [menuItem_zoom setTitle:AILocalizedString(@"Zoom", "Zoom menu item title in the Window menu")];
449 [menuItem_bringAllToFront setTitle:AILocalizedString(@"Bring All to Front",nil)];
452 [menuItem_adiumHelp setTitle:AILocalizedString(@"Adium Help",nil)];
453 [menuItem_reportABug setTitle:AILocalizedString(@"Report a Bug",nil)];
454 [menuItem_sendFeedback setTitle:AILocalizedString(@"Send Feedback",nil)];
455 [menuItem_adiumForums setTitle:AILocalizedString(@"Adium Forums",nil)];
458 #pragma mark Menu delegate (contextual menu)
461 * @brief Give menu items' targets the chance to update the menu before it is displayed
463 * This is useful since the menu is generated by many disparate classes; a single class as delegate can't handle all items.
465 - (void)menuNeedsUpdate:(NSMenu *)menu
467 NSArray *menuItems = [[[menu itemArray] copy] autorelease];
468 NSEnumerator *enumerator = [menuItems objectEnumerator];
469 NSMenuItem *menuItem;
470 while ((menuItem = [enumerator nextObject])) {
471 id target = [menuItem target];
472 if ([target respondsToSelector:@selector(menu:needsUpdateForMenuItem:)])
473 [target menu:menu needsUpdateForMenuItem:menuItem];