1 /* Copyright (c) 2006-2009 Christopher J. W. Lloyd <cjwl@objc.net>
3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
8 #import <AppKit/NSToolbarItem.h>
9 #import <AppKit/NSToolbar.h>
10 #import <AppKit/NSToolbarItemView.h>
11 #import <AppKit/NSMenuItem.h>
12 #import <AppKit/NSImage.h>
13 #import <AppKit/NSColor.h>
14 #import <AppKit/NSDocument.h>
15 #import <AppKit/NSStringDrawing.h>
16 #import <AppKit/NSStringDrawer.h>
17 #import <AppKit/NSApplication.h>
18 #import <AppKit/NSFontManager.h>
19 #import <AppKit/NSAttributedString.h>
20 #import <AppKit/NSRaise.h>
21 #import <AppKit/NSGraphicsContext.h>
23 NSString * const NSToolbarCustomizeToolbarItemIdentifier=@"NSToolbarCustomizeToolbarItem";
24 NSString * const NSToolbarFlexibleSpaceItemIdentifier=@"NSToolbarFlexibleSpaceItem";
25 NSString * const NSToolbarPrintItemIdentifier=@"NSToolbarPrintItem";
26 NSString * const NSToolbarSeparatorItemIdentifier=@"NSToolbarSeparatorItem";
27 NSString * const NSToolbarShowColorsItemIdentifier=@"NSToolbarShowColorsItem";
28 NSString * const NSToolbarShowFontsItemIdentifier=@"NSToolbarShowFontsItem";
29 NSString * const NSToolbarSpaceItemIdentifier=@"NSToolbarSpaceItem";
31 extern NSSize _NSToolbarSizeRegular;
32 extern NSSize _NSToolbarSizeSmall;
33 extern NSSize _NSToolbarIconSizeRegular;
34 extern NSSize _NSToolbarIconSizeSmall;
36 @interface NSToolbar(private)
38 -(void)itemSizeDidChange;
39 -(NSDictionary *)_labelAttributes;
40 -(NSDictionary *)_labelAttributesForSizeMode:(NSToolbarSizeMode)sizeMode;
43 @implementation NSToolbarItem
45 extern NSSize _NSToolbarIconSizeRegular;
46 extern NSSize _NSToolbarIconSizeSmall;
48 -(void)_configureAsStandardItemIfNeeded {
49 if ([_itemIdentifier isEqualToString:NSToolbarSeparatorItemIdentifier]){
53 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Separator", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Describes a toolbar separator item")];
56 size = [self minSize];
57 size.width = floor(size.width/2);
58 [self setMinSize:size];
59 size = [self maxSize];
60 size.width = floor(size.width/2);
61 [self setMaxSize:size];
64 else if ([_itemIdentifier isEqualToString:NSToolbarSpaceItemIdentifier]){
68 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Space", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Describes a toolbar space item")];
71 size = [self minSize];
73 [self setMinSize:size];
74 size = [self maxSize];
76 [self setMaxSize:size];
79 else if ([_itemIdentifier isEqualToString:NSToolbarFlexibleSpaceItemIdentifier]){
83 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Flexible Space", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Describes a toolbar flexible space item")];
86 size = [self minSize];
88 [self setMinSize:size];
89 [self setMaxSize:NSMakeSize(-1, [self maxSize].height)];
93 else if ([_itemIdentifier isEqualToString:NSToolbarShowColorsItemIdentifier]){
94 [self setLabel: NSLocalizedStringFromTableInBundle(@"Colors", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a color picker")];
95 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Show Colors", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a color picker")];
96 [self setTarget:[NSApplication sharedApplication]];
97 [self setAction:@selector(orderFrontColorPanel:)];
98 [self setImage:[NSImage imageNamed:NSToolbarShowColorsItemIdentifier]];
99 [self setToolTip: NSLocalizedStringFromTableInBundle(@"Show the Colors panel.", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a color picker")];
101 else if ([_itemIdentifier isEqualToString:NSToolbarShowFontsItemIdentifier]){
102 [self setLabel: NSLocalizedStringFromTableInBundle(@"Fonts", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a font picker")];
103 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Show Fonts", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a font picker")];
104 [self setTarget:[NSFontManager sharedFontManager]];
105 [self setAction:@selector(orderFrontFontPanel:)];
106 [self setImage:[NSImage imageNamed:NSToolbarShowFontsItemIdentifier]];
107 [self setToolTip: NSLocalizedStringFromTableInBundle(@"Show the Fonts panel.", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a font picker")];
109 else if ([_itemIdentifier isEqualToString:NSToolbarCustomizeToolbarItemIdentifier]){
110 [self setLabel: NSLocalizedStringFromTableInBundle(@"Customize", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a toolbar customization dialog")];
111 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Customize", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a toolbar customization dialog")];
112 [self setTarget:nil];
113 [self setAction:@selector(runToolbarCustomizationPalette:)];
114 [self setImage:[NSImage imageNamed:NSToolbarCustomizeToolbarItemIdentifier]];
115 [self setToolTip: NSLocalizedStringFromTableInBundle(@"Customize this toolbar.", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"Reveals a toolbar customization dialog")];
117 else if ([_itemIdentifier isEqualToString:NSToolbarPrintItemIdentifier]){
118 [self setLabel: NSLocalizedStringFromTableInBundle(@"Print", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"")];
119 [self setPaletteLabel: NSLocalizedStringFromTableInBundle(@"Print Document", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"")];
120 [self setTarget:nil];
121 [self setAction:@selector(printDocument:)];
122 [self setImage:[NSImage imageNamed:NSToolbarPrintItemIdentifier]];
123 [self setToolTip: NSLocalizedStringFromTableInBundle(@"Print this document.", nil, [NSBundle bundleForClass: [NSToolbarItem class]], @"")];
127 -initWithItemIdentifier:(NSString *)identifier {
128 _itemIdentifier=[identifier retain];
130 _enclosingView=[[NSToolbarItemView alloc] init];
131 [_enclosingView setToolbarItem:self];
137 _menuFormRepresentation=nil;
141 _visibilityPriority=NSToolbarItemVisibilityPriorityStandard;
144 [self _configureAsStandardItemIfNeeded];
150 [_itemIdentifier release];
154 [_paletteLabel release];
155 [_menuFormRepresentation release];
160 -copyWithZone:(NSZone *)zone {
161 // FIXME: copying views, ugh
162 NSToolbarItem *copy=NSCopyObject(self, 0, zone);
164 copy->_itemIdentifier=[_itemIdentifier copy];
166 copy->_image=[_image copy];
167 copy->_label=[_label copy];
168 copy->_paletteLabel=[_paletteLabel copy];
169 copy->_menuFormRepresentation=[_menuFormRepresentation copy];
170 copy->_view=[_view copy];
175 -(NSView *)_enclosingView {
176 return _enclosingView;
179 -(void)_setToolbar:(NSToolbar *)toolbar {
183 -(NSString *)itemIdentifier {
184 return _itemIdentifier;
187 -(NSToolbar *)toolbar {
195 -(NSString *)paletteLabel {
196 return _paletteLabel;
199 // By default, this method returns a singleton menu item with item label as the title. For standard items, the target, action is set.
200 - (NSMenuItem *)menuFormRepresentation
202 // FIX should update standard item for action/target/label changes?
203 if (_menuFormRepresentation == nil && [self label] != nil) {
204 _menuFormRepresentation = [[NSMenuItem alloc] initWithTitle:[self label] action:[self action] keyEquivalent:@""];
205 [_menuFormRepresentation setImage:[self image]];
206 [_menuFormRepresentation setTarget:[self target]];
207 [_menuFormRepresentation setRepresentedObject:self];
210 return _menuFormRepresentation;
225 -(NSInteger)visibilityPriority {
226 return _visibilityPriority;
229 -(BOOL)autovalidates {
230 return _autovalidates;
233 -(BOOL)allowsDuplicatesInToolbar {
238 // Changing a toolbar item doesn't change the height of the toolbar - so this call is not helping
239 // [_toolbar itemSizeDidChange];
241 [_enclosingView setNeedsDisplay:YES];
244 -(void)setLabel:(NSString *)label {
245 // does not forward to view
252 -(void)setPaletteLabel:(NSString *)label {
253 // does not forward to view
254 [_paletteLabel release];
255 _paletteLabel = [label retain];
259 -(void)setMenuFormRepresentation:(NSMenuItem *)menuItem {
260 // does not forward to view
261 menuItem=[menuItem retain];
262 [_menuFormRepresentation release];
263 _menuFormRepresentation=menuItem;
266 -(void)setView:(NSView *)view {
271 _minSize=[_view frame].size;
272 _maxSize=[_view frame].size;
274 [_enclosingView setSubview:_view];
278 -(void)setMinSize:(NSSize)size {
283 -(void)setMaxSize:(NSSize)size {
288 -(void)setVisibilityPriority:(NSInteger)value {
289 _visibilityPriority=value;
293 -(void)setAutovalidates:(BOOL)value {
294 _autovalidates=value;
298 /* The understanding is that NSToolbarItem only forwards enabled/tag/action/target/image setters and getters to the publicly settable view. The rest are managed internally.
302 if([_view respondsToSelector:@selector(image)])
303 return [(id)_view image];
309 if ([_view respondsToSelector:@selector(target)])
310 return [(id)_view target];
316 if ([_view respondsToSelector:@selector(action)])
317 return [(id)_view action];
323 if ([_view respondsToSelector:@selector(tag)])
324 return [(id)_view tag];
330 if([_view respondsToSelector:@selector(isEnabled)])
331 return [(id)_view isEnabled];
336 -(NSString *)toolTip {
340 -(void)setImage:(NSImage *)image {
341 image=[image retain];
345 if([_view respondsToSelector:@selector(setImage:)]) {
346 [(id)_view setImage:image];
351 -(void)setTarget:target {
354 if([_view respondsToSelector:@selector(setTarget:)])
355 [(id)_view setTarget:target];
358 -(void)setAction:(SEL)action {
361 if([_view respondsToSelector:@selector(setAction:)])
362 [(id)_view setAction:action];
365 -(void)setTag:(NSInteger)tag {
367 if ([_view respondsToSelector:@selector(setTag:)])
368 [(id)_view setTag:tag];
371 -(void)setEnabled:(BOOL)enabled {
373 if([_view respondsToSelector:@selector(setEnabled:)])
374 [(id)_view setEnabled:enabled];
378 -(void)setToolTip:(NSString *)tip {
388 id target=[NSApp targetForAction:[self action] to:[self target] from:nil];
390 if ([self action] == nil && [self view] != nil) {
391 // Views can be arbitrarily complex - so let's not try to figure out what they
392 // want. Apple docs say use a subclass to do more sophisticated validation in this case.
394 } else if ((target == nil) || ![target respondsToSelector:[self action]]) {
396 } else if ([target respondsToSelector:@selector(validateToolbarItem:)]) {
397 enabled = [target validateToolbarItem:self];
398 } else if ([target respondsToSelector:@selector(validateUserInterfaceItem:)]) { // New validation scheme
399 enabled = [target validateUserInterfaceItem:self];
403 if (enabled != [self isEnabled]) {
404 [self setEnabled:enabled];
408 -(NSSize)_labelSize {
409 switch ([_toolbar displayMode]) {
410 case NSToolbarDisplayModeIconOnly:
413 case NSToolbarDisplayModeLabelOnly:
414 case NSToolbarDisplayModeIconAndLabel:
415 case NSToolbarDisplayModeDefault:
417 return [_label sizeWithAttributes:[_toolbar _labelAttributesForSizeMode:[_toolbar sizeMode]]];
422 -(NSSize)sizeForSizeMode:(NSToolbarSizeMode)sizeMode displayMode:(NSToolbarDisplayMode)displayMode minSize:(NSSize)minSize maxSize:(NSSize)maxSize {
426 case NSToolbarSizeModeSmall:
427 result = _NSToolbarSizeSmall;
430 case NSToolbarSizeModeRegular:
431 case NSToolbarSizeModeDefault:
433 result = _NSToolbarSizeRegular;
437 if (minSize.width > 0 && result.width < minSize.width)
438 result.width = minSize.width;
439 if (minSize.height > 0 && result.height < minSize.height)
440 result.height = minSize.height;
441 if (maxSize.width > 0 && result.width > maxSize.width)
442 result.width = maxSize.width;
443 if (maxSize.height > 0 && result.height > maxSize.height)
444 result.height = maxSize.height;
446 NSSize labelSize=[_label sizeWithAttributes:[_toolbar _labelAttributesForSizeMode:sizeMode]];
447 labelSize.width+=8; // label margins
449 switch (displayMode) {
450 case NSToolbarDisplayModeIconOnly:
453 case NSToolbarDisplayModeLabelOnly:
454 result.height=labelSize.height;
455 if(result.width<labelSize.width)
456 result.width=labelSize.width;
459 case NSToolbarDisplayModeIconAndLabel:
460 case NSToolbarDisplayModeDefault:
462 result.height+=labelSize.height;
463 if(result.width<labelSize.width)
464 result.width=labelSize.width;
471 -(NSSize)sizeForSizeMode:(NSToolbarSizeMode)sizeMode displayMode:(NSToolbarDisplayMode)displayMode {
472 return [self sizeForSizeMode:sizeMode displayMode:displayMode minSize:_minSize maxSize:_maxSize];
475 -(NSSize)constrainedSize {
476 return [self sizeForSizeMode:[_toolbar sizeMode] displayMode:[_toolbar displayMode] minSize:_minSize maxSize:_maxSize];
479 -(void)_setItemViewFrame:(NSRect)rect {
480 [_enclosingView setFrame:rect];
483 -(CGFloat)_expandWidth:(CGFloat)width {
484 if([self view]!=nil){
485 return MIN(width,_maxSize.width);
488 if([_itemIdentifier isEqualToString:NSToolbarFlexibleSpaceItemIdentifier])
491 return [self constrainedSize].width;
494 -(void)drawInRect:(NSRect)bounds highlighted:(BOOL)highlighted {
496 if([_itemIdentifier isEqualToString:NSToolbarSeparatorItemIdentifier]){
497 bounds.origin.x = floor(bounds.origin.x + (bounds.size.width/2));
498 bounds.size.width = 1;
499 [[NSColor blackColor] set];
500 NSDottedFrameRect(bounds);
504 CGFloat labelHeight=0;
508 if([_toolbar displayMode]!=NSToolbarDisplayModeIconOnly){
509 NSMutableDictionary *attributes=[NSMutableDictionary dictionaryWithDictionary:[_toolbar _labelAttributes]];
510 NSColor *color=[self isEnabled]?[NSColor controlTextColor]:[NSColor disabledControlTextColor];
512 [attributes setObject:color forKey:NSForegroundColorAttributeName];
515 labelRect.size=[_label sizeWithAttributes:attributes];
516 labelRect.origin.x=floor((bounds.size.width-labelRect.size.width)/2);
517 labelRect.origin.y=bounds.origin.y;
520 NSMutableDictionary *shadowAttributes=[[attributes mutableCopy] autorelease];
521 NSRect shadowRect=labelRect;
523 [shadowAttributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
525 shadowRect.origin.y--;
527 [_label _clipAndDrawInRect:shadowRect withAttributes:shadowAttributes];
530 [_label _clipAndDrawInRect:labelRect withAttributes:attributes];
531 labelHeight=labelRect.size.height;
532 labelHeight+=padding;
535 if([_toolbar displayMode]!=NSToolbarDisplayModeLabelOnly){
536 if([self view]==nil){
537 NSImage *image=[self image];
540 if([_toolbar sizeMode]==NSToolbarSizeModeSmall)
541 imageRect.size=_NSToolbarIconSizeSmall;
543 imageRect.size=_NSToolbarIconSizeRegular;
545 imageRect.origin.y=bounds.origin.y+labelHeight;
546 imageRect.origin.x=bounds.origin.x+floor((bounds.size.width-imageRect.size.width)/2);
547 CGContextRef ctx = NULL;
548 if ([self isEnabled] == NO) {
549 ctx = [[NSGraphicsContext currentContext] graphicsPort];
550 CGContextClipToRect(ctx, imageRect);
551 CGContextBeginTransparencyLayer(ctx, NULL);
553 [image drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:highlighted?0.5:1.0];
554 if ([self isEnabled] == NO) {
555 [[NSColor colorWithCalibratedWhite:0.0 alpha:0.33] set];
556 NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop);
558 CGContextEndTransparencyLayer(ctx);
563 -(NSString *)description {
564 return [NSString stringWithFormat:@"<%@[0x%lx] %@ label: \"%@\" image: %@ view: %@>",
565 [self class], self, _itemIdentifier, _label, [self image], _view];
568 -initWithCoder:(NSCoder *)coder {
569 if(![coder allowsKeyedCoding])
570 NSUnimplementedMethod();
572 _itemIdentifier=[[coder decodeObjectForKey:@"NSToolbarItemIdentifier"] retain];
574 _enclosingView=[[NSToolbarItemView alloc] init];
575 [_enclosingView setToolbarItem:self];
576 [self setView:[coder decodeObjectForKey:@"NSToolbarItemView"]];
577 [self setTarget:[coder decodeObjectForKey:@"NSToolbarItemTarget"]];
578 [self setAction:NSSelectorFromString([coder decodeObjectForKey:@"NSToolbarItemAction"])];
580 [self setImage:[coder decodeObjectForKey:@"NSToolbarItemImage"]];
581 [self setLabel:[coder decodeObjectForKey:@"NSToolbarItemLabel"]];
582 [self setPaletteLabel:[coder decodeObjectForKey:@"NSToolbarItemPaletteLabel"]];
584 _maxSize=[coder decodeSizeForKey:@"NSToolbarItemMaxSize"];
585 _minSize=[coder decodeSizeForKey:@"NSToolbarItemMinSize"];
586 [self setEnabled:[coder decodeBoolForKey:@"NSToolbarItemEnabled"]];
587 [self setTag:[coder decodeIntForKey:@"NSToolbarItemTag"]];
589 [self setAutovalidates:[coder decodeBoolForKey:@"NSToolbarItemAutovalidates"]];
590 [self setToolTip:[coder decodeObjectForKey:@"NSToolbarItemToolTip"]];
591 [self setVisibilityPriority:[coder decodeIntForKey:@"NSToolbarItemVisibilityPriority"]];
594 NSToolbarIsUserRemovable = 1;
596 [self _configureAsStandardItemIfNeeded];
603 @interface NSToolbarSpaceItem : NSToolbarItem
606 @implementation NSToolbarSpaceItem
610 @interface NSToolbarFlexibleSpaceItem : NSToolbarItem
613 @implementation NSToolbarFlexibleSpaceItem
616 @interface NSToolbarSeparatorItem : NSToolbarItem
619 @implementation NSToolbarSeparatorItem