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