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.
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;
34 * @class BGEmoticonMenuPlugin
35 * @brief Component to manage the Emoticons menu in its various forms
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"
52 //init the menus and menuItems
53 quickMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
55 action:@selector(dummyTarget:)
57 quickContextualMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
59 action:@selector(dummyTarget:)
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:).
66 tempMenu = [[NSMenu alloc] init];
67 [tempMenu setDelegate:self];
68 [quickMenuItem setSubmenu:tempMenu];
71 tempMenu = [[NSMenu alloc] init];
72 [tempMenu setDelegate:self];
73 [quickContextualMenuItem setSubmenu:tempMenu];
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];
80 toolbarItems = [[NSMutableSet alloc] init];
81 [self registerToolbarItem];
83 [[NSNotificationCenter defaultCenter] addObserver:self
84 selector:@selector(toolbarWillAddItem:)
85 name:NSToolbarWillAddItemNotification
87 [[NSNotificationCenter defaultCenter] addObserver:self
88 selector:@selector(toolbarDidRemoveItem:)
89 name:NSToolbarDidRemoveItemNotification
96 - (void)uninstallPlugin
98 [[NSNotificationCenter defaultCenter] removeObserver:self];
99 [[adium preferenceController] unregisterPreferenceObserver:self];
107 [toolbarItems release];
113 * @brief Add the emoticon menu as an item goes into a toolbar
115 - (void)toolbarWillAddItem:(NSNotification *)notification
117 NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
119 if ([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]) {
120 NSMenu *theEmoticonMenu = [[[NSMenu alloc] init] autorelease];
122 [theEmoticonMenu setDelegate:self];
125 [[item view] setMenu:theEmoticonMenu];
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];
133 [toolbarItems addObject:item];
138 * @brief Stop tracking when an item is removed from a toolbar
140 - (void)toolbarDidRemoveItem:(NSNotification *)notification
142 NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
143 if ([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]) {
145 [toolbarItems removeObject:item];
150 * @brief Register our toolbar item
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
162 paletteLabel:TITLE_INSERT_EMOTICON
163 toolTip:TOOLTIP_INSERT_EMOTICON
165 settingSelector:@selector(setView:)
167 action:@selector(insertEmoticon:)
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
184 - (NSMenu *)flatEmoticonMenuForPack:(AIEmoticonPack *)incomingPack
186 NSMenu *packMenu = [[NSMenu alloc] initWithTitle:TITLE_EMOTICON];
187 NSEnumerator *emoteEnum = [[incomingPack emoticons] objectEnumerator];
188 AIEmoticon *anEmoticon;
190 [packMenu setMenuChangedMessagesEnabled:NO];
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];
200 textEquivalent = @"";
202 NSString *menuTitle = [NSString stringWithFormat:@"%@ %@",[anEmoticon name],textEquivalent];
203 NSMenuItem *newItem = [[NSMenuItem alloc] initWithTitle:menuTitle
205 action:@selector(insertEmoticon:)
208 [newItem setImage:[[anEmoticon image] imageByScalingForMenuItem]];
209 [newItem setRepresentedObject:anEmoticon];
210 [packMenu addItem:newItem];
215 [packMenu setMenuChangedMessagesEnabled:YES];
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
230 - (void)insertEmoticon:(id)sender
232 if ([sender isKindOfClass:[NSMenuItem class]]) {
233 NSString *emoString = [[[sender representedObject] textEquivalents] objectAtIndex:0];
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)];
241 [responder insertText:emoString];
247 * @brief Just a target so we get the validateMenuItem: call for the emoticon menu
249 - (IBAction)dummyTarget:(id)sender
255 * @brief Validate menu item
257 * Disable the emoticon menu if a text field is not active
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;
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];
280 * @brief We don't want to get -menuNeedsUpdate: called on every keystroke. This method suppresses that.
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
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.
295 - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(int)index shouldCancel:(BOOL)shouldCancel
297 NSArray *activePacks = [[adium emoticonController] activeEmoticonPacks];
298 AIEmoticonPack *pack;
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.
304 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
306 item = [[NSMenuItem alloc] init];
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];
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]];
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
350 - (int)numberOfItemsInMenu:(NSMenu *)menu
352 NSArray *activePacks = [[adium emoticonController] activeEmoticonPacks];
355 itemCounts = [activePacks count];
358 itemCounts = [[[activePacks objectAtIndex:0] enabledEmoticons] count];
361 if ([menu numberOfItems] > 0) {
362 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {