Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / custom_button.cc
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 gfx::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 }