├── .gitignore ├── AMOptionMenu-demo.mov ├── AMOptionMenu ├── AMOptionMenuController.h ├── AMOptionMenuController.m ├── AMOptionPopUpButton.h ├── AMOptionPopUpButton.m ├── AMOptionPopUpButtonCell.h └── AMOptionPopUpButtonCell.m ├── AMOptionMenuDemo.xcodeproj ├── TemplateIcon.icns ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── AMOptionMenuDemo_Prefix.pch ├── AppController.h ├── AppController.m ├── DinosaurOptions.plist ├── English.lproj ├── InfoPlist.strings └── MainMenu.xib ├── Info.plist ├── LICENSE ├── README.mdown ├── main.m ├── screenshot.png └── version.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyc 10 | 11 | # Numerous always-ignore extensions 12 | ################### 13 | *.diff 14 | *.err 15 | *.orig 16 | *.log 17 | *.rej 18 | *.swo 19 | *.swp 20 | *.vi 21 | *~ 22 | 23 | *.sass-cache 24 | # Folders to ignore 25 | ################### 26 | .hg 27 | .svn 28 | .CVS 29 | # OS or Editor folders 30 | ################### 31 | .DS_Store 32 | Icon? 33 | Thumbs.db 34 | ehthumbs.db 35 | nbproject 36 | .cache 37 | .project 38 | .settings 39 | .tmproj 40 | *.esproj 41 | *.sublime-project 42 | *.sublime-workspace 43 | # Dreamweaver added files 44 | ################### 45 | _notes 46 | dwsync.xml 47 | # Komodo 48 | ################### 49 | *.komodoproject 50 | .komodotools 51 | -------------------------------------------------------------------------------- /AMOptionMenu-demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrox/AMOptionMenu/cb54aa8368f654c3cd04f79de79cc588782d1e52/AMOptionMenu-demo.mov -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionMenuController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionMenu.h 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/8/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @protocol AMOptionMenuControllerDelegate 32 | - (void)didChooseOption:(NSString *)value forSection:(NSString *)section; 33 | @end 34 | 35 | extern NSString* const kAMOptionPopUpButtonTitle; 36 | 37 | @interface AMOptionMenuItem : NSObject {} 38 | 39 | @property (nonatomic, retain) NSString* identifier; 40 | @property (nonatomic, retain) NSString* title; 41 | @property (nonatomic, retain) NSString* shortTitle; 42 | @property (nonatomic, readonly) NSString* titleForSummary; 43 | @property (nonatomic, readonly) BOOL bindValue; 44 | 45 | + (AMOptionMenuItem*) itemWithIdentifier:(NSString*)identifier title:(NSString*)title bindValue:(BOOL)bindValue; 46 | + (AMOptionMenuItem*) itemWithIdentifier:(NSString*)identifier title:(NSString*)title shortTitle:(NSString*)shortTitle bindValue:(BOOL)bindValue; 47 | @end 48 | 49 | 50 | extern NSString* const kAMOptionMenuContentWillChange; 51 | extern NSString* const kAMOptionMenuContentDidChange; 52 | 53 | 54 | @interface AMOptionMenuController : NSObject {} 55 | 56 | @property (nonatomic, readonly) NSString* summaryString; 57 | @property (nonatomic, assign) BOOL shouldSeparateSections; 58 | @property (nonatomic, assign) BOOL allowUnkownOptions; 59 | @property (nonatomic, assign) NSUInteger maxValuesInSummary; // defaults to NSUIntegerMax; 60 | @property (nonatomic, assign) id delegate; 61 | 62 | @property (nonatomic, retain) NSDictionary* valuesDict; 63 | @property (nonatomic, retain) NSArray* options; 64 | 65 | - (void) insertOptionWithIdentifier:(NSString*)identifier title:(NSString*)title atIndex:(NSInteger)index; 66 | - (void) setAlternatives:(NSArray*)alternatives forOptionWithIdentifier:(NSString*)optionIdentifier; 67 | - (void) insertOption:(AMOptionMenuItem*)option atIndex:(NSInteger)insertIndex withAternatives:(NSArray*)alternatives; 68 | 69 | - (BOOL) insertOptionsFromPropertyListWithURL:(NSURL*)url atIndex:(NSInteger)insertIndex; 70 | 71 | - (void) insertItemsInMenu:(NSMenu*)menu atIndex:(NSInteger)insertIndex; 72 | 73 | // TODO: make a property? 74 | - (NSDictionary*) state; 75 | 76 | @end 77 | 78 | -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionMenuController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionMenu.m 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/8/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "AMOptionMenuController.h" 30 | 31 | 32 | @interface AMOptionMenuItem () 33 | @property (nonatomic, readwrite) BOOL bindValue; 34 | @end 35 | 36 | NSString* const kAMOptionPopUpButtonTitle = @"kAMOptionPopUpButtonTitle"; 37 | 38 | 39 | @implementation AMOptionMenuItem 40 | 41 | 42 | + (AMOptionMenuItem*) itemWithIdentifier:(NSString*)identifier title:(NSString*)title shortTitle:(NSString*)shortTitle bindValue:(BOOL)bindValue 43 | { 44 | AMOptionMenuItem* item = [[AMOptionMenuItem alloc] init]; 45 | [item setIdentifier:identifier]; 46 | [item setTitle:title]; 47 | [item setShortTitle:shortTitle]; 48 | [item setBindValue:bindValue]; 49 | 50 | return item; 51 | } 52 | 53 | 54 | + (AMOptionMenuItem*) itemWithIdentifier:(NSString*)identifier title:(NSString*)title bindValue:(BOOL)bindValue 55 | { 56 | return [self itemWithIdentifier:identifier title:title shortTitle:nil bindValue:bindValue]; 57 | } 58 | 59 | - (NSString*) titleForSummary 60 | { 61 | if( [self shortTitle] ) 62 | return [self shortTitle]; 63 | return [self title]; 64 | } 65 | 66 | @end 67 | 68 | #pragma mark - 69 | 70 | NSString* const kAMOptionMenuContentWillChange = @"kAMOptionMenuDataWillChange"; 71 | NSString* const kAMOptionMenuContentDidChange = @"kAMOptionMenuDataDidChange"; 72 | 73 | 74 | @interface AMOptionMenuController () 75 | { 76 | NSMutableArray* _options; 77 | NSMutableDictionary* _valuesDict; 78 | NSMutableDictionary* _stateDict; 79 | } 80 | 81 | - (NSArray*) createMenuItems; 82 | - (NSArray*) optionValuesForGroupWithIdentifier:(NSString*)identifier; 83 | - (NSMenuItem*) menuItemForGroup:(AMOptionMenuItem*)group; 84 | - (NSMenuItem*) menuItemForOption:(AMOptionMenuItem*)option inGroup:(AMOptionMenuItem*)group; 85 | - (void) choseOptionWithKeyPath:(NSString*)keypath; 86 | 87 | @end 88 | 89 | @implementation AMOptionMenuController 90 | 91 | @synthesize options = _options; 92 | @synthesize valuesDict = _valuesDict; 93 | 94 | - (id) init 95 | { 96 | self = [super init]; 97 | if (self != nil) 98 | { 99 | _options = [[NSMutableArray alloc] init]; 100 | _valuesDict = [[NSMutableDictionary alloc] init]; 101 | _stateDict = [[NSMutableDictionary alloc] init]; 102 | _maxValuesInSummary = NSUIntegerMax; 103 | } 104 | return self; 105 | } 106 | 107 | - (NSArray*) optionValuesForGroupWithIdentifier:(NSString*)identifier 108 | { 109 | return [_valuesDict objectForKey:identifier]; 110 | } 111 | 112 | 113 | - (NSArray*) createMenuItems 114 | { 115 | NSMutableArray* array = [NSMutableArray array]; 116 | for( AMOptionMenuItem* option in [self options] ) 117 | { 118 | [array addObject:[self menuItemForGroup:option]]; 119 | 120 | for( AMOptionMenuItem* value in [self optionValuesForGroupWithIdentifier:[option identifier]] ) 121 | { 122 | [array addObject:[self menuItemForOption:value inGroup:option]]; 123 | } 124 | 125 | // -- don't add the separator for the last item. Might be more efficient way to do, but it's a small data set. 126 | if( [self shouldSeparateSections] && [[self options] lastObject] != option ) 127 | { 128 | [array addObject:[NSMenuItem separatorItem]]; 129 | } 130 | 131 | } 132 | return array; 133 | } 134 | 135 | 136 | - (void) insertItemsInMenu:(NSMenu*)menu atIndex:(NSInteger)insertIndex 137 | { 138 | for( NSMenuItem* item in [self createMenuItems] ) 139 | { 140 | [menu insertItem:item atIndex:insertIndex++]; 141 | } 142 | } 143 | 144 | 145 | - (NSMenuItem*) menuItemForGroup:(AMOptionMenuItem*)group 146 | { 147 | NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:[group title] action:nil keyEquivalent:@""]; 148 | [menuItem setEnabled:NO]; 149 | return menuItem; 150 | } 151 | 152 | 153 | - (NSMenuItem*) menuItemForOption:(AMOptionMenuItem*)option inGroup:(AMOptionMenuItem*)group 154 | { 155 | NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:[option title] action:@selector(optionChosen:) keyEquivalent:@""]; 156 | 157 | [menuItem setIndentationLevel:1]; 158 | [menuItem setTarget:self]; 159 | [menuItem setEnabled:YES]; 160 | 161 | NSString* keypath = [NSString stringWithFormat:@"%@.%@", [group identifier], [option identifier]]; 162 | [menuItem setRepresentedObject:keypath]; 163 | 164 | NSDictionary *bindingOptions = nil; 165 | 166 | if (option.bindValue) 167 | [menuItem bind:@"value" toObject:self withKeyPath:keypath options:bindingOptions]; 168 | 169 | return menuItem; 170 | } 171 | 172 | 173 | - (void) optionChosen:(id)sender 174 | { 175 | // ???: the visual check next to the selected item did not update correctly without deferring to the next run loop 176 | //[self choseOptionWithKeyPath:[sender representedObject]]; 177 | 178 | [self performSelector:@selector(choseOptionWithKeyPath:) withObject:[sender representedObject] afterDelay:0.0]; 179 | } 180 | 181 | 182 | - (void) choseOptionWithKeyPath:(NSString*)keypath 183 | { 184 | NSLog( @"--------- choseOptionWithKeyPath: %@ ---------", keypath ); 185 | [self setValue:[NSNumber numberWithBool:YES] forKeyPath:keypath]; 186 | 187 | NSArray *keypathComponents = [keypath componentsSeparatedByString:@"."]; 188 | assert(keypathComponents.count == 2); 189 | [_delegate didChooseOption:keypathComponents[1] forSection:keypathComponents[0]]; 190 | } 191 | 192 | 193 | - (id) valueForUndefinedKey:(NSString*)key 194 | { 195 | if( [_valuesDict objectForKey:key] ) 196 | { 197 | id val = [_stateDict objectForKey:key]; 198 | //NSLog( @"valueForUndefinedKey: %@ = %@", key, val ); 199 | return val; 200 | } 201 | 202 | return [super valueForUndefinedKey:key]; 203 | } 204 | 205 | 206 | - (void)setValue:(id)value forUndefinedKey:(NSString *)key 207 | { 208 | if( [self allowUnkownOptions] || [_valuesDict objectForKey:key] ) 209 | { 210 | NSLog( @"settingValue:%@ forKey:%@", value, key ); 211 | [self willChangeValueForKey:@"summaryString"]; 212 | [_stateDict setObject:value forKey:key]; 213 | [self didChangeValueForKey:@"summaryString"]; 214 | return; 215 | } 216 | [super setValue:value forUndefinedKey:key]; 217 | } 218 | 219 | 220 | - (id) valueForKeyPath:(NSString*)keyPath 221 | { 222 | NSArray* keyPathComponents = [keyPath componentsSeparatedByString:@"."]; 223 | 224 | if( [keyPathComponents count] == 2 ) 225 | { 226 | NSString* groupKey = [keyPathComponents objectAtIndex:0]; 227 | NSString* optionKey = [keyPathComponents objectAtIndex:1]; 228 | 229 | if( [_valuesDict objectForKey:groupKey] ) 230 | { 231 | if( [[_stateDict objectForKey:groupKey] isEqual:optionKey] ) 232 | { 233 | NSLog( @"valueForKeyPath:%@ is YES", keyPath ); 234 | return [NSNumber numberWithBool:YES]; 235 | } 236 | else 237 | { 238 | NSLog( @"valueForKeyPath:%@ is NO", keyPath ); 239 | return [NSNumber numberWithBool:NO]; 240 | } 241 | } 242 | } 243 | return [super valueForKeyPath:keyPath]; 244 | } 245 | 246 | 247 | - (void)setValue:(id)value forKeyPath:(NSString*)keyPath 248 | { 249 | NSLog( @"setting value:%@ forKeyPath:%@", value, keyPath ); 250 | NSArray* keyPathComponents = [keyPath componentsSeparatedByString:@"."]; 251 | if( [keyPathComponents count] == 2 ) 252 | { 253 | NSString* sectionKey = [keyPathComponents objectAtIndex:0]; 254 | NSString* optionKey = [keyPathComponents objectAtIndex:1]; 255 | 256 | NSString* option = optionKey; 257 | 258 | if( [_valuesDict objectForKey:sectionKey] ) 259 | { 260 | if( [value boolValue] ) 261 | { 262 | [self setValue:option forKey:sectionKey]; 263 | } 264 | return; 265 | } 266 | } 267 | [super setValue:value forKeyPath:keyPath]; 268 | } 269 | 270 | 271 | - (NSString*) summaryString 272 | { 273 | NSMutableString* string = [NSMutableString string]; 274 | NSUInteger count = 0; 275 | for( AMOptionMenuItem* group in [self options] ) 276 | { 277 | if( count++ == [self maxValuesInSummary] ) 278 | break; 279 | 280 | if( [string length] ) 281 | [string appendString:@" | "]; 282 | 283 | // TODO: make nicer 284 | NSString* optionId = [self valueForKey:[group identifier]]; 285 | 286 | NSString* title = nil; 287 | for( AMOptionMenuItem* menuItem in [_valuesDict objectForKey:[group identifier]] ) 288 | { 289 | if( [[menuItem identifier] isEqualToString:optionId] ) 290 | { 291 | title = [menuItem titleForSummary]; 292 | break; 293 | } 294 | } 295 | [string appendFormat:@"%@", title]; 296 | } 297 | return [NSString stringWithString:string]; 298 | } 299 | 300 | 301 | - (void) initializeStateForOptionWithIdentifier:(NSString*)optionId 302 | { 303 | if( ![_stateDict objectForKey:optionId] ) 304 | { 305 | NSString* valueId = [[[[self valuesDict] objectForKey:optionId] objectAtIndex:0] identifier]; 306 | [_stateDict setObject:valueId forKey:optionId]; 307 | } 308 | } 309 | 310 | 311 | - (NSDictionary*) state 312 | { 313 | return [_stateDict copy]; 314 | } 315 | 316 | 317 | - (void) setAlternatives:(NSArray*)values forOptionWithIdentifier:(NSString*)optionId; 318 | { 319 | [[NSNotificationCenter defaultCenter] postNotificationName:kAMOptionMenuContentWillChange object:self]; 320 | [_valuesDict setObject:values forKey:optionId]; 321 | [self initializeStateForOptionWithIdentifier:optionId]; 322 | [[NSNotificationCenter defaultCenter] postNotificationName:kAMOptionMenuContentDidChange object:self]; 323 | 324 | } 325 | 326 | 327 | - (void) insertOptionWithIdentifier:(NSString*)identifier title:(NSString*)title atIndex:(NSInteger)index 328 | { 329 | [[NSNotificationCenter defaultCenter] postNotificationName:kAMOptionMenuContentWillChange object:self]; 330 | AMOptionMenuItem* option = [AMOptionMenuItem itemWithIdentifier:identifier title:title bindValue:YES]; 331 | [_options insertObject:option atIndex:index]; 332 | [[NSNotificationCenter defaultCenter] postNotificationName:kAMOptionMenuContentDidChange object:self]; 333 | 334 | } 335 | 336 | 337 | - (void) insertOption:(AMOptionMenuItem*)option atIndex:(NSInteger)insertIndex withAternatives:(NSArray*)alternatives 338 | { 339 | [_options insertObject:option atIndex:insertIndex]; 340 | [self setAlternatives:alternatives forOptionWithIdentifier:[option identifier]]; 341 | } 342 | 343 | 344 | - (BOOL) insertOptionsFromPropertyListWithURL:(NSURL*)url atIndex:(NSInteger)insertIndex 345 | { 346 | NSArray* options = [NSArray arrayWithContentsOfURL:url]; 347 | if( !options ) 348 | return NO; 349 | 350 | for( NSDictionary* option in options ) 351 | { 352 | NSString* optionIdentifier = [option objectForKey:@"identifier"]; 353 | NSString* optionTitle = [option objectForKey:@"title"]; 354 | [self insertOptionWithIdentifier:optionIdentifier title:optionTitle atIndex:insertIndex]; 355 | 356 | NSArray* alternatives = [option objectForKey:@"alternatives"]; 357 | NSMutableArray* valueItems = [NSMutableArray arrayWithCapacity:[alternatives count]]; 358 | for( NSDictionary* value in alternatives ) 359 | { 360 | NSString* valueIdentifer = [value objectForKey:@"identifier"]; 361 | NSString* valueTitle = [value objectForKey:@"title"]; 362 | NSString* valueShortTitle = [value objectForKey:@"shortTitle"]; 363 | 364 | AMOptionMenuItem* valueItem = [AMOptionMenuItem itemWithIdentifier:valueIdentifer 365 | title:valueTitle 366 | shortTitle:valueShortTitle 367 | bindValue:YES]; 368 | [valueItems addObject:valueItem]; 369 | } 370 | [self setAlternatives:valueItems forOptionWithIdentifier:optionIdentifier]; 371 | } 372 | return YES; 373 | } 374 | 375 | @end 376 | 377 | -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionPopUpButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionPopUpButton.h 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/9/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @class AMOptionMenuController; 32 | 33 | @interface AMOptionPopUpButton : NSPopUpButton 34 | { 35 | BOOL _smartTitleTruncation; 36 | } 37 | 38 | @property (nonatomic, weak) AMOptionMenuController* optionMenuController; 39 | 40 | @property (nonatomic, assign) BOOL smartTitleTruncation; 41 | 42 | - (id) initWithFrame:(NSRect)buttonFrame; 43 | 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionPopUpButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionPopUpButton.m 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/9/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "AMOptionPopUpButton.h" 30 | #import "AMOptionPopUpButtonCell.h" 31 | 32 | 33 | #import "AMOptionMenuController.h" 34 | 35 | @implementation AMOptionPopUpButton 36 | 37 | @synthesize smartTitleTruncation = _smartTitleTruncation; 38 | 39 | + (Class) cellClass 40 | { 41 | return [AMOptionPopUpButtonCell class]; 42 | } 43 | 44 | 45 | - (void) configure 46 | { 47 | NSDictionary* titleBindingOptions = [NSDictionary dictionaryWithObjectsAndKeys: 48 | @"(No Data Source)", NSNullPlaceholderBindingOption, 49 | nil]; 50 | [self bind:@"title" toObject:self withKeyPath:@"cell.optionMenuController.summaryString" options:titleBindingOptions]; 51 | } 52 | 53 | 54 | - (id) initWithFrame:(NSRect)buttonFrame pullsDown:(BOOL)flag 55 | { 56 | self = [super initWithFrame:buttonFrame pullsDown:flag]; 57 | if( self ) 58 | { 59 | [self configure]; 60 | } 61 | return self; 62 | } 63 | 64 | 65 | - (id) initWithFrame:(NSRect)buttonFrame 66 | { 67 | return [self initWithFrame:buttonFrame pullsDown:YES]; 68 | } 69 | 70 | 71 | - (void) awakeFromNib 72 | { 73 | [self configure]; 74 | } 75 | 76 | 77 | - (AMOptionMenuController*) optionMenuController 78 | { 79 | return [[self cell] optionMenuController]; 80 | } 81 | 82 | 83 | - (void) setOptionMenuController:(AMOptionMenuController*) controller 84 | { 85 | [[self cell] setOptionMenuController:controller]; 86 | } 87 | 88 | 89 | - (void)setFrameSize:(NSSize)newSize 90 | { 91 | [super setFrameSize:newSize]; 92 | if( self.smartTitleTruncation ) 93 | { 94 | [self setTitle:[[self optionMenuController] summaryString]]; 95 | } 96 | } 97 | 98 | 99 | - (void) setTitle:(NSString*)title 100 | { 101 | if( self.smartTitleTruncation ) 102 | { 103 | NSRect titleRect = [[self cell] titleRectForBounds:[self bounds]]; 104 | NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys: 105 | [self font], NSFontAttributeName, 106 | nil]; 107 | 108 | NSAttributedString* tmpString = [[NSAttributedString alloc] initWithString:title attributes:attributes]; 109 | 110 | //NSLog( @"title string size: %@", NSStringFromSize( [tmpString size] ) ); 111 | //NSLog( @"control text size: %@", NSStringFromSize( titleRect.size) ); 112 | 113 | if( [tmpString size].width > titleRect.size.width ) 114 | { 115 | // -- try to trim it down 116 | 117 | NSRange range = [title rangeOfString:@"|" options:NSBackwardsSearch]; 118 | if( range.location != NSNotFound ) 119 | { 120 | NSString* newTitle = [[title substringToIndex:range.location] 121 | stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 122 | 123 | [self setTitle:newTitle]; 124 | return; 125 | } 126 | } 127 | } 128 | 129 | [super setTitle:title]; 130 | } 131 | 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionPopUpButtonCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionPopUpButtonCell.h 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/10/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @class AMOptionMenuController; 32 | 33 | @interface AMOptionPopUpButtonCell : NSPopUpButtonCell 34 | { 35 | __weak AMOptionMenuController* _optionMenuController; 36 | } 37 | 38 | 39 | @property (nonatomic, weak) AMOptionMenuController *optionMenuController; 40 | 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /AMOptionMenu/AMOptionPopUpButtonCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMOptionPopUpButtonCell.m 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/10/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "AMOptionPopUpButtonCell.h" 30 | 31 | #import "AMOptionMenuController.h" 32 | 33 | 34 | @interface AMOptionPopUpButtonCell () 35 | - (void) updateMenu; 36 | @end 37 | 38 | 39 | @implementation AMOptionPopUpButtonCell 40 | 41 | - (void) configure 42 | { 43 | [self setPullsDown:YES]; 44 | [self setAutoenablesItems:NO]; 45 | [self setArrowPosition:NSPopUpArrowAtBottom]; 46 | [self setAltersStateOfSelectedItem:NO]; 47 | [self setUsesItemFromMenu:NO]; 48 | 49 | NSMenuItem* titleItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; 50 | [self setMenuItem:titleItem]; 51 | } 52 | 53 | 54 | - (id) init 55 | { 56 | self = [super initTextCell:@"" pullsDown:YES]; 57 | if( self ) 58 | { 59 | [self configure]; 60 | } 61 | return self; 62 | } 63 | 64 | 65 | - (id) initTextCell:(NSString*)stringValue pullsDown:(BOOL)pullDown 66 | { 67 | return [self init]; 68 | } 69 | 70 | 71 | - (void) awakeFromNib 72 | { 73 | [self configure]; 74 | } 75 | 76 | 77 | - (void) dealloc 78 | { 79 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 80 | } 81 | 82 | 83 | - (BOOL) pullsDown 84 | { 85 | return YES; 86 | } 87 | 88 | 89 | - (void)setPullsDown:(BOOL)flag 90 | { 91 | NSAssert( flag, @"must put pull down to operate correctly" ); 92 | [super setPullsDown:flag]; 93 | } 94 | 95 | 96 | - (void) setOptionMenuController:(AMOptionMenuController*)controller 97 | { 98 | if( controller == _optionMenuController ) 99 | return; 100 | 101 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kAMOptionMenuContentDidChange object:_optionMenuController]; 102 | 103 | _optionMenuController = controller; 104 | 105 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(optionsChanged:) name:kAMOptionMenuContentDidChange object:_optionMenuController]; 106 | 107 | [self updateMenu]; 108 | } 109 | 110 | 111 | - (void) optionsChanged:(NSNotification*) note 112 | { 113 | [self updateMenu]; 114 | } 115 | 116 | 117 | - (void) updateMenu 118 | { 119 | NSMenu* newMenu = [[NSMenu alloc] initWithTitle:@""]; 120 | [newMenu setAutoenablesItems:NO]; 121 | [[self optionMenuController] insertItemsInMenu:newMenu atIndex:0]; 122 | [newMenu insertItemWithTitle:@"dummy" action:nil keyEquivalent:@"" atIndex:0]; 123 | [self setMenu:newMenu]; 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /AMOptionMenuDemo.xcodeproj/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrox/AMOptionMenu/cb54aa8368f654c3cd04f79de79cc588782d1e52/AMOptionMenuDemo.xcodeproj/TemplateIcon.icns -------------------------------------------------------------------------------- /AMOptionMenuDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 11 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 12 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 14 | B3999D3710CAC1C200EA2116 /* DinosaurOptions.plist in Resources */ = {isa = PBXBuildFile; fileRef = B3999D3610CAC1C200EA2116 /* DinosaurOptions.plist */; }; 15 | B3DADB5B100589E700789685 /* AMOptionMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3DADB5A100589E700789685 /* AMOptionMenuController.m */; }; 16 | B3DADC3410058E5E00789685 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3DADC3310058E5E00789685 /* AppController.m */; }; 17 | B3DAE0981006823F00789685 /* AMOptionPopUpButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B3DAE0971006823F00789685 /* AMOptionPopUpButton.m */; }; 18 | E12A723C10077C000049FD9E /* AMOptionPopUpButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E12A723B10077C000049FD9E /* AMOptionPopUpButtonCell.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 23 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 24 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 25 | 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 26 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 28 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 29 | 32CA4F630368D1EE00C91783 /* AMOptionMenuDemo_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMOptionMenuDemo_Prefix.pch; sourceTree = ""; }; 30 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 8D1107320486CEB800E47090 /* AMOptionMenuDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AMOptionMenuDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | B3999D3610CAC1C200EA2116 /* DinosaurOptions.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DinosaurOptions.plist; sourceTree = ""; }; 33 | B3DADB59100589E700789685 /* AMOptionMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMOptionMenuController.h; sourceTree = ""; }; 34 | B3DADB5A100589E700789685 /* AMOptionMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMOptionMenuController.m; sourceTree = ""; }; 35 | B3DADC3210058E5E00789685 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; 36 | B3DADC3310058E5E00789685 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; 37 | B3DAE0961006823F00789685 /* AMOptionPopUpButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMOptionPopUpButton.h; sourceTree = ""; }; 38 | B3DAE0971006823F00789685 /* AMOptionPopUpButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMOptionPopUpButton.m; sourceTree = ""; }; 39 | E12A723A10077C000049FD9E /* AMOptionPopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMOptionPopUpButtonCell.h; sourceTree = ""; }; 40 | E12A723B10077C000049FD9E /* AMOptionPopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMOptionPopUpButtonCell.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 080E96DDFE201D6D7F000001 /* Classes */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | B3DADC3210058E5E00789685 /* AppController.h */, 59 | B3DADC3310058E5E00789685 /* AppController.m */, 60 | ); 61 | name = Classes; 62 | sourceTree = ""; 63 | }; 64 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 68 | ); 69 | name = "Linked Frameworks"; 70 | sourceTree = ""; 71 | }; 72 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 76 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 77 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 78 | ); 79 | name = "Other Frameworks"; 80 | sourceTree = ""; 81 | }; 82 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 8D1107320486CEB800E47090 /* AMOptionMenuDemo.app */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 29B97314FDCFA39411CA2CEA /* AMOptionMenuDemo */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 080E96DDFE201D6D7F000001 /* Classes */, 94 | B3DADC0D10058E1500789685 /* AMOptionMenu */, 95 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 96 | 29B97317FDCFA39411CA2CEA /* Resources */, 97 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 98 | 19C28FACFE9D520D11CA2CBB /* Products */, 99 | ); 100 | name = AMOptionMenuDemo; 101 | sourceTree = ""; 102 | }; 103 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 32CA4F630368D1EE00C91783 /* AMOptionMenuDemo_Prefix.pch */, 107 | 29B97316FDCFA39411CA2CEA /* main.m */, 108 | ); 109 | name = "Other Sources"; 110 | sourceTree = ""; 111 | }; 112 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | B3999D3610CAC1C200EA2116 /* DinosaurOptions.plist */, 116 | 8D1107310486CEB800E47090 /* Info.plist */, 117 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 118 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, 119 | ); 120 | name = Resources; 121 | sourceTree = ""; 122 | }; 123 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 127 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 128 | ); 129 | name = Frameworks; 130 | sourceTree = ""; 131 | }; 132 | B3DADC0D10058E1500789685 /* AMOptionMenu */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | B3DADB59100589E700789685 /* AMOptionMenuController.h */, 136 | B3DADB5A100589E700789685 /* AMOptionMenuController.m */, 137 | B3DAE0961006823F00789685 /* AMOptionPopUpButton.h */, 138 | B3DAE0971006823F00789685 /* AMOptionPopUpButton.m */, 139 | E12A723A10077C000049FD9E /* AMOptionPopUpButtonCell.h */, 140 | E12A723B10077C000049FD9E /* AMOptionPopUpButtonCell.m */, 141 | ); 142 | path = AMOptionMenu; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | 8D1107260486CEB800E47090 /* AMOptionMenuDemo */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "AMOptionMenuDemo" */; 151 | buildPhases = ( 152 | 8D1107290486CEB800E47090 /* Resources */, 153 | 8D11072C0486CEB800E47090 /* Sources */, 154 | 8D11072E0486CEB800E47090 /* Frameworks */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = AMOptionMenuDemo; 161 | productInstallPath = "$(HOME)/Applications"; 162 | productName = AMOptionMenuDemo; 163 | productReference = 8D1107320486CEB800E47090 /* AMOptionMenuDemo.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastUpgradeCheck = 0440; 173 | }; 174 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AMOptionMenuDemo" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 1; 178 | knownRegions = ( 179 | en, 180 | ); 181 | mainGroup = 29B97314FDCFA39411CA2CEA /* AMOptionMenuDemo */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | 8D1107260486CEB800E47090 /* AMOptionMenuDemo */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | 8D1107290486CEB800E47090 /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 196 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, 197 | B3999D3710CAC1C200EA2116 /* DinosaurOptions.plist in Resources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXResourcesBuildPhase section */ 202 | 203 | /* Begin PBXSourcesBuildPhase section */ 204 | 8D11072C0486CEB800E47090 /* Sources */ = { 205 | isa = PBXSourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 209 | B3DADB5B100589E700789685 /* AMOptionMenuController.m in Sources */, 210 | B3DADC3410058E5E00789685 /* AppController.m in Sources */, 211 | B3DAE0981006823F00789685 /* AMOptionPopUpButton.m in Sources */, 212 | E12A723C10077C000049FD9E /* AMOptionPopUpButtonCell.m in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin PBXVariantGroup section */ 219 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | 089C165DFE840E0CC02AAC07 /* English */, 223 | ); 224 | name = InfoPlist.strings; 225 | sourceTree = ""; 226 | }; 227 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { 228 | isa = PBXVariantGroup; 229 | children = ( 230 | 1DDD58150DA1D0A300B32029 /* English */, 231 | ); 232 | name = MainMenu.xib; 233 | sourceTree = ""; 234 | }; 235 | /* End PBXVariantGroup section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | C01FCF4B08A954540054247B /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | COMBINE_HIDPI_IMAGES = YES; 243 | COPY_PHASE_STRIP = NO; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_MODEL_TUNING = G5; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 248 | GCC_PREFIX_HEADER = AMOptionMenuDemo_Prefix.pch; 249 | INFOPLIST_FILE = Info.plist; 250 | INSTALL_PATH = "$(HOME)/Applications"; 251 | PRODUCT_NAME = AMOptionMenuDemo; 252 | }; 253 | name = Debug; 254 | }; 255 | C01FCF4C08A954540054247B /* Release */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | COMBINE_HIDPI_IMAGES = YES; 260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 261 | GCC_MODEL_TUNING = G5; 262 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 263 | GCC_PREFIX_HEADER = AMOptionMenuDemo_Prefix.pch; 264 | INFOPLIST_FILE = Info.plist; 265 | INSTALL_PATH = "$(HOME)/Applications"; 266 | PRODUCT_NAME = AMOptionMenuDemo; 267 | }; 268 | name = Release; 269 | }; 270 | C01FCF4F08A954540054247B /* Debug */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 274 | GCC_C_LANGUAGE_STANDARD = c99; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | MACOSX_DEPLOYMENT_TARGET = 10.6; 279 | ONLY_ACTIVE_ARCH = YES; 280 | }; 281 | name = Debug; 282 | }; 283 | C01FCF5008A954540054247B /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 287 | GCC_C_LANGUAGE_STANDARD = c99; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 289 | GCC_WARN_UNUSED_VARIABLE = YES; 290 | MACOSX_DEPLOYMENT_TARGET = 10.6; 291 | }; 292 | name = Release; 293 | }; 294 | /* End XCBuildConfiguration section */ 295 | 296 | /* Begin XCConfigurationList section */ 297 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "AMOptionMenuDemo" */ = { 298 | isa = XCConfigurationList; 299 | buildConfigurations = ( 300 | C01FCF4B08A954540054247B /* Debug */, 301 | C01FCF4C08A954540054247B /* Release */, 302 | ); 303 | defaultConfigurationIsVisible = 0; 304 | defaultConfigurationName = Release; 305 | }; 306 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AMOptionMenuDemo" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | C01FCF4F08A954540054247B /* Debug */, 310 | C01FCF5008A954540054247B /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | /* End XCConfigurationList section */ 316 | }; 317 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 318 | } 319 | -------------------------------------------------------------------------------- /AMOptionMenuDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AMOptionMenuDemo_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AMOptionMenuDemo' target in the 'AMOptionMenuDemo' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /AppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.h 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/8/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @class AMOptionMenuController; 32 | @class AMOptionPopUpButton; 33 | 34 | @interface AppController : NSObject { 35 | 36 | AMOptionPopUpButton* popUpButton; 37 | 38 | IBOutlet NSMenuItem* testMenu; 39 | 40 | IBOutlet NSWindow* window; 41 | 42 | AMOptionMenuController* optionMenuController; 43 | 44 | } 45 | 46 | @property (assign) IBOutlet AMOptionPopUpButton* popUpButton; 47 | 48 | - (IBAction) setBlue:(id)sender; 49 | - (IBAction) setReallyAwesome:(id)sender; 50 | 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /AppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppController.m 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/8/09. 6 | // 7 | // Copyright (c) 2009 Andy Mroczkowski 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "AppController.h" 30 | 31 | #import "AMOptionMenuController.h" 32 | 33 | #import "AMOptionPopUpButton.h" 34 | #import "AMOptionPopUpButtonCell.h" 35 | 36 | @implementation AppController 37 | 38 | @synthesize popUpButton; 39 | 40 | - (void) awakeFromNib 41 | { 42 | optionMenuController = [[AMOptionMenuController alloc] init]; 43 | 44 | // -- uncomment the following to add separators between sections 45 | optionMenuController.shouldSeparateOptions = YES; 46 | 47 | // -- add some options in code 48 | NSArray* colorAlternatives = [NSArray arrayWithObjects: 49 | [AMOptionMenuItem itemWithIdentifier:@"Red" title:@"Red"], 50 | [AMOptionMenuItem itemWithIdentifier:@"Green" title:@"Green"], 51 | [AMOptionMenuItem itemWithIdentifier:@"Blue" title:@"Blue"], 52 | nil]; 53 | [optionMenuController insertOptionWithIdentifier:@"Color" title:@"Color" atIndex:0]; 54 | [optionMenuController setAlternatives:colorAlternatives forOptionWithIdentifier:@"Color"]; 55 | 56 | 57 | // -- add some options form a resource file 58 | NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"DinosaurOptions" ofType:@"plist"]; 59 | [optionMenuController insertOptionsFromPropertyListWithURL:[NSURL fileURLWithPath:plistPath] atIndex:1]; 60 | 61 | // -- useful for setting some state that isn't represented by a menu item 62 | [optionMenuController setAllowUnkownOptions:YES]; 63 | [optionMenuController setValue:@"beep" forKey:@"honk"]; 64 | 65 | // -- to limit the max number of values in the summary 66 | //[optionMenuController setMaxValuesInSummary:1]; 67 | 68 | // -- configure a popp button in a nib 69 | [popUpButton setOptionMenuController:optionMenuController]; 70 | popUpButton.smartTitleTruncation = YES; 71 | 72 | // -- create a menu 73 | NSMenu* thingsMenu = [[NSMenu alloc] initWithTitle:@"Things"]; 74 | [optionMenuController insertItemsInMenu:thingsMenu atIndex:0]; 75 | [testMenu setSubmenu:thingsMenu]; 76 | [thingsMenu release]; 77 | 78 | // -- create a popup button programmatically 79 | NSRect myFrame = NSMakeRect( 10, 10, 200, 30); 80 | AMOptionPopUpButton* myPopupButton = [[AMOptionPopUpButton alloc] initWithFrame:myFrame]; 81 | [myPopupButton setOptionMenuController:optionMenuController]; 82 | [myPopupButton setAlignment:NSCenterTextAlignment]; 83 | [[window contentView] addSubview:myPopupButton]; 84 | [myPopupButton release]; 85 | } 86 | 87 | 88 | - (IBAction) setBlue:(id)sender 89 | { 90 | [optionMenuController setValue:@"Blue" forKeyPath:@"Color"]; 91 | } 92 | 93 | 94 | - (IBAction) setReallyAwesome:(id)sender 95 | { 96 | [optionMenuController setValue:[NSNumber numberWithBool:YES] forKeyPath:@"Dinosaurs.isReallyAwesome"]; 97 | } 98 | 99 | 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /DinosaurOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | identifier 7 | Dinosaurs 8 | alternatives 9 | 10 | 11 | identifier 12 | Awesome 13 | title 14 | Awesome 15 | 16 | 17 | identifier 18 | ReallyAwesome 19 | shortTitle 20 | R. Awe. 21 | title 22 | Really Awesome 23 | 24 | 25 | title 26 | Dinosaurs 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrox/AMOptionMenu/cb54aa8368f654c3cd04f79de79cc588782d1e52/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.${PRODUCT_NAME:identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Andy Mroczkowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | AMOptionMenu 2 | ------------ 3 | 4 | AMOptionMenu is a Cocoa multi-option popup menu, similar to Xcode's "Overview" toolbar item. It's features include: 5 | 6 | **Demo Video** 7 | 8 | Screenshot 9 | 10 | * Multiple controls can be driven by the same data source 11 | * Setting state via key-value coding 12 | * Populating a standard NSMenu instead of/addition to popup buttons 13 | * Optional short titles for summaries 14 | * Explicitly or automatically limiting the number of items in the summary. 15 | * Instantiating controls from code or nib 16 | * Loading data from code or external plists 17 | 18 | 19 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AMOptionMenuDemo 4 | // 5 | // Created by Andy Mroczkowski on 7/8/09. 6 | // Copyright Andy Mroczkowski 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrox/AMOptionMenu/cb54aa8368f654c3cd04f79de79cc588782d1e52/screenshot.png -------------------------------------------------------------------------------- /version.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildVersion 6 | 3 7 | CFBundleVersion 8 | 1.0 9 | ProductBuildVersion 10 | 9M2729 11 | ProjectName 12 | DevToolsWizardTemplates 13 | SourceVersion 14 | 11600000 15 | 16 | 17 | --------------------------------------------------------------------------------