├── .gitignore ├── GCJumpBar ├── Categories │ ├── NSIndexPath+GCJumpBar.h │ ├── NSIndexPath+GCJumpBar.m │ ├── NSMenu+IndexPath.h │ └── NSMenu+IndexPath.m ├── GCJumpBar.h ├── GCJumpBar.m ├── GCJumpBarLabel.h ├── GCJumpBarLabel.m └── Image │ ├── GCJumpBarAccessorySeparator.png │ ├── GCJumpBarAccessorySeparator@2x.png │ ├── GCJumpBarSeparator.png │ └── GCJumpBarSeparator@2x.png ├── GCJumpBarAccessorySeparator.psd ├── GCJumpBarDemo.xcodeproj └── project.pbxproj ├── GCJumpBarDemo ├── GCJumpBarDemo-Info.plist ├── GCJumpBarDemo-Prefix.pch ├── GCJumpBarDemoAppDelegate.h ├── GCJumpBarDemoAppDelegate.m ├── en.lproj │ └── MainMenu.xib ├── main.m └── xcode.png ├── GCJumpBarSeparator.psd ├── LICENSE.txt └── README.textile /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | 4 | # Xcode 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | *.xcuserstate 10 | project.xcworkspace/ 11 | xcuserdata/ 12 | 13 | # Generated files 14 | build/ 15 | *.[oa] 16 | *.pyc 17 | 18 | # Backup files 19 | *~.nib -------------------------------------------------------------------------------- /GCJumpBar/Categories/NSIndexPath+GCJumpBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+GCJumpBar.h 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-25. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSIndexPath (GCJumpBar) 12 | 13 | //Return a string of form @"1.2.3.4.5" 14 | @property (nonatomic, readonly) NSString* stringRepresentation; 15 | 16 | - (NSIndexPath*) indexPathByAddingIndexPath:(NSIndexPath*) indexPath; 17 | - (NSIndexPath*) indexPathByAddingIndexInFront:(NSUInteger) index; 18 | 19 | - (NSIndexPath*) subIndexPathFromPosition:(NSUInteger) position; 20 | - (NSIndexPath*) subIndexPathToPosition:(NSUInteger) position; 21 | - (NSIndexPath*) subIndexPathWithRange:(NSRange) range; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /GCJumpBar/Categories/NSIndexPath+GCJumpBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+GCJumpBar.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-25. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import "NSIndexPath+GCJumpBar.h" 10 | 11 | @implementation NSIndexPath (GCJumpBar) 12 | 13 | - (NSString *)stringRepresentation { 14 | NSMutableString* reprensentation = [[NSMutableString alloc] initWithCapacity:[self length] * 2 - 1]; 15 | [reprensentation appendFormat:@"%ld", (long)[self indexAtPosition:0]]; 16 | 17 | for (NSUInteger position = 1; position < self.length ; position ++) { 18 | [reprensentation appendFormat:@".%ld", (long)[self indexAtPosition:position]]; 19 | } 20 | 21 | return [reprensentation autorelease]; 22 | } 23 | 24 | - (NSIndexPath*) indexPathByAddingIndexPath:(NSIndexPath*) indexPath { 25 | NSIndexPath* path = [[self copy] autorelease]; 26 | for (NSUInteger position = 0; position < indexPath.length ; position ++) { 27 | path = [path indexPathByAddingIndex:[indexPath indexAtPosition:position]]; 28 | } 29 | return path; 30 | } 31 | 32 | - (NSIndexPath *)indexPathByAddingIndexInFront:(NSUInteger)index { 33 | NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:index]; 34 | return [indexPath indexPathByAddingIndexPath:self]; 35 | } 36 | 37 | - (NSIndexPath *)subIndexPathFromPosition:(NSUInteger)position { 38 | return [self subIndexPathWithRange:NSMakeRange(position, self.length - position)]; 39 | } 40 | 41 | - (NSIndexPath *)subIndexPathToPosition:(NSUInteger)position { 42 | return [self subIndexPathWithRange:NSMakeRange(0, position)]; 43 | } 44 | 45 | - (NSIndexPath *)subIndexPathWithRange:(NSRange)range { 46 | NSIndexPath* path = [[[NSIndexPath alloc] init] autorelease]; 47 | 48 | for (NSUInteger position = range.location; position < (range.location + range.length) ; position ++) { 49 | path = [path indexPathByAddingIndex:[self indexAtPosition:position]]; 50 | } 51 | 52 | return path; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /GCJumpBar/Categories/NSMenu+IndexPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenu+IndexPath.h 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-25. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSMenu (IndexPath) 12 | 13 | - (NSMenuItem*) itemAtIndexPath:(NSIndexPath*) indexPath; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /GCJumpBar/Categories/NSMenu+IndexPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenu+IndexPath.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-25. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import "NSMenu+IndexPath.h" 10 | 11 | @implementation NSMenu (IndexPath) 12 | 13 | - (NSMenuItem *)itemAtIndexPath:(NSIndexPath *)indexPath { 14 | NSMenu* menu = self; 15 | for (NSUInteger position = 0; position < indexPath.length - 1 ; position ++) { 16 | menu = [[menu itemAtIndex:[indexPath indexAtPosition:position]] submenu]; 17 | } 18 | 19 | return [menu itemAtIndex:[indexPath indexAtPosition:indexPath.length - 1]]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /GCJumpBar/GCJumpBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBar.h 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol GCJumpBarDelegate; 12 | 13 | @interface GCJumpBar : NSControl 14 | 15 | @property (nonatomic, assign) IBOutlet id delegate; 16 | 17 | @property (nonatomic, retain) IBOutlet NSMenu* menu; 18 | @property (nonatomic, retain) IBOutlet NSMenu* accessoryMenu; 19 | 20 | @property (nonatomic, retain) NSString* accessoryMessage; 21 | @property (nonatomic, retain) NSImage* accessoryImage; 22 | 23 | @property (nonatomic, retain) NSIndexPath* selectedIndexPath; 24 | @property (nonatomic, assign) NSUInteger accessoryMenuSelectedIndex; 25 | 26 | //Change automatically the font in the menu to be the same as the Jump Bar and resize the image to 16x16 27 | @property (nonatomic, assign) BOOL changeFontAndImageInMenu; 28 | 29 | @property (nonatomic, retain) NSMenuItem* selectedMenuItem; 30 | @property (nonatomic, retain) NSMenuItem* selectedAccessoryMenuItem; 31 | 32 | @property (nonatomic, retain) NSGradient* backgroundGradient; 33 | 34 | - (id) initWithFrame:(NSRect)frameRect menu:(NSMenu*) aMenu; 35 | - (NSMenuItem*) menuItemAtIndexPath:(NSIndexPath*) indexPath; 36 | - (void) changeFontAndImageInMenu:(NSMenu*) subMenu; 37 | 38 | @end 39 | 40 | @protocol GCJumpBarDelegate 41 | 42 | @optional 43 | - (void) jumpBar:(GCJumpBar*) jumpBar didSelectItemAtIndexPath:(NSIndexPath*) indexPath; 44 | - (void) jumpBar:(GCJumpBar *)jumpBar didSelectAccessoryMenuItemAtIndex:(NSUInteger) index; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /GCJumpBar/GCJumpBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBar.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import "GCJumpBar.h" 10 | #import "GCJumpBarLabel.h" 11 | #import "NSIndexPath+GCJumpBar.h" 12 | #import "NSMenu+IndexPath.h" 13 | 14 | const CGFloat GCJumpBarNormalHeight = 23.0; 15 | const CGFloat GCJumpBarNormalImageSize = 16.0; 16 | 17 | const NSInteger GCJumpBarAccessoryMenuLabelTag = -1; 18 | 19 | @interface GCJumpBar () 20 | 21 | @property (nonatomic, assign, getter = isUnderIdealWidth) BOOL underIdealWidth; 22 | 23 | - (void) performLayout; 24 | - (void) lookForOverflowWidth; 25 | - (void) placeLabelAndSetValue; 26 | - (void) removeUnusedLabels; 27 | 28 | - (void) performLayoutIfNeededWithNewSize:(CGSize) size; 29 | - (GCJumpBarLabel*) labelAtLevel:(NSUInteger) level; 30 | 31 | @end 32 | 33 | @implementation GCJumpBar 34 | 35 | @synthesize underIdealWidth = underIdealWidth; 36 | 37 | @synthesize delegate = delegate; 38 | @synthesize menu, accessoryMenu; 39 | 40 | @synthesize selectedIndexPath, accessoryMenuSelectedIndex; 41 | @synthesize changeFontAndImageInMenu = changeFontAndImageInMenu; 42 | 43 | - (id)initWithCoder:(NSCoder *)aDecoder { 44 | self = [super initWithCoder:aDecoder]; 45 | if (self) { 46 | NSRect frame = self.frame; 47 | frame.size.height = GCJumpBarNormalHeight; 48 | self.frame = frame; 49 | 50 | self.changeFontAndImageInMenu = YES; 51 | self.underIdealWidth = NO; 52 | } 53 | 54 | return self; 55 | } 56 | 57 | - (id)initWithFrame:(NSRect)frameRect { 58 | return [self initWithFrame:frameRect menu:nil]; 59 | } 60 | 61 | - (id) initWithFrame:(NSRect)frameRect menu:(NSMenu*) aMenu { 62 | frameRect.size.height = GCJumpBarNormalHeight; 63 | 64 | self = [super initWithFrame:frameRect]; 65 | if (self) { 66 | self.menu = aMenu; 67 | self.changeFontAndImageInMenu = YES; 68 | self.accessoryMenuSelectedIndex = 0; 69 | } 70 | 71 | return self; 72 | } 73 | 74 | #pragma mark - Subclass 75 | 76 | - (NSMenu *)menuForEvent:(NSEvent *)event { 77 | return nil; 78 | } 79 | 80 | #pragma mark - Setters 81 | 82 | - (void)setMenu:(NSMenu *)newMenu { 83 | if (newMenu != menu) { 84 | [menu release]; 85 | menu = [newMenu retain]; 86 | 87 | if (menu != nil && self.selectedIndexPath == nil) self.selectedIndexPath = [NSIndexPath indexPathWithIndex:0]; 88 | if (self.changeFontAndImageInMenu) [self changeFontAndImageInMenu:self.menu]; 89 | 90 | [self performLayout]; 91 | 92 | if ([self.delegate respondsToSelector:@selector(jumpBar:didSelectItemAtIndexPath:)]) { 93 | [self.delegate jumpBar:self didSelectItemAtIndexPath:self.selectedIndexPath]; 94 | } 95 | } 96 | } 97 | 98 | - (void)setAccessoryMenu:(NSMenu *)newAccessoryMenu { 99 | if (newAccessoryMenu != accessoryMenu) { 100 | [accessoryMenu release]; 101 | accessoryMenu = [newAccessoryMenu retain]; 102 | 103 | if (self.changeFontAndImageInMenu) [self changeFontAndImageInMenu:self.accessoryMenu]; 104 | 105 | [self performLayout]; 106 | 107 | if ([self.delegate respondsToSelector:@selector(jumpBar:didSelectAccessoryMenuItemAtIndex:)]) { 108 | [self.delegate jumpBar:self didSelectAccessoryMenuItemAtIndex:self.accessoryMenuSelectedIndex]; 109 | } 110 | } 111 | } 112 | 113 | - (void)setSelectedIndexPath:(NSIndexPath *)newSelectedIndexPath { 114 | if (newSelectedIndexPath != selectedIndexPath) { 115 | [selectedIndexPath release]; 116 | selectedIndexPath = [newSelectedIndexPath retain]; 117 | 118 | [self performLayout]; 119 | } 120 | 121 | if ([self.delegate respondsToSelector:@selector(jumpBar:didSelectItemAtIndexPath:)]) { 122 | [self.delegate jumpBar:self didSelectItemAtIndexPath:self.selectedIndexPath]; 123 | } 124 | } 125 | 126 | - (void)setAccessoryMenuSelectedIndex:(NSUInteger)newAccessoryMenuSelectedIndex { 127 | if (accessoryMenuSelectedIndex != newAccessoryMenuSelectedIndex) { 128 | accessoryMenuSelectedIndex = newAccessoryMenuSelectedIndex; 129 | 130 | [self performLayout]; 131 | } 132 | 133 | if ([self.delegate respondsToSelector:@selector(jumpBar:didSelectAccessoryMenuItemAtIndex:)]) { 134 | [self.delegate jumpBar:self didSelectAccessoryMenuItemAtIndex:self.accessoryMenuSelectedIndex]; 135 | } 136 | } 137 | 138 | - (void)setEnabled:(BOOL)flag { 139 | [super setEnabled:flag]; 140 | 141 | for (NSControl* view in [self subviews]) { 142 | [view setEnabled:flag]; 143 | } 144 | 145 | [self setNeedsDisplay]; 146 | } 147 | 148 | - (void)setFrame:(NSRect)frameRect { 149 | [super setFrame:frameRect]; 150 | [self performLayoutIfNeededWithNewSize:frameRect.size]; 151 | } 152 | 153 | - (void)setBounds:(NSRect)aRect { 154 | [super setBounds:aRect]; 155 | [self performLayoutIfNeededWithNewSize:aRect.size]; 156 | } 157 | 158 | #pragma mark - Layout 159 | 160 | - (void) performLayoutIfNeededWithNewSize:(CGSize) size { 161 | if (self.accessoryMenu != nil || self.accessoryMessage != nil || self.underIdealWidth) [self performLayout]; 162 | else { 163 | GCJumpBarLabel* lastLabel = [self viewWithTag:self.selectedIndexPath.length]; 164 | CGFloat endFloat = lastLabel.frame.size.width + lastLabel.frame.origin.x; 165 | 166 | if (size.width < endFloat) { 167 | [self performLayout]; 168 | } 169 | } 170 | } 171 | 172 | - (void)performLayout { 173 | self.underIdealWidth = NO; 174 | 175 | [self placeLabelAndSetValue]; 176 | [self lookForOverflowWidth]; 177 | [self removeUnusedLabels]; 178 | } 179 | 180 | - (void)placeLabelAndSetValue { 181 | NSIndexPath* atThisPointIndexPath = [[[NSIndexPath alloc] init] autorelease]; 182 | CGFloat baseX = 0; 183 | for (NSUInteger position = 0; position < self.selectedIndexPath.length ; position ++) { 184 | NSUInteger selectedIndex = [self.selectedIndexPath indexAtPosition:position]; 185 | atThisPointIndexPath = [atThisPointIndexPath indexPathByAddingIndex:selectedIndex]; 186 | 187 | GCJumpBarLabel* label = [self labelAtLevel:atThisPointIndexPath.length]; 188 | label.lastLabel = (position == (self.selectedIndexPath.length - 1)); 189 | 190 | NSMenuItem* item = [self.menu itemAtIndexPath:atThisPointIndexPath]; 191 | label.text = item.title; 192 | label.image = item.image; 193 | label.indexInLevel = selectedIndex; 194 | 195 | [label sizeToFit]; 196 | NSRect frame = [label frame]; 197 | frame.origin.x = baseX; 198 | baseX += frame.size.width; 199 | label.frame = frame; 200 | } 201 | 202 | if (self.accessoryMenu != nil) { 203 | GCJumpBarLabel* accessoryLabel = [self labelAtLevel:GCJumpBarAccessoryMenuLabelTag]; 204 | 205 | NSMenuItem* item = [self.accessoryMenu itemAtIndex:self.accessoryMenuSelectedIndex]; 206 | accessoryLabel.image = item.image; 207 | accessoryLabel.text = item.title; 208 | accessoryLabel.indexInLevel = self.accessoryMenuSelectedIndex; 209 | [accessoryLabel sizeToFit]; 210 | 211 | NSRect frame = [accessoryLabel frame]; 212 | frame.origin.x = self.frame.size.width - accessoryLabel.frame.size.width; 213 | accessoryLabel.frame = frame; 214 | } 215 | else if (_accessoryMessage != nil) 216 | { 217 | GCJumpBarLabel* accessoryLabel = [self labelAtLevel:GCJumpBarAccessoryMenuLabelTag]; 218 | 219 | accessoryLabel.image = _accessoryImage; 220 | accessoryLabel.text = _accessoryMessage; 221 | accessoryLabel.indexInLevel = self.accessoryMenuSelectedIndex; 222 | [accessoryLabel sizeToFit]; 223 | 224 | NSRect frame = [accessoryLabel frame]; 225 | frame.origin.x = self.frame.size.width - accessoryLabel.frame.size.width; 226 | accessoryLabel.frame = frame; 227 | 228 | } 229 | } 230 | 231 | - (void)lookForOverflowWidth { 232 | GCJumpBarLabel* lastLabel = [self viewWithTag:self.selectedIndexPath.length]; 233 | CGFloat endFloat = lastLabel.frame.size.width + lastLabel.frame.origin.x; 234 | 235 | if (self.accessoryMenu != nil || self.accessoryMessage != nil) { 236 | endFloat += [[self viewWithTag:GCJumpBarAccessoryMenuLabelTag] frame].size.width; 237 | } 238 | 239 | if (self.frame.size.width < endFloat) { 240 | self.underIdealWidth = YES; 241 | 242 | //Set new width for the overflow 243 | CGFloat overMargin = endFloat - self.frame.size.width; 244 | for (NSUInteger position = 0; position < self.selectedIndexPath.length + (self.accessoryMenu != nil); position ++) { 245 | if (position == self.selectedIndexPath.length) position = GCJumpBarAccessoryMenuLabelTag - 1; 246 | GCJumpBarLabel* label = [self labelAtLevel:position + 1]; 247 | if ((overMargin + label.minimumWidth - label.frame.size.width) < 0) { 248 | CGRect frame = label.frame; 249 | frame.size.width -= overMargin; 250 | label.frame = frame; 251 | break; 252 | } 253 | else { 254 | overMargin -= (label.frame.size.width - label.minimumWidth); 255 | 256 | CGRect frame = label.frame; 257 | frame.size.width = label.minimumWidth; 258 | label.frame = frame; 259 | } 260 | } 261 | 262 | //Replace the labels at the right place 263 | CGFloat baseX = 0; 264 | for (NSUInteger position = 0; position < self.selectedIndexPath.length ; position ++) { 265 | GCJumpBarLabel* label = [self labelAtLevel:position + 1]; 266 | 267 | NSRect frame = [label frame]; 268 | frame.origin.x = baseX; 269 | baseX += frame.size.width; 270 | label.frame = frame; 271 | } 272 | 273 | if (self.accessoryMenu != nil || self.accessoryMessage != nil) { 274 | GCJumpBarLabel* accessoryLabel = [self labelAtLevel:GCJumpBarAccessoryMenuLabelTag]; 275 | 276 | NSRect frame = [accessoryLabel frame]; 277 | frame.origin.x = self.frame.size.width - accessoryLabel.frame.size.width; 278 | accessoryLabel.frame = frame; 279 | } 280 | } 281 | } 282 | 283 | - (void)removeUnusedLabels { 284 | //Remove old views 285 | NSView* viewToRemove = nil; 286 | NSUInteger position = self.selectedIndexPath.length + 1; 287 | while ((viewToRemove = [self viewWithTag:position])) { 288 | [viewToRemove removeFromSuperview]; 289 | position ++; 290 | } 291 | 292 | if (self.accessoryMenu == nil && self.accessoryMessage == nil) [[self viewWithTag:GCJumpBarAccessoryMenuLabelTag] removeFromSuperview]; 293 | } 294 | 295 | #pragma mark - Drawing 296 | 297 | - (void)drawRect:(NSRect)dirtyRect { 298 | //Draw main gradient 299 | dirtyRect.size.height = self.bounds.size.height; 300 | dirtyRect.origin.y = 0; 301 | 302 | NSGradient* mainGradient = nil; 303 | if (_backgroundGradient) 304 | { 305 | mainGradient = [_backgroundGradient retain]; 306 | } 307 | else 308 | { 309 | if (!self.isEnabled || !self.window.isKeyWindow) 310 | mainGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.96 alpha:1.0] 311 | endingColor:[NSColor colorWithCalibratedWhite:0.85 alpha:1.0]]; 312 | else 313 | mainGradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.85 alpha:1.0] 314 | endingColor:[NSColor colorWithCalibratedWhite:0.73 alpha:1.0]]; 315 | } 316 | [mainGradient drawInRect:dirtyRect angle:-90]; 317 | [mainGradient release]; 318 | 319 | //Draw both stroke lines 320 | if (!self.isEnabled || !self.window.isKeyWindow) [[NSColor colorWithCalibratedWhite:0.5 alpha:1.0] set]; 321 | else [[NSColor colorWithCalibratedWhite:0.33 alpha:1.0] set]; 322 | 323 | dirtyRect.size.height = 1; 324 | NSRectFill(dirtyRect); 325 | 326 | dirtyRect.origin.y = self.frame.size.height - 1; 327 | NSRectFill(dirtyRect); 328 | } 329 | 330 | - (void)viewWillMoveToWindow:(NSWindow *)newWindow { 331 | [super viewWillMoveToWindow:newWindow]; 332 | 333 | [[NSNotificationCenter defaultCenter] removeObserver:self 334 | name:NSWindowDidResignKeyNotification object:self.window]; 335 | [[NSNotificationCenter defaultCenter] removeObserver:self 336 | name:NSWindowDidBecomeKeyNotification object:self.window]; 337 | } 338 | 339 | - (void)viewDidMoveToWindow { 340 | [super viewDidMoveToWindow]; 341 | 342 | if (self.window != nil) { 343 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setNeedsDisplay) 344 | name:NSWindowDidResignKeyNotification object:self.window]; 345 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setNeedsDisplay) 346 | name:NSWindowDidBecomeKeyNotification object:self.window]; 347 | } 348 | } 349 | 350 | #pragma mark - Helper 351 | 352 | - (GCJumpBarLabel *)labelAtLevel:(NSUInteger)level { 353 | GCJumpBarLabel* label = [self viewWithTag:level]; 354 | if (label == nil) { 355 | label = [[GCJumpBarLabel alloc] init]; 356 | label.level = level; 357 | label.frame = NSMakeRect(0, 0, 0, self.frame.size.height); 358 | label.delegate = self; 359 | label.enabled = self.isEnabled; 360 | 361 | [self addSubview:label]; 362 | [label release]; 363 | } 364 | 365 | return label; 366 | } 367 | 368 | - (void) changeFontAndImageInMenu:(NSMenu*) subMenu { 369 | for (NSMenuItem* item in [subMenu itemArray]) { 370 | NSMutableAttributedString* attributedString = [[item attributedTitle] mutableCopy]; 371 | if (attributedString == nil) attributedString = [[NSMutableAttributedString alloc] initWithString:item.title]; 372 | 373 | NSDictionary* attribues = (attributedString.length != 0) ? [attributedString attributesAtIndex:0 effectiveRange:nil] : nil; 374 | NSFont* font = [attribues objectForKey:NSFontAttributeName]; 375 | NSString* fontDescrition = [font fontName]; 376 | if (fontDescrition != nil) { 377 | if ([fontDescrition rangeOfString:@"Bold" options:NSCaseInsensitiveSearch].location != NSNotFound) { 378 | font = [NSFont boldSystemFontOfSize:12.0]; 379 | } 380 | else font = [NSFont systemFontOfSize:12.0]; 381 | } 382 | else font = [NSFont systemFontOfSize:12.0]; 383 | 384 | [attributedString addAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName] 385 | range:NSMakeRange(0, attributedString.length)]; 386 | [item setAttributedTitle:attributedString]; 387 | [attributedString release]; 388 | 389 | [item.image setSize:NSMakeSize(GCJumpBarNormalImageSize, GCJumpBarNormalImageSize)]; 390 | 391 | if ([item hasSubmenu]) [self changeFontAndImageInMenu:[item submenu]]; 392 | } 393 | } 394 | 395 | - (NSMenuItem *)menuItemAtIndexPath:(NSIndexPath *)indexPath { 396 | return [self.menu itemAtIndexPath:indexPath]; 397 | } 398 | 399 | - (NSMenuItem *)selectedMenuItem { 400 | return [self menuItemAtIndexPath:self.selectedIndexPath]; 401 | } 402 | 403 | - (void)setSelectedMenuItem:(NSMenuItem *)selectedMenuItem { 404 | NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:[selectedMenuItem.menu indexOfItem:selectedMenuItem]]; 405 | while (selectedMenuItem.parentItem != nil) { 406 | selectedMenuItem = selectedMenuItem.parentItem; 407 | indexPath = [indexPath indexPathByAddingIndexInFront:[selectedMenuItem.menu indexOfItem:selectedMenuItem]]; 408 | } 409 | 410 | self.selectedIndexPath = indexPath; 411 | } 412 | 413 | -(void)setAccessoryMessage:(NSString *)accessoryMessage 414 | { 415 | _accessoryMessage = accessoryMessage; 416 | [self performLayout]; 417 | } 418 | 419 | - (void)setAccessoryImage:(NSImage *)accessoryImage 420 | { 421 | _accessoryImage = accessoryImage; 422 | [self performLayout]; 423 | } 424 | 425 | - (void)setBackgroundGradient:(NSGradient *)backgroundGradient 426 | { 427 | if (backgroundGradient != _backgroundGradient) 428 | { 429 | [_backgroundGradient release]; 430 | _backgroundGradient = [backgroundGradient retain]; 431 | [self setNeedsDisplay]; 432 | } 433 | } 434 | 435 | - (NSMenuItem *)selectedAccessoryMenuItem { 436 | return [self.accessoryMenu itemAtIndex:self.accessoryMenuSelectedIndex]; 437 | } 438 | 439 | - (void)setSelectedAccessoryMenuItem:(NSMenuItem *)selectedAccessoryMenuItem { 440 | self.accessoryMenuSelectedIndex = [selectedAccessoryMenuItem.menu indexOfItem:selectedAccessoryMenuItem]; 441 | } 442 | 443 | #pragma mark - GCJumpBarLabelDelegate 444 | 445 | - (NSMenu *)menuToPresentWhenClickedForJumpBarLabel:(GCJumpBarLabel *)label { 446 | if (label.tag == GCJumpBarAccessoryMenuLabelTag) return self.accessoryMenu; 447 | else { 448 | NSIndexPath* subIndexPath = [self.selectedIndexPath subIndexPathToPosition:label.level]; 449 | 450 | return [[self.menu itemAtIndexPath:subIndexPath] menu]; 451 | } 452 | } 453 | 454 | - (void)jumpBarLabel:(GCJumpBarLabel *)label didReceivedClickOnItemAtIndexPath:(NSIndexPath *)indexPath { 455 | if (label.tag == GCJumpBarAccessoryMenuLabelTag) self.accessoryMenuSelectedIndex = [indexPath indexAtPosition:0]; 456 | else { 457 | NSIndexPath* subIndexPath = [self.selectedIndexPath subIndexPathToPosition:label.level - 1]; 458 | self.selectedIndexPath = [subIndexPath indexPathByAddingIndexPath:indexPath]; 459 | } 460 | } 461 | 462 | @end 463 | -------------------------------------------------------------------------------- /GCJumpBar/GCJumpBarLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBarLabel.h 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol GCJumpBarLabelDelegate; 12 | 13 | @interface GCJumpBarLabel : NSControl 14 | 15 | @property (nonatomic, retain) NSImage* image; 16 | @property (nonatomic, retain) NSString* text; 17 | @property (nonatomic, getter = isLastLabel) BOOL lastLabel; 18 | 19 | @property (nonatomic, assign) NSUInteger indexInLevel; 20 | @property (nonatomic, assign) NSUInteger level; 21 | 22 | @property (nonatomic, readonly) CGFloat minimumWidth; 23 | 24 | @property (nonatomic, assign) id delegate; 25 | 26 | @end 27 | 28 | @protocol GCJumpBarLabelDelegate 29 | 30 | - (NSMenu*) menuToPresentWhenClickedForJumpBarLabel:(GCJumpBarLabel*) label; 31 | - (void) jumpBarLabel:(GCJumpBarLabel*) label didReceivedClickOnItemAtIndexPath:(NSIndexPath*) indexPath; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /GCJumpBar/GCJumpBarLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBarLabel.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import "GCJumpBarLabel.h" 10 | #import "NSIndexPath+GCJumpBar.h" 11 | 12 | const CGFloat GCJumpBarLabelMargin = 5.0; 13 | 14 | const NSInteger GCJumpBarLabelAccessoryMenuLabelTag = -1; 15 | 16 | @interface GCJumpBarLabel () 17 | 18 | @property (nonatomic, readonly) NSDictionary* attributes; 19 | @property (nonatomic, retain) NSMenu* clickedMenu; 20 | 21 | - (void)setPropretyOnMenu:(NSMenu *)menu; 22 | 23 | @end 24 | 25 | @implementation GCJumpBarLabel 26 | 27 | @synthesize image, text, lastLabel; 28 | @synthesize indexInLevel, clickedMenu; 29 | @synthesize delegate = delegate; 30 | 31 | #pragma mark - View subclass 32 | 33 | - (void)sizeToFit { 34 | [super sizeToFit]; 35 | 36 | CGFloat width = (2 + (self.image != nil)) * GCJumpBarLabelMargin; 37 | 38 | NSSize textSize = [self.text sizeWithAttributes:self.attributes]; 39 | width += ceil(textSize.width); 40 | width += ceil(self.image.size.width); 41 | if (!self.lastLabel) width += 7; //Separator image 42 | 43 | NSRect frame = self.frame; 44 | frame.size.width = width; 45 | self.frame = frame; 46 | } 47 | 48 | #pragma mark - Getter/Setters 49 | 50 | - (CGFloat)minimumWidth { 51 | return GCJumpBarLabelMargin + self.image.size.width + (self.image != nil) * GCJumpBarLabelMargin + (!self.lastLabel) * 7; 52 | } 53 | 54 | - (void)setImage:(NSImage *)newImage { 55 | if (image != newImage) { 56 | [image release]; 57 | image = [newImage retain]; 58 | 59 | [self setNeedsDisplay]; 60 | } 61 | } 62 | 63 | - (void)setText:(NSString *)newText { 64 | if (text != newText) { 65 | [text release]; 66 | text = [newText retain]; 67 | 68 | [self setNeedsDisplay]; 69 | } 70 | } 71 | 72 | - (NSUInteger)level { 73 | return self.tag; 74 | } 75 | 76 | - (void)setLevel:(NSUInteger)level { 77 | self.tag = level; 78 | } 79 | 80 | #pragma mark - Delegate 81 | 82 | - (void)mouseDown:(NSEvent *)theEvent { 83 | if (self.isEnabled) { 84 | self.clickedMenu = [self.delegate menuToPresentWhenClickedForJumpBarLabel:self]; 85 | [self setPropretyOnMenu:self.clickedMenu]; 86 | 87 | CGFloat xPoint = (self.tag == GCJumpBarLabelAccessoryMenuLabelTag ? - 9 : - 16); 88 | 89 | [self.clickedMenu popUpMenuPositioningItem:[self.clickedMenu itemAtIndex:self.indexInLevel] 90 | atLocation:NSMakePoint(xPoint , self.frame.size.height - 4) inView:self]; 91 | } 92 | } 93 | 94 | - (void) menuClicked:(id) sender { 95 | NSMenuItem* item = sender; 96 | NSIndexPath* indexPath = [[[NSIndexPath alloc] init] autorelease]; 97 | 98 | if (self.tag != GCJumpBarLabelAccessoryMenuLabelTag) { 99 | while (![[self.clickedMenu itemArray] containsObject:item]) { 100 | indexPath = [indexPath indexPathByAddingIndexInFront:[[item menu] indexOfItem:item]]; 101 | item = [item parentItem]; 102 | } 103 | } 104 | indexPath = [indexPath indexPathByAddingIndexInFront:[[item menu] indexOfItem:item]]; 105 | 106 | [self.delegate jumpBarLabel:self didReceivedClickOnItemAtIndexPath:indexPath]; 107 | 108 | self.clickedMenu = nil; 109 | } 110 | 111 | #pragma mark - Dawing 112 | 113 | - (void)drawRect:(NSRect)dirtyRect { 114 | CGFloat baseLeft = 0; 115 | 116 | if (self.tag == GCJumpBarLabelAccessoryMenuLabelTag) { 117 | NSImage* separatorImage = [NSImage imageNamed:@"GCJumpBarAccessorySeparator"]; 118 | [separatorImage drawAtPoint:NSMakePoint(baseLeft + 1, self.frame.size.height / 2 - separatorImage.size.height / 2) 119 | fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; 120 | baseLeft += separatorImage.size.width + GCJumpBarLabelMargin; 121 | } 122 | else baseLeft = GCJumpBarLabelMargin; 123 | 124 | if (self.image != nil) { 125 | int top = 0; 126 | if (self.isFlipped) 127 | top = floor(self.frame.size.height / 2 + self.image.size.height / 2); 128 | else 129 | top = floor(self.frame.size.height / 2 - self.image.size.height / 2); 130 | [self.image drawInRect:NSMakeRect(baseLeft, floor(self.frame.size.height / 2 - self.image.size.height / 2), self.image.size.width, self.image.size.height) 131 | fromRect:NSZeroRect 132 | operation:NSCompositeSourceOver 133 | fraction:1.0 134 | respectFlipped:YES 135 | hints:nil]; 136 | baseLeft += ceil(self.image.size.width) + GCJumpBarLabelMargin; 137 | } 138 | 139 | if (self.text != nil) { 140 | NSSize textSize = [self.text sizeWithAttributes:self.attributes]; 141 | CGFloat width = self.frame.size.width - baseLeft - GCJumpBarLabelMargin; 142 | if (!self.lastLabel && self.tag != GCJumpBarLabelAccessoryMenuLabelTag) width -= 7; 143 | 144 | if (width > 0) { 145 | [self.text drawInRect:CGRectMake(baseLeft, self.frame.size.height / 2 - textSize.height / 2 146 | , width, textSize.height) 147 | withAttributes:self.attributes]; 148 | baseLeft += width + GCJumpBarLabelMargin; 149 | } 150 | } 151 | 152 | if (!self.lastLabel && self.tag != GCJumpBarLabelAccessoryMenuLabelTag) { 153 | NSImage* separatorImage = [NSImage imageNamed:@"GCJumpBarSeparator"]; 154 | [separatorImage drawAtPoint:NSMakePoint(baseLeft, self.frame.size.height / 2 - separatorImage.size.height / 2) 155 | fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; 156 | } 157 | } 158 | 159 | #pragma mark - Helper 160 | 161 | - (NSDictionary *)attributes { 162 | NSShadow* highlightShadow = [[NSShadow alloc] init]; 163 | highlightShadow.shadowOffset = CGSizeMake(0, -1.0); 164 | highlightShadow.shadowColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.5]; 165 | highlightShadow.shadowBlurRadius = 0.0; 166 | 167 | NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init]; 168 | [style setLineBreakMode:NSLineBreakByTruncatingTail]; 169 | 170 | NSDictionary* attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor blackColor], NSForegroundColorAttributeName, 171 | highlightShadow, NSShadowAttributeName, 172 | [NSFont systemFontOfSize:12.0], NSFontAttributeName , 173 | style, NSParagraphStyleAttributeName, nil]; 174 | [highlightShadow release]; 175 | [style release]; 176 | 177 | return [attributes autorelease]; 178 | } 179 | 180 | - (void)setPropretyOnMenu:(NSMenu *)menu { 181 | for (NSMenuItem* item in [menu itemArray]) { 182 | if (item.isEnabled) { 183 | [item setTarget:self]; 184 | [item setAction:@selector(menuClicked:)]; 185 | if ([item hasSubmenu]) [self setPropretyOnMenu:item.submenu]; 186 | } 187 | } 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /GCJumpBar/Image/GCJumpBarAccessorySeparator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBar/Image/GCJumpBarAccessorySeparator.png -------------------------------------------------------------------------------- /GCJumpBar/Image/GCJumpBarAccessorySeparator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBar/Image/GCJumpBarAccessorySeparator@2x.png -------------------------------------------------------------------------------- /GCJumpBar/Image/GCJumpBarSeparator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBar/Image/GCJumpBarSeparator.png -------------------------------------------------------------------------------- /GCJumpBar/Image/GCJumpBarSeparator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBar/Image/GCJumpBarSeparator@2x.png -------------------------------------------------------------------------------- /GCJumpBarAccessorySeparator.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBarAccessorySeparator.psd -------------------------------------------------------------------------------- /GCJumpBarDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B03A4025138C295C004677BE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B03A4024138C295C004677BE /* Cocoa.framework */; }; 11 | B03A4031138C295D004677BE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4030138C295D004677BE /* main.m */; }; 12 | B03A4038138C295D004677BE /* GCJumpBarDemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4037138C295D004677BE /* GCJumpBarDemoAppDelegate.m */; }; 13 | B03A403B138C295D004677BE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = B03A4039138C295D004677BE /* MainMenu.xib */; }; 14 | B03A4050138D5C34004677BE /* xcode.png in Resources */ = {isa = PBXBuildFile; fileRef = B03A404F138D5C34004677BE /* xcode.png */; }; 15 | B03A406C138D6F3A004677BE /* NSIndexPath+GCJumpBar.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4063138D6F3A004677BE /* NSIndexPath+GCJumpBar.m */; }; 16 | B03A406D138D6F3A004677BE /* NSMenu+IndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4065138D6F3A004677BE /* NSMenu+IndexPath.m */; }; 17 | B03A406E138D6F3A004677BE /* GCJumpBar.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4067138D6F3A004677BE /* GCJumpBar.m */; }; 18 | B03A406F138D6F3A004677BE /* GCJumpBarLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = B03A4069138D6F3A004677BE /* GCJumpBarLabel.m */; }; 19 | B03A4070138D6F3A004677BE /* GCJumpBarSeparator.png in Resources */ = {isa = PBXBuildFile; fileRef = B03A406B138D6F3A004677BE /* GCJumpBarSeparator.png */; }; 20 | B04247D413A9A99C005FF30D /* GCJumpBarAccessorySeparator.png in Resources */ = {isa = PBXBuildFile; fileRef = B04247D313A9A99B005FF30D /* GCJumpBarAccessorySeparator.png */; }; 21 | B05396D815884DAF004B4EB3 /* GCJumpBarAccessorySeparator@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B05396D615884DAF004B4EB3 /* GCJumpBarAccessorySeparator@2x.png */; }; 22 | B05396D915884DAF004B4EB3 /* GCJumpBarSeparator@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B05396D715884DAF004B4EB3 /* GCJumpBarSeparator@2x.png */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | B03A4020138C295C004677BE /* GCJumpBarDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GCJumpBarDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B03A4024138C295C004677BE /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 28 | B03A4027138C295C004677BE /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 29 | B03A4028138C295C004677BE /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 30 | B03A4029138C295C004677BE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 31 | B03A402C138C295C004677BE /* GCJumpBarDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GCJumpBarDemo-Info.plist"; sourceTree = ""; }; 32 | B03A4030138C295D004677BE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | B03A4032138C295D004677BE /* GCJumpBarDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GCJumpBarDemo-Prefix.pch"; sourceTree = ""; }; 34 | B03A4036138C295D004677BE /* GCJumpBarDemoAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCJumpBarDemoAppDelegate.h; sourceTree = ""; }; 35 | B03A4037138C295D004677BE /* GCJumpBarDemoAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCJumpBarDemoAppDelegate.m; sourceTree = ""; }; 36 | B03A403A138C295D004677BE /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 37 | B03A404F138D5C34004677BE /* xcode.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = xcode.png; sourceTree = ""; }; 38 | B03A4062138D6F3A004677BE /* NSIndexPath+GCJumpBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+GCJumpBar.h"; sourceTree = ""; }; 39 | B03A4063138D6F3A004677BE /* NSIndexPath+GCJumpBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+GCJumpBar.m"; sourceTree = ""; }; 40 | B03A4064138D6F3A004677BE /* NSMenu+IndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMenu+IndexPath.h"; sourceTree = ""; }; 41 | B03A4065138D6F3A004677BE /* NSMenu+IndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMenu+IndexPath.m"; sourceTree = ""; }; 42 | B03A4066138D6F3A004677BE /* GCJumpBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCJumpBar.h; sourceTree = ""; }; 43 | B03A4067138D6F3A004677BE /* GCJumpBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCJumpBar.m; sourceTree = ""; }; 44 | B03A4068138D6F3A004677BE /* GCJumpBarLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCJumpBarLabel.h; sourceTree = ""; }; 45 | B03A4069138D6F3A004677BE /* GCJumpBarLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCJumpBarLabel.m; sourceTree = ""; }; 46 | B03A406B138D6F3A004677BE /* GCJumpBarSeparator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GCJumpBarSeparator.png; sourceTree = ""; }; 47 | B04247D313A9A99B005FF30D /* GCJumpBarAccessorySeparator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GCJumpBarAccessorySeparator.png; sourceTree = ""; }; 48 | B05396D615884DAF004B4EB3 /* GCJumpBarAccessorySeparator@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GCJumpBarAccessorySeparator@2x.png"; sourceTree = ""; }; 49 | B05396D715884DAF004B4EB3 /* GCJumpBarSeparator@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GCJumpBarSeparator@2x.png"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | B03A401D138C295C004677BE /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | B03A4025138C295C004677BE /* Cocoa.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | B03A4015138C295C004677BE = { 65 | isa = PBXGroup; 66 | children = ( 67 | B03A402A138C295C004677BE /* GCJumpBarDemo */, 68 | B03A4023138C295C004677BE /* Frameworks */, 69 | B03A4021138C295C004677BE /* Products */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | B03A4021138C295C004677BE /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | B03A4020138C295C004677BE /* GCJumpBarDemo.app */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | B03A4023138C295C004677BE /* Frameworks */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | B03A4024138C295C004677BE /* Cocoa.framework */, 85 | B03A4026138C295C004677BE /* Other Frameworks */, 86 | ); 87 | name = Frameworks; 88 | sourceTree = ""; 89 | }; 90 | B03A4026138C295C004677BE /* Other Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | B03A4027138C295C004677BE /* AppKit.framework */, 94 | B03A4028138C295C004677BE /* CoreData.framework */, 95 | B03A4029138C295C004677BE /* Foundation.framework */, 96 | ); 97 | name = "Other Frameworks"; 98 | sourceTree = ""; 99 | }; 100 | B03A402A138C295C004677BE /* GCJumpBarDemo */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | B03A4060138D6F3A004677BE /* GCJumpBar */, 104 | B03A402B138C295C004677BE /* Supporting Files */, 105 | ); 106 | path = GCJumpBarDemo; 107 | sourceTree = ""; 108 | }; 109 | B03A402B138C295C004677BE /* Supporting Files */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | B03A4036138C295D004677BE /* GCJumpBarDemoAppDelegate.h */, 113 | B03A4037138C295D004677BE /* GCJumpBarDemoAppDelegate.m */, 114 | B03A404F138D5C34004677BE /* xcode.png */, 115 | B03A4039138C295D004677BE /* MainMenu.xib */, 116 | B03A402C138C295C004677BE /* GCJumpBarDemo-Info.plist */, 117 | B03A4030138C295D004677BE /* main.m */, 118 | B03A4032138C295D004677BE /* GCJumpBarDemo-Prefix.pch */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | B03A4060138D6F3A004677BE /* GCJumpBar */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | B03A4061138D6F3A004677BE /* Categories */, 127 | B03A4066138D6F3A004677BE /* GCJumpBar.h */, 128 | B03A4067138D6F3A004677BE /* GCJumpBar.m */, 129 | B03A4068138D6F3A004677BE /* GCJumpBarLabel.h */, 130 | B03A4069138D6F3A004677BE /* GCJumpBarLabel.m */, 131 | B03A406A138D6F3A004677BE /* Image */, 132 | ); 133 | path = GCJumpBar; 134 | sourceTree = SOURCE_ROOT; 135 | }; 136 | B03A4061138D6F3A004677BE /* Categories */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | B03A4062138D6F3A004677BE /* NSIndexPath+GCJumpBar.h */, 140 | B03A4063138D6F3A004677BE /* NSIndexPath+GCJumpBar.m */, 141 | B03A4064138D6F3A004677BE /* NSMenu+IndexPath.h */, 142 | B03A4065138D6F3A004677BE /* NSMenu+IndexPath.m */, 143 | ); 144 | path = Categories; 145 | sourceTree = ""; 146 | }; 147 | B03A406A138D6F3A004677BE /* Image */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | B04247D313A9A99B005FF30D /* GCJumpBarAccessorySeparator.png */, 151 | B05396D615884DAF004B4EB3 /* GCJumpBarAccessorySeparator@2x.png */, 152 | B03A406B138D6F3A004677BE /* GCJumpBarSeparator.png */, 153 | B05396D715884DAF004B4EB3 /* GCJumpBarSeparator@2x.png */, 154 | ); 155 | path = Image; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | B03A401F138C295C004677BE /* GCJumpBarDemo */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = B03A403E138C295D004677BE /* Build configuration list for PBXNativeTarget "GCJumpBarDemo" */; 164 | buildPhases = ( 165 | B03A401C138C295C004677BE /* Sources */, 166 | B03A401D138C295C004677BE /* Frameworks */, 167 | B03A401E138C295C004677BE /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = GCJumpBarDemo; 174 | productName = GCJumpBarDemo; 175 | productReference = B03A4020138C295C004677BE /* GCJumpBarDemo.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | B03A4017138C295C004677BE /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | LastUpgradeCheck = 0420; 185 | ORGANIZATIONNAME = LittleKiwi; 186 | }; 187 | buildConfigurationList = B03A401A138C295C004677BE /* Build configuration list for PBXProject "GCJumpBarDemo" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | ); 194 | mainGroup = B03A4015138C295C004677BE; 195 | productRefGroup = B03A4021138C295C004677BE /* Products */; 196 | projectDirPath = ""; 197 | projectRoot = ""; 198 | targets = ( 199 | B03A401F138C295C004677BE /* GCJumpBarDemo */, 200 | ); 201 | }; 202 | /* End PBXProject section */ 203 | 204 | /* Begin PBXResourcesBuildPhase section */ 205 | B03A401E138C295C004677BE /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | B03A403B138C295D004677BE /* MainMenu.xib in Resources */, 210 | B03A4050138D5C34004677BE /* xcode.png in Resources */, 211 | B03A4070138D6F3A004677BE /* GCJumpBarSeparator.png in Resources */, 212 | B04247D413A9A99C005FF30D /* GCJumpBarAccessorySeparator.png in Resources */, 213 | B05396D815884DAF004B4EB3 /* GCJumpBarAccessorySeparator@2x.png in Resources */, 214 | B05396D915884DAF004B4EB3 /* GCJumpBarSeparator@2x.png in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | B03A401C138C295C004677BE /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | B03A4031138C295D004677BE /* main.m in Sources */, 226 | B03A4038138C295D004677BE /* GCJumpBarDemoAppDelegate.m in Sources */, 227 | B03A406C138D6F3A004677BE /* NSIndexPath+GCJumpBar.m in Sources */, 228 | B03A406D138D6F3A004677BE /* NSMenu+IndexPath.m in Sources */, 229 | B03A406E138D6F3A004677BE /* GCJumpBar.m in Sources */, 230 | B03A406F138D6F3A004677BE /* GCJumpBarLabel.m in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXSourcesBuildPhase section */ 235 | 236 | /* Begin PBXVariantGroup section */ 237 | B03A4039138C295D004677BE /* MainMenu.xib */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | B03A403A138C295D004677BE /* en */, 241 | ); 242 | name = MainMenu.xib; 243 | sourceTree = ""; 244 | }; 245 | /* End PBXVariantGroup section */ 246 | 247 | /* Begin XCBuildConfiguration section */ 248 | B03A403C138C295D004677BE /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 253 | COPY_PHASE_STRIP = NO; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 263 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 265 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | MACOSX_DEPLOYMENT_TARGET = 10.7; 269 | ONLY_ACTIVE_ARCH = YES; 270 | SDKROOT = macosx; 271 | }; 272 | name = Debug; 273 | }; 274 | B03A403D138C295D004677BE /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 279 | COPY_PHASE_STRIP = YES; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | GCC_C_LANGUAGE_STANDARD = gnu99; 282 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 283 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 284 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 285 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | MACOSX_DEPLOYMENT_TARGET = 10.7; 289 | SDKROOT = macosx; 290 | }; 291 | name = Release; 292 | }; 293 | B03A403F138C295D004677BE /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 297 | GCC_PREFIX_HEADER = "GCJumpBarDemo/GCJumpBarDemo-Prefix.pch"; 298 | INFOPLIST_FILE = "GCJumpBarDemo/GCJumpBarDemo-Info.plist"; 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | WRAPPER_EXTENSION = app; 301 | }; 302 | name = Debug; 303 | }; 304 | B03A4040138C295D004677BE /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 308 | GCC_PREFIX_HEADER = "GCJumpBarDemo/GCJumpBarDemo-Prefix.pch"; 309 | INFOPLIST_FILE = "GCJumpBarDemo/GCJumpBarDemo-Info.plist"; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | WRAPPER_EXTENSION = app; 312 | }; 313 | name = Release; 314 | }; 315 | /* End XCBuildConfiguration section */ 316 | 317 | /* Begin XCConfigurationList section */ 318 | B03A401A138C295C004677BE /* Build configuration list for PBXProject "GCJumpBarDemo" */ = { 319 | isa = XCConfigurationList; 320 | buildConfigurations = ( 321 | B03A403C138C295D004677BE /* Debug */, 322 | B03A403D138C295D004677BE /* Release */, 323 | ); 324 | defaultConfigurationIsVisible = 0; 325 | defaultConfigurationName = Release; 326 | }; 327 | B03A403E138C295D004677BE /* Build configuration list for PBXNativeTarget "GCJumpBarDemo" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | B03A403F138C295D004677BE /* Debug */, 331 | B03A4040138C295D004677BE /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | /* End XCConfigurationList section */ 337 | }; 338 | rootObject = B03A4017138C295C004677BE /* Project object */; 339 | } 340 | -------------------------------------------------------------------------------- /GCJumpBarDemo/GCJumpBarDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.guillaumecampagna.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2011 LittleKiwi. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /GCJumpBarDemo/GCJumpBarDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'GCJumpBarDemo' target in the 'GCJumpBarDemo' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /GCJumpBarDemo/GCJumpBarDemoAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBarDemoAppDelegate.h 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GCJumpBar.h" 11 | 12 | @interface GCJumpBarDemoAppDelegate : NSObject { 13 | NSWindow *window; 14 | NSTextField *label; 15 | } 16 | 17 | @property (assign) IBOutlet NSWindow *window; 18 | @property (assign) IBOutlet NSTextField *label; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /GCJumpBarDemo/GCJumpBarDemoAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // GCJumpBarDemoAppDelegate.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. All rights reserved. 7 | // 8 | 9 | #import "GCJumpBarDemoAppDelegate.h" 10 | #import "NSIndexPath+GCJumpBar.h" 11 | 12 | @implementation GCJumpBarDemoAppDelegate 13 | 14 | @synthesize window = window; 15 | @synthesize label = label; 16 | 17 | - (void)jumpBar:(GCJumpBar *)jumpBar didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 18 | label.stringValue = [NSString stringWithFormat:@"Index path : %@", indexPath.stringRepresentation]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /GCJumpBarDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GCJumpBarDemo 4 | // 5 | // Created by Guillaume Campagna on 11-05-24. 6 | // Copyright 2011 LittleKiwi. 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 | -------------------------------------------------------------------------------- /GCJumpBarDemo/xcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBarDemo/xcode.png -------------------------------------------------------------------------------- /GCJumpBarSeparator.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcamp/GCJumpBar/7854dda07866e58dbad1f2477a77028bd99b0b9a/GCJumpBarSeparator.psd -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This control is dual licensed: 2 | 3 | You can use it for free under the MIT licence below or, if you require non-attribution you can purchase the commercial licence available at http://www.cocoacontrols.com/authors/gcamp 4 | 5 | --- 6 | 7 | Copyright (c) 2011 Guillaume Campagna 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. GCJumpBar 2 | 3 | @NSControl@ subclass that mimics the Xcode 4 Jump Bar. 4 | 5 | !http://littlekiwi.co.cc/GithubImages/GCJumpBarDemo.png! 6 | 7 | h2. Usage 8 | 9 | (See the demo project included) 10 | 11 | When you have added the @GCJumpBar@ folder in your project, simply allocate the Jump Bar and pass it a @NSMenu@. That's all. 12 | 13 | The Jump Bar can be used and its delegate and menu can be set in Interface Builder. 14 | 15 | You can set a delegate that will receive messages when the Jump Bar changes selection. 16 | 17 |
18 | - (void) jumpBar:(GCJumpBar*) jumpBar didSelectItemAtIndexPath:(NSIndexPath*) indexPath;
19 | 
20 | 21 | h2. Missing features 22 | 23 | @GCJumpBar@ doesn't have all the feature present in Xcode 4. The subset of feature present right now was what I needed. However, I will continue to work on it and you're welcome to fork it! --------------------------------------------------------------------------------