Home | History | Annotate | Download | only in gtk
      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 }
    398