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