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