Bug Summary

File:Source/BGEmoticonMenuPlugin.m
Location:line 307, column 4
Description:Memory Leak
Code is compiled without garbage collection.

Annotated Source Code

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