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 <Adium/AIContentControllerProtocol.h>
18 #import <Adium/AIMenuControllerProtocol.h>
19 #import <Adium/AIToolbarControllerProtocol.h>
20 #import "ESApplescriptabilityController.h"
21 #import "GBApplescriptFiltersPlugin.h"
22 #import <AIUtilities/AIMenuAdditions.h>
23 #import <AIUtilities/AIToolbarUtilities.h>
24 #import <AIUtilities/AIImageAdditions.h>
25 #import <AIUtilities/MVMenuButton.h>
26 #import <AIUtilities/AIExceptionHandlingUtilities.h>
27 #import <Adium/AIContentObject.h>
28 #import <Adium/AIHTMLDecoder.h>
30 #include <sys/errno.h>
33 #define TITLE_INSERT_SCRIPT AILocalizedString(@"Insert Script",nil)
34 #define SCRIPT_BUNDLE_EXTENSION @"AdiumScripts"
35 #define SCRIPTS_PATH_NAME @"Scripts"
36 #define SCRIPT_EXTENSION @"scpt"
37 #define SCRIPT_IDENTIFIER @"InsertScript"
39 #define SCRIPT_TIMEOUT 30
41 @interface GBApplescriptFiltersPlugin (PRIVATE)
43 - (void)_replaceKeyword:(NSString *)keyword
44 withScript:(NSMutableDictionary *)infoDict
45 inString:(NSString *)inString
46 inAttributedString:(NSMutableAttributedString *)attributedString
47 uniqueID:(unsigned long long)uniqueID;
49 - (void)_executeScript:(NSMutableDictionary *)infoDict
50 withArguments:(NSArray *)arguments
51 forAttributedString:(NSMutableAttributedString *)attributedString
52 keywordRange:(NSRange)keywordRange
53 uniqueID:(unsigned long long)uniqueID;
55 - (NSArray *)_argumentsFromString:(NSString *)inString forScript:(NSMutableDictionary *)scriptDict;
57 - (void)buildScriptMenu;
58 - (void)_appendScripts:(NSArray *)scripts toMenu:(NSMenu *)menu;
59 - (void)_sortScriptsByTitle:(NSMutableArray *)sortArray;
61 - (void)registerToolbarItem;
63 - (void)scriptDidFinish:(NSNotification *)aNotification;
65 - (void)_replaceKeyword:(NSString *)keyword
66 withScript:(NSMutableDictionary *)infoDict
67 inString:(NSString *)inString
68 inAttributedString:(NSMutableAttributedString *)attributedString
70 uniqueID:(unsigned long long)uniqueID;
72 - (void)_executeScript:(NSMutableDictionary *)infoDict
73 withArguments:(NSArray *)arguments
74 forAttributedString:(NSMutableAttributedString *)attributedString
75 keywordRange:(NSRange)keywordRange
77 uniqueID:(unsigned long long)uniqueID;
80 int _scriptTitleSort(id scriptA, id scriptB, void *context);
81 int _scriptKeywordLengthSort(id scriptA, id scriptB, void *context);
84 * @class GBApplescriptFiltersPlugin
85 * @brief Filter component to allow .AdiumScripts applescript-based filters for outgoing messages
87 @implementation GBApplescriptFiltersPlugin
95 [[AIObject sharedAdiumInstance] createResourcePathForName:@"Scripts"];
97 //We have an array of scripts for building the menu, and a dictionary of scripts used for the actual substition
99 flatScriptArray = nil;
101 //Prepare our script menu item (which will have the Scripts menu as its submenu)
102 scriptMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_SCRIPT
104 action:@selector(dummyTarget:)
107 //Perform substitutions on outgoing content; we may be slow, so register as a delayed content filter
108 [[adium contentController] registerDelayedContentFilter:self
109 ofType:AIFilterContent
110 direction:AIFilterOutgoing];
112 //Observe for installation of new scripts
113 [[adium notificationCenter] addObserver:self
114 selector:@selector(xtrasChanged:)
115 name:Adium_Xtras_Changed
117 [[NSNotificationCenter defaultCenter] addObserver:self
118 selector:@selector(toolbarWillAddItem:)
119 name:NSToolbarWillAddItemNotification
122 //Start building the script menu
124 [self buildScriptMenu]; //this also sets the submenu for the menu item.
126 [[adium menuController] addMenuItem:scriptMenuItem toLocation:LOC_Edit_Additions];
128 contextualScriptMenuItem = [scriptMenuItem copy];
129 [[adium menuController] addContextualMenuItem:contextualScriptMenuItem toLocation:Context_TextView_Edit];
137 [[NSNotificationCenter defaultCenter] removeObserver:self];
138 [[adium notificationCenter] removeObserver:self];
140 [scriptArray release]; scriptArray = nil;
141 [flatScriptArray release]; flatScriptArray = nil;
142 [scriptMenuItem release]; scriptMenuItem = nil;
143 [contextualScriptMenuItem release]; contextualScriptMenuItem = nil;
149 * @brief Xtras changes
151 * If the scripts xtras changed, rebuild our menus.
153 - (void)xtrasChanged:(NSNotification *)notification
155 if ([[notification object] caseInsensitiveCompare:@"AdiumScripts"] == 0) {
156 [self buildScriptMenu];
158 [self registerToolbarItem];
160 //Update our toolbar item's menu
161 //[self toolbarWillAddItem:nil];
166 //Script Loading -------------------------------------------------------------------------------------------------------
167 #pragma mark Script Loading
169 * @brief Load our scripts
171 * This will clear out and then load from available scripts (external and internal) into flatScriptArray and scriptArray.
175 NSEnumerator *enumerator;
177 NSBundle *scriptBundle;
180 [scriptArray release]; scriptArray = [[NSMutableArray alloc] init];
181 [flatScriptArray release]; flatScriptArray = [[NSMutableArray alloc] init];
184 enumerator = [[adium allResourcesForName:@"Scripts" withExtensions:SCRIPT_BUNDLE_EXTENSION] objectEnumerator];
185 while ((filePath = [enumerator nextObject])) {
186 if ((scriptBundle = [NSBundle bundleWithPath:filePath])) {
188 NSString *scriptsSetName;
189 NSEnumerator *scriptEnumerator;
190 NSDictionary *scriptDict;
191 NSDictionary *infoDict = [scriptBundle infoDictionary];
193 //Get the name of the set these scripts will go into
194 scriptsSetName = [infoDict objectForKey:@"Set"];
196 //Now enumerate each script the bundle claims as its own
197 scriptEnumerator = [[infoDict objectForKey:@"Scripts"] objectEnumerator];
199 while ((scriptDict = [scriptEnumerator nextObject])) {
200 NSString *scriptFileName, *scriptFilePath, *keyword, *title;
202 NSNumber *prefixOnlyNumber;
204 if ((scriptFileName = [scriptDict objectForKey:@"File"]) &&
205 (scriptFilePath = [scriptBundle pathForResource:scriptFileName
206 ofType:SCRIPT_EXTENSION])) {
208 keyword = [scriptDict objectForKey:@"Keyword"];
209 title = [scriptDict objectForKey:@"Title"];
211 if (keyword && [keyword length] && title && [title length]) {
212 NSMutableDictionary *infoDict;
214 arguments = [[scriptDict objectForKey:@"Arguments"] componentsSeparatedByString:@","];
216 //Assume "Prefix Only" is NO unless told otherwise or the keyword starts with '/'
217 prefixOnlyNumber = [scriptDict objectForKey:@"Prefix Only"];
218 if (!prefixOnlyNumber) {
219 prefixOnlyNumber = [NSNumber numberWithBool:([keyword characterAtIndex:0] == '/')];
222 infoDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
223 scriptFilePath, @"Path", keyword, @"Keyword", title, @"Title",
224 prefixOnlyNumber, @"PrefixOnly", nil];
226 //The bundle may not be part of (or for defining) a set of scripts
227 if (scriptsSetName) {
228 [infoDict setObject:scriptsSetName forKey:@"Set"];
230 //Arguments may be nil
232 [infoDict setObject:arguments forKey:@"Arguments"];
235 //Place the entry in our script arrays
236 [scriptArray addObject:infoDict];
237 [flatScriptArray addObject:infoDict];
239 //Scripts must always be updated via polling
240 [[adium contentController] registerFilterStringWhichRequiresPolling:keyword];
249 //Script Menu ----------------------------------------------------------------------------------------------------------
250 #pragma mark Script Menu
252 * @brief Build the script menu
254 * Loads the scrpts as necessary, sorts them, then builds menus for the menu bar, the contextual menu,
255 * and the toolbar item.
257 - (void)buildScriptMenu
262 [scriptArray sortUsingFunction:_scriptTitleSort context:nil];
263 [flatScriptArray sortUsingFunction:_scriptKeywordLengthSort context:nil];
266 [scriptMenu release]; scriptMenu = [[NSMenu alloc] initWithTitle:TITLE_INSERT_SCRIPT];
267 [self _appendScripts:scriptArray toMenu:scriptMenu];
268 [scriptMenuItem setSubmenu:scriptMenu];
269 [contextualScriptMenuItem setSubmenu:[[scriptMenu copy] autorelease]];
271 [self registerToolbarItem];
275 * @brief Sort first by set, then by title within sets
277 int _scriptTitleSort(id scriptA, id scriptB, void *context) {
278 NSComparisonResult result;
280 NSString *setA = [scriptA objectForKey:@"Set"];
281 NSString *setB = [scriptB objectForKey:@"Set"];
285 //If both are within sets, sort by set; if they are within the same set, sort by title
286 if ((result = [setA caseInsensitiveCompare:setB]) == NSOrderedSame) {
287 result = [(NSString *)[scriptA objectForKey:@"Title"] caseInsensitiveCompare:[scriptB objectForKey:@"Title"]];
290 //Sort by title if neither is in a set; otherwise sort the one in a set to the top
292 if (!setA && !setB) {
293 result = [(NSString *)[scriptA objectForKey:@"Title"] caseInsensitiveCompare:[scriptB objectForKey:@"Title"]];
296 result = NSOrderedDescending;
298 result = NSOrderedAscending;
306 * @brief Sort by descending length so the longest keywords are at the beginning of the array
308 int _scriptKeywordLengthSort(id scriptA, id scriptB, void *context)
310 NSComparisonResult result;
312 unsigned int lengthA = [(NSString *)[scriptA objectForKey:@"Keyword"] length];
313 unsigned int lengthB = [(NSString *)[scriptB objectForKey:@"Keyword"] length];
314 if (lengthA > lengthB) {
315 result = NSOrderedAscending;
316 } else if (lengthA < lengthB) {
317 result = NSOrderedDescending;
319 result = NSOrderedSame;
326 * @brief Append an array of scripts to a menu
328 * @param scripts The scripts, each of which is represented by an NSDictionary instance
329 * @param menu The menu to which to add the scripts
331 - (void)_appendScripts:(NSArray *)scripts toMenu:(NSMenu *)menu
333 NSEnumerator *enumerator;
334 NSDictionary *appendDict;
335 NSString *lastSet = nil;
337 int indentationLevel;
339 enumerator = [scripts objectEnumerator];
340 while ((appendDict = [enumerator nextObject])) {
344 if ((set = [appendDict objectForKey:@"Set"])) {
345 indentationLevel = 1;
347 if (![set isEqualToString:lastSet]) {
348 //We have a new set of scripts; create a section header for them
349 item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:set
352 keyEquivalent:@""] autorelease];
353 if ([item respondsToSelector:@selector(setIndentationLevel:)]) [item setIndentationLevel:0];
356 [lastSet release]; lastSet = [set retain];
359 //Scripts not in sets need not be indented
360 indentationLevel = 0;
361 [lastSet release]; lastSet = nil;
364 if ([appendDict objectForKey:@"Title"]) {
365 title = [NSString stringWithFormat:@"%@ (%@)", [appendDict objectForKey:@"Title"], [appendDict objectForKey:@"Keyword"]];
367 title = [appendDict objectForKey:@"Keyword"];
370 item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
372 action:@selector(selectScript:)
373 keyEquivalent:@""] autorelease];
375 [item setRepresentedObject:appendDict];
376 if ([item respondsToSelector:@selector(setIndentationLevel:)]) [item setIndentationLevel:indentationLevel];
382 * @brief Insert a script's keyword into the text entry area
384 * This will be called by an NSMenuItem when it is clicked.
386 - (IBAction)selectScript:(id)sender
388 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
390 //Append our string into the responder if possible
391 if (responder && [responder isKindOfClass:[NSTextView class]]) {
392 NSArray *arguments = [[sender representedObject] objectForKey:@"Arguments"];
393 NSString *replacementText = [[sender representedObject] objectForKey:@"Keyword"];
395 [(NSTextView *)responder insertText:replacementText];
397 //Append arg list to replacement string, to show the user what they can pass
399 NSEnumerator *argumentEnumerator = [arguments objectEnumerator];
400 NSDictionary *originalTypingAttributes = [(NSTextView *)responder typingAttributes];
401 NSMutableDictionary *italicizedTypingAttributes = [originalTypingAttributes mutableCopy];
402 NSString *anArgument;
403 BOOL insertedFirst = NO;
405 [italicizedTypingAttributes setObject:[[NSFontManager sharedFontManager] convertFont:[originalTypingAttributes objectForKey:NSFontAttributeName]
406 toHaveTrait:NSItalicFontMask]
407 forKey:NSFontAttributeName];
409 [(NSTextView *)responder insertText:@"{"];
411 //Will that be a five minute argument or the full half hour?
412 while ((anArgument = [argumentEnumerator nextObject])) {
413 //Insert a comma after each argument past the first
415 [(NSTextView *)responder insertText:@","];
420 //Turn on the italics version, insert the argument, then go back to normal for either the comma or the ending
421 [(NSTextView *)responder setTypingAttributes:italicizedTypingAttributes];
422 [(NSTextView *)responder insertText:anArgument];
423 [(NSTextView *)responder setTypingAttributes:originalTypingAttributes];
426 [(NSTextView *)responder insertText:@"}"];
428 [italicizedTypingAttributes release];
434 * @brief Fake target to allow validateMenuItem: to be called
436 -(IBAction)dummyTarget:(id)sender{
440 * @brief Validate menu item
441 * Disable the insertion if a text field is not active
443 - (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
445 if ((menuItem == scriptMenuItem) || (menuItem == contextualScriptMenuItem)) {
446 return YES; //Always keep the submenu enabled so users can see the available scripts
448 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
449 if (responder && [responder isKindOfClass:[NSText class]]) {
450 return [(NSText *)responder isEditable];
457 //Message Filtering ----------------------------------------------------------------------------------------------------
458 #pragma mark Message Filtering
460 * @brief Delayed filter messages for keywords to replace
462 * Will eventually replace any script keywords with the result of running the script (with arguments as appropriate).
463 * @result YES if we began a delayed filtration; NO if we did not
465 - (BOOL)delayedFilterAttributedString:(NSAttributedString *)inAttributedString context:(id)context uniqueID:(unsigned long long)uniqueID
467 BOOL beganProcessing = NO;
468 NSString *stringMessage;
470 if ((stringMessage = [inAttributedString string])) {
471 NSEnumerator *enumerator;
472 NSMutableDictionary *infoDict;
474 //Replace all keywords
475 enumerator = [flatScriptArray objectEnumerator];
476 while ((infoDict = [enumerator nextObject]) && !beganProcessing) {
477 NSString *keyword = [infoDict objectForKey:@"Keyword"];
478 BOOL prefixOnly = [[infoDict objectForKey:@"PrefixOnly"] boolValue];
480 if ((prefixOnly && ([stringMessage rangeOfString:keyword options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location == 0)) ||
481 (!prefixOnly && [stringMessage rangeOfString:keyword options:NSCaseInsensitiveSearch].location != NSNotFound)) {
482 NSNumber *shouldSendNumber;
484 [self _replaceKeyword:keyword
486 inString:stringMessage
487 inAttributedString:[[inAttributedString mutableCopy] autorelease]
491 shouldSendNumber = [infoDict objectForKey:@"ShouldSend"];
492 if ((shouldSendNumber) &&
493 (![shouldSendNumber boolValue]) &&
494 ([context isKindOfClass:[AIContentObject class]])) {
495 [(AIContentObject *)context setSendContent:NO];
498 beganProcessing = YES;
503 return beganProcessing;
507 * @brief Filter priority
509 * Filter earlier than the default
511 - (float)filterPriority
513 return HIGH_FILTER_PRIORITY;
517 * @brief Replace one instance of a keyword within a string. This will be called once for each instance.
519 - (void)_replaceKeyword:(NSString *)keyword
520 withScript:(NSMutableDictionary *)infoDict
521 inString:(NSString *)inString
522 inAttributedString:(NSMutableAttributedString *)attributedString
524 uniqueID:(unsigned long long)uniqueID
527 BOOL foundKeyword = NO;
529 //Scan for the keyword
530 scanner = [NSScanner scannerWithString:inString];
531 while (![scanner isAtEnd] && !foundKeyword) {
532 [scanner scanUpToString:keyword intoString:nil];
534 if (([scanner scanString:keyword intoString:nil]) &&
535 ([attributedString attribute:NSLinkAttributeName
536 atIndex:([scanner scanLocation]-1) /* The scanner ends up one past the keyword */
537 effectiveRange:nil] == nil)) {
538 //Scan the keyword and ensure it was not found within a link
539 int keywordStart, keywordEnd;
540 NSArray *argArray = nil;
544 keywordStart = [scanner scanLocation] - [keyword length];
545 if ([scanner scanString:@"{" intoString:nil]) {
546 if ([scanner scanUpToString:@"}" intoString:&argString]) {
547 argArray = [self _argumentsFromString:argString forScript:infoDict];
548 [scanner scanString:@"}" intoString:nil];
551 keywordEnd = [scanner scanLocation];
553 if (keywordStart != 0 && [inString characterAtIndex:keywordStart - 1] == '\\') {
554 //Ignore the script (It was escaped) and delete the escape character
555 //XXX This is broken now; escaping scripts is no longer possible. Do we care? I don't. -evands
556 [attributedString replaceCharactersInRange:NSMakeRange(keywordStart - 1, 1) withString:@""];
561 NSRange keywordRange = NSMakeRange(keywordStart, keywordEnd - keywordStart);
563 [self _executeScript:infoDict
564 withArguments:argArray
565 forAttributedString:attributedString
566 keywordRange:keywordRange
577 * @brief Execute the script as a separate task
579 * When the task is complete, we will be notified, at which point we perform the replacement for the script result
580 * and pass the modified attributed string back to the content controller for use.
582 - (void)_executeScript:(NSMutableDictionary *)infoDict
583 withArguments:(NSArray *)arguments
584 forAttributedString:(NSMutableAttributedString *)attributedString
585 keywordRange:(NSRange)keywordRange
587 uniqueID:(unsigned long long)uniqueID
589 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
590 attributedString, @"Mutable Attributed String",
591 NSStringFromRange(keywordRange), @"Range",
592 [NSNumber numberWithUnsignedLongLong:uniqueID], @"uniqueID",
593 (context ? context : [NSNull null]), @"context",
596 [[adium applescriptabilityController] runApplescriptAtPath:[infoDict objectForKey:@"Path"]
597 function:@"substitute"
600 selector:@selector(applescriptDidRun:resultString:)
605 * @brief A script finished running
607 - (void)applescriptDidRun:(id)userInfo resultString:(NSString *)resultString
609 NSMutableAttributedString *attributedString = [userInfo objectForKey:@"Mutable Attributed String"];
610 NSRange keywordRange = NSRangeFromString([userInfo objectForKey:@"Range"]);
611 unsigned long long uniqueID = [[userInfo objectForKey:@"uniqueID"] unsignedLongLongValue];
613 //If the script fails, eat the keyword
614 if (!resultString) resultString = @"";
616 //Replace the substring with script result
617 if (NSMaxRange(keywordRange) <= [attributedString length]) {
618 if (([resultString hasPrefix:@"<HTML>"])) {
619 //Obtain the attributed string version of the HTML, passing our current attributes as the default ones
620 NSAttributedString *attributedScriptResult = [AIHTMLDecoder decodeHTML:resultString
621 withDefaultAttributes:[attributedString attributesAtIndex:keywordRange.location
622 effectiveRange:nil]];
623 [attributedString replaceCharactersInRange:keywordRange
624 withAttributedString:attributedScriptResult];
627 [attributedString replaceCharactersInRange:keywordRange
628 withString:resultString];
632 //Inform the content controller that we're done if we don't need to do any more filtering
633 if (![self delayedFilterAttributedString:attributedString
634 context:[userInfo objectForKey:@"context"]
635 uniqueID:uniqueID]) {
636 [[adium contentController] delayedFilterDidFinish:attributedString
642 * @brief Determine the arguments for a script execution
644 * @param inString The string of potential arguments
645 * @param scriptDict The script being executed
647 * @result An NSArray of NSString instances
649 - (NSArray *)_argumentsFromString:(NSString *)inString forScript:(NSMutableDictionary *)scriptDict
651 NSArray *scriptArguments = [scriptDict objectForKey:@"Arguments"];
652 NSMutableArray *argArray = [NSMutableArray array];
653 NSArray *inStringComponents = [inString componentsSeparatedByString:@","];
656 unsigned count = (scriptArguments ? [scriptArguments count] : 0);
657 unsigned inStringComponentsCount = [inStringComponents count];
659 //Add each argument of inString to argArray so long as the number of arguments is less
660 //than the number of expected arguments for the script and the number of supplied arguments
661 while ((i < count) && (i < inStringComponentsCount)) {
662 [argArray addObject:[inStringComponents objectAtIndex:i]];
666 //If more components were passed than were actually requested, the last argument gets the
668 if (i < inStringComponentsCount) {
669 NSRange remainingRange;
671 //i was incremented to end the while loop if i > 0, so subtract 1 to reexamine the last object
672 remainingRange.location = ((i > 0) ? i-1 : 0);
673 remainingRange.length = (inStringComponentsCount - remainingRange.location);
675 if (remainingRange.location >= 0) {
676 NSString *lastArgument;
678 //Remove that last, incomplete argument if it was added
679 if ([argArray count]) [argArray removeLastObject];
681 //Create the last argument by joining all remaining comma-separated arguments with a comma
682 lastArgument = [[inStringComponents subarrayWithRange:remainingRange] componentsJoinedByString:@","];
684 [argArray addObject:lastArgument];
691 #pragma mark Toolbar item
693 * @brief Register our insert script toolbar item
695 - (void)registerToolbarItem
697 MVMenuButton *button;
699 //Unregister the existing toolbar item first
701 [[adium toolbarController] unregisterToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
702 [toolbarItem release]; toolbarItem = nil;
705 //Register our toolbar item
706 button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
707 [button setImage:[NSImage imageNamed:@"scriptToolbar" forClass:[self class]]];
708 toolbarItem = [[AIToolbarUtilities toolbarItemWithIdentifier:SCRIPT_IDENTIFIER
709 label:AILocalizedString(@"Scripts",nil)
710 paletteLabel:TITLE_INSERT_SCRIPT
711 toolTip:AILocalizedString(@"Insert a script",nil)
713 settingSelector:@selector(setView:)
715 action:@selector(selectScript:)
717 [toolbarItem setMinSize:NSMakeSize(32,32)];
718 [toolbarItem setMaxSize:NSMakeSize(32,32)];
719 [button setToolbarItem:toolbarItem];
720 [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
724 * @brief After the toolbar has added the item we can set up the submenus
726 - (void)toolbarWillAddItem:(NSNotification *)notification
728 NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
730 if (!notification || ([[item itemIdentifier] isEqualToString:SCRIPT_IDENTIFIER])) {
731 NSMenu *menu = [[[scriptMenuItem submenu] copy] autorelease];
734 [[item view] setMenu:menu];
736 //Add menu to toolbar item (for text mode)
737 NSMenuItem *mItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] init] autorelease];
738 [mItem setSubmenu:menu];
739 [mItem setTitle:[menu title]];
740 [item setMenuFormRepresentation:mItem];