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]) {
144 [toolbarItems removeObject:item];
149 * @brief Register our toolbar item
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
161 paletteLabel:TITLE_INSERT_EMOTICON
162 toolTip:TOOLTIP_INSERT_EMOTICON
164 settingSelector:@selector(setView:)
166 action:@selector(insertEmoticon:)
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
183 - (NSMenu *)flatEmoticonMenuForPack:(AIEmoticonPack *)incomingPack
185 NSMenu *packMenu = [[NSMenu alloc] initWithTitle:TITLE_EMOTICON];
186 NSEnumerator *emoteEnum = [[incomingPack emoticons] objectEnumerator];
187 AIEmoticon *anEmoticon;
189 [packMenu setMenuChangedMessagesEnabled:NO];
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]
196 action:@selector(insertEmoticon:)
199 [newItem setImage:[[anEmoticon image] imageByScalingForMenuItem]];
200 [newItem setRepresentedObject:anEmoticon];
201 [packMenu addItem:newItem];
206 [packMenu setMenuChangedMessagesEnabled:YES];
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
221 - (void)insertEmoticon:(id)sender
223 if ([sender isKindOfClass:[NSMenuItem class]]) {
224 NSString *emoString = [[[sender representedObject] textEquivalents] objectAtIndex:0];
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)];
232 [responder insertText:emoString];
238 * @brief Just a target so we get the validateMenuItem: call for the emoticon menu
240 - (IBAction)dummyTarget:(id)sender
246 * @brief Validate menu item
248 * Disable the emoticon menu if a text field is not active
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;
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];
271 * @brief We don't want to get -menuNeedsUpdate: called on every keystroke. This method suppresses that.
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
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.
286 - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(int)index shouldCancel:(BOOL)shouldCancel
288 NSArray *activePacks = [[adium emoticonController] activeEmoticonPacks];
289 AIEmoticonPack *pack;
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.
295 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {
297 item = [[NSMenuItem alloc] init];
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];
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]];
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
341 - (int)numberOfItemsInMenu:(NSMenu *)menu
343 NSArray *activePacks = [[adium emoticonController] activeEmoticonPacks];
346 itemCounts = [activePacks count];
349 itemCounts = [[[activePacks objectAtIndex:0] enabledEmoticons] count];
352 if ([menu numberOfItems] > 0) {
353 if ([[[menu itemAtIndex:0] title] isEqualToString:TITLE_EMOTICON]) {