French updates
[adiumx.git] / Source / BGEmoticonMenuPlugin.m
blob16f08a4152f06d0c9e74b08d1032a917f3cc1dc7
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 "AIMenuController.h"
19 #import "AIPreferenceController.h"
20 #import "AIToolbarController.h"
21 #import "BGEmoticonMenuPlugin.h"
22 #import <AIUtilities/AIMenuAdditions.h>
23 #import <AIUtilities/AIToolbarUtilities.h>
24 #import <AIUtilities/CBApplicationAdditions.h>
25 #import <AIUtilities/ESImageAdditions.h>
26 #import <AIUtilities/MVMenuButton.h>
27 #import <Adium/AIEmoticon.h>
29 @interface BGEmoticonMenuPlugin(PRIVATE)
30 - (void)registerToolbarItem;
31 - (void)menuNeedsUpdate:(NSMenu *)inMenu;
32 @end
34 /*!
35  * @class BGEmoticonMenuPlugin
36  * @brief Component to manage the Emoticons menu in its various forms
37  */
38 @implementation BGEmoticonMenuPlugin
40 #define PREF_GROUP_EMOTICONS                    @"Emoticons"
42 #define TITLE_INSERT_EMOTICON                   AILocalizedString(@"Insert Emoticon",nil)
43 #define TOOLTIP_INSERT_EMOTICON                 AILocalizedString(@"Insert an emoticon into the text",nil)
44 #define TITLE_EMOTICON                                  AILocalizedString(@"Emoticon",nil)
46 #define TOOLBAR_EMOTICON_IDENTIFIER             @"InsertEmoticon"
48 /*!
49  * @brief Install
50  */
51 - (void)installPlugin
53     //init the menus and menuItems
54     quickMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
55                                                                                            target:self
56                                                                                            action:@selector(dummyTarget:) 
57                                                                                 keyEquivalent:@""];
58     quickContextualMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_EMOTICON
59                                                                                                                  target:self
60                                                                                                                  action:@selector(dummyTarget:)
61                                                                                                   keyEquivalent:@""];
62         needToRebuildMenus = YES;
63         
64         if([NSApp isOnPantherOrBetter]){
65                 /* On Panther and better, create a submenu for these so menuNeedsUpdate will be called 
66                  * to populate them later. Don't need to check respondsToSelector:@selector(setDelegate:).
67                  */
68                 NSMenu  *tempMenu = [[NSMenu alloc] init];
69                 [tempMenu setDelegate:self];
70                 [quickMenuItem setSubmenu:tempMenu];
71                 [tempMenu release];
73                 tempMenu = [[NSMenu alloc] init];
74                 [tempMenu setDelegate:self];
75                 [quickContextualMenuItem setSubmenu:tempMenu];
76                 [tempMenu release];
77                 
78         }else{
79                 /* On Jaguar, call menu needs update immediately to make the menus initially populate. They won't autoupdate in
80                  * the future, but that's yet another cost of living in 1986.  Then again, they do get to hear Run D.M.C.'s
81                  * awesome "Walk This Way" cover, so maybe that's worth it to them.
82                  */
83                 [self menuNeedsUpdate:nil];
84         }
86     //add the items to their menus.
87     AIMenuController *menuController = [adium menuController];
88     [menuController addContextualMenuItem:quickContextualMenuItem toLocation:Context_TextView_Edit];    
89     [menuController addMenuItem:quickMenuItem toLocation:LOC_Edit_Additions];
90         
91         toolbarItems = [[NSMutableSet alloc] init];
92         [self registerToolbarItem];
93         
94         //
95         [[NSNotificationCenter defaultCenter] addObserver:self
96                                                 selector:@selector(toolbarWillAddItem:)
97                                                     name:NSToolbarWillAddItemNotification
98                                                   object:nil];
99         [[NSNotificationCenter defaultCenter] addObserver:self
100                                                                                          selector:@selector(toolbarDidRemoveItem:)
101                                                                                                  name:NSToolbarDidRemoveItemNotification
102                                                                                            object:nil];
104         //Observe prefs    
105         [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_EMOTICONS];
109  * @brief Uninstall
110  */
111 - (void)uninstallPlugin
113         [[NSNotificationCenter defaultCenter] removeObserver:self];
114         [[adium preferenceController] unregisterPreferenceObserver:self];
118  * @brief Deallocate
119  */
120 - (void)dealloc
122         [toolbarItems release];
123         
124         [super dealloc];
128  * @brief Add the emoticon menu as an item goes into a toolbar
129  */
130 - (void)toolbarWillAddItem:(NSNotification *)notification
132         NSToolbarItem   *item = [[notification userInfo] objectForKey:@"item"];
133         
134         if([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]){
135                 NSMenu          *theEmoticonMenu = [self emoticonMenu];
137                 //Add menu to view
138                 [[item view] setMenu:theEmoticonMenu];
139                 
140                 //Add menu to toolbar item (for text mode)
141                 NSMenuItem      *mItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] init] autorelease];
142                 [mItem setSubmenu:theEmoticonMenu];
143                 [mItem setTitle:AILocalizedString(@"Emoticon",nil)];
144                 [item setMenuFormRepresentation:mItem];
145                 
146                 [toolbarItems addObject:item];
147         }
151  * @brief Stop tracking when an item is removed from a toolbar
152  */
153 - (void)toolbarDidRemoveItem:(NSNotification *)notification
155         NSToolbarItem   *item = [[notification userInfo] objectForKey:@"item"];
156         if([[item itemIdentifier] isEqualToString:TOOLBAR_EMOTICON_IDENTIFIER]){
157                 [toolbarItems removeObject:item];
158         }
162  * @brief Emoticons changed
163  */
164 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
165                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
167         //Flush the cached emoticon menu
168         [emoticonMenu release]; emoticonMenu = nil;
169         
170         //Flag our menus as dirty
171         needToRebuildMenus = YES;
175  * @brief Register our toolbar item
176  */
177 - (void)registerToolbarItem
179         NSToolbarItem   *toolbarItem;
180         MVMenuButton    *button;
182         //Register our toolbar item
183         button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
184         [button setImage:[NSImage imageNamed:@"emoticon32" forClass:[self class]]];
185         toolbarItem = [[AIToolbarUtilities toolbarItemWithIdentifier:TOOLBAR_EMOTICON_IDENTIFIER
186                                                                                                                    label:TITLE_EMOTICON
187                                                                                                         paletteLabel:TITLE_INSERT_EMOTICON
188                                                                                                                  toolTip:TOOLTIP_INSERT_EMOTICON
189                                                                                                                   target:self
190                                                                                                  settingSelector:@selector(setView:)
191                                                                                                          itemContent:button
192                                                                                                                   action:@selector(insertEmoticon:)
193                                                                                                                         menu:nil] retain];
194         [toolbarItem setMinSize:NSMakeSize(32,32)];
195         [toolbarItem setMaxSize:NSMakeSize(32,32)];
196         [button setToolbarItem:toolbarItem];
197         [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
201 //Menu Generation ------------------------------------------------------------------------------------------------------
202 #pragma mark Menu Generation
204  * @brief Build the emoticon menu
206  * Generation of the menu itself is cached.
208  * @result An autoreleased copy of the cached emoticon menu
209  */
210 - (NSMenu *)emoticonMenu
212         NSMenu  *emoticonMenuCopy;
213         
214         if(!emoticonMenu){
215                 NSArray         *emoticonPacks = [[adium emoticonController] activeEmoticonPacks];
217                 if([emoticonPacks count] == 1){
218                         //If there is only 1 emoticon pack loaded, do not create submenus
219                         emoticonMenu = [[self flatEmoticonMenuForPack:[emoticonPacks objectAtIndex:0]] retain];
221                 }else{
222                         NSEnumerator    *packEnum = [emoticonPacks objectEnumerator];
223                         AIEmoticonPack  *pack;
224                         NSMenuItem              *packItem;
225                         
226                         emoticonMenu = [[NSMenu alloc] initWithTitle:@""];
227                         
228                         [emoticonMenu setMenuChangedMessagesEnabled:NO];
229                         while(pack = [packEnum nextObject]){
230                                 packItem = [[NSMenuItem alloc] initWithTitle:[pack name] action:nil keyEquivalent:@""];
231                                 [packItem setSubmenu:[self flatEmoticonMenuForPack:pack]]; 
232                                 [emoticonMenu addItem:packItem];
233                                 [packItem release];
234                         }
235                         [emoticonMenu setMenuChangedMessagesEnabled:YES];
236                 }
237         }
238         
239         //Always return a copy so we can freely modify the menu's item array without messing up our cached copy
240         emoticonMenuCopy = [emoticonMenu copy];
241         if([emoticonMenuCopy respondsToSelector:@selector(setDelegate:)]){
242                 [emoticonMenuCopy setDelegate:self];
243         }
244         
245         return [emoticonMenuCopy autorelease];
249  * @brief Build a flat emoticon menu for a single pack
251  * @result A menu for the pack
252  */
253 - (NSMenu *)flatEmoticonMenuForPack:(AIEmoticonPack *)incomingPack
255     NSMenu                      *packMenu = [[NSMenu alloc] initWithTitle:TITLE_EMOTICON];
256     NSEnumerator        *emoteEnum = [[incomingPack emoticons] objectEnumerator];
257     AIEmoticon          *anEmoticon;
258         
259         [packMenu setMenuChangedMessagesEnabled:NO];
260         
261     //loop through each emoticon and add a menu item for each
262     while(anEmoticon = [emoteEnum nextObject]){
263         if([anEmoticon isEnabled] == YES){
264             NSMenuItem *newItem = [[NSMenuItem alloc] initWithTitle:[anEmoticon name]
265                                                              target:self
266                                                              action:@selector(insertEmoticon:)
267                                                       keyEquivalent:@""];
269                         //We need to make a copy of the emoticons for our menu, otherwise the menu flips them in an unpredictable
270                         //way, causing problems in the emoticon preferences
271             [newItem setImage:[[anEmoticon image] imageByScalingToSize:NSMakeSize(16, 16)]];
272                         [newItem setRepresentedObject:anEmoticon];
273                         [packMenu addItem:newItem];
274                         [newItem release];
275         }
276     }
277     
278     [packMenu setMenuChangedMessagesEnabled:YES];
279         
280     return([packMenu autorelease]);
284 //Menu Control ---------------------------------------------------------------------------------------------------------
285 #pragma mark Menu Control
287  * @brief Insert an emoticon into the first responder if possible
289  * First responder must be an editable NSTextView.
291  * @param sender An NSMenuItem whose representedObject is an AIEmoticon
292  */
293 - (void)insertEmoticon:(id)sender
295         if([sender isKindOfClass:[NSMenuItem class]]){
296                 NSString *emoString = [[[sender representedObject] textEquivalents] objectAtIndex:0];
297                 
298                 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
299                 if(emoString && [responder isKindOfClass:[NSTextView class]] && [(NSTextView *)responder isEditable]){
300                         NSRange tmpRange = [(NSTextView *)responder selectedRange];
301                         if(0 != tmpRange.length){
302                                 [(NSTextView *)responder setSelectedRange:NSMakeRange((tmpRange.location + tmpRange.length),0)];
303                         }
304                         [responder insertText:emoString];
305                 }
306     }
310  * @brief Just a target so we get the validateMenuItem: call for the emoticon menu
311  */
312 - (IBAction)dummyTarget:(id)sender
314         //Empty
318  * @brief Validate menu item
320  * Disable the emoticon menu if a text field is not active
321  */
322 - (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
324         if(menuItem == quickMenuItem || menuItem == quickContextualMenuItem){
325                 BOOL    haveEmoticons = ([[[adium emoticonController] activeEmoticonPacks] count] != 0);
327                 //Disable the main emoticon menu items if no emoticons are available
328                 return(haveEmoticons);
329                 
330         }else{
331                 //Disable the emoticon menu items if we're not in a text field
332                 NSResponder     *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
333                 if(responder && [responder isKindOfClass:[NSText class]]){
334                         return([(NSText *)responder isEditable]);
335                 }else{
336                         return(NO);
337                 }
338                 
339         }
343  * @brief We don't want to get -menuNeedsUpdate: called on every keystroke. This method suppresses that.
344  */
345 - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
346         *target = nil;  //use menu's target
347         *action = NULL; //use menu's action
348         return NO;
352  * @brief Update our menus if necessary
354  * Called each time before any of our menus are displayed.  If needToRebuildMenus is YES, rebuild them all now,
355  * then set needToRebuildMenus to NO so we don't have to do it next time.
357  * We set the delegate each time we copy because it seems that NSMenu doesn't do so itself when copying. Odd.
358  */
359 - (void)menuNeedsUpdate:(NSMenu *)inMenu
360 {       
361         //Build the emoticon menus if necessary
362         if(needToRebuildMenus){
363                 NSMenu                  *theEmoticonMenu, *tempMenu;
364                 NSMenuItem              *menuItem;
365                 NSEnumerator    *enumerator;
366                 NSToolbarItem   *toolbarItem;
367                 
368                 //Build the new emoticon menu
369                 theEmoticonMenu = [self emoticonMenu];
370                 
371                 /* For each item, only set its submenu (so we won't have to worry about it in the future) if its current
372                  * submenu isn't the one for which we are currently updating. One of them WILL be identical to inMenu, as
373                  * that's why we got here (the delegate call) in the first place.  For that one, we'll remove inMenu's items
374                  * and then add the items from emoticonMenu. */
375                 if([quickMenuItem submenu] != inMenu){
376                         tempMenu = [theEmoticonMenu copy];
377                         [quickMenuItem setSubmenu:tempMenu];
378                         if([tempMenu respondsToSelector:@selector(setDelegate:)]){
379                                 [tempMenu setDelegate:self];
380                         }
381                         [tempMenu release];
382                 }
383                 
384                 if([quickContextualMenuItem submenu] != inMenu){
385                         tempMenu = [theEmoticonMenu copy];
386                         [quickContextualMenuItem setSubmenu:tempMenu];
387                         if([tempMenu respondsToSelector:@selector(setDelegate:)]){
388                                 [tempMenu setDelegate:self];
389                         }
390                         [tempMenu release];
391                 }
392                 
393                 enumerator = [toolbarItems objectEnumerator];
394                 while(toolbarItem = [enumerator nextObject]){
395                         if([[toolbarItem view] menu] != inMenu){
396                                 //We can use the same menu for both
397                                 tempMenu = [theEmoticonMenu copy];
399                                 if([tempMenu respondsToSelector:@selector(setDelegate:)]){
400                                         [tempMenu setDelegate:self];
401                                 }
402                                 
403                                 //Add menu to view
404                                 [[toolbarItem view] setMenu:tempMenu];
405                                 
406                                 //Add menu to toolbar item (for text mode)
407                                 [[toolbarItem menuFormRepresentation] setSubmenu:tempMenu];
408                                 
409                                 [tempMenu release];
410                         }
411                 }
412                 
413                 /* Now update inMenu.  We update the menu rather than replacing it with another menu so that
414                  * the menu will appear properly immediately rather than next time it is viewed.  Also, I suspect
415                  * it's a bad idea to release inMenu (by replacing it with another one) in the middle of this
416                  * delegate call.
417                  * 
418                  * Have to copy and autorelease here since the itemArray will change as we go through the items.
419                  */
420                 [inMenu removeAllItems];
421                 enumerator = [[[[theEmoticonMenu itemArray] copy] autorelease] objectEnumerator];
422                 while (menuItem = [enumerator nextObject]){
423                         [menuItem retain];
424                         [theEmoticonMenu removeItem:menuItem];
425                         [inMenu addItem:menuItem];
426                         [menuItem release];
427                 }
428                 
429                 needToRebuildMenus = NO;
430         }
431 }       
433 @end