diff --git a/Commands/alert.mm b/Commands/alert.mm index eac1f90..452700f 100644 --- a/Commands/alert.mm +++ b/Commands/alert.mm @@ -44,24 +44,65 @@ - (void)handleCommand:(CLIProxy*)proxy [alert setMessageText:msg]; if(NSString* txt = [args objectForKey:@"body"]) [alert setInformativeText:txt]; + if(NSString* sup = [args objectForKey:@"suppression"]) + { + [alert setShowsSuppressionButton:YES]; + [[alert suppressionButton] setTitle:sup]; + } + + if(NSString* iconPath = [args objectForKey:@"icon"]) + { + NSImage *icon = nil; + iconPath = [iconPath stringByResolvingSymlinksInPath]; + BOOL isDir = NO; + if([[NSFileManager defaultManager] fileExistsAtPath:iconPath isDirectory:&isDir] && !isDir) + { + icon = [[NSImage alloc] initByReferencingFile:iconPath]; + if(icon && [icon isValid]) + { + [alert setIcon:icon]; + [icon release]; + } + } + else if(icon = [NSImage imageNamed:iconPath]) + [alert setIcon:icon]; + else if(icon = [NSImage imageNamed:[iconPath stringByReplacingOccurrencesOfString:@"ImageName" withString:@""]]) + [alert setIcon:icon]; + + if(!icon) + fprintf(stderr, "Passed icon path or named image '%s' not found.\n", [iconPath UTF8String]); + + } int i = 0; while(NSString* button = [args objectForKey:[NSString stringWithFormat:@"button%d", ++i]]) [alert addButtonWithTitle:button]; int alertResult = ([alert runModal] - NSAlertFirstButtonReturn); - NSDictionary* resultDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:alertResult] forKey:@"buttonClicked"]; + NSMutableDictionary* resultDict = [NSMutableDictionary dictionary]; + [resultDict setObject:[NSNumber numberWithInt:alertResult] forKey:@"buttonClicked"]; + if([args objectForKey:@"suppression"]) + [resultDict setObject:[NSNumber numberWithInt:[[alert suppressionButton] state]] forKey:@"suppressionButtonState"]; - [TMDCommand writePropertyList:resultDict toFileHandle:[proxy outputHandle]]; + [TMDCommand writePropertyList:resultDict toFileHandle:[proxy outputHandle] withProxy:proxy]; } - (NSString *)commandDescription { - return @"Show an alert box."; + return @"Shows a customizable alert box and returns the index of the chosen button - counting from the right."; } - (NSString *)usageForInvocation:(NSString *)invocation; { - return [NSString stringWithFormat:@"\t%1$@ --alertStyle warning --title 'Delete File?' --body 'You cannot undo this action.' --button1 Delete --button2 Cancel\n", invocation]; + return [NSString stringWithFormat: + @"\t%1$@ --alertStyle critical --title 'Delete File?' --body 'You cannot undo this action.' --button1 Delete --button2 Cancel\n" + @"\t%1$@ --filter buttonClicked --title 'Delete File?' --body 'You cannot undo this action.' --button1 Delete --button2 Cancel\n" + @"\t%1$@ --icon NSUserAccounts --title 'Delete Account?' --body 'You cannot undo this action.' --button1 Delete --button2 Cancel\n" + @"\t%1$@ --icon '~/Pictures/iChat Icons/Flags/Denmark.png' --title 'First Run?' --body 'Please note this.' --suppression 'Do not show this again'\n" + @"\nOption:\n" + @"\t--alertStyle {informational, warning, critical}\n" + @"\t\t if not specified the default style is 'informational'\n" + @"\t--icon «image path or known image name»\n" + @"\t--suppression «title»\n", invocation]; } @end diff --git a/Commands/defaults.mm b/Commands/defaults.mm index 11f2e3a..d5279b2 100644 --- a/Commands/defaults.mm +++ b/Commands/defaults.mm @@ -27,13 +27,13 @@ - (void)handleCommand:(CLIProxy*)proxy if(NSString* key = [args objectForKey:@"read"]) { if(id obj = [[NSUserDefaults standardUserDefaults] objectForKey:key]) - [TMDCommand writePropertyList:obj toFileHandle:[proxy outputHandle]]; + [TMDCommand writePropertyList:obj toFileHandle:[proxy outputHandle] withProxy:proxy]; } } - (NSString *)commandDescription { - return @"Register default values for user settings."; + return @"Registers default values for user settings."; } - (NSString *)usageForInvocation:(NSString *)invocation; diff --git a/Commands/help.mm b/Commands/help.mm index 7306e86..41f464b 100644 --- a/Commands/help.mm +++ b/Commands/help.mm @@ -22,29 +22,38 @@ - (NSString *)commandDescription - (NSString *)usageForInvocation:(NSString *)invocation; { - return [NSString stringWithFormat:@"%@ help [command]", invocation]; + return [NSString stringWithFormat:@"\t%1$@ [«command»]\n", invocation]; } - (NSString *)commandSummaryText { NSDictionary *commands = [TMDCommand registeredCommands]; - + NSArray *sortedItems = [[commands allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSMutableString *help = [NSMutableString stringWithCapacity:100]; - int commandCount = 0; - for(NSEnumerator *enumerator = [commands keyEnumerator]; NSString *commandName = [enumerator nextObject]; ) + NSInteger commandCount = 0; + for(NSString *commandName in sortedItems) { if(![commandName hasPrefix:@"x-"]) { ++commandCount; - TMDCommand *command = [commands objectForKey:commandName]; - NSString *description = [command commandDescription]; - [help appendFormat:@"\t%@: %@\n", commandName, description]; + NSString *description = [[(TMDCommand*)[commands objectForKey:commandName] commandDescription] + stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t\t"]; + [help appendFormat:@"\t%@\n\t\t%@\n", commandName, description]; } } - [help insertString:[NSString stringWithFormat:@"%d commands registered:\n", commandCount] atIndex:0]; + [help insertString:[NSString stringWithFormat:@"%ld commands registered:\n", commandCount] atIndex:0]; - [help appendString:@"Use `\"$DIALOG\" help command` for detailed help\n"]; + [help appendString:@"\nUse `\"$DIALOG\" help command` for detailed help\n\n"]; + [help appendString: + @"Options:\n" + @"\t--filter \n" + @"\t--filter \n" + @"\t\tFor commands returning a property list as default specify the \n" + @"\t\twhose value(s) should be outputted as plain string\n" + @"\t\tseparated by a new line character.\n" + @"\t\tIf a passed doesn't exist it returns an empty string.\n"]; return help; } @@ -57,7 +66,7 @@ - (NSString *)helpForCommand:(NSString *)commandName if(![commandName hasPrefix:@"x-"] && (command = [TMDCommand objectForCommand:commandName])) { [help appendFormat:@"%@\n\n",[command commandDescription]]; - [help appendFormat:@"%@ usage:\n",commandName]; + [help appendFormat:@"'%@' usage:\n",commandName]; [help appendFormat:@"%@\n",[command usageForInvocation:[NSString stringWithFormat:@"\"$DIALOG\" %@", commandName]]]; } else diff --git a/Commands/images.mm b/Commands/images.mm index cd23d67..c7bed89 100644 --- a/Commands/images.mm +++ b/Commands/images.mm @@ -34,7 +34,7 @@ - (void)handleCommand:(CLIProxy*)proxy - (NSString *)commandDescription { - return @"Add image files as named images for use by other commands/nibs."; + return @"Adds image files as named images for use by other commands/nibs."; } - (NSString *)usageForInvocation:(NSString *)invocation; diff --git a/Commands/menu.mm b/Commands/menu.mm index 5d1363c..7784d84 100644 --- a/Commands/menu.mm +++ b/Commands/menu.mm @@ -1,4 +1,3 @@ -#import #import "../Dialog2.h" #import "../TMDCommand.h" #import "Utilities/TextMate.h" // -positionForWindowUnderCaret @@ -12,31 +11,43 @@ "$DIALOG" menu --items '({title = "foo"; header = 1;},{title = "bar";})' */ +#define kMenuTitleKey @"title" +#define kMenuItemsKey @"items" +#define kMenuSeparatorKey @"separator" +#define kMenuHeaderKey @"header" +#define kMenuMenuKey @"menu" + @interface DialogPopupMenuTarget : NSObject { - NSInteger selectedIndex; + NSDictionary *selectedObject; } -@property NSInteger selectedIndex; +@property (nonatomic, retain) NSDictionary *selectedObject; @end @implementation DialogPopupMenuTarget -@synthesize selectedIndex; +@synthesize selectedObject; - (id)init { if((self = [super init])) - self.selectedIndex = NSNotFound; + self.selectedObject = nil; return self; } - (BOOL)validateMenuItem:(NSMenuItem*)menuItem { - return [menuItem action] == @selector(takeSelectedItemIndexFrom:); + return [menuItem action] == @selector(takeSelectedItemFrom:); } -- (void)takeSelectedItemIndexFrom:(id)sender +- (void)takeSelectedItemFrom:(id)sender { NSAssert([sender isKindOfClass:[NSMenuItem class]], @"Unexpected sender for menu target"); - self.selectedIndex = [(NSMenuItem*)sender tag]; + self.selectedObject = [(NSMenuItem*)sender representedObject]; +} + +- (void)dealloc +{ + if(selectedObject) [selectedObject release]; + [super dealloc]; } @end @@ -48,23 +59,26 @@ @interface TMDMenuCommand : TMDCommand @implementation TMDMenuCommand + (void)load { - [TMDCommand registerObject:[self new] forCommand:@"menu"]; + [TMDCommand registerObject:[self new] forCommand:kMenuMenuKey]; } - (NSString *)commandDescription { - return @"Presents a menu using the given structure and returns the option chosen by the user"; + return @"Presents a menu using the given structure and returns the underlying object chosen by the user."; } - (NSString *)usageForInvocation:(NSString *)invocation; { - return [NSString stringWithFormat:@"\t%1$@ --items '({title = foo;}, {separator = 1;}, {header=1; title = bar;}, {title = baz;})'\n", invocation]; + return [NSString stringWithFormat:@"\ + %1$@ --items '({title = foo;}, {separator = 1;}, {header=1; title = bar;}, {title = baz;})'\n\ + %1$@ --items '({title = foo;}, {separator = 1;}, {header=1; title = bar1;}, {title = baz; ofHeader = bar1;}, {header=1; title = bar2;}, {title = baz; ofHeader = bar2;}, {menu = { title = aSubmenu; items = ( {title = baz; ofSubmenu = aSubmenu;}, {separator = 1;}, {header=1; title = bar2;}, {title = subbaz;} ); };})'\n", + invocation]; } - (void)handleCommand:(CLIProxy*)proxy { NSDictionary* args = [proxy parameters]; - NSArray* menuItems = [args objectForKey:@"items"]; + NSArray* menuItems = [args objectForKey:kMenuItemsKey]; // FIXME this is needed only because we presently can’t express argument constraints (CLIProxy would otherwise correctly validate/convert CLI arguments) if([menuItems isKindOfClass:[NSString class]]) @@ -74,31 +88,84 @@ - (void)handleCommand:(CLIProxy*)proxy [menu setFont:[NSFont menuFontOfSize:([[NSUserDefaults standardUserDefaults] integerForKey:@"OakBundleManagerDisambiguateMenuFontSize"] ?: [NSFont smallSystemFontSize])]]; DialogPopupMenuTarget* menuTarget = [[[DialogPopupMenuTarget alloc] init] autorelease]; - int item_id = 0; - bool in_section = false; + NSInteger item_id_key = 0; + BOOL in_section = false; + enumerate(menuItems, NSDictionary* menuItem) { - if([[menuItem objectForKey:@"separator"] intValue]) + // check for separator + if([[menuItem objectForKey:kMenuSeparatorKey] intValue]) { [menu addItem:[NSMenuItem separatorItem]]; } - else if([[menuItem objectForKey:@"header"] intValue]) + // check for header and indent following items + else if([[menuItem objectForKey:kMenuHeaderKey] intValue]) { - [menu addItemWithTitle:[menuItem objectForKey:@"title"] action:NULL keyEquivalent:@""]; - in_section = true; + if(NSString *item = [menuItem objectForKey:kMenuTitleKey]) + { + [menu addItemWithTitle:item action:NULL keyEquivalent:@""]; + in_section = true; + } } + // check for a submenu + else if(NSDictionary *aSubMenu = [menuItem objectForKey:kMenuMenuKey]) + { + if([aSubMenu objectForKey:kMenuTitleKey] && + [aSubMenu objectForKey:kMenuItemsKey] && + [[aSubMenu objectForKey:kMenuItemsKey] isKindOfClass:[NSArray class]]) + { + NSArray *subMenuItems = (NSArray*)[aSubMenu objectForKey:kMenuItemsKey]; + NSMenu* submenu = [[[NSMenu alloc] init] autorelease]; + [submenu setFont:[NSFont menuFontOfSize:([[NSUserDefaults standardUserDefaults] integerForKey:@"OakBundleManagerDisambiguateMenuFontSize"] ?: [NSFont smallSystemFontSize])]]; + + NSString *submenuTitle = [aSubMenu objectForKey:kMenuTitleKey]; + BOOL subin_section = false; + + enumerate(subMenuItems, NSDictionary* menuItem) + { + if([[menuItem objectForKey:kMenuSeparatorKey] intValue]) + { + [submenu addItem:[NSMenuItem separatorItem]]; + } + else if([[menuItem objectForKey:kMenuHeaderKey] intValue]) + { + if(NSString *item = [menuItem objectForKey:kMenuTitleKey]) + { + [submenu addItemWithTitle:item action:NULL keyEquivalent:@""]; + subin_section = true; + } + } + else if(NSString *item = [menuItem objectForKey:kMenuTitleKey]) + { + NSMenuItem* theItem = [submenu addItemWithTitle:item action:@selector(takeSelectedItemFrom:) keyEquivalent:@""]; + [theItem setTarget:menuTarget]; + [theItem setRepresentedObject:menuItem]; + if(subin_section) + [theItem setIndentationLevel:1]; + } + } + NSMenuItem* subMenuItem = [[NSMenuItem alloc] initWithTitle:submenuTitle action:NULL keyEquivalent:@""]; + [subMenuItem setSubmenu:submenu]; + [menu addItem:subMenuItem]; + [subMenuItem release]; + } + } + // check for items specified by the key 'title' else { - NSMenuItem* theItem = [menu addItemWithTitle:[menuItem objectForKey:@"title"] action:@selector(takeSelectedItemIndexFrom:) keyEquivalent:@""]; - [theItem setTarget:menuTarget]; - [theItem setTag:item_id]; - if(++item_id <= 10) + if(NSString *item = [menuItem objectForKey:kMenuTitleKey]) { - [theItem setKeyEquivalent:[NSString stringWithFormat:@"%d", item_id % 10]]; - [theItem setKeyEquivalentModifierMask:0]; + NSMenuItem* theItem = [menu addItemWithTitle:item action:@selector(takeSelectedItemFrom:) keyEquivalent:@""]; + [theItem setTarget:menuTarget]; + [theItem setRepresentedObject:menuItem]; + if(++item_id_key <= 10) + { + [theItem setKeyEquivalent:[NSString stringWithFormat:@"%ld", item_id_key % 10]]; + [theItem setKeyEquivalentModifierMask:0]; + } + if(in_section) + [theItem setIndentationLevel:1]; } - if (in_section) - [theItem setIndentationLevel:1]; } } @@ -107,7 +174,8 @@ - (void)handleCommand:(CLIProxy*)proxy pos = [textView positionForWindowUnderCaret]; - if([menu popUpMenuPositioningItem:nil atLocation:pos inView:nil] && menuTarget.selectedIndex != NSNotFound) - [TMDCommand writePropertyList:[menuItems objectAtIndex:menuTarget.selectedIndex] toFileHandle:[proxy outputHandle]]; + if([menu popUpMenuPositioningItem:nil atLocation:pos inView:nil] && menuTarget.selectedObject) + [TMDCommand writePropertyList:menuTarget.selectedObject toFileHandle:[proxy outputHandle] withProxy:proxy]; + } @end diff --git a/Commands/nib/TMDNibController.mm b/Commands/nib/TMDNibController.mm index 0cd9aaf..e5846a5 100644 --- a/Commands/nib/TMDNibController.mm +++ b/Commands/nib/TMDNibController.mm @@ -173,7 +173,7 @@ - (void)return:(NSDictionary*)eventInfo nil]; enumerate(clientFileHandles, NSFileHandle* fileHandle) - [TMDCommand writePropertyList:res toFileHandle:fileHandle]; + [TMDCommand writePropertyList:res toFileHandle:fileHandle withProxy:NULL]; [clientFileHandles removeAllObjects]; } diff --git a/Commands/nib/nib.mm b/Commands/nib/nib.mm index f1b96b0..7978f28 100644 --- a/Commands/nib/nib.mm +++ b/Commands/nib/nib.mm @@ -153,11 +153,11 @@ - (NSString *)commandDescription - (NSString *)usageForInvocation:(NSString *)invocation; { return [NSString stringWithFormat: - @"%1$@ --load «nib file» [«options»]\n" - @"%1$@ --update «token» [«options»]\n" - @"%1$@ --wait «token»\n" - @"%1$@ --dispose «token»\n" - @"%1$@ --list\n" + @"\t%1$@ --load «nib file» [«options»]\n" + @"\t%1$@ --update «token» [«options»]\n" + @"\t%1$@ --wait «token»\n" + @"\t%1$@ --dispose «token»\n" + @"\t%1$@ --list\n" @"\nOptions:\n" @"\t--center\n" @"\t--model «plist»\n", diff --git a/Commands/popup/popup.mm b/Commands/popup/popup.mm index 486d677..0c313f3 100644 --- a/Commands/popup/popup.mm +++ b/Commands/popup/popup.mm @@ -53,12 +53,21 @@ - (void)handleCommand:(CLIProxy*)proxy - (NSString *)commandDescription { - return @"Presents the user with a list of items which can be filtered down by typing to select the item they want."; + return @"Presents the user with a list of items which can be filtered down by typing."; } - (NSString *)usageForInvocation:(NSString *)invocation; { - return [NSString stringWithFormat:@"\t%1$@ --suggestions '( { display = law; }, { display = laws; insert = \"(${1:hello}, ${2:again})\"; } )'\n", invocation]; + return [NSString stringWithFormat: + @"\t%1$@ --suggestions '( { display = law; }, { display = laws; insert = \"(${1:hello}, ${2:again})\"; } )'\n\n" + @"\tThe chosen item given by the key “display” or, if the “insert” key is specified,\n" + @"\tthe content of that key which also can be a snippet will be inserted into the front-most document.\n\n" + @"Options:\n" + @"\t--alreadyTyped «string»\n" + @"\t--staticPrefix «string»\n" + @"\t--additionalWordCharacters «string»\n" + @"\t--returnChoice «boolean»\n" + @"\t--caseInsensitive «boolean»\n", invocation]; } @end diff --git a/Commands/prototype/prototype.mm b/Commands/prototype/prototype.mm index a950d4a..f3210fa 100644 --- a/Commands/prototype/prototype.mm +++ b/Commands/prototype/prototype.mm @@ -37,7 +37,7 @@ - (void)handleCommand:(CLIProxy*)proxy - (NSString *)commandDescription { - return @"Register classes for use with NSArrayController."; + return @"Registers classes for use with NSArrayController."; } - (NSString *)usageForInvocation:(NSString *)invocation; diff --git a/Commands/tooltip/tooltip.mm b/Commands/tooltip/tooltip.mm index cad1b72..06fffb1 100644 --- a/Commands/tooltip/tooltip.mm +++ b/Commands/tooltip/tooltip.mm @@ -21,7 +21,11 @@ - (NSString *)commandDescription - (NSString *)usageForInvocation:(NSString *)invocation; { - return [NSString stringWithFormat:@"\t%1$@ --text 'regular text'\n\t%1$@ --html 'html'\nUse --transparent to give the tooltip window a transparent background (10.5+ only)", invocation]; + return [NSString stringWithFormat: + @"\t%1$@ --text 'regular text'\n" + @"\t%1$@ --html 'html'\n" + @"\nOption:\n" + @"\t--transparent transparent background\n", invocation]; } - (void)handleCommand:(CLIProxy*)proxy diff --git a/TMDCommand.h b/TMDCommand.h index ccb3d6a..40cbbd3 100644 --- a/TMDCommand.h +++ b/TMDCommand.h @@ -7,7 +7,7 @@ + (id)objectForCommand:(NSString*)aCommand; + (id)readPropertyList:(NSFileHandle*)aFileHandle error:(NSString**)error; -+ (void)writePropertyList:(id)aPlist toFileHandle:(NSFileHandle*)aFileHandle; ++ (void)writePropertyList:(id)aPlist toFileHandle:(NSFileHandle*)aFileHandle withProxy:(CLIProxy*)proxy; - (NSString *)commandDescription; - (NSString *)usageForInvocation:(NSString *)invocation; diff --git a/TMDCommand.mm b/TMDCommand.mm index 6682884..eb7a545 100644 --- a/TMDCommand.mm +++ b/TMDCommand.mm @@ -31,11 +31,51 @@ + (id)readPropertyList:(NSFileHandle*)aFileHandle error:(NSString**)error; return plist; } -+ (void)writePropertyList:(id)aPlist toFileHandle:(NSFileHandle*)aFileHandle ++ (void)writePropertyList:(id)aPlist toFileHandle:(NSFileHandle*)aFileHandle withProxy:(CLIProxy*)proxy { NSString* error = nil; + if(NSData* data = [NSPropertyListSerialization dataFromPropertyList:aPlist format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]) { + + // check, if a proxy is passed, for --filter option + // if so then only return the passed 'key' value as a plain string + // or for --filter '(array,of,keys)' then return the values of these keys + // separated by a new line \n + if(proxy) + { + NSDictionary *args = [proxy parameters]; + if(NSString *outputKeys = [args objectForKey:@"filter"]) + { + // check argument and try to convert it to an array + id raw_keys = [NSPropertyListSerialization propertyListFromData:[(NSString*)outputKeys dataUsingEncoding:NSUTF8StringEncoding] mutabilityOption:NSPropertyListImmutable format:nil errorDescription:NULL]; + NSArray *keys = nil; + if([raw_keys isKindOfClass:[NSString class]]) + keys = [NSArray arrayWithObject:raw_keys]; + else if([raw_keys isKindOfClass:[NSArray class]]) + keys = raw_keys; + else + { + fprintf(stderr, "no single string or array passed as value for option '--filter'\n"); + return; + } + NSMutableArray *out = [NSMutableArray arrayWithCapacity:[keys count]]; + for(NSString *outputKey in keys) + { + if(NSString *output = [aPlist objectForKey:outputKey]) + { + [out addObject:output]; + } + else + { + [out addObject:@""]; + fprintf(stderr, "no key '%s' found in returned property list\n", [outputKey UTF8String]); + } + } + [aFileHandle writeString:[out componentsJoinedByString:@"\n"]]; + return; + } + } [aFileHandle writeData:data]; } else