custom_button.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
19CustomDrawButtonBase::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
59CustomDrawButtonBase::~CustomDrawButtonBase() {
60}
61
62int CustomDrawButtonBase::Width() const {
63  return surfaces_[0]->Width();
64}
65
66int CustomDrawButtonBase::Height() const {
67  return surfaces_[0]->Height();
68}
69
70gboolean 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
127void 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
143void 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
159CairoCachedSurface* 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
171CustomDrawHoverController::CustomDrawHoverController(GtkWidget* widget)
172    : slide_animation_(this),
173      widget_(NULL) {
174  Init(widget);
175}
176
177CustomDrawHoverController::CustomDrawHoverController()
178    : slide_animation_(this),
179      widget_(NULL) {
180}
181
182CustomDrawHoverController::~CustomDrawHoverController() {
183}
184
185void 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
194void CustomDrawHoverController::AnimationProgressed(
195    const ui::Animation* animation) {
196  gtk_widget_queue_draw(widget_);
197}
198
199gboolean CustomDrawHoverController::OnEnter(
200    GtkWidget* widget,
201    GdkEventCrossing* event) {
202  slide_animation_.Show();
203  return FALSE;
204}
205
206gboolean 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
219CustomDrawButton::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
231CustomDrawButton::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
251CustomDrawButton::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
269CustomDrawButton::~CustomDrawButton() {
270  widget_.Destroy();
271  native_widget_.Destroy();
272}
273
274void 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
282void CustomDrawButton::Observe(NotificationType type,
283    const NotificationSource& source, const NotificationDetails& details) {
284  DCHECK(NotificationType::BROWSER_THEME_CHANGED == type);
285  SetBrowserTheme();
286}
287
288void 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
294void 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
300void CustomDrawButton::SetBackground(SkColor color,
301                                     SkBitmap* image, SkBitmap* mask) {
302  button_base_.SetBackground(color, image, mask);
303}
304
305gboolean 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
317CustomDrawButton* 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
324void 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
343bool CustomDrawButton::UseGtkTheme() {
344  return theme_service_ && theme_service_->UseGtkTheme();
345}
346