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