1 // Copyright (c) 2012 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/ui/gtk/custom_button.h" 6 7 #include "base/basictypes.h" 8 #include "base/debug/trace_event.h" 9 #include "base/logging.h" 10 #include "chrome/browser/chrome_notification_types.h" 11 #include "chrome/browser/ui/gtk/gtk_chrome_button.h" 12 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 13 #include "chrome/browser/ui/gtk/gtk_util.h" 14 #include "content/public/browser/notification_source.h" 15 #include "grit/theme_resources.h" 16 #include "grit/ui_resources.h" 17 #include "third_party/skia/include/core/SkBitmap.h" 18 #include "ui/base/resource/resource_bundle.h" 19 #include "ui/gfx/gtk_util.h" 20 #include "ui/gfx/image/cairo_cached_surface.h" 21 #include "ui/gfx/image/image.h" 22 #include "ui/gfx/skbitmap_operations.h" 23 24 namespace { 25 26 GdkPixbuf* GetImage(int resource_id) { 27 if (!resource_id) 28 return NULL; 29 return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( 30 resource_id, ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf(); 31 } 32 33 } // namespace 34 35 CustomDrawButtonBase::CustomDrawButtonBase(GtkThemeService* theme_provider, 36 int normal_id, 37 int pressed_id, 38 int hover_id, 39 int disabled_id) 40 : paint_override_(-1), 41 normal_id_(normal_id), 42 pressed_id_(pressed_id), 43 hover_id_(hover_id), 44 disabled_id_(disabled_id), 45 theme_service_(theme_provider), 46 flipped_(false) { 47 for (int i = 0; i < (GTK_STATE_INSENSITIVE + 1); ++i) 48 surfaces_[i].reset(new gfx::CairoCachedSurface); 49 background_image_.reset(new gfx::CairoCachedSurface); 50 51 if (theme_provider) { 52 // Load images by pretending that we got a BROWSER_THEME_CHANGED 53 // notification. 54 theme_provider->InitThemesFor(this); 55 56 registrar_.Add(this, 57 chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 58 content::Source<ThemeService>(theme_provider)); 59 } else { 60 // Load the button images from the resource bundle. 61 surfaces_[GTK_STATE_NORMAL]->UsePixbuf(GetImage(normal_id_)); 62 surfaces_[GTK_STATE_ACTIVE]->UsePixbuf(GetImage(pressed_id_)); 63 surfaces_[GTK_STATE_PRELIGHT]->UsePixbuf(GetImage(hover_id_)); 64 surfaces_[GTK_STATE_SELECTED]->UsePixbuf(NULL); 65 surfaces_[GTK_STATE_INSENSITIVE]->UsePixbuf(GetImage(disabled_id_)); 66 } 67 } 68 69 CustomDrawButtonBase::~CustomDrawButtonBase() { 70 } 71 72 int CustomDrawButtonBase::Width() const { 73 return surfaces_[0]->Width(); 74 } 75 76 int CustomDrawButtonBase::Height() const { 77 return surfaces_[0]->Height(); 78 } 79 80 gboolean CustomDrawButtonBase::OnExpose(GtkWidget* widget, 81 GdkEventExpose* e, 82 gdouble hover_state) { 83 TRACE_EVENT0("ui::gtk", "CustomDrawButtonBase::OnExpose"); 84 int paint_state = paint_override_ >= 0 ? 85 paint_override_ : gtk_widget_get_state(widget); 86 87 // If the paint state is PRELIGHT then set it to NORMAL (we will paint the 88 // hover state according to |hover_state_|). 89 if (paint_state == GTK_STATE_PRELIGHT) 90 paint_state = GTK_STATE_NORMAL; 91 bool animating_hover = hover_state > 0.0 && 92 paint_state == GTK_STATE_NORMAL; 93 gfx::CairoCachedSurface* pixbuf = PixbufForState(paint_state); 94 gfx::CairoCachedSurface* hover_pixbuf = PixbufForState(GTK_STATE_PRELIGHT); 95 96 if (!pixbuf || !pixbuf->valid()) 97 return FALSE; 98 if (animating_hover && (!hover_pixbuf || !hover_pixbuf->valid())) 99 return FALSE; 100 101 cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE( 102 gtk_widget_get_window(widget))); 103 GtkAllocation allocation; 104 gtk_widget_get_allocation(widget, &allocation); 105 cairo_translate(cairo_context, allocation.x, allocation.y); 106 107 if (flipped_) { 108 // Horizontally flip the image for non-LTR/RTL reasons. 109 cairo_translate(cairo_context, allocation.width, 0.0f); 110 cairo_scale(cairo_context, -1.0f, 1.0f); 111 } 112 113 // The widget might be larger than the pixbuf. Paint the pixbuf flush with the 114 // start of the widget (left for LTR, right for RTL) and its bottom. 115 gfx::Rect bounds = gfx::Rect(0, 0, pixbuf->Width(), 0); 116 int x = gtk_util::MirroredLeftPointForRect(widget, bounds); 117 int y = allocation.height - pixbuf->Height(); 118 119 if (background_image_->valid()) { 120 background_image_->SetSource(cairo_context, widget, x, y); 121 cairo_paint(cairo_context); 122 } 123 124 pixbuf->SetSource(cairo_context, widget, x, y); 125 cairo_paint(cairo_context); 126 127 if (animating_hover) { 128 hover_pixbuf->SetSource(cairo_context, widget, x, y); 129 cairo_paint_with_alpha(cairo_context, hover_state); 130 } 131 132 cairo_destroy(cairo_context); 133 134 GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget)); 135 if (child) 136 gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e); 137 138 return TRUE; 139 } 140 141 void CustomDrawButtonBase::SetBackground(SkColor color, 142 const SkBitmap& image, 143 const SkBitmap& mask) { 144 if (image.isNull() || mask.isNull()) { 145 if (background_image_->valid()) { 146 background_image_->UsePixbuf(NULL); 147 } 148 } else { 149 SkBitmap img = 150 SkBitmapOperations::CreateButtonBackground(color, image, mask); 151 152 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(img); 153 background_image_->UsePixbuf(pixbuf); 154 g_object_unref(pixbuf); 155 } 156 } 157 158 void CustomDrawButtonBase::Observe(int type, 159 const content::NotificationSource& source, 160 const content::NotificationDetails& details) { 161 DCHECK(theme_service_); 162 DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED == type); 163 164 surfaces_[GTK_STATE_NORMAL]->UsePixbuf(normal_id_ ? 165 theme_service_->GetRTLEnabledPixbufNamed(normal_id_) : NULL); 166 surfaces_[GTK_STATE_ACTIVE]->UsePixbuf(pressed_id_ ? 167 theme_service_->GetRTLEnabledPixbufNamed(pressed_id_) : NULL); 168 surfaces_[GTK_STATE_PRELIGHT]->UsePixbuf(hover_id_ ? 169 theme_service_->GetRTLEnabledPixbufNamed(hover_id_) : NULL); 170 surfaces_[GTK_STATE_SELECTED]->UsePixbuf(NULL); 171 surfaces_[GTK_STATE_INSENSITIVE]->UsePixbuf(disabled_id_ ? 172 theme_service_->GetRTLEnabledPixbufNamed(disabled_id_) : NULL); 173 } 174 175 gfx::CairoCachedSurface* CustomDrawButtonBase::PixbufForState(int state) { 176 gfx::CairoCachedSurface* pixbuf = surfaces_[state].get(); 177 178 // Fall back to the default image if we don't have one for this state. 179 if (!pixbuf || !pixbuf->valid()) 180 pixbuf = surfaces_[GTK_STATE_NORMAL].get(); 181 182 return pixbuf; 183 } 184 185 // CustomDrawHoverController --------------------------------------------------- 186 187 CustomDrawHoverController::CustomDrawHoverController(GtkWidget* widget) 188 : slide_animation_(this), 189 widget_(NULL) { 190 Init(widget); 191 } 192 193 CustomDrawHoverController::CustomDrawHoverController() 194 : slide_animation_(this), 195 widget_(NULL) { 196 } 197 198 CustomDrawHoverController::~CustomDrawHoverController() { 199 } 200 201 void CustomDrawHoverController::Init(GtkWidget* widget) { 202 DCHECK(widget_ == NULL); 203 widget_ = widget; 204 g_signal_connect(widget_, "enter-notify-event", 205 G_CALLBACK(OnEnterThunk), this); 206 g_signal_connect(widget_, "leave-notify-event", 207 G_CALLBACK(OnLeaveThunk), this); 208 } 209 210 void CustomDrawHoverController::AnimationProgressed( 211 const ui::Animation* animation) { 212 gtk_widget_queue_draw(widget_); 213 } 214 215 gboolean CustomDrawHoverController::OnEnter( 216 GtkWidget* widget, 217 GdkEventCrossing* event) { 218 slide_animation_.Show(); 219 return FALSE; 220 } 221 222 gboolean CustomDrawHoverController::OnLeave( 223 GtkWidget* widget, 224 GdkEventCrossing* event) { 225 // When the user is holding a mouse button, we don't want to animate. 226 if (event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) 227 slide_animation_.Reset(); 228 else 229 slide_animation_.Hide(); 230 return FALSE; 231 } 232 233 // CustomDrawButton ------------------------------------------------------------ 234 235 CustomDrawButton::CustomDrawButton(int normal_id, 236 int pressed_id, 237 int hover_id, 238 int disabled_id) 239 : button_base_(NULL, normal_id, pressed_id, hover_id, disabled_id), 240 theme_service_(NULL), 241 forcing_chrome_theme_(false) { 242 Init(); 243 244 // Initialize the theme stuff with no theme_provider. 245 SetBrowserTheme(); 246 } 247 248 CustomDrawButton::CustomDrawButton(GtkThemeService* theme_provider, 249 int normal_id, 250 int pressed_id, 251 int hover_id, 252 int disabled_id, 253 const char* stock_id, 254 GtkIconSize stock_size) 255 : button_base_(theme_provider, normal_id, pressed_id, hover_id, 256 disabled_id), 257 theme_service_(theme_provider), 258 forcing_chrome_theme_(false) { 259 native_widget_.Own(gtk_image_new_from_stock(stock_id, stock_size)); 260 261 Init(); 262 263 theme_service_->InitThemesFor(this); 264 registrar_.Add(this, 265 chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 266 content::Source<ThemeService>(theme_provider)); 267 } 268 269 CustomDrawButton::CustomDrawButton(GtkThemeService* theme_provider, 270 int normal_id, 271 int pressed_id, 272 int hover_id, 273 int disabled_id, 274 GtkWidget* native_widget) 275 : button_base_(theme_provider, normal_id, pressed_id, hover_id, 276 disabled_id), 277 native_widget_(native_widget), 278 theme_service_(theme_provider), 279 forcing_chrome_theme_(false) { 280 Init(); 281 282 theme_service_->InitThemesFor(this); 283 registrar_.Add(this, 284 chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 285 content::Source<ThemeService>(theme_provider)); 286 } 287 288 CustomDrawButton::~CustomDrawButton() { 289 widget_.Destroy(); 290 native_widget_.Destroy(); 291 } 292 293 void CustomDrawButton::Init() { 294 widget_.Own(gtk_chrome_button_new()); 295 gtk_widget_set_can_focus(widget(), FALSE); 296 g_signal_connect(widget(), "expose-event", 297 G_CALLBACK(OnCustomExposeThunk), this); 298 hover_controller_.Init(widget()); 299 } 300 301 void CustomDrawButton::ForceChromeTheme() { 302 forcing_chrome_theme_ = true; 303 SetBrowserTheme(); 304 } 305 306 void CustomDrawButton::Observe(int type, 307 const content::NotificationSource& source, 308 const content::NotificationDetails& details) { 309 DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED == type); 310 SetBrowserTheme(); 311 } 312 313 GtkAllocation CustomDrawButton::WidgetAllocation() const { 314 GtkAllocation allocation; 315 gtk_widget_get_allocation(widget_.get(), &allocation); 316 return allocation; 317 } 318 319 int CustomDrawButton::SurfaceWidth() const { 320 return button_base_.Width(); 321 } 322 323 int CustomDrawButton::SurfaceHeight() const { 324 return button_base_.Height(); 325 } 326 327 void CustomDrawButton::SetPaintOverride(GtkStateType state) { 328 button_base_.set_paint_override(state); 329 gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget()), state); 330 gtk_widget_queue_draw(widget()); 331 } 332 333 void CustomDrawButton::UnsetPaintOverride() { 334 button_base_.set_paint_override(-1); 335 gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(widget())); 336 gtk_widget_queue_draw(widget()); 337 } 338 339 void CustomDrawButton::SetBackground(SkColor color, 340 const SkBitmap& image, 341 const SkBitmap& mask) { 342 button_base_.SetBackground(color, image, mask); 343 } 344 345 gboolean CustomDrawButton::OnCustomExpose(GtkWidget* sender, 346 GdkEventExpose* e) { 347 UNSHIPPED_TRACE_EVENT0("ui::gtk", "CustomDrawButtonBase::OnCustomExpose"); 348 if (UseGtkTheme()) { 349 // Continue processing this expose event. 350 return FALSE; 351 } else { 352 double hover_state = hover_controller_.GetCurrentValue(); 353 return button_base_.OnExpose(sender, e, hover_state); 354 } 355 } 356 357 // static 358 CustomDrawButton* CustomDrawButton::CloseButtonBar( 359 GtkThemeService* theme_provider) { 360 CustomDrawButton* button = new CustomDrawButton(theme_provider, 361 IDR_CLOSE_1, IDR_CLOSE_1_P, IDR_CLOSE_1_H, 0, 362 GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); 363 return button; 364 } 365 366 // static 367 CustomDrawButton* CustomDrawButton::CloseButtonBubble( 368 GtkThemeService* theme_provider) { 369 CustomDrawButton* button = new CustomDrawButton(theme_provider, 370 IDR_CLOSE_2, IDR_CLOSE_2_P, IDR_CLOSE_2_H, 0, 371 GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); 372 return button; 373 } 374 375 void CustomDrawButton::SetBrowserTheme() { 376 if (UseGtkTheme()) { 377 if (native_widget_.get()) 378 gtk_button_set_image(GTK_BUTTON(widget()), native_widget_.get()); 379 gtk_widget_set_size_request(widget(), -1, -1); 380 gtk_widget_set_app_paintable(widget(), FALSE); 381 } else { 382 if (native_widget_.get()) 383 gtk_button_set_image(GTK_BUTTON(widget()), NULL); 384 gtk_widget_set_size_request(widget(), button_base_.Width(), 385 button_base_.Height()); 386 387 gtk_widget_set_app_paintable(widget(), TRUE); 388 } 389 390 gtk_chrome_button_set_use_gtk_rendering( 391 GTK_CHROME_BUTTON(widget()), UseGtkTheme()); 392 } 393 394 bool CustomDrawButton::UseGtkTheme() { 395 return !forcing_chrome_theme_ && theme_service_ && 396 theme_service_->UsingNativeTheme(); 397 } 398