summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/gtk/custom_button.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/gtk/custom_button.cc')
-rw-r--r--chrome/browser/ui/gtk/custom_button.cc345
1 files changed, 345 insertions, 0 deletions
diff --git a/chrome/browser/ui/gtk/custom_button.cc b/chrome/browser/ui/gtk/custom_button.cc
new file mode 100644
index 0000000..31d1cf6
--- /dev/null
+++ b/chrome/browser/ui/gtk/custom_button.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/custom_button.h"
+
+#include "app/resource_bundle.h"
+#include "base/basictypes.h"
+#include "chrome/browser/gtk/cairo_cached_surface.h"
+#include "chrome/browser/gtk/gtk_chrome_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/common/notification_service.h"
+#include "gfx/gtk_util.h"
+#include "gfx/skbitmap_operations.h"
+#include "grit/theme_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+CustomDrawButtonBase::CustomDrawButtonBase(GtkThemeProvider* theme_provider,
+ int normal_id,
+ int pressed_id,
+ int hover_id,
+ int disabled_id)
+ : background_image_(NULL),
+ paint_override_(-1),
+ normal_id_(normal_id),
+ pressed_id_(pressed_id),
+ hover_id_(hover_id),
+ disabled_id_(disabled_id),
+ theme_provider_(theme_provider),
+ flipped_(false) {
+ for (int i = 0; i < (GTK_STATE_INSENSITIVE + 1); ++i)
+ surfaces_[i].reset(new CairoCachedSurface);
+ background_image_.reset(new CairoCachedSurface);
+
+ if (theme_provider) {
+ // Load images by pretending that we got a BROWSER_THEME_CHANGED
+ // notification.
+ theme_provider->InitThemesFor(this);
+
+ registrar_.Add(this,
+ NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+ } else {
+ // Load the button images from the resource bundle.
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ surfaces_[GTK_STATE_NORMAL]->UsePixbuf(
+ normal_id_ ? rb.GetRTLEnabledPixbufNamed(normal_id_) : NULL);
+ surfaces_[GTK_STATE_ACTIVE]->UsePixbuf(
+ pressed_id_ ? rb.GetRTLEnabledPixbufNamed(pressed_id_) : NULL);
+ surfaces_[GTK_STATE_PRELIGHT]->UsePixbuf(
+ hover_id_ ? rb.GetRTLEnabledPixbufNamed(hover_id_) : NULL);
+ surfaces_[GTK_STATE_SELECTED]->UsePixbuf(NULL);
+ surfaces_[GTK_STATE_INSENSITIVE]->UsePixbuf(
+ disabled_id_ ? rb.GetRTLEnabledPixbufNamed(disabled_id_) : NULL);
+ }
+}
+
+CustomDrawButtonBase::~CustomDrawButtonBase() {
+}
+
+int CustomDrawButtonBase::Width() const {
+ return surfaces_[0]->Width();
+}
+
+int CustomDrawButtonBase::Height() const {
+ return surfaces_[0]->Height();
+}
+
+gboolean CustomDrawButtonBase::OnExpose(GtkWidget* widget,
+ GdkEventExpose* e,
+ gdouble hover_state) {
+ int paint_state = paint_override_ >= 0 ?
+ paint_override_ : GTK_WIDGET_STATE(widget);
+
+ // If the paint state is PRELIGHT then set it to NORMAL (we will paint the
+ // hover state according to |hover_state_|).
+ if (paint_state == GTK_STATE_PRELIGHT)
+ paint_state = GTK_STATE_NORMAL;
+ bool animating_hover = hover_state > 0.0 &&
+ paint_state == GTK_STATE_NORMAL;
+ CairoCachedSurface* pixbuf = PixbufForState(paint_state);
+ CairoCachedSurface* hover_pixbuf = PixbufForState(GTK_STATE_PRELIGHT);
+
+ if (!pixbuf || !pixbuf->valid())
+ return FALSE;
+ if (animating_hover && (!hover_pixbuf || !hover_pixbuf->valid()))
+ return FALSE;
+
+ cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(widget->window));
+ cairo_translate(cairo_context, widget->allocation.x, widget->allocation.y);
+
+ if (flipped_) {
+ // Horizontally flip the image for non-LTR/RTL reasons.
+ cairo_translate(cairo_context, widget->allocation.width, 0.0f);
+ cairo_scale(cairo_context, -1.0f, 1.0f);
+ }
+
+ // The widget might be larger than the pixbuf. Paint the pixbuf flush with the
+ // start of the widget (left for LTR, right for RTL) and its bottom.
+ gfx::Rect bounds = gfx::Rect(0, 0, pixbuf->Width(), 0);
+ int x = gtk_util::MirroredLeftPointForRect(widget, bounds);
+ int y = widget->allocation.height - pixbuf->Height();
+
+ if (background_image_->valid()) {
+ background_image_->SetSource(cairo_context, x, y);
+ cairo_paint(cairo_context);
+ }
+
+ pixbuf->SetSource(cairo_context, x, y);
+ cairo_paint(cairo_context);
+
+ if (animating_hover) {
+ hover_pixbuf->SetSource(cairo_context, x, y);
+ cairo_paint_with_alpha(cairo_context, hover_state);
+ }
+
+ cairo_destroy(cairo_context);
+
+ GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget));
+ if (child)
+ gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e);
+
+ return TRUE;
+}
+
+void CustomDrawButtonBase::SetBackground(SkColor color,
+ SkBitmap* image, SkBitmap* mask) {
+ if (!image || !mask) {
+ if (background_image_->valid()) {
+ background_image_->UsePixbuf(NULL);
+ }
+ } else {
+ SkBitmap img =
+ SkBitmapOperations::CreateButtonBackground(color, *image, *mask);
+
+ GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&img);
+ background_image_->UsePixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+}
+
+void CustomDrawButtonBase::Observe(NotificationType type,
+ const NotificationSource& source, const NotificationDetails& details) {
+ DCHECK(theme_provider_);
+ DCHECK(NotificationType::BROWSER_THEME_CHANGED == type);
+
+ surfaces_[GTK_STATE_NORMAL]->UsePixbuf(normal_id_ ?
+ theme_provider_->GetRTLEnabledPixbufNamed(normal_id_) : NULL);
+ surfaces_[GTK_STATE_ACTIVE]->UsePixbuf(pressed_id_ ?
+ theme_provider_->GetRTLEnabledPixbufNamed(pressed_id_) : NULL);
+ surfaces_[GTK_STATE_PRELIGHT]->UsePixbuf(hover_id_ ?
+ theme_provider_->GetRTLEnabledPixbufNamed(hover_id_) : NULL);
+ surfaces_[GTK_STATE_SELECTED]->UsePixbuf(NULL);
+ surfaces_[GTK_STATE_INSENSITIVE]->UsePixbuf(disabled_id_ ?
+ theme_provider_->GetRTLEnabledPixbufNamed(disabled_id_) : NULL);
+}
+
+CairoCachedSurface* CustomDrawButtonBase::PixbufForState(int state) {
+ CairoCachedSurface* pixbuf = surfaces_[state].get();
+
+ // Fall back to the default image if we don't have one for this state.
+ if (!pixbuf || !pixbuf->valid())
+ pixbuf = surfaces_[GTK_STATE_NORMAL].get();
+
+ return pixbuf;
+}
+
+// CustomDrawHoverController ---------------------------------------------------
+
+CustomDrawHoverController::CustomDrawHoverController(GtkWidget* widget)
+ : slide_animation_(this),
+ widget_(NULL) {
+ Init(widget);
+}
+
+CustomDrawHoverController::CustomDrawHoverController()
+ : slide_animation_(this),
+ widget_(NULL) {
+}
+
+CustomDrawHoverController::~CustomDrawHoverController() {
+}
+
+void CustomDrawHoverController::Init(GtkWidget* widget) {
+ DCHECK(widget_ == NULL);
+ widget_ = widget;
+ g_signal_connect(widget_, "enter-notify-event",
+ G_CALLBACK(OnEnterThunk), this);
+ g_signal_connect(widget_, "leave-notify-event",
+ G_CALLBACK(OnLeaveThunk), this);
+}
+
+void CustomDrawHoverController::AnimationProgressed(
+ const ui::Animation* animation) {
+ gtk_widget_queue_draw(widget_);
+}
+
+gboolean CustomDrawHoverController::OnEnter(
+ GtkWidget* widget,
+ GdkEventCrossing* event) {
+ slide_animation_.Show();
+ return FALSE;
+}
+
+gboolean CustomDrawHoverController::OnLeave(
+ GtkWidget* widget,
+ GdkEventCrossing* event) {
+ // When the user is holding a mouse button, we don't want to animate.
+ if (event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))
+ slide_animation_.Reset();
+ else
+ slide_animation_.Hide();
+ return FALSE;
+}
+
+// CustomDrawButton ------------------------------------------------------------
+
+CustomDrawButton::CustomDrawButton(int normal_id,
+ int pressed_id,
+ int hover_id,
+ int disabled_id)
+ : button_base_(NULL, normal_id, pressed_id, hover_id, disabled_id),
+ theme_provider_(NULL) {
+ Init();
+
+ // Initialize the theme stuff with no theme_provider.
+ SetBrowserTheme();
+}
+
+CustomDrawButton::CustomDrawButton(GtkThemeProvider* theme_provider,
+ int normal_id,
+ int pressed_id,
+ int hover_id,
+ int disabled_id,
+ const char* stock_id,
+ GtkIconSize stock_size)
+ : button_base_(theme_provider, normal_id, pressed_id, hover_id,
+ disabled_id),
+ theme_provider_(theme_provider) {
+ native_widget_.Own(gtk_image_new_from_stock(stock_id, stock_size));
+
+ Init();
+
+ theme_provider_->InitThemesFor(this);
+ registrar_.Add(this,
+ NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+}
+
+CustomDrawButton::CustomDrawButton(GtkThemeProvider* theme_provider,
+ int normal_id,
+ int pressed_id,
+ int hover_id,
+ int disabled_id,
+ GtkWidget* native_widget)
+ : button_base_(theme_provider, normal_id, pressed_id, hover_id,
+ disabled_id),
+ native_widget_(native_widget),
+ theme_provider_(theme_provider) {
+ Init();
+
+ theme_provider_->InitThemesFor(this);
+ registrar_.Add(this,
+ NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+}
+
+CustomDrawButton::~CustomDrawButton() {
+ widget_.Destroy();
+ native_widget_.Destroy();
+}
+
+void CustomDrawButton::Init() {
+ widget_.Own(gtk_chrome_button_new());
+ GTK_WIDGET_UNSET_FLAGS(widget(), GTK_CAN_FOCUS);
+ g_signal_connect(widget(), "expose-event",
+ G_CALLBACK(OnCustomExposeThunk), this);
+ hover_controller_.Init(widget());
+}
+
+void CustomDrawButton::Observe(NotificationType type,
+ const NotificationSource& source, const NotificationDetails& details) {
+ DCHECK(NotificationType::BROWSER_THEME_CHANGED == type);
+ SetBrowserTheme();
+}
+
+void CustomDrawButton::SetPaintOverride(GtkStateType state) {
+ button_base_.set_paint_override(state);
+ gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget()), state);
+ gtk_widget_queue_draw(widget());
+}
+
+void CustomDrawButton::UnsetPaintOverride() {
+ button_base_.set_paint_override(-1);
+ gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(widget()));
+ gtk_widget_queue_draw(widget());
+}
+
+void CustomDrawButton::SetBackground(SkColor color,
+ SkBitmap* image, SkBitmap* mask) {
+ button_base_.SetBackground(color, image, mask);
+}
+
+gboolean CustomDrawButton::OnCustomExpose(GtkWidget* sender,
+ GdkEventExpose* e) {
+ if (UseGtkTheme()) {
+ // Continue processing this expose event.
+ return FALSE;
+ } else {
+ double hover_state = hover_controller_.GetCurrentValue();
+ return button_base_.OnExpose(sender, e, hover_state);
+ }
+}
+
+// static
+CustomDrawButton* CustomDrawButton::CloseButton(
+ GtkThemeProvider* theme_provider) {
+ CustomDrawButton* button = new CustomDrawButton(theme_provider, IDR_CLOSE_BAR,
+ IDR_CLOSE_BAR_P, IDR_CLOSE_BAR_H, 0, GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ return button;
+}
+
+void CustomDrawButton::SetBrowserTheme() {
+ if (UseGtkTheme()) {
+ if (native_widget_.get())
+ gtk_button_set_image(GTK_BUTTON(widget()), native_widget_.get());
+ gtk_widget_set_size_request(widget(), -1, -1);
+ gtk_widget_set_app_paintable(widget(), FALSE);
+ } else {
+ if (native_widget_.get())
+ gtk_button_set_image(GTK_BUTTON(widget()), NULL);
+ gtk_widget_set_size_request(widget(), button_base_.Width(),
+ button_base_.Height());
+
+ gtk_widget_set_app_paintable(widget(), TRUE);
+ }
+
+ gtk_chrome_button_set_use_gtk_rendering(
+ GTK_CHROME_BUTTON(widget()), UseGtkTheme());
+}
+
+bool CustomDrawButton::UseGtkTheme() {
+ return theme_provider_ && theme_provider_->UseGtkTheme();
+}