Updating svn:mergeinfo
[adiumx.git] / Source / BGEmoticonMenuPlugin.m
blob884612a431cbe5363ae7528fca8bb0f7c3913b06
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 #import "AIEmoticonController.h"
18 #import <Adium/AIMenuControllerProtocol.h>
19 #import <Adium/AIPreferenceControllerProtocol.h>
20 #import <Adium/AIToolbarControllerProtocol.h>
21 #import "BGEmoticonMenuPlugin.h"
22 #import <AIUtilities/AIMenuAdditions.h>
23 #import <AIUtilities/AIToolbarUtilities.h>
24 #import <AIUtilities/AIApplicationAdditions.h>
25 #import <AIUtilities/AIImageAdditions.h>
26 #import <AIUtilities/MVMenuButton.h>
27 #import <Adium/AIEmoticon.h>
29 @interface BGEmoticonMenuPlugin(PRIVATE)
30 - (void)registerToolbarItem;
31 @end
33 /*!
34  * @class BGEmoticonMenuPlugin
35  * @brief Component to manage the Emoticons menu in its various forms
36  */
37 @implementation BGEmoticonMenuPlugin
39 #define PREF_GROUP_EMOTICONS                    @"Emoticons"
41 #define TITLE_INSERT_EMOTICON                   AILocalizedString(@"Insert Emoticon",nil)
42 #define TOOLTIP_INSERT_EMOTICON                 AILocalizedString(@"Insert an emoticon into the text",nil)
43 #define TITLE_EMOTICON                                  AILocalizedString(@"Emoticon",nil)
45 #define TOOLBAR_EMOTICON_IDENTIFIER             @"InsertEmoticon"
47 /*!
48  * @brief Install
49  */
50 - (void)installPlugin
52     //init the menus and menuItems
53     quickMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
54                                                                                            target:self
55                                                                                            action:@selector(dummyTarget:) 
56                                                                                 keyEquivalent:@""];
57     quickContextualMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
58                                                                                                                  target:self
59                                                                                                                  action:@selector(dummyTarget:)
60                                                                                                   keyEquivalent:@""];
61         
62         /* Create a submenu for these so menu:updateItem:atIndex:shouldCancel: will be called 
63          * to populate them later. Don't need to check respondsToSelector:@selector(setDelegate:).
64          */
65         NSMenu  *tempMenu;
66         tempMenu = [[NSMenu alloc] init];
67         [tempMenu setDelegate:self];
68         [quickMenuItem setSubmenu:tempMenu];
69         [tempMenu release];
70         
71         tempMenu = [[NSMenu alloc] init];
72         [tempMenu setDelegate:self];
73         [quickContextualMenuItem setSubmenu:tempMenu];
74         [tempMenu release];
76     //add the items to their menus.
77     [[adium menuController] addContextualMenuItem:quickContextualMenuItem toLocation:Context_TextView_Edit];    
78     [[adium menuController] addMenuItem:quickMenuItem toLocation:LOC_Edit_Additions];
79         
80         toolbarItems = [[NSMutableSet alloc] init];
81         [self registerToolbarItem];
82         
83         [[NSNotificationCenter defaultCenter] addObserver:self
84                                                                                          selector:@selector(toolbarWillAddItem:)
85                                                                                                  name:NSToolbarWillAddItemNotification
86                                                                                            object:nil];
87         [[NSNotificationCenter defaultCenter] addObserver:self
88                                                                                          selector:@selector(toolbarDidRemoveItem:)
89                                                                                                  name:NSToolbarDidRemoveItemNotification
90                                                                                            object:nil];
93 /*!
94  * @brief Uninstall
95  */
96 - (void)uninstallPlugin
98         [[NSNotificationCenter defaultCenter] removeObserver:self];
99         [[adium preferenceController] unregisterPreferenceObserver:self];
103  * @brief Deallocate
104  */
105 - (void)dealloc
107         [toolbarItems release];
108         
109         [super dealloc];
113  * @brief Add the emoticon menu as an item goes into a toolbar
114  */
115 - (void)toolbarWillAddItem:(NSNotification *)notification
117         NSToolbarItem   *item = [[notification userInfo] objectForKey:@"item"];
118         
119         if ([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]) {
120                 NSMenu          *theEmoticonMenu = [[[NSMenu alloc] init] autorelease];
121                 
122                 [theEmoticonMenu setDelegate:self];
124                 //Add menu to view
125                 [[item view] setMenu:theEmoticonMenu];
126                 
127                 //Add menu to toolbar item (for text mode)
128                 NSMenuItem      *mItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] init] autorelease];
129                 [mItem setSubmenu:theEmoticonMenu];
130                 [mItem setTitle:TITLE_EMOTICON];
131                 [item setMenuFormRepresentation:mItem];
132                 
133                 [toolbarItems addObject:item];
134         }
138  * @brief Stop tracking when an item is removed from a toolbar
139  */
140 - (void)toolbarDidRemoveItem:(NSNotification *)notification
142         NSToolbarItem   *item = [[notification userInfo] objectForKey:@"item"];
143         if ([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]) {
144                 [toolbarItems removeObject:item];
145         }
149  * @brief Register our toolbar item
150  */
151 - (void)registerToolbarItem
153         NSToolbarItem   *toolbarItem;
154         MVMenuButton    *button;
156         //Register our toolbar item
157         button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
158         [button setImage:[NSImage imageNamed:@"emoticon32" forClass:[self class]]];
159         toolbarItem = [[AIToolbarUtilities toolbarItemWithIdentifier:TOOLBAR_EMOTICON_IDENTIFIER
160                                                                                                                    label:TITLE_EMOTICON
161                                                                                                         paletteLabel:TITLE_INSERT_EMOTICON
162                                                                                                                  toolTip:TOOLTIP_INSERT_EMOTICON
163                                                                                                                   target:self
164                                                                                                  settingSelector:@selector(setView:)
165                                                                                                          itemContent:button
166                                                                                                                   action:@selector(insertEmoticon:)
167                                                                                                                         menu:nil] retain];
168         [toolbarItem setMinSize:NSMakeSize(32,32)];
169         [toolbarItem setMaxSize:NSMakeSize(32,32)];
170         [button setToolbarItem:toolbarItem];
171         [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
175 //Menu Generation ------------------------------------------------------------------------------------------------------
176 #pragma mark Menu Generation
179  * @brief Build a flat emoticon menu for a single pack
181  * @result A menu for the pack
182  */
183 - (NSMenu *)flatEmoticonMenuForPack:(AIEmoticonPack *)incomingPack
185     NSMenu                      *packMenu = [[NSMenu alloc] initWithTitle:TITLE_EMOTICON];
186     NSEnumerator        *emoteEnum = [[incomingPack emoticons] objectEnumerator];
187     AIEmoticon          *anEmoticon;
188         
189         [packMenu setMenuChangedMessagesEnabled:NO];
190         
191     //loop through each emoticon and add a menu item for each
192     while ((anEmoticon = [emoteEnum nextObject])) {
193         if ([anEmoticon isEnabled] == YES) {
194             NSMenuItem *newItem = [[NSMenuItem alloc] initWithTitle:[anEmoticon name]
195                                                              target:self
196                                                              action:@selector(insertEmoticon:)
197                                                       keyEquivalent:@""];
199             [newItem setImage:[[anEmoticon image] imageByScalingForMenuItem]];
200                         [newItem setRepresentedObject:anEmoticon];
201                         [packMenu addItem:newItem];
202                         [newItem release];
203         }
204     }
205     
206     [packMenu setMenuChangedMessagesEnabled:YES];
207         
208     return [packMenu autorelease];
212 //Menu Control ---------------------------------------------------------------------------------------------------------
213 #pragma mark Menu Control
215  * @brief Insert an emoticon into the first responder if possible
217  * First responder must be an editable NSTextView.
219  * @param sender An NSMenuItem whose representedObject is an AIEmoticon
220  */
221 - (void)insertEmoticon:(id)sender
223         if ([sender isKindOfClass:[NSMenuItem class]]) {
224                 NSString *emoString = [[[sender representedObject] textEquivalents] objectAtIndex:0];
225                 
226                 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
227                 if (emoString && [responder isKindOfClass:[NSTextView class]] && [(NSTextView *)responder isEditable]) {
228                         NSRange tmpRange = [(NSTextView *)responder selectedRange];
229                         if (0 != tmpRange.length) {
230                                 [(NSTextView *)responder setSelectedRange:NSMakeRange((tmpRange.location + tmpRange.length),0)];
231                         }
232                         [responder insertText:emoString];
233                 }
234     }
238  * @brief Just a target so we get the validateMenuItem: call for the emoticon menu
239  */
240 - (IBAction)dummyTarget:(id)sender
242         //Empty
246  * @brief Validate menu item
248  * Disable the emoticon menu if a text field is not active
249  */
250 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
252         if (menuItem == quickMenuItem || menuItem == quickContextualMenuItem) {
253                 BOOL    haveEmoticons = ([[[adium emoticonController] activeEmoticonPacks] count] != 0);
255                 //Disable the main emoticon menu items if no emoticons are available
256                 return haveEmoticons;
257                 
258         } else {
259                 //Disable the emoticon menu items if we're not in a text field
260                 NSResponder     *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
261                 if (responder && [responder isKindOfClass:[NSText class]]) {
262                         return [(NSText *)responder isEditable];
263                 } else {
264                         return NO;
265                 }
266                 
267         }
271  * @brief We don't want to get -menuNeedsUpdate: called on every keystroke. This method suppresses that.
272  */
273 - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
274         *target = nil;  //use menu's target
275         *action = NULL; //use menu's action
276         return NO;
280  * @brief Update our menus if necessary
282  * Called each time before any of our menus are displayed.
283  * This rebuilds menus incrementally, in place, and only updating items that need it.
285  */
286 - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(int)index shouldCancel:(BOOL)shouldCancel
288         NSArray                 *activePacks = [[adium emoticonController] activeEmoticonPacks];
289         AIEmoticonPack  *pack;
290                 
291    /* We need special voodoo here to identify if the menu belongs to a toolbar,
292         * add the necessary pad item, and then adjust the index accordingly.
293         * this shouldn't be necessary, but NSToolbar is evil.
294         */
295         if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
296                 if (index == 0) {
297                         item = [[NSMenuItem alloc] init];
298                         return YES;
299                 } else {
300                         --index;
301                 }
302         }
303         
304         // Add in flat emoticon menu
305         if ([activePacks count] == 1) {
306                 pack = [activePacks objectAtIndex:0];
307                 AIEmoticon      *emoticon = [[pack enabledEmoticons] objectAtIndex:index];
308                 if ([emoticon isEnabled] && ![[item representedObject] isEqualTo:emoticon]) {
309                         [item setTitle:[emoticon name]];
310                         [item setTarget:self];
311                         [item setAction:@selector(insertEmoticon:)];
312                         [item setKeyEquivalent:@""];
313                         [item setImage:[[emoticon image] imageByScalingForMenuItem]];
314                         [item setRepresentedObject:emoticon];
315                         [item setSubmenu:nil];
316                 }
317         // Add in multi-pack menu
318         } else if ([activePacks count] > 1) {
319                 pack = [activePacks objectAtIndex:index];
320                 if (![[item title] isEqualToString:[pack name]]){
321                         [item setTitle:[pack name]];
322                         [item setTarget:nil];
323                         [item setAction:nil];
324                         [item setKeyEquivalent:@""];
325                         [item setImage:[[pack menuPreviewImage] imageByScalingForMenuItem]];
326                         [item setRepresentedObject:nil];
327                         [item setSubmenu:[self flatEmoticonMenuForPack:pack]];
328                 }
329         }
331         return YES;
335  * @brief Set the number of items that should be in the menu.
337  * Toolbars need one empty item to display properly.  We increase the number by 1, if the menu
338  * is in a toolbar
340  */
341 - (int)numberOfItemsInMenu:(NSMenu *)menu
342 {       
343         NSArray                 *activePacks = [[adium emoticonController] activeEmoticonPacks];
344         int                              itemCounts = -1;
345         
346         itemCounts = [activePacks count];
347         
348         if (itemCounts == 1)
349                 itemCounts = [[[activePacks objectAtIndex:0] enabledEmoticons] count];
350         
352         if ([menu numberOfItems] > 0) {
353                 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
354                         ++itemCounts;
355                 }
356         }
358         return itemCounts;
361 @end