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 <Adium/AIContentObject.h>
27 #import <Adium/AIHTMLDecoder.h>
29 #include <sys/errno.h>
32 #define TITLE_INSERT_SCRIPT AILocalizedString(@"Insert Script",nil)
33 #define SCRIPT_BUNDLE_EXTENSION @"AdiumScripts"
34 #define SCRIPTS_PATH_NAME @"Scripts"
35 #define SCRIPT_EXTENSION @"scpt"
36 #define SCRIPT_IDENTIFIER @"InsertScript"
38 #define SCRIPT_TIMEOUT 30
40 @interface GBApplescriptFiltersPlugin (PRIVATE)
42 - (void)_replaceKeyword:(NSString *)keyword
43 withScript:(NSMutableDictionary *)infoDict
44 inString:(NSString *)inString
45 inAttributedString:(NSMutableAttributedString *)attributedString
46 uniqueID:(unsigned long long)uniqueID;
48 - (void)_executeScript:(NSMutableDictionary *)infoDict
49 withArguments:(NSArray *)arguments
50 forAttributedString:(NSMutableAttributedString *)attributedString
51 keywordRange:(NSRange)keywordRange
52 uniqueID:(unsigned long long)uniqueID;
54 - (NSArray *)_argumentsFromString:(NSString *)inString forScript:(NSMutableDictionary *)scriptDict;
56 - (void)buildScriptMenu;
57 - (void)_appendScripts:(NSArray *)scripts toMenu:(NSMenu *)menu;
58 - (void)_sortScriptsByTitle:(NSMutableArray *)sortArray;
60 - (void)registerToolbarItem;
62 - (void)scriptDidFinish:(NSNotification *)aNotification;
64 - (void)_replaceKeyword:(NSString *)keyword
65 withScript:(NSMutableDictionary *)infoDict
66 inString:(NSString *)inString
67 inAttributedString:(NSMutableAttributedString *)attributedString
69 uniqueID:(unsigned long long)uniqueID;
71 - (void)_executeScript:(NSMutableDictionary *)infoDict
72 withArguments:(NSArray *)arguments
73 forAttributedString:(NSMutableAttributedString *)attributedString
74 keywordRange:(NSRange)keywordRange
76 uniqueID:(unsigned long long)uniqueID;
79 int _scriptTitleSort(id scriptA, id scriptB, void *context);
80 int _scriptKeywordLengthSort(id scriptA, id scriptB, void *context);
83 * @class GBApplescriptFiltersPlugin
84 * @brief Filter component to allow .AdiumScripts applescript-based filters for outgoing messages
86 @implementation GBApplescriptFiltersPlugin
94 [[AIObject sharedAdiumInstance] createResourcePathForName:@"Scripts"];
96 //We have an array of scripts for building the menu, and a dictionary of scripts used for the actual substition
98 flatScriptArray = nil;
100 //Prepare our script menu item (which will have the Scripts menu as its submenu)
101 scriptMenuItem = [[NSMenuItem alloc] initWithTitle:TITLE_INSERT_SCRIPT
103 action:@selector(dummyTarget:)
106 //Perform substitutions on outgoing content; we may be slow, so register as a delayed content filter
107 [[adium contentController] registerDelayedContentFilter:self
108 ofType:AIFilterContent
109 direction:AIFilterOutgoing];
111 //Observe for installation of new scripts
112 [[adium notificationCenter] addObserver:self
113 selector:@selector(xtrasChanged:)
114 name:AIXtrasDidChangeNotification
116 [[NSNotificationCenter defaultCenter] addObserver:self
117 selector:@selector(toolbarWillAddItem:)
118 name:NSToolbarWillAddItemNotification
121 //Start building the script menu
123 [self buildScriptMenu]; //this also sets the submenu for the menu item.
125 [[adium menuController] addMenuItem:scriptMenuItem toLocation:LOC_Edit_Additions];
127 contextualScriptMenuItem = [scriptMenuItem copy];
128 [[adium menuController] addContextualMenuItem:contextualScriptMenuItem toLocation:Context_TextView_Edit];
136 [[NSNotificationCenter defaultCenter] removeObserver:self];
137 [[adium notificationCenter] removeObserver:self];
139 [scriptArray release]; scriptArray = nil;
140 [flatScriptArray release]; flatScriptArray = nil;
141 [scriptMenuItem release]; scriptMenuItem = nil;
142 [contextualScriptMenuItem release]; contextualScriptMenuItem = nil;
148 * @brief Xtras changes
150 * If the scripts xtras changed, rebuild our menus.
152 - (void)xtrasChanged:(NSNotification *)notification
154 if ([[notification object] caseInsensitiveCompare:@"AdiumScripts"] == 0) {
155 [self buildScriptMenu];
157 [self registerToolbarItem];
159 //Update our toolbar item's menu
160 //[self toolbarWillAddItem:nil];
165 //Script Loading -------------------------------------------------------------------------------------------------------
166 #pragma mark Script Loading
168 * @brief Load our scripts
170 * This will clear out and then load from available scripts (external and internal) into flatScriptArray and scriptArray.
174 NSEnumerator *enumerator;
176 NSBundle *scriptBundle;
179 [scriptArray release]; scriptArray = [[NSMutableArray alloc] init];
180 [flatScriptArray release]; flatScriptArray = [[NSMutableArray alloc] init];
183 enumerator = [[adium allResourcesForName:@"Scripts" withExtensions:SCRIPT_BUNDLE_EXTENSION] objectEnumerator];
184 while ((filePath = [enumerator nextObject])) {
185 if ((scriptBundle = [NSBundle bundleWithPath:filePath])) {
186 NSString *scriptsSetName;
187 NSEnumerator *scriptEnumerator;
188 NSDictionary *scriptDict;
189 NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfFile:[[scriptBundle bundlePath] stringByAppendingPathComponent:@"Info.plist"]];
190 if (!infoDict) infoDict= [scriptBundle infoDictionary];
192 NSDictionary *localizedInfoDict = [scriptBundle localizedInfoDictionary];
194 //Get the name of the set these scripts will go into
195 scriptsSetName = [localizedInfoDict objectForKey:@"Set"];
196 if (!scriptsSetName) scriptsSetName = [infoDict objectForKey:@"Set"];
198 //Now enumerate each script the bundle claims as its own
199 scriptEnumerator = [[infoDict objectForKey:@"Scripts"] objectEnumerator];
201 while ((scriptDict = [scriptEnumerator nextObject])) {
202 NSString *scriptFileName, *scriptFilePath, *keyword, *title;
204 NSNumber *prefixOnlyNumber;
206 if ((scriptFileName = [scriptDict objectForKey:@"File"]) &&
207 (scriptFilePath = [scriptBundle pathForResource:scriptFileName
208 ofType:SCRIPT_EXTENSION])) {
210 keyword = [scriptDict objectForKey:@"Keyword"];
211 title = [scriptDict objectForKey:@"Title"];
213 //The keywords titles are keyed by their English version in the localized info dict
214 NSString *localizedKeyword = [localizedInfoDict objectForKey:keyword];
215 if (localizedKeyword) keyword = localizedKeyword;
217 NSString *localizedTitle = [localizedInfoDict objectForKey:title];
218 if (localizedTitle) title = localizedTitle;
220 if (keyword && [keyword length] && title && [title length]) {
221 NSMutableDictionary *infoDict;
223 arguments = [[scriptDict objectForKey:@"Arguments"] componentsSeparatedByString:@","];
225 //Assume "Prefix Only" is NO unless told otherwise or the keyword starts with '/'
226 prefixOnlyNumber = [scriptDict objectForKey:@"Prefix Only"];
227 if (!prefixOnlyNumber) {
228 prefixOnlyNumber = [NSNumber numberWithBool:([keyword characterAtIndex:0] == '/')];
231 infoDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
232 scriptFilePath, @"Path", keyword, @"Keyword", title, @"Title",
233 prefixOnlyNumber, @"PrefixOnly", nil];
235 //The bundle may not be part of (or for defining) a set of scripts
236 if (scriptsSetName) {
237 [infoDict setObject:scriptsSetName forKey:@"Set"];
239 //Arguments may be nil
241 [infoDict setObject:arguments forKey:@"Arguments"];
244 //Place the entry in our script arrays
245 [scriptArray addObject:infoDict];
246 [flatScriptArray addObject:infoDict];
248 //Scripts must always be updated via polling
249 [[adium contentController] registerFilterStringWhichRequiresPolling:keyword];
254 NSLog(@"Warning: Could not load Adium script bundle at %@",filePath);
260 //Script Menu ----------------------------------------------------------------------------------------------------------
261 #pragma mark Script Menu
263 * @brief Build the script menu
265 * Loads the scrpts as necessary, sorts them, then builds menus for the menu bar, the contextual menu,
266 * and the toolbar item.
268 - (void)buildScriptMenu
273 [scriptArray sortUsingFunction:_scriptTitleSort context:nil];
274 [flatScriptArray sortUsingFunction:_scriptKeywordLengthSort context:nil];
277 [scriptMenu release]; scriptMenu = [[NSMenu alloc] initWithTitle:TITLE_INSERT_SCRIPT];
278 [self _appendScripts:scriptArray toMenu:scriptMenu];
279 [scriptMenuItem setSubmenu:scriptMenu];
280 [contextualScriptMenuItem setSubmenu:[[scriptMenu copy] autorelease]];
282 [self registerToolbarItem];
286 * @brief Sort first by set, then by title within sets
288 int _scriptTitleSort(id scriptA, id scriptB, void *context) {
289 NSComparisonResult result;
291 NSString *setA = [scriptA objectForKey:@"Set"];
292 NSString *setB = [scriptB objectForKey:@"Set"];
296 //If both are within sets, sort by set; if they are within the same set, sort by title
297 if ((result = [setA caseInsensitiveCompare:setB]) == NSOrderedSame) {
298 result = [(NSString *)[scriptA objectForKey:@"Title"] caseInsensitiveCompare:[scriptB objectForKey:@"Title"]];
301 //Sort by title if neither is in a set; otherwise sort the one in a set to the top
303 if (!setA && !setB) {
304 result = [(NSString *)[scriptA objectForKey:@"Title"] caseInsensitiveCompare:[scriptB objectForKey:@"Title"]];
307 result = NSOrderedDescending;
309 result = NSOrderedAscending;
317 * @brief Sort by descending length so the longest keywords are at the beginning of the array
319 int _scriptKeywordLengthSort(id scriptA, id scriptB, void *context)
321 NSComparisonResult result;
323 unsigned int lengthA = [(NSString *)[scriptA objectForKey:@"Keyword"] length];
324 unsigned int lengthB = [(NSString *)[scriptB objectForKey:@"Keyword"] length];
325 if (lengthA > lengthB) {
326 result = NSOrderedAscending;
327 } else if (lengthA < lengthB) {
328 result = NSOrderedDescending;
330 result = NSOrderedSame;
337 * @brief Append an array of scripts to a menu
339 * @param scripts The scripts, each of which is represented by an NSDictionary instance
340 * @param menu The menu to which to add the scripts
342 - (void)_appendScripts:(NSArray *)scripts toMenu:(NSMenu *)menu
344 NSEnumerator *enumerator;
345 NSDictionary *appendDict;
346 NSString *lastSet = nil;
348 int indentationLevel;
350 enumerator = [scripts objectEnumerator];
351 while ((appendDict = [enumerator nextObject])) {
355 if ((set = [appendDict objectForKey:@"Set"])) {
356 indentationLevel = 1;
358 if (![set isEqualToString:lastSet]) {
359 //We have a new set of scripts; create a section header for them
360 item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:set
363 keyEquivalent:@""] autorelease];
364 if ([item respondsToSelector:@selector(setIndentationLevel:)]) [item setIndentationLevel:0];
367 [lastSet release]; lastSet = [set retain];
370 //Scripts not in sets need not be indented
371 indentationLevel = 0;
372 [lastSet release]; lastSet = nil;
375 if ([appendDict objectForKey:@"Title"]) {
376 title = [NSString stringWithFormat:@"%@ (%@)", [appendDict objectForKey:@"Title"], [appendDict objectForKey:@"Keyword"]];
378 title = [appendDict objectForKey:@"Keyword"];
381 item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
383 action:@selector(selectScript:)
384 keyEquivalent:@""] autorelease];
386 [item setRepresentedObject:appendDict];
387 if ([item respondsToSelector:@selector(setIndentationLevel:)]) [item setIndentationLevel:indentationLevel];
393 * @brief Insert a script's keyword into the text entry area
395 * This will be called by an NSMenuItem when it is clicked.
397 - (IBAction)selectScript:(id)sender
399 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
401 //Append our string into the responder if possible
402 if (responder && [responder isKindOfClass:[NSTextView class]]) {
403 NSArray *arguments = [[sender representedObject] objectForKey:@"Arguments"];
404 NSString *replacementText = [[sender representedObject] objectForKey:@"Keyword"];
406 [(NSTextView *)responder insertText:replacementText];
408 //Append arg list to replacement string, to show the user what they can pass
410 NSEnumerator *argumentEnumerator = [arguments objectEnumerator];
411 NSDictionary *originalTypingAttributes = [(NSTextView *)responder typingAttributes];
412 NSMutableDictionary *italicizedTypingAttributes = [originalTypingAttributes mutableCopy];
413 NSString *anArgument;
414 BOOL insertedFirst = NO;
416 [italicizedTypingAttributes setObject:[[NSFontManager sharedFontManager] convertFont:[originalTypingAttributes objectForKey:NSFontAttributeName]
417 toHaveTrait:NSItalicFontMask]
418 forKey:NSFontAttributeName];
420 [(NSTextView *)responder insertText:@"{"];
422 //Will that be a five minute argument or the full half hour?
423 while ((anArgument = [argumentEnumerator nextObject])) {
424 //Insert a comma after each argument past the first
426 [(NSTextView *)responder insertText:@","];
431 //Turn on the italics version, insert the argument, then go back to normal for either the comma or the ending
432 [(NSTextView *)responder setTypingAttributes:italicizedTypingAttributes];
433 [(NSTextView *)responder insertText:anArgument];
434 [(NSTextView *)responder setTypingAttributes:originalTypingAttributes];
437 [(NSTextView *)responder insertText:@"}"];
439 [italicizedTypingAttributes release];
445 * @brief Fake target to allow validateMenuItem: to be called
447 -(IBAction)dummyTarget:(id)sender{
451 * @brief Validate menu item
452 * Disable the insertion if a text field is not active
454 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
456 if ((menuItem == scriptMenuItem) || (menuItem == contextualScriptMenuItem)) {
457 return YES; //Always keep the submenu enabled so users can see the available scripts
459 NSResponder *responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
460 if (responder && [responder isKindOfClass:[NSText class]]) {
461 return [(NSText *)responder isEditable];
468 //Message Filtering ----------------------------------------------------------------------------------------------------
469 #pragma mark Message Filtering
471 * @brief Delayed filter messages for keywords to replace
473 * Will eventually replace any script keywords with the result of running the script (with arguments as appropriate).
474 * @result YES if we began a delayed filtration; NO if we did not
476 - (BOOL)delayedFilterAttributedString:(NSAttributedString *)inAttributedString context:(id)context uniqueID:(unsigned long long)uniqueID
478 BOOL beganProcessing = NO;
479 NSString *stringMessage;
481 if ((stringMessage = [inAttributedString string])) {
482 NSEnumerator *enumerator;
483 NSMutableDictionary *infoDict;
485 //Replace all keywords
486 enumerator = [flatScriptArray objectEnumerator];
487 while ((infoDict = [enumerator nextObject]) && !beganProcessing) {
488 NSString *keyword = [infoDict objectForKey:@"Keyword"];
489 BOOL prefixOnly = [[infoDict objectForKey:@"PrefixOnly"] boolValue];
491 if ((prefixOnly && ([stringMessage rangeOfString:keyword options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location == 0)) ||
492 (!prefixOnly && [stringMessage rangeOfString:keyword options:NSCaseInsensitiveSearch].location != NSNotFound)) {
493 NSNumber *shouldSendNumber;
495 [self _replaceKeyword:keyword
497 inString:stringMessage
498 inAttributedString:[[inAttributedString mutableCopy] autorelease]
502 shouldSendNumber = [infoDict objectForKey:@"ShouldSend"];
503 if ((shouldSendNumber) &&
504 (![shouldSendNumber boolValue]) &&
505 ([context isKindOfClass:[AIContentObject class]])) {
506 [(AIContentObject *)context setSendContent:NO];
509 beganProcessing = YES;
514 return beganProcessing;
518 * @brief Filter priority
520 * Filter earlier than the default
522 - (float)filterPriority
524 return HIGH_FILTER_PRIORITY;
528 * @brief Replace one instance of a keyword within a string. This will be called once for each instance.
530 - (void)_replaceKeyword:(NSString *)keyword
531 withScript:(NSMutableDictionary *)infoDict
532 inString:(NSString *)inString
533 inAttributedString:(NSMutableAttributedString *)attributedString
535 uniqueID:(unsigned long long)uniqueID
538 BOOL foundKeyword = NO;
540 //Scan for the keyword
541 scanner = [NSScanner scannerWithString:inString];
542 while (![scanner isAtEnd] && !foundKeyword) {
543 [scanner scanUpToString:keyword intoString:nil];
545 if (([scanner scanString:keyword intoString:nil]) &&
546 ([attributedString attribute:NSLinkAttributeName
547 atIndex:([scanner scanLocation]-1) /* The scanner ends up one past the keyword */
548 effectiveRange:nil] == nil)) {
549 //Scan the keyword and ensure it was not found within a link
550 int keywordStart, keywordEnd;
551 NSArray *argArray = nil;
555 keywordStart = [scanner scanLocation] - [keyword length];
556 if ([scanner scanString:@"{" intoString:nil]) {
557 if ([scanner scanUpToString:@"}" intoString:&argString]) {
558 argArray = [self _argumentsFromString:argString forScript:infoDict];
559 [scanner scanString:@"}" intoString:nil];
562 keywordEnd = [scanner scanLocation];
565 NSRange keywordRange = NSMakeRange(keywordStart, keywordEnd - keywordStart);
566 [self _executeScript:infoDict
567 withArguments:argArray
568 forAttributedString:attributedString
569 keywordRange:keywordRange
579 * @brief Execute the script as a separate task
581 * When the task is complete, we will be notified, at which point we perform the replacement for the script result
582 * and pass the modified attributed string back to the content controller for use.
584 - (void)_executeScript:(NSMutableDictionary *)infoDict
585 withArguments:(NSArray *)arguments
586 forAttributedString:(NSMutableAttributedString *)attributedString
587 keywordRange:(NSRange)keywordRange
589 uniqueID:(unsigned long long)uniqueID
591 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
592 attributedString, @"Mutable Attributed String",
593 NSStringFromRange(keywordRange), @"Range",
594 [NSNumber numberWithUnsignedLongLong:uniqueID], @"uniqueID",
595 (context ? context : [NSNull null]), @"context",
598 [[adium applescriptabilityController] runApplescriptAtPath:[infoDict objectForKey:@"Path"]
599 function:@"substitute"
602 selector:@selector(applescriptDidRun:resultString:)
607 * @brief A script finished running
609 - (void)applescriptDidRun:(id)userInfo resultString:(NSString *)resultString
611 NSMutableAttributedString *attributedString = [userInfo objectForKey:@"Mutable Attributed String"];
612 NSRange keywordRange = NSRangeFromString([userInfo objectForKey:@"Range"]);
613 unsigned long long uniqueID = [[userInfo objectForKey:@"uniqueID"] unsignedLongLongValue];
615 //If the script fails, eat the keyword
616 if (!resultString) resultString = @"";
618 //Replace the substring with script result
619 if (NSMaxRange(keywordRange) <= [attributedString length]) {
620 if (([resultString hasPrefix:@"<HTML>"])) {
621 //Obtain the attributed string version of the HTML, passing our current attributes as the default ones
622 NSAttributedString *attributedScriptResult = [AIHTMLDecoder decodeHTML:resultString
623 withDefaultAttributes:[attributedString attributesAtIndex:keywordRange.location
624 effectiveRange:nil]];
625 [attributedString replaceCharactersInRange:keywordRange
626 withAttributedString:attributedScriptResult];
629 [attributedString replaceCharactersInRange:keywordRange
630 withString:resultString];
634 //Inform the content controller that we're done if we don't need to do any more filtering
635 if (![self delayedFilterAttributedString:attributedString
636 context:[userInfo objectForKey:@"context"]
637 uniqueID:uniqueID]) {
638 [[adium contentController] delayedFilterDidFinish:attributedString
644 * @brief Determine the arguments for a script execution
646 * @param inString The string of potential arguments
647 * @param scriptDict The script being executed
649 * @result An NSArray of NSString instances
651 - (NSArray *)_argumentsFromString:(NSString *)inString forScript:(NSMutableDictionary *)scriptDict
653 NSArray *scriptArguments = [scriptDict objectForKey:@"Arguments"];
654 NSMutableArray *argArray = [NSMutableArray array];
655 NSArray *inStringComponents = [inString componentsSeparatedByString:@","];
658 unsigned count = (scriptArguments ? [scriptArguments count] : 0);
659 unsigned inStringComponentsCount = [inStringComponents count];
661 //Add each argument of inString to argArray so long as the number of arguments is less
662 //than the number of expected arguments for the script and the number of supplied arguments
663 while ((i < count) && (i < inStringComponentsCount)) {
664 [argArray addObject:[inStringComponents objectAtIndex:i]];
668 //If more components were passed than were actually requested, the last argument gets the
670 if (i < inStringComponentsCount) {
671 NSRange remainingRange;
673 //i was incremented to end the while loop if i > 0, so subtract 1 to reexamine the last object
674 remainingRange.location = ((i > 0) ? i-1 : 0);
675 remainingRange.length = (inStringComponentsCount - remainingRange.location);
677 if (remainingRange.location >= 0) {
678 NSString *lastArgument;
680 //Remove that last, incomplete argument if it was added
681 if ([argArray count]) [argArray removeLastObject];
683 //Create the last argument by joining all remaining comma-separated arguments with a comma
684 lastArgument = [[inStringComponents subarrayWithRange:remainingRange] componentsJoinedByString:@","];
686 [argArray addObject:lastArgument];
693 #pragma mark Toolbar item
695 * @brief Register our insert script toolbar item
697 - (void)registerToolbarItem
699 MVMenuButton *button;
701 //Unregister the existing toolbar item first
703 [[adium toolbarController] unregisterToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
704 [toolbarItem release]; toolbarItem = nil;
707 //Register our toolbar item
708 button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
709 [button setImage:[NSImage imageNamed:@"scriptToolbar" forClass:[self class] loadLazily:YES]];
710 toolbarItem = [[AIToolbarUtilities toolbarItemWithIdentifier:SCRIPT_IDENTIFIER
711 label:AILocalizedString(@"Scripts",nil)
712 paletteLabel:TITLE_INSERT_SCRIPT
713 toolTip:AILocalizedString(@"Insert a script",nil)
715 settingSelector:@selector(setView:)
717 action:@selector(selectScript:)
719 [toolbarItem setMinSize:NSMakeSize(32,32)];
720 [toolbarItem setMaxSize:NSMakeSize(32,32)];
721 [button setToolbarItem:toolbarItem];
722 [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"TextEntry"];
726 * @brief After the toolbar has added the item we can set up the submenus
728 - (void)toolbarWillAddItem:(NSNotification *)notification
730 NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"];
732 if (!notification || ([[item itemIdentifier] isEqualToString:SCRIPT_IDENTIFIER])) {
733 NSMenu *menu = [[[scriptMenuItem submenu] copy] autorelease];
736 [[item view] setMenu:menu];
738 //Add menu to toolbar item (for text mode)
739 NSMenuItem *mItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] init] autorelease];
740 [mItem setSubmenu:menu];
741 [mItem setTitle:[menu title]];
742 [item setMenuFormRepresentation:mItem];