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#include "chrome/browser/themes/theme_service.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include "base/logging.h" 10#include "chrome/browser/themes/browser_theme_pack.h" 11#include "chrome/browser/themes/theme_properties.h" 12#include "skia/ext/skia_utils_mac.h" 13#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h" 14#include "ui/base/resource/resource_bundle.h" 15#include "ui/gfx/color_utils.h" 16#include "ui/gfx/image/image.h" 17#include "skia/ext/skia_utils_mac.h" 18 19NSString* const kBrowserThemeDidChangeNotification = 20 @"BrowserThemeDidChangeNotification"; 21 22typedef ThemeProperties Properties; 23 24namespace { 25 26void HSLToHSB(const color_utils::HSL& hsl, CGFloat* h, CGFloat* s, CGFloat* b) { 27 SkColor color = color_utils::HSLToSkColor(hsl, 255); // alpha doesn't matter 28 SkScalar hsv[3]; 29 SkColorToHSV(color, hsv); 30 31 *h = SkScalarToDouble(hsv[0]) / 360.0; 32 *s = SkScalarToDouble(hsv[1]); 33 *b = SkScalarToDouble(hsv[2]); 34} 35 36} // namespace 37 38NSImage* ThemeService::GetNSImageNamed(int id) const { 39 DCHECK(CalledOnValidThread()); 40 41 // Check to see if we already have the image in the cache. 42 NSImageMap::const_iterator nsimage_iter = nsimage_cache_.find(id); 43 if (nsimage_iter != nsimage_cache_.end()) 44 return nsimage_iter->second; 45 46 // Why don't we load the file directly into the image instead of the whole 47 // gfx::Image > native conversion? 48 // - For consistency with other platforms. 49 // - To get the generated tinted images. 50 NSImage* nsimage = nil; 51 if (theme_supplier_.get()) { 52 gfx::Image image = theme_supplier_->GetImageNamed(id); 53 if (!image.IsEmpty()) 54 nsimage = image.ToNSImage(); 55 } 56 57 // If the theme didn't override this image then load it from the resource 58 // bundle. 59 if (!nsimage) { 60 nsimage = rb_.GetNativeImageNamed(id).ToNSImage(); 61 } 62 63 // We loaded successfully. Cache the image. 64 if (nsimage) { 65 nsimage_cache_[id] = [nsimage retain]; 66 return nsimage; 67 } 68 69 // We failed to retrieve the bitmap, show a debugging red square. 70 LOG(WARNING) << "Unable to load NSImage with id " << id; 71 NOTREACHED(); // Want to assert in debug mode. 72 73 static NSImage* empty_image = NULL; 74 if (!empty_image) { 75 // The placeholder image is bright red so people notice the problem. This 76 // image will be leaked, but this code should never be hit. 77 NSRect image_rect = NSMakeRect(0, 0, 32, 32); 78 empty_image = [[NSImage alloc] initWithSize:image_rect.size]; 79 [empty_image lockFocus]; 80 [[NSColor redColor] set]; 81 NSRectFill(image_rect); 82 [empty_image unlockFocus]; 83 } 84 85 return empty_image; 86} 87 88NSColor* ThemeService::GetNSImageColorNamed(int id) const { 89 DCHECK(CalledOnValidThread()); 90 91 // Check to see if we already have the color in the cache. 92 NSColorMap::const_iterator nscolor_iter = nscolor_cache_.find(id); 93 if (nscolor_iter != nscolor_cache_.end()) 94 return nscolor_iter->second; 95 96 NSImage* image = GetNSImageNamed(id); 97 if (!image) 98 return nil; 99 NSColor* image_color = [NSColor colorWithPatternImage:image]; 100 101 // We loaded successfully. Cache the color. 102 if (image_color) 103 nscolor_cache_[id] = [image_color retain]; 104 105 return image_color; 106} 107 108NSColor* ThemeService::GetNSColor(int id) const { 109 DCHECK(CalledOnValidThread()); 110 111 // Check to see if we already have the color in the cache. 112 NSColorMap::const_iterator nscolor_iter = nscolor_cache_.find(id); 113 if (nscolor_iter != nscolor_cache_.end()) 114 return nscolor_iter->second; 115 116 SkColor sk_color = GetColor(id); 117 NSColor* color = gfx::SkColorToCalibratedNSColor(sk_color); 118 119 // We loaded successfully. Cache the color. 120 if (color) 121 nscolor_cache_[id] = [color retain]; 122 123 return color; 124} 125 126NSColor* ThemeService::GetNSColorTint(int id) const { 127 DCHECK(CalledOnValidThread()); 128 129 // Check to see if we already have the color in the cache. 130 NSColorMap::const_iterator nscolor_iter = nscolor_cache_.find(id); 131 if (nscolor_iter != nscolor_cache_.end()) 132 return nscolor_iter->second; 133 134 color_utils::HSL tint = GetTint(id); 135 NSColor* tint_color = nil; 136 if (tint.h == -1 && tint.s == -1 && tint.l == -1) { 137 tint_color = [NSColor blackColor]; 138 } else { 139 CGFloat hue, saturation, brightness; 140 HSLToHSB(tint, &hue, &saturation, &brightness); 141 142 tint_color = [NSColor colorWithCalibratedHue:hue 143 saturation:saturation 144 brightness:brightness 145 alpha:1.0]; 146 } 147 148 // We loaded successfully. Cache the color. 149 if (tint_color) 150 nscolor_cache_[id] = [tint_color retain]; 151 152 return tint_color; 153} 154 155NSGradient* ThemeService::GetNSGradient(int id) const { 156 DCHECK(CalledOnValidThread()); 157 158 // Check to see if we already have the gradient in the cache. 159 NSGradientMap::const_iterator nsgradient_iter = nsgradient_cache_.find(id); 160 if (nsgradient_iter != nsgradient_cache_.end()) 161 return nsgradient_iter->second; 162 163 NSGradient* gradient = nil; 164 165 // Note that we are not leaking when we assign a retained object to 166 // |gradient|; in all cases we cache it before we return. 167 switch (id) { 168 case Properties::GRADIENT_FRAME_INCOGNITO: 169 case Properties::GRADIENT_FRAME_INCOGNITO_INACTIVE: { 170 // TODO(avi): can we simplify this? 171 BOOL active = id == Properties::GRADIENT_FRAME_INCOGNITO; 172 NSColor* base_color = [NSColor colorWithCalibratedRed:83/255.0 173 green:108.0/255.0 174 blue:140/255.0 175 alpha:1.0]; 176 177 NSColor *start_color = 178 [base_color gtm_colorAdjustedFor:GTMColorationBaseMidtone 179 faded:!active]; 180 NSColor *end_color = 181 [base_color gtm_colorAdjustedFor:GTMColorationBaseShadow 182 faded:!active]; 183 184 if (!active) { 185 start_color = [start_color gtm_colorByAdjustingLuminance:0.1 186 saturation:0.5]; 187 end_color = [end_color gtm_colorByAdjustingLuminance:0.1 188 saturation:0.5]; 189 } 190 191 gradient = [[NSGradient alloc] initWithStartingColor:start_color 192 endingColor:end_color]; 193 break; 194 } 195 196 case Properties::GRADIENT_TOOLBAR: 197 case Properties::GRADIENT_TOOLBAR_INACTIVE: { 198 NSColor* base_color = [NSColor colorWithCalibratedWhite:0.2 alpha:1.0]; 199 BOOL faded = (id == Properties::GRADIENT_TOOLBAR_INACTIVE ) || 200 (id == Properties::GRADIENT_TOOLBAR_BUTTON_INACTIVE); 201 NSColor* start_color = 202 [base_color gtm_colorAdjustedFor:GTMColorationLightHighlight 203 faded:faded]; 204 NSColor* mid_color = 205 [base_color gtm_colorAdjustedFor:GTMColorationLightMidtone 206 faded:faded]; 207 NSColor* end_color = 208 [base_color gtm_colorAdjustedFor:GTMColorationLightShadow 209 faded:faded]; 210 NSColor* glow_color = 211 [base_color gtm_colorAdjustedFor:GTMColorationLightPenumbra 212 faded:faded]; 213 214 gradient = 215 [[NSGradient alloc] initWithColorsAndLocations:start_color, 0.0, 216 mid_color, 0.25, 217 end_color, 0.5, 218 glow_color, 0.75, 219 nil]; 220 break; 221 } 222 223 case Properties::GRADIENT_TOOLBAR_BUTTON: 224 case Properties::GRADIENT_TOOLBAR_BUTTON_INACTIVE: { 225 NSColor* start_color = [NSColor colorWithCalibratedWhite:1.0 alpha:0.0]; 226 NSColor* end_color = [NSColor colorWithCalibratedWhite:1.0 alpha:0.3]; 227 gradient = [[NSGradient alloc] initWithStartingColor:start_color 228 endingColor:end_color]; 229 break; 230 } 231 case Properties::GRADIENT_TOOLBAR_BUTTON_PRESSED: 232 case Properties::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE: { 233 NSColor* base_color = [NSColor colorWithCalibratedWhite:0.5 alpha:1.0]; 234 BOOL faded = id == Properties::GRADIENT_TOOLBAR_BUTTON_PRESSED_INACTIVE; 235 NSColor* start_color = 236 [base_color gtm_colorAdjustedFor:GTMColorationBaseShadow 237 faded:faded]; 238 NSColor* end_color = 239 [base_color gtm_colorAdjustedFor:GTMColorationBaseMidtone 240 faded:faded]; 241 242 gradient = [[NSGradient alloc] initWithStartingColor:start_color 243 endingColor:end_color]; 244 break; 245 } 246 default: 247 LOG(WARNING) << "Gradient request with unknown id " << id; 248 NOTREACHED(); // Want to assert in debug mode. 249 break; 250 } 251 252 // We loaded successfully. Cache the gradient. 253 if (gradient) 254 nsgradient_cache_[id] = gradient; // created retained 255 256 return gradient; 257} 258 259// Let all the browser views know that themes have changed in a platform way. 260void ThemeService::NotifyPlatformThemeChanged() { 261 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; 262 [defaultCenter postNotificationName:kBrowserThemeDidChangeNotification 263 object:[NSValue valueWithPointer:this]]; 264} 265 266void ThemeService::FreePlatformCaches() { 267 DCHECK(CalledOnValidThread()); 268 269 // Free images. 270 for (NSImageMap::iterator i = nsimage_cache_.begin(); 271 i != nsimage_cache_.end(); i++) { 272 [i->second release]; 273 } 274 nsimage_cache_.clear(); 275 276 // Free colors. 277 for (NSColorMap::iterator i = nscolor_cache_.begin(); 278 i != nscolor_cache_.end(); i++) { 279 [i->second release]; 280 } 281 nscolor_cache_.clear(); 282 283 // Free gradients. 284 for (NSGradientMap::iterator i = nsgradient_cache_.begin(); 285 i != nsgradient_cache_.end(); i++) { 286 [i->second release]; 287 } 288 nsgradient_cache_.clear(); 289} 290