1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/image_button_cell.h" 6 7 #include "base/logging.h" 8 #import "chrome/browser/themes/theme_service.h" 9 #import "chrome/browser/ui/cocoa/rect_path_utils.h" 10 #import "chrome/browser/ui/cocoa/themed_window.h" 11 #import "ui/base/cocoa/nsview_additions.h" 12 #include "ui/gfx/image/image.h" 13 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 14 15 // When the window doesn't have focus then we want to draw the button with a 16 // slightly lighter color. We do this by just reducing the alpha. 17 const CGFloat kImageNoFocusAlpha = 0.65; 18 19 @interface ImageButtonCell (Private) 20 - (void)sharedInit; 21 - (image_button_cell::ButtonState)currentButtonState; 22 - (NSImage*)imageForID:(NSInteger)imageID 23 controlView:(NSView*)controlView; 24 @end 25 26 @implementation ImageButtonCell 27 28 @synthesize isMouseInside = isMouseInside_; 29 30 // For nib instantiations 31 - (id)initWithCoder:(NSCoder*)decoder { 32 if ((self = [super initWithCoder:decoder])) { 33 [self sharedInit]; 34 } 35 return self; 36 } 37 38 // For programmatic instantiations 39 - (id)initTextCell:(NSString*)string { 40 if ((self = [super initTextCell:string])) { 41 [self sharedInit]; 42 } 43 return self; 44 } 45 46 - (void)sharedInit { 47 [self setHighlightsBy:NSNoCellMask]; 48 49 // We need to set this so that we can override |-mouseEntered:| and 50 // |-mouseExited:| to change the button image on hover states. 51 [self setShowsBorderOnlyWhileMouseInside:YES]; 52 } 53 54 - (NSImage*)imageForState:(image_button_cell::ButtonState)state 55 view:(NSView*)controlView{ 56 if (image_[state].imageId) 57 return [self imageForID:image_[state].imageId controlView:controlView]; 58 return image_[state].image; 59 } 60 61 - (void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 62 image_button_cell::ButtonState state = [self currentButtonState]; 63 BOOL windowHasFocus = [[controlView window] isMainWindow] || 64 [[controlView window] isKeyWindow]; 65 CGFloat alpha = [self imageAlphaForWindowState:[controlView window]]; 66 NSImage* image = [self imageForState:state view:controlView]; 67 68 if (!windowHasFocus) { 69 NSImage* defaultImage = [self 70 imageForState:image_button_cell::kDefaultStateBackground 71 view:controlView]; 72 NSImage* hoverImage = [self 73 imageForState:image_button_cell::kHoverStateBackground 74 view:controlView]; 75 if ([self currentButtonState] == image_button_cell::kDefaultState && 76 defaultImage) { 77 image = defaultImage; 78 alpha = 1.0; 79 } else if ([self currentButtonState] == image_button_cell::kHoverState && 80 hoverImage) { 81 image = hoverImage; 82 alpha = 1.0; 83 } 84 } 85 86 NSRect imageRect; 87 imageRect.size = [image size]; 88 imageRect.origin.x = cellFrame.origin.x + 89 roundf((NSWidth(cellFrame) - NSWidth(imageRect)) / 2.0); 90 imageRect.origin.y = cellFrame.origin.y + 91 roundf((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); 92 93 [image drawInRect:imageRect 94 fromRect:NSZeroRect 95 operation:NSCompositeSourceOver 96 fraction:alpha 97 respectFlipped:YES 98 hints:nil]; 99 } 100 101 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 102 [self drawImageWithFrame:cellFrame inView:controlView]; 103 // Only draw custom focus ring if the 10.7 focus ring APIs are not available. 104 // TODO(groby): Remove once we build against the 10.7 SDK. 105 if (![self respondsToSelector:@selector(drawFocusRingMaskWithFrame:inView:)]) 106 [self drawFocusRingWithFrame:cellFrame inView:controlView]; 107 } 108 109 - (void)setImageID:(NSInteger)imageID 110 forButtonState:(image_button_cell::ButtonState)state { 111 DCHECK_GE(state, 0); 112 DCHECK_LT(state, image_button_cell::kButtonStateCount); 113 114 image_[state].image.reset(); 115 image_[state].imageId = imageID; 116 [[self controlView] setNeedsDisplay:YES]; 117 } 118 119 // Sets the image for the given button state using an image. 120 - (void)setImage:(NSImage*)image 121 forButtonState:(image_button_cell::ButtonState)state { 122 DCHECK_GE(state, 0); 123 DCHECK_LT(state, image_button_cell::kButtonStateCount); 124 125 image_[state].image.reset([image retain]); 126 image_[state].imageId = 0; 127 [[self controlView] setNeedsDisplay:YES]; 128 } 129 130 - (CGFloat)imageAlphaForWindowState:(NSWindow*)window { 131 BOOL windowHasFocus = [window isMainWindow] || [window isKeyWindow]; 132 return windowHasFocus ? 1.0 : kImageNoFocusAlpha; 133 } 134 135 - (void)drawFocusRingWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 136 if (![self showsFirstResponder]) 137 return; 138 gfx::ScopedNSGraphicsContextSaveGState scoped_state; 139 const CGFloat lineWidth = [controlView cr_lineWidth]; 140 rect_path_utils::FrameRectWithInset(rect_path_utils::RoundedCornerAll, 141 NSInsetRect(cellFrame, 0, lineWidth), 142 0.0, // insetX 143 0.0, // insetY 144 3.0, // outerRadius 145 lineWidth * 2, // lineWidth 146 [controlView 147 cr_keyboardFocusIndicatorColor]); 148 } 149 150 - (image_button_cell::ButtonState)currentButtonState { 151 bool (^has)(image_button_cell::ButtonState) = 152 ^(image_button_cell::ButtonState state) { 153 return image_[state].image || image_[state].imageId; 154 }; 155 if (![self isEnabled] && has(image_button_cell::kDisabledState)) 156 return image_button_cell::kDisabledState; 157 if ([self isHighlighted] && has(image_button_cell::kPressedState)) 158 return image_button_cell::kPressedState; 159 if ([self isMouseInside] && has(image_button_cell::kHoverState)) 160 return image_button_cell::kHoverState; 161 return image_button_cell::kDefaultState; 162 } 163 164 - (NSImage*)imageForID:(NSInteger)imageID 165 controlView:(NSView*)controlView { 166 if (!imageID) 167 return nil; 168 169 ui::ThemeProvider* themeProvider = [[controlView window] themeProvider]; 170 if (!themeProvider) 171 return nil; 172 173 return themeProvider->GetNSImageNamed(imageID); 174 } 175 176 - (void)setIsMouseInside:(BOOL)isMouseInside { 177 if (isMouseInside_ != isMouseInside) { 178 isMouseInside_ = isMouseInside; 179 NSView<ImageButton>* control = 180 static_cast<NSView<ImageButton>*>([self controlView]); 181 if ([control respondsToSelector:@selector(mouseInsideStateDidChange:)]) { 182 [control mouseInsideStateDidChange:isMouseInside]; 183 } 184 [control setNeedsDisplay:YES]; 185 } 186 } 187 188 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)show { 189 VLOG_IF(1, !show) << "setShowsBorderOnlyWhileMouseInside:NO ignored"; 190 } 191 192 - (BOOL)showsBorderOnlyWhileMouseInside { 193 // Always returns YES so that buttons always get mouse tracking even when 194 // disabled. The reload button (and possibly others) depend on this. 195 return YES; 196 } 197 198 - (void)mouseEntered:(NSEvent*)theEvent { 199 [self setIsMouseInside:YES]; 200 } 201 202 - (void)mouseExited:(NSEvent*)theEvent { 203 [self setIsMouseInside:NO]; 204 } 205 206 @end 207