• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CustomDrawButtonBase(GtkThemeService * theme_provider,int normal_id,int pressed_id,int hover_id,int disabled_id)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 
~CustomDrawButtonBase()59 CustomDrawButtonBase::~CustomDrawButtonBase() {
60 }
61 
Width() const62 int CustomDrawButtonBase::Width() const {
63   return surfaces_[0]->Width();
64 }
65 
Height() const66 int CustomDrawButtonBase::Height() const {
67   return surfaces_[0]->Height();
68 }
69 
OnExpose(GtkWidget * widget,GdkEventExpose * e,gdouble hover_state)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 
SetBackground(SkColor color,SkBitmap * image,SkBitmap * mask)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
PixbufForState(int state)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 
CustomDrawHoverController(GtkWidget * widget)171 CustomDrawHoverController::CustomDrawHoverController(GtkWidget* widget)
172     : slide_animation_(this),
173       widget_(NULL) {
174   Init(widget);
175 }
176 
CustomDrawHoverController()177 CustomDrawHoverController::CustomDrawHoverController()
178     : slide_animation_(this),
179       widget_(NULL) {
180 }
181 
~CustomDrawHoverController()182 CustomDrawHoverController::~CustomDrawHoverController() {
183 }
184 
Init(GtkWidget * widget)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 
AnimationProgressed(const ui::Animation * animation)194 void CustomDrawHoverController::AnimationProgressed(
195     const ui::Animation* animation) {
196   gtk_widget_queue_draw(widget_);
197 }
198 
OnEnter(GtkWidget * widget,GdkEventCrossing * event)199 gboolean CustomDrawHoverController::OnEnter(
200     GtkWidget* widget,
201     GdkEventCrossing* event) {
202   slide_animation_.Show();
203   return FALSE;
204 }
205 
OnLeave(GtkWidget * widget,GdkEventCrossing * event)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 
CustomDrawButton(int normal_id,int pressed_id,int hover_id,int disabled_id)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 
CustomDrawButton(GtkThemeService * theme_provider,int normal_id,int pressed_id,int hover_id,int disabled_id,const char * stock_id,GtkIconSize stock_size)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 
CustomDrawButton(GtkThemeService * theme_provider,int normal_id,int pressed_id,int hover_id,int disabled_id,GtkWidget * native_widget)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 
~CustomDrawButton()269 CustomDrawButton::~CustomDrawButton() {
270   widget_.Destroy();
271   native_widget_.Destroy();
272 }
273 
Init()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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)282 void CustomDrawButton::Observe(NotificationType type,
283     const NotificationSource& source, const NotificationDetails& details) {
284   DCHECK(NotificationType::BROWSER_THEME_CHANGED == type);
285   SetBrowserTheme();
286 }
287 
SetPaintOverride(GtkStateType state)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 
UnsetPaintOverride()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 
SetBackground(SkColor color,SkBitmap * image,SkBitmap * mask)300 void CustomDrawButton::SetBackground(SkColor color,
301                                      SkBitmap* image, SkBitmap* mask) {
302   button_base_.SetBackground(color, image, mask);
303 }
304 
OnCustomExpose(GtkWidget * sender,GdkEventExpose * e)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
CloseButton(GtkThemeService * theme_provider)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 
SetBrowserTheme()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 
UseGtkTheme()343 bool CustomDrawButton::UseGtkTheme() {
344   return theme_service_ && theme_service_->UseGtkTheme();
345 }
346