Forgot to remove an import I added temporarily
[adiumx.git] / Source / AIMenuController.m
blobbc1c0c4454cd9386243034381015ea8fdfbca186
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
17 // $Id$
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;
32 @end
34 @implementation AIMenuController
36 - (id)init
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;
50         }
51         
52         return self;
55 - (void)awakeFromNib
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
71 {       
72         [self localizeMenuTitles];      
75 //Close
76 - (void)controllerWillClose
78         //There's no need to remove the menu items, the system will take them out for us.
81 //Add a menu item
82 - (void)addMenuItem:(NSMenuItem *)newItem toLocation:(AIMenuLocation)location
84         NSMenuItem  *menuItem;
85         NSMenu          *targetMenu = nil;
86         int                     targetIndex;
87         int                     destination;
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)) {
93                 destination--;
94                 menuItem = [locationArray objectAtIndex:destination];
95         }
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];
101                 
102                 //If the next item is its alternate, skip over it
103                 if ((targetIndex < [targetMenu numberOfItems]-1) && [[targetMenu itemAtIndex:targetIndex+1] isAlternate]) {
104                         targetIndex++;
105                 }
106         } else {
107                 //If it's attached to an NSMenu (and not an NSMenuItem), insert at the top of the menu
108                 targetMenu = (NSMenu *)menuItem;
109                 targetIndex = -1;
110         }
112         //Insert the new item and a divider (if necessary)
113         if (location != destination) {
114                 [targetMenu insertItem:[NSMenuItem separatorItem] atIndex:++targetIndex];
115         }
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];
125 //Remove a menu item
126 - (void)removeMenuItem:(NSMenuItem *)targetItem
128         NSMenu          *targetMenu = [targetItem menu];
129         if (!targetMenu) return;
131         int                     targetIndex = [targetMenu indexOfItem:targetItem];
132         unsigned        loop, maxLoop;
133         
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];
145                                 } else {
146                                         [locationArray replaceObjectAtIndex:loop withObject:previousItem];
147                                 }
148                         } else {
149                                 //If there are no more items, attach to the menu
150                                 [locationArray replaceObjectAtIndex:loop withObject:targetMenu];
151                         }
152                 }
153         }
155         //Remove the item
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
165                         }
166                 }
167                 
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
171                  */
172                 [[adium notificationCenter] postNotificationName:AIMenuDidChnge object:targetMenu userInfo:nil];
173         }
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.
193  */
194 - (void)addContextualMenuItem:(NSMenuItem *)newItem toLocation:(AIContextMenuLocation)location
196         NSNumber                        *key;
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
204         if (!itemArray) {
205                 itemArray = [[NSMutableArray alloc] init];
206                 [contextualMenuItemDict setObject:itemArray forKey:key];
207         }
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
218  */
219 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray forListObject:(AIListObject *)inObject
221         NSMenu          *workingMenu;
222         BOOL            separatorItem;
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
232         separatorItem = YES;
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
240                                                                   toMenu:workingMenu
241                                                    separatorItem:&separatorItem];
242                 }
244         } else  if ([inObject isKindOfClass:[AIListContact class]]) {
245                 [self addMenuItemsForContact:(AIListContact *)inObject
246                                                           toMenu:workingMenu
247                                            separatorItem:&separatorItem];
248         }
250         return workingMenu;
253 - (NSMenu *)contextualMenuWithLocations:(NSArray *)inLocationArray forListObject:(AIListObject *)inObject inChat:(AIChat *)inChat
255         [currentContextMenuChat release];
256         currentContextMenuChat = [inChat retain];
257         
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.
266  */
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]];
277                         *separatorItem = NO;
278                 }
280                 enumerator = [itemArray objectEnumerator];
281                 while ((menuItem = [enumerator nextObject])) {
282                         [workingMenu addItem:menuItem];
283                 }
284         }
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;
299         NSNumber                *location;
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;
312                 //Add a seperator
313                 if (itemsAbove && [menuItems count]) {
314                         [inMenu addItem:[NSMenuItem separatorItem]];
315                         itemsAbove = NO;
316                 }
318                 //Add each menu item in the location
319                 itemEnumerator = [menuItems objectEnumerator];
320                 while ((menuItem = [itemEnumerator nextObject])) {
321                         //Add the menu item
322                         [inMenu addItem:menuItem];
323                         itemsAbove = YES;
324                 }
325         }
327         return inMenu;
330 - (AIListObject *)currentContextMenuObject
332         return currentContextMenuObject;
335 - (AIChat *)currentContextMenuChat
337         return currentContextMenuChat;
340 - (NSTextView *)contextualMenuTextView
342         return contextualMenu_TextView;
345 #pragma mark Italics
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")];
370         
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")];
380         
381         //Adium menu
382         [menuItem_aboutAdium setTitle:AILocalizedString(@"About Adium",nil)];
383         [menuItem_adiumXtras setTitle:[AILocalizedString(@"Xtras Manager",nil) stringByAppendingEllipsis]];
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)];
395         //File menu     
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]];
403         //Edit menu
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)];
429         
430         //View menu
431         [menuItem_customizeToolbar setTitle:[AILocalizedString(@"Customize Toolbar",nil) stringByAppendingEllipsis]];
433         //Format menu
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")];
445         
446         //Window menu
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)];
451         //Help menu
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.
464  */
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];
474         }
477 @end