Unescape the HREF attribute's text before passing it to NSURL which does not expect...
[adiumx.git] / Source / BGEmoticonMenuPlugin.m
blob3acd9f246dd81d1a56847d1d75e5f5b172b2a24e
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                 [item setView:nil];
145                 [toolbarItems removeObject:item];
146         }
150  * @brief Register our toolbar item
151  */
152 - (void)registerToolbarItem
154         NSToolbarItem   *toolbarItem;
155         MVMenuButton    *button;
157         //Register our toolbar item
158         button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
159         [button setImage:[NSImage imageNamed:@"emoticon32" forClass:[self class] loadLazily:YES]];
160         toolbarItem = [[AIToolbarUtilities toolbarItemWithIdentifier:TOOLBAR_EMOTICON_IDENTIFIER
161                                                                                                                    label:TITLE_EMOTICON
162                                                                                                         paletteLabel:TITLE_INSERT_EMOTICON
163                                                                                                                  toolTip:TOOLTIP_INSERT_EMOTICON
164                                                                                                                   target:self
165                                                                                                  settingSelector:@selector(setView:)
166                                                                                                          itemContent:button
167                                                                                                                   action:@selector(insertEmoticon:)
168                                                                                                                         menu:nil] retain];
169         [toolbarItem setMinSize:NSMakeSize(32,32)];
170         [toolbarItem setMaxSize:NSMakeSize(32,32)];
171         [button setToolbarItem:toolbarItem];
172         [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
176 //Menu Generation ------------------------------------------------------------------------------------------------------
177 #pragma mark Menu Generation
180  * @brief Build a flat emoticon menu for a single pack
182  * @result A menu for the pack
183  */
184 - (NSMenu *)flatEmoticonMenuForPack:(AIEmoticonPack *)incomingPack
186     NSMenu                      *packMenu = [[NSMenu alloc] initWithTitle:TITLE_EMOTICON];
187     NSEnumerator        *emoteEnum = [[incomingPack emoticons] objectEnumerator];
188     AIEmoticon          *anEmoticon;
189         
190         [packMenu setMenuChangedMessagesEnabled:NO];
191         
192     //loop through each emoticon and add a menu item for each
193     while ((anEmoticon = [emoteEnum nextObject])) {
194         if ([anEmoticon isEnabled] == YES) {
195                         NSArray *textEquivalents = [anEmoticon textEquivalents];
196                         NSString *textEquivalent;
197                         if ([textEquivalents count]) {
198                                 textEquivalent = [textEquivalents objectAtIndex:0];
199                         } else {
200                                 textEquivalent = @"";
201                         }
202                         NSString *menuTitle = [NSString stringWithFormat:@"%@ %@",[anEmoticon name],textEquivalent];
203                         NSMenuItem *newItem = [[NSMenuItem alloc] initWithTitle:menuTitle
204                                                              target:self
205                                                              action:@selector(insertEmoticon:)
206                                                       keyEquivalent:@""];
208             [newItem setImage:[[anEmoticon image] imageByScalingForMenuItem]];
209                         [newItem setRepresentedObject:anEmoticon];
210                         [packMenu addItem:newItem];
211                         [newItem release];
212         }
213     }
214     
215     [packMenu setMenuChangedMessagesEnabled:YES];
216         
217     return [packMenu autorelease];
221 //Menu Control ---------------------------------------------------------------------------------------------------------
222 #pragma mark Menu Control
224  * @brief Insert an emoticon into the first responder if possible
226  * First responder must be an editable NSTextView.
228  * @param sender An NSMenuItem whose representedObject is an AIEmoticon
229  */
230 - (void)insertEmoticon:(id)sender
232         if ([sender isKindOfClass:[NSMenuItem class]]) {
233                 NSString *emoString = [[[sender representedObject] textEquivalents] objectAtIndex:0];
234                 
235                 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
236                 if (emoString && [responder isKindOfClass:[NSTextView class]] && [(NSTextView *)responder isEditable]) {
237                         NSRange tmpRange = [(NSTextView *)responder selectedRange];
238                         if (0 != tmpRange.length) {
239                                 [(NSTextView *)responder setSelectedRange:NSMakeRange((tmpRange.location + tmpRange.length),0)];
240                         }
241                         [responder insertText:emoString];
242                 }
243     }
247  * @brief Just a target so we get the validateMenuItem: call for the emoticon menu
248  */
249 - (IBAction)dummyTarget:(id)sender
251         //Empty
255  * @brief Validate menu item
257  * Disable the emoticon menu if a text field is not active
258  */
259 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
261         if (menuItem == quickMenuItem || menuItem == quickContextualMenuItem) {
262                 BOOL    haveEmoticons = ([[[adium emoticonController] activeEmoticonPacks] count] != 0);
264                 //Disable the main emoticon menu items if no emoticons are available
265                 return haveEmoticons;
266                 
267         } else {
268                 //Disable the emoticon menu items if we're not in a text field
269                 NSResponder     *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
270                 if (responder && [responder isKindOfClass:[NSText class]]) {
271                         return [(NSText *)responder isEditable];
272                 } else {
273                         return NO;
274                 }
275                 
276         }
280  * @brief We don't want to get -menuNeedsUpdate: called on every keystroke. This method suppresses that.
281  */
282 - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
283         *target = nil;  //use menu's target
284         *action = NULL; //use menu's action
285         return NO;
289  * @brief Update our menus if necessary
291  * Called each time before any of our menus are displayed.
292  * This rebuilds menus incrementally, in place, and only updating items that need it.
294  */
295 - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(int)index shouldCancel:(BOOL)shouldCancel
297         NSArray                 *activePacks = [[adium emoticonController] activeEmoticonPacks];
298         AIEmoticonPack  *pack;
299                 
300    /* We need special voodoo here to identify if the menu belongs to a toolbar,
301         * add the necessary pad item, and then adjust the index accordingly.
302         * this shouldn't be necessary, but NSToolbar is evil.
303         */
304         if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
305                 if (index == 0) {
306                         item = [[NSMenuItem alloc] init];
307                         return YES;
308                 } else {
309                         --index;
310                 }
311         }
312         
313         // Add in flat emoticon menu
314         if ([activePacks count] == 1) {
315                 pack = [activePacks objectAtIndex:0];
316                 AIEmoticon      *emoticon = [[pack enabledEmoticons] objectAtIndex:index];
317                 if ([emoticon isEnabled] && ![[item representedObject] isEqualTo:emoticon]) {
318                         [item setTitle:[emoticon name]];
319                         [item setTarget:self];
320                         [item setAction:@selector(insertEmoticon:)];
321                         [item setKeyEquivalent:@""];
322                         [item setImage:[[emoticon image] imageByScalingForMenuItem]];
323                         [item setRepresentedObject:emoticon];
324                         [item setSubmenu:nil];
325                 }
326         // Add in multi-pack menu
327         } else if ([activePacks count] > 1) {
328                 pack = [activePacks objectAtIndex:index];
329                 if (![[item title] isEqualToString:[pack name]]){
330                         [item setTitle:[pack name]];
331                         [item setTarget:nil];
332                         [item setAction:nil];
333                         [item setKeyEquivalent:@""];
334                         [item setImage:[[pack menuPreviewImage] imageByScalingForMenuItem]];
335                         [item setRepresentedObject:nil];
336                         [item setSubmenu:[self flatEmoticonMenuForPack:pack]];
337                 }
338         }
340         return YES;
344  * @brief Set the number of items that should be in the menu.
346  * Toolbars need one empty item to display properly.  We increase the number by 1, if the menu
347  * is in a toolbar
349  */
350 - (int)numberOfItemsInMenu:(NSMenu *)menu
351 {       
352         NSArray                 *activePacks = [[adium emoticonController] activeEmoticonPacks];
353         int                              itemCounts = -1;
354         
355         itemCounts = [activePacks count];
356         
357         if (itemCounts == 1)
358                 itemCounts = [[[activePacks objectAtIndex:0] enabledEmoticons] count];
359         
361         if ([menu numberOfItems] > 0) {
362                 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
363                         ++itemCounts;
364                 }
365         }
367         return itemCounts;
370 @end