Home | History | Annotate | Download | only in tabs
      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/tabs/tab_renderer_gtk.h"
      6 
      7 #include <algorithm>
      8 #include <utility>
      9 
     10 #include "base/debug/trace_event.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/defaults.h"
     14 #include "chrome/browser/extensions/tab_helper.h"
     15 #include "chrome/browser/favicon/favicon_tab_helper.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/themes/theme_properties.h"
     18 #include "chrome/browser/ui/browser.h"
     19 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
     20 #include "chrome/browser/ui/gtk/custom_button.h"
     21 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     22 #include "chrome/browser/ui/gtk/gtk_util.h"
     23 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
     24 #include "chrome/browser/ui/tabs/tab_utils.h"
     25 #include "content/public/browser/notification_source.h"
     26 #include "content/public/browser/web_contents.h"
     27 #include "grit/generated_resources.h"
     28 #include "grit/theme_resources.h"
     29 #include "grit/ui_resources.h"
     30 #include "skia/ext/image_operations.h"
     31 #include "ui/base/gtk/gtk_screen_util.h"
     32 #include "ui/base/l10n/l10n_util.h"
     33 #include "ui/base/resource/resource_bundle.h"
     34 #include "ui/gfx/animation/slide_animation.h"
     35 #include "ui/gfx/animation/throb_animation.h"
     36 #include "ui/gfx/canvas_skia_paint.h"
     37 #include "ui/gfx/favicon_size.h"
     38 #include "ui/gfx/gtk_compat.h"
     39 #include "ui/gfx/gtk_util.h"
     40 #include "ui/gfx/image/cairo_cached_surface.h"
     41 #include "ui/gfx/image/image.h"
     42 #include "ui/gfx/pango_util.h"
     43 #include "ui/gfx/platform_font_pango.h"
     44 #include "ui/gfx/skbitmap_operations.h"
     45 
     46 using content::WebContents;
     47 
     48 namespace {
     49 
     50 const int kFontPixelSize = 12;
     51 const int kLeftPadding = 16;
     52 const int kTopPadding = 6;
     53 const int kRightPadding = 15;
     54 const int kBottomPadding = 5;
     55 const int kFaviconTitleSpacing = 4;
     56 const int kTitleCloseButtonSpacing = 5;
     57 const int kStandardTitleWidth = 175;
     58 const int kDropShadowOffset = 2;
     59 const int kInactiveTabBackgroundOffsetY = 15;
     60 
     61 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
     62 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
     63 // is rendered as a normal tab. This is done to avoid having the title
     64 // immediately disappear when transitioning a tab from normal to mini-tab.
     65 const int kMiniTabRendererAsNormalTabWidth =
     66     browser_defaults::kMiniTabWidth + 30;
     67 
     68 // The tab images are designed to overlap the toolbar by 1 pixel. For now we
     69 // don't actually overlap the toolbar, so this is used to know how many pixels
     70 // at the bottom of the tab images are to be ignored.
     71 const int kToolbarOverlap = 1;
     72 
     73 // How long the hover state takes.
     74 const int kHoverDurationMs = 90;
     75 
     76 // How opaque to make the hover state (out of 1).
     77 const double kHoverOpacity = 0.33;
     78 
     79 // Opacity for non-active selected tabs.
     80 const double kSelectedTabOpacity = 0.45;
     81 
     82 // Selected (but not active) tabs have their throb value scaled down by this.
     83 const double kSelectedTabThrobScale = 0.5;
     84 
     85 // Max opacity for the mini-tab title change animation.
     86 const double kMiniTitleChangeThrobOpacity = 0.75;
     87 
     88 // Duration for when the title of an inactive mini-tab changes.
     89 const int kMiniTitleChangeThrobDuration = 1000;
     90 
     91 // The horizontal offset used to position the close button in the tab.
     92 const int kCloseButtonHorzFuzz = 4;
     93 
     94 // Gets the bounds of |widget| relative to |parent|.
     95 gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent,
     96                                           GtkWidget* widget) {
     97   gfx::Rect bounds = ui::GetWidgetScreenBounds(widget);
     98   bounds.Offset(-ui::GetWidgetScreenOffset(parent));
     99   return bounds;
    100 }
    101 
    102 // Returns a GdkPixbuf after resizing the SkBitmap as necessary to the
    103 // specified desired width and height. Caller must g_object_unref the returned
    104 // pixbuf when no longer used.
    105 GdkPixbuf* GetResizedGdkPixbufFromSkBitmap(const SkBitmap& bitmap,
    106                                            int dest_w,
    107                                            int dest_h) {
    108   float float_dest_w = static_cast<float>(dest_w);
    109   float float_dest_h = static_cast<float>(dest_h);
    110   int bitmap_w = bitmap.width();
    111   int bitmap_h = bitmap.height();
    112 
    113   // Scale proportionately.
    114   float scale = std::min(float_dest_w / bitmap_w,
    115                          float_dest_h / bitmap_h);
    116   int final_dest_w = static_cast<int>(bitmap_w * scale);
    117   int final_dest_h = static_cast<int>(bitmap_h * scale);
    118 
    119   GdkPixbuf* pixbuf;
    120   if (final_dest_w == bitmap_w && final_dest_h == bitmap_h) {
    121     pixbuf = gfx::GdkPixbufFromSkBitmap(bitmap);
    122   } else {
    123     SkBitmap resized_icon = skia::ImageOperations::Resize(
    124         bitmap,
    125         skia::ImageOperations::RESIZE_BETTER,
    126         final_dest_w, final_dest_h);
    127     pixbuf = gfx::GdkPixbufFromSkBitmap(resized_icon);
    128   }
    129   return pixbuf;
    130 }
    131 
    132 }  // namespace
    133 
    134 TabRendererGtk::LoadingAnimation::Data::Data(
    135     GtkThemeService* theme_service) {
    136   // The loading animation image is a strip of states. Each state must be
    137   // square, so the height must divide the width evenly.
    138   SkBitmap loading_animation_frames =
    139       theme_service->GetImageNamed(IDR_THROBBER).AsBitmap();
    140   DCHECK(!loading_animation_frames.isNull());
    141   DCHECK_EQ(loading_animation_frames.width() %
    142             loading_animation_frames.height(), 0);
    143   loading_animation_frame_count =
    144       loading_animation_frames.width() /
    145       loading_animation_frames.height();
    146 
    147   SkBitmap waiting_animation_frames =
    148       theme_service->GetImageNamed(IDR_THROBBER_WAITING).AsBitmap();
    149   DCHECK(!waiting_animation_frames.isNull());
    150   DCHECK_EQ(waiting_animation_frames.width() %
    151             waiting_animation_frames.height(), 0);
    152   waiting_animation_frame_count =
    153       waiting_animation_frames.width() /
    154       waiting_animation_frames.height();
    155 
    156   waiting_to_loading_frame_count_ratio =
    157       waiting_animation_frame_count /
    158       loading_animation_frame_count;
    159   // TODO(beng): eventually remove this when we have a proper themeing system.
    160   //             themes not supporting IDR_THROBBER_WAITING are causing this
    161   //             value to be 0 which causes DIV0 crashes. The value of 5
    162   //             matches the current bitmaps in our source.
    163   if (waiting_to_loading_frame_count_ratio == 0)
    164     waiting_to_loading_frame_count_ratio = 5;
    165 }
    166 
    167 TabRendererGtk::LoadingAnimation::Data::Data(
    168     int loading, int waiting, int waiting_to_loading)
    169     : loading_animation_frame_count(loading),
    170       waiting_animation_frame_count(waiting),
    171       waiting_to_loading_frame_count_ratio(waiting_to_loading) {
    172 }
    173 
    174 bool TabRendererGtk::initialized_ = false;
    175 int TabRendererGtk::tab_active_l_width_ = 0;
    176 int TabRendererGtk::tab_active_l_height_ = 0;
    177 int TabRendererGtk::tab_inactive_l_height_ = 0;
    178 gfx::Font* TabRendererGtk::title_font_ = NULL;
    179 int TabRendererGtk::title_font_height_ = 0;
    180 int TabRendererGtk::close_button_width_ = 0;
    181 int TabRendererGtk::close_button_height_ = 0;
    182 
    183 ////////////////////////////////////////////////////////////////////////////////
    184 // TabRendererGtk::LoadingAnimation, public:
    185 //
    186 TabRendererGtk::LoadingAnimation::LoadingAnimation(
    187     GtkThemeService* theme_service)
    188     : data_(new Data(theme_service)),
    189       theme_service_(theme_service),
    190       animation_state_(ANIMATION_NONE),
    191       animation_frame_(0) {
    192   registrar_.Add(this,
    193                  chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    194                  content::Source<ThemeService>(theme_service_));
    195 }
    196 
    197 TabRendererGtk::LoadingAnimation::LoadingAnimation(
    198     const LoadingAnimation::Data& data)
    199     : data_(new Data(data)),
    200       theme_service_(NULL),
    201       animation_state_(ANIMATION_NONE),
    202       animation_frame_(0) {
    203 }
    204 
    205 TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
    206 
    207 bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation(
    208     AnimationState animation_state) {
    209   bool has_changed = false;
    210   if (animation_state_ != animation_state) {
    211     // The waiting animation is the reverse of the loading animation, but at a
    212     // different rate - the following reverses and scales the animation_frame_
    213     // so that the frame is at an equivalent position when going from one
    214     // animation to the other.
    215     if (animation_state_ == ANIMATION_WAITING &&
    216         animation_state == ANIMATION_LOADING) {
    217       animation_frame_ = data_->loading_animation_frame_count -
    218           (animation_frame_ / data_->waiting_to_loading_frame_count_ratio);
    219     }
    220     animation_state_ = animation_state;
    221     has_changed = true;
    222   }
    223 
    224   if (animation_state_ != ANIMATION_NONE) {
    225     animation_frame_ = (animation_frame_ + 1) %
    226                        ((animation_state_ == ANIMATION_WAITING) ?
    227                          data_->waiting_animation_frame_count :
    228                          data_->loading_animation_frame_count);
    229     has_changed = true;
    230   } else {
    231     animation_frame_ = 0;
    232   }
    233   return has_changed;
    234 }
    235 
    236 void TabRendererGtk::LoadingAnimation::Observe(
    237     int type,
    238     const content::NotificationSource& source,
    239     const content::NotificationDetails& details) {
    240   DCHECK(type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
    241   data_.reset(new Data(theme_service_));
    242 }
    243 
    244 TabRendererGtk::TabData::TabData()
    245     : is_default_favicon(false),
    246       loading(false),
    247       crashed(false),
    248       incognito(false),
    249       show_icon(true),
    250       mini(false),
    251       blocked(false),
    252       animating_mini_change(false),
    253       app(false),
    254       media_state(TAB_MEDIA_STATE_NONE),
    255       previous_media_state(TAB_MEDIA_STATE_NONE) {
    256 }
    257 
    258 TabRendererGtk::TabData::~TabData() {}
    259 
    260 ////////////////////////////////////////////////////////////////////////////////
    261 // FaviconCrashAnimation
    262 //
    263 //  A custom animation subclass to manage the favicon crash animation.
    264 class TabRendererGtk::FaviconCrashAnimation : public gfx::LinearAnimation,
    265                                               public gfx::AnimationDelegate {
    266  public:
    267   explicit FaviconCrashAnimation(TabRendererGtk* target)
    268       : gfx::LinearAnimation(1000, 25, this),
    269         target_(target) {
    270   }
    271   virtual ~FaviconCrashAnimation() {}
    272 
    273   // gfx::Animation overrides:
    274   virtual void AnimateToState(double state) OVERRIDE {
    275     const double kHidingOffset = 27;
    276 
    277     if (state < .5) {
    278       target_->SetFaviconHidingOffset(
    279           static_cast<int>(floor(kHidingOffset * 2.0 * state)));
    280     } else {
    281       target_->DisplayCrashedFavicon();
    282       target_->SetFaviconHidingOffset(
    283           static_cast<int>(
    284               floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
    285     }
    286   }
    287 
    288   // gfx::AnimationDelegate overrides:
    289   virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
    290     target_->SetFaviconHidingOffset(0);
    291   }
    292 
    293  private:
    294   TabRendererGtk* target_;
    295 
    296   DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
    297 };
    298 
    299 ////////////////////////////////////////////////////////////////////////////////
    300 // TabRendererGtk, public:
    301 
    302 TabRendererGtk::TabRendererGtk(GtkThemeService* theme_service)
    303     : showing_icon_(false),
    304       showing_media_indicator_(false),
    305       showing_close_button_(false),
    306       favicon_hiding_offset_(0),
    307       should_display_crashed_favicon_(false),
    308       animating_media_state_(TAB_MEDIA_STATE_NONE),
    309       loading_animation_(theme_service),
    310       background_offset_x_(0),
    311       background_offset_y_(kInactiveTabBackgroundOffsetY),
    312       theme_service_(theme_service),
    313       close_button_color_(0),
    314       is_active_(false),
    315       selected_title_color_(SK_ColorBLACK),
    316       unselected_title_color_(SkColorSetRGB(64, 64, 64)) {
    317   InitResources();
    318 
    319   theme_service_->InitThemesFor(this);
    320   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    321                  content::Source<ThemeService>(theme_service_));
    322 
    323   tab_.Own(gtk_fixed_new());
    324   gtk_widget_set_app_paintable(tab_.get(), TRUE);
    325   g_signal_connect(tab_.get(), "expose-event",
    326                    G_CALLBACK(OnExposeEventThunk), this);
    327   g_signal_connect(tab_.get(), "size-allocate",
    328                    G_CALLBACK(OnSizeAllocateThunk), this);
    329   close_button_.reset(MakeCloseButton());
    330   gtk_widget_show(tab_.get());
    331 
    332   hover_animation_.reset(new gfx::SlideAnimation(this));
    333   hover_animation_->SetSlideDuration(kHoverDurationMs);
    334 }
    335 
    336 TabRendererGtk::~TabRendererGtk() {
    337   tab_.Destroy();
    338 }
    339 
    340 void TabRendererGtk::Observe(int type,
    341                              const content::NotificationSource& source,
    342                              const content::NotificationDetails& details) {
    343   DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
    344   selected_title_color_ =
    345       theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
    346   unselected_title_color_ =
    347       theme_service_->GetColor(ThemeProperties::COLOR_BACKGROUND_TAB_TEXT);
    348 }
    349 
    350 void TabRendererGtk::UpdateData(WebContents* contents,
    351                                 bool app,
    352                                 bool loading_only) {
    353   DCHECK(contents);
    354   FaviconTabHelper* favicon_tab_helper =
    355       FaviconTabHelper::FromWebContents(contents);
    356 
    357   if (!loading_only) {
    358     data_.title = contents->GetTitle();
    359     data_.incognito = contents->GetBrowserContext()->IsOffTheRecord();
    360 
    361     TabMediaState next_media_state;
    362     if (contents->IsCrashed()) {
    363       data_.crashed = true;
    364       next_media_state = TAB_MEDIA_STATE_NONE;
    365     } else {
    366       data_.crashed = false;
    367       next_media_state = chrome::GetTabMediaStateForContents(contents);
    368     }
    369     if (data_.media_state != next_media_state) {
    370       data_.previous_media_state = data_.media_state;
    371       data_.media_state = next_media_state;
    372     }
    373 
    374     data_.favicon = favicon_tab_helper->GetFavicon().AsBitmap();
    375     data_.app = app;
    376 
    377     // Make a cairo cached version of the favicon.
    378     if (!data_.favicon.isNull()) {
    379       // Instead of resizing the icon during each frame, create our resized
    380       // icon resource now, send it to the xserver and use that each frame
    381       // instead.
    382 
    383       // For source images smaller than the favicon square, scale them as if
    384       // they were padded to fit the favicon square, so we don't blow up tiny
    385       // favicons into larger or nonproportional results.
    386       GdkPixbuf* pixbuf = GetResizedGdkPixbufFromSkBitmap(data_.favicon,
    387           gfx::kFaviconSize, gfx::kFaviconSize);
    388       data_.cairo_favicon.UsePixbuf(pixbuf);
    389       g_object_unref(pixbuf);
    390     } else {
    391       data_.cairo_favicon.Reset();
    392     }
    393 
    394     // This is kind of a hacky way to determine whether our icon is the default
    395     // favicon. But the plumbing that would be necessary to do it right would
    396     // be a good bit of work and would sully code for other platforms which
    397     // don't care to custom-theme the favicon. Hopefully the default favicon
    398     // will eventually be chromium-themable and this code will go away.
    399     data_.is_default_favicon =
    400         (data_.favicon.pixelRef() ==
    401         ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    402             IDR_DEFAULT_FAVICON).AsBitmap().pixelRef());
    403   }
    404 
    405   // Loading state also involves whether we show the favicon, since that's where
    406   // we display the throbber.
    407   data_.loading = contents->IsLoading();
    408   data_.show_icon = favicon_tab_helper->ShouldDisplayFavicon();
    409 }
    410 
    411 void TabRendererGtk::UpdateFromModel() {
    412   // Force a layout, since the tab may have grown a favicon.
    413   Layout();
    414   SchedulePaint();
    415 
    416   if (data_.crashed) {
    417     if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
    418       StartCrashAnimation();
    419   } else {
    420     if (IsPerformingCrashAnimation())
    421       StopCrashAnimation();
    422     ResetCrashedFavicon();
    423   }
    424 
    425   if (data_.media_state != data_.previous_media_state) {
    426     data_.previous_media_state = data_.media_state;
    427     if (data_.media_state != TAB_MEDIA_STATE_NONE)
    428       animating_media_state_ = data_.media_state;
    429     StartMediaIndicatorAnimation();
    430   }
    431 }
    432 
    433 void TabRendererGtk::SetBlocked(bool blocked) {
    434   if (data_.blocked == blocked)
    435     return;
    436   data_.blocked = blocked;
    437   // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
    438 }
    439 
    440 bool TabRendererGtk::is_blocked() const {
    441   return data_.blocked;
    442 }
    443 
    444 bool TabRendererGtk::IsActive() const {
    445   return is_active_;
    446 }
    447 
    448 bool TabRendererGtk::IsSelected() const {
    449   return true;
    450 }
    451 
    452 bool TabRendererGtk::IsVisible() const {
    453   return gtk_widget_get_visible(tab_.get());
    454 }
    455 
    456 void TabRendererGtk::SetVisible(bool visible) const {
    457   if (visible) {
    458     gtk_widget_show(tab_.get());
    459     if (data_.mini)
    460       gtk_widget_show(close_button_->widget());
    461   } else {
    462     gtk_widget_hide_all(tab_.get());
    463   }
    464 }
    465 
    466 bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
    467   return loading_animation_.ValidateLoadingAnimation(animation_state);
    468 }
    469 
    470 void TabRendererGtk::PaintFaviconArea(GtkWidget* widget, cairo_t* cr) {
    471   DCHECK(ShouldShowIcon());
    472 
    473   cairo_rectangle(cr,
    474                   x() + favicon_bounds_.x(),
    475                   y() + favicon_bounds_.y(),
    476                   favicon_bounds_.width(),
    477                   favicon_bounds_.height());
    478   cairo_clip(cr);
    479 
    480   // The tab is rendered into a windowless widget whose offset is at the
    481   // coordinate event->area.  Translate by these offsets so we can render at
    482   // (0,0) to match Windows' rendering metrics.
    483   cairo_matrix_t cairo_matrix;
    484   cairo_matrix_init_translate(&cairo_matrix, x(), y());
    485   cairo_set_matrix(cr, &cairo_matrix);
    486 
    487   // Which background should we be painting?
    488   int theme_id;
    489   int offset_y = 0;
    490   if (IsActive()) {
    491     theme_id = IDR_THEME_TOOLBAR;
    492   } else {
    493     theme_id = data_.incognito ? IDR_THEME_TAB_BACKGROUND_INCOGNITO :
    494                IDR_THEME_TAB_BACKGROUND;
    495 
    496     if (!theme_service_->HasCustomImage(theme_id))
    497       offset_y = background_offset_y_;
    498   }
    499 
    500   // Paint the background behind the favicon.
    501   const gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
    502   tab_bg.ToCairo()->SetSource(cr, widget, -x(), -offset_y);
    503   cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
    504   cairo_rectangle(cr,
    505                   favicon_bounds_.x(), favicon_bounds_.y(),
    506                   favicon_bounds_.width(), favicon_bounds_.height());
    507   cairo_fill(cr);
    508 
    509   if (!IsActive()) {
    510     double throb_value = GetThrobValue();
    511     if (throb_value > 0) {
    512       cairo_push_group(cr);
    513       gfx::Image active_bg =
    514           theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
    515       active_bg.ToCairo()->SetSource(cr, widget, -x(), 0);
    516       cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
    517 
    518       cairo_rectangle(cr,
    519                       favicon_bounds_.x(), favicon_bounds_.y(),
    520                       favicon_bounds_.width(), favicon_bounds_.height());
    521       cairo_fill(cr);
    522 
    523       cairo_pop_group_to_source(cr);
    524       cairo_paint_with_alpha(cr, throb_value);
    525     }
    526   }
    527 
    528   PaintIcon(widget, cr);
    529 }
    530 
    531 void TabRendererGtk::MaybeAdjustLeftForMiniTab(gfx::Rect* icon_bounds) const {
    532   if (!(mini() || data_.animating_mini_change) ||
    533       bounds_.width() >= kMiniTabRendererAsNormalTabWidth)
    534     return;
    535   const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
    536   const int ideal_delta = bounds_.width() - GetMiniWidth();
    537   const int ideal_x = (GetMiniWidth() - icon_bounds->width()) / 2;
    538   icon_bounds->set_x(icon_bounds->x() + static_cast<int>(
    539       (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) *
    540       (ideal_x - icon_bounds->x())));
    541 }
    542 
    543 // static
    544 gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
    545   InitResources();
    546 
    547   gfx::Size minimum_size;
    548   minimum_size.set_width(kLeftPadding + kRightPadding);
    549   // Since we use bitmap images, the real minimum height of the image is
    550   // defined most accurately by the height of the end cap images.
    551   minimum_size.set_height(tab_active_l_height_ - kToolbarOverlap);
    552   return minimum_size;
    553 }
    554 
    555 // static
    556 gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
    557   gfx::Size minimum_size = GetMinimumUnselectedSize();
    558   minimum_size.set_width(kLeftPadding + gfx::kFaviconSize + kRightPadding);
    559   return minimum_size;
    560 }
    561 
    562 // static
    563 gfx::Size TabRendererGtk::GetStandardSize() {
    564   gfx::Size standard_size = GetMinimumUnselectedSize();
    565   standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
    566   return standard_size;
    567 }
    568 
    569 // static
    570 int TabRendererGtk::GetMiniWidth() {
    571   return browser_defaults::kMiniTabWidth;
    572 }
    573 
    574 // static
    575 int TabRendererGtk::GetContentHeight() {
    576   // The height of the content of the Tab is the largest of the favicon,
    577   // the title text and the close button graphic.
    578   int content_height = std::max(gfx::kFaviconSize, title_font_height_);
    579   return std::max(content_height, close_button_height_);
    580 }
    581 
    582 gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
    583   // The tabstrip widget is a windowless widget so the tab widget's allocation
    584   // is relative to the browser titlebar.  We need the bounds relative to the
    585   // tabstrip.
    586   gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
    587   bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
    588   return bounds;
    589 }
    590 
    591 gfx::Rect TabRendererGtk::GetRequisition() const {
    592   return gfx::Rect(requisition_.x(), requisition_.y(),
    593                    requisition_.width(), requisition_.height());
    594 }
    595 
    596 void TabRendererGtk::StartMiniTabTitleAnimation() {
    597   if (!mini_title_animation_.get()) {
    598     mini_title_animation_.reset(new gfx::ThrobAnimation(this));
    599     mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
    600   }
    601 
    602   if (!mini_title_animation_->is_animating())
    603     mini_title_animation_->StartThrobbing(-1);
    604 }
    605 
    606 void TabRendererGtk::StopMiniTabTitleAnimation() {
    607   if (mini_title_animation_.get())
    608     mini_title_animation_->Stop();
    609 }
    610 
    611 void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
    612   requisition_ = bounds;
    613   gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
    614 }
    615 
    616 ////////////////////////////////////////////////////////////////////////////////
    617 // TabRendererGtk, protected:
    618 
    619 void TabRendererGtk::Raise() const {
    620   if (gtk_button_get_event_window(GTK_BUTTON(close_button_->widget())))
    621     gdk_window_raise(gtk_button_get_event_window(
    622         GTK_BUTTON(close_button_->widget())));
    623 }
    624 
    625 base::string16 TabRendererGtk::GetTitle() const {
    626   return data_.title;
    627 }
    628 
    629 ///////////////////////////////////////////////////////////////////////////////
    630 // TabRendererGtk, gfx::AnimationDelegate implementation:
    631 
    632 void TabRendererGtk::AnimationProgressed(const gfx::Animation* animation) {
    633   gtk_widget_queue_draw(tab_.get());
    634 }
    635 
    636 void TabRendererGtk::AnimationCanceled(const gfx::Animation* animation) {
    637   if (media_indicator_animation_ == animation)
    638     animating_media_state_ = data_.media_state;
    639   AnimationEnded(animation);
    640 }
    641 
    642 void TabRendererGtk::AnimationEnded(const gfx::Animation* animation) {
    643   if (media_indicator_animation_ == animation)
    644     animating_media_state_ = data_.media_state;
    645   gtk_widget_queue_draw(tab_.get());
    646 }
    647 
    648 ////////////////////////////////////////////////////////////////////////////////
    649 // TabRendererGtk, private:
    650 
    651 void TabRendererGtk::StartCrashAnimation() {
    652   if (!crash_animation_.get())
    653     crash_animation_.reset(new FaviconCrashAnimation(this));
    654   crash_animation_->Stop();
    655   crash_animation_->Start();
    656 }
    657 
    658 void TabRendererGtk::StopCrashAnimation() {
    659   if (!crash_animation_.get())
    660     return;
    661   crash_animation_->Stop();
    662 }
    663 
    664 bool TabRendererGtk::IsPerformingCrashAnimation() const {
    665   return crash_animation_.get() && crash_animation_->is_animating();
    666 }
    667 
    668 void TabRendererGtk::StartMediaIndicatorAnimation() {
    669   media_indicator_animation_ =
    670       chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state);
    671   media_indicator_animation_->set_delegate(this);
    672   media_indicator_animation_->Start();
    673 }
    674 
    675 void TabRendererGtk::SetFaviconHidingOffset(int offset) {
    676   favicon_hiding_offset_ = offset;
    677   SchedulePaint();
    678 }
    679 
    680 void TabRendererGtk::DisplayCrashedFavicon() {
    681   should_display_crashed_favicon_ = true;
    682 }
    683 
    684 void TabRendererGtk::ResetCrashedFavicon() {
    685   should_display_crashed_favicon_ = false;
    686 }
    687 
    688 void TabRendererGtk::Paint(GtkWidget* widget, cairo_t* cr) {
    689   // Don't paint if we're narrower than we can render correctly. (This should
    690   // only happen during animations).
    691   if (width() < GetMinimumUnselectedSize().width() && !mini())
    692     return;
    693 
    694   // See if the model changes whether the icons should be painted.
    695   const bool show_icon = ShouldShowIcon();
    696   const bool show_media_indicator = ShouldShowMediaIndicator();
    697   const bool show_close_button = ShouldShowCloseBox();
    698   if (show_icon != showing_icon_ ||
    699       show_media_indicator != showing_media_indicator_ ||
    700       show_close_button != showing_close_button_)
    701     Layout();
    702 
    703   PaintTabBackground(widget, cr);
    704 
    705   if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
    706     PaintTitle(widget, cr);
    707 
    708   if (show_icon)
    709     PaintIcon(widget, cr);
    710 
    711   if (show_media_indicator)
    712     PaintMediaIndicator(widget, cr);
    713 }
    714 
    715 cairo_surface_t* TabRendererGtk::PaintToSurface(GtkWidget* widget,
    716                                                 cairo_t* cr) {
    717   cairo_surface_t* target = cairo_get_target(cr);
    718   cairo_surface_t* out_surface = cairo_surface_create_similar(
    719       target,
    720       CAIRO_CONTENT_COLOR_ALPHA,
    721       width(), height());
    722 
    723   cairo_t* out_cr = cairo_create(out_surface);
    724   Paint(widget, out_cr);
    725   cairo_destroy(out_cr);
    726 
    727   return out_surface;
    728 }
    729 
    730 void TabRendererGtk::SchedulePaint() {
    731   gtk_widget_queue_draw(tab_.get());
    732 }
    733 
    734 gfx::Rect TabRendererGtk::GetLocalBounds() {
    735   return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
    736 }
    737 
    738 void TabRendererGtk::Layout() {
    739   gfx::Rect local_bounds = GetLocalBounds();
    740   if (local_bounds.IsEmpty())
    741     return;
    742   local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
    743 
    744   // Figure out who is tallest.
    745   int content_height = GetContentHeight();
    746 
    747   // Size the Favicon.
    748   showing_icon_ = ShouldShowIcon();
    749   if (showing_icon_) {
    750     int favicon_top = kTopPadding + (content_height - gfx::kFaviconSize) / 2;
    751     favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
    752                             gfx::kFaviconSize, gfx::kFaviconSize);
    753     MaybeAdjustLeftForMiniTab(&favicon_bounds_);
    754   } else {
    755     favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
    756   }
    757 
    758   // Size the Close button.
    759   showing_close_button_ = ShouldShowCloseBox();
    760   if (showing_close_button_) {
    761     int close_button_top = kTopPadding +
    762         (content_height - close_button_height_) / 2;
    763     int close_button_left =
    764         local_bounds.right() - close_button_width_ + kCloseButtonHorzFuzz;
    765     close_button_bounds_.SetRect(close_button_left,
    766                                  close_button_top,
    767                                  close_button_width_,
    768                                  close_button_height_);
    769 
    770     // If the close button color has changed, generate a new one.
    771     if (theme_service_) {
    772       SkColor tab_text_color =
    773           theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
    774       if (!close_button_color_ || tab_text_color != close_button_color_) {
    775         close_button_color_ = tab_text_color;
    776         ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    777         close_button_->SetBackground(close_button_color_,
    778             rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(),
    779             rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap());
    780       }
    781     }
    782   } else {
    783     close_button_bounds_.SetRect(0, 0, 0, 0);
    784   }
    785 
    786   showing_media_indicator_ = ShouldShowMediaIndicator();
    787   if (showing_media_indicator_) {
    788     const gfx::Image& media_indicator_image =
    789         chrome::GetTabMediaIndicatorImage(animating_media_state_);
    790     media_indicator_bounds_.set_width(media_indicator_image.Width());
    791     media_indicator_bounds_.set_height(media_indicator_image.Height());
    792     media_indicator_bounds_.set_y(
    793         kTopPadding + (content_height - media_indicator_bounds_.height()) / 2);
    794     const int right = showing_close_button_ ?
    795         close_button_bounds_.x() : local_bounds.right();
    796     media_indicator_bounds_.set_x(std::max(
    797         local_bounds.x(), right - media_indicator_bounds_.width()));
    798     MaybeAdjustLeftForMiniTab(&media_indicator_bounds_);
    799   } else {
    800     media_indicator_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
    801   }
    802 
    803   if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
    804     // Size the Title text to fill the remaining space.
    805     int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
    806     int title_top = kTopPadding;
    807 
    808     // If the user has big fonts, the title will appear rendered too far down
    809     // on the y-axis if we use the regular top padding, so we need to adjust it
    810     // so that the text appears centered.
    811     gfx::Size minimum_size = GetMinimumUnselectedSize();
    812     int text_height = title_top + title_font_height_ + kBottomPadding;
    813     if (text_height > minimum_size.height())
    814       title_top -= (text_height - minimum_size.height()) / 2;
    815 
    816     int title_width;
    817     if (showing_media_indicator_) {
    818       title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing -
    819           title_left;
    820     } else if (close_button_bounds_.width() && close_button_bounds_.height()) {
    821       title_width = close_button_bounds_.x() - kTitleCloseButtonSpacing -
    822           title_left;
    823     } else {
    824       title_width = local_bounds.width() - title_left;
    825     }
    826     title_width = std::max(title_width, 0);
    827     title_bounds_.SetRect(title_left, title_top, title_width, content_height);
    828   }
    829 
    830   favicon_bounds_.set_x(
    831       gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
    832   media_indicator_bounds_.set_x(
    833       gtk_util::MirroredLeftPointForRect(tab_.get(), media_indicator_bounds_));
    834   close_button_bounds_.set_x(
    835       gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
    836   title_bounds_.set_x(
    837       gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
    838 
    839   MoveCloseButtonWidget();
    840 }
    841 
    842 void TabRendererGtk::MoveCloseButtonWidget() {
    843   if (!close_button_bounds_.IsEmpty()) {
    844     gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
    845                    close_button_bounds_.x(), close_button_bounds_.y());
    846     gtk_widget_show(close_button_->widget());
    847   } else {
    848     gtk_widget_hide(close_button_->widget());
    849   }
    850 }
    851 
    852 void TabRendererGtk::PaintTab(GtkWidget* widget, GdkEventExpose* event) {
    853   cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
    854   gdk_cairo_rectangle(cr, &event->area);
    855   cairo_clip(cr);
    856 
    857   // The tab is rendered into a windowless widget whose offset is at the
    858   // coordinate event->area.  Translate by these offsets so we can render at
    859   // (0,0) to match Windows' rendering metrics.
    860   cairo_matrix_t cairo_matrix;
    861   cairo_matrix_init_translate(&cairo_matrix, event->area.x, event->area.y);
    862   cairo_set_matrix(cr, &cairo_matrix);
    863 
    864   // Save the original x offset so we can position background images properly.
    865   background_offset_x_ = event->area.x;
    866 
    867   Paint(widget, cr);
    868   cairo_destroy(cr);
    869 }
    870 
    871 void TabRendererGtk::PaintTitle(GtkWidget* widget, cairo_t* cr) {
    872   if (title_bounds_.IsEmpty())
    873     return;
    874 
    875   // Paint the Title.
    876   base::string16 title = data_.title;
    877   if (title.empty()) {
    878     title = data_.loading ?
    879         l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
    880         CoreTabHelper::GetDefaultTitle();
    881   } else {
    882     Browser::FormatTitleForDisplay(&title);
    883   }
    884 
    885   GtkAllocation allocation;
    886   gtk_widget_get_allocation(widget, &allocation);
    887   gfx::Rect bounds(allocation);
    888 
    889   // Draw the text directly onto the Cairo context. This is necessary for
    890   // getting the draw order correct, and automatically applying transformations
    891   // such as scaling when a tab is detached.
    892   gfx::CanvasSkiaPaintCairo canvas(cr, bounds.size(), true);
    893 
    894   SkColor title_color = IsSelected() ? selected_title_color_
    895                                      : unselected_title_color_;
    896 
    897   // Disable subpixel rendering. This does not work because the canvas has a
    898   // transparent background.
    899   int flags = gfx::Canvas::NO_ELLIPSIS | gfx::Canvas::NO_SUBPIXEL_RENDERING;
    900   canvas.DrawFadeTruncatingStringRectWithFlags(
    901       title, gfx::Canvas::TruncateFadeTail, gfx::FontList(*title_font_),
    902       title_color, title_bounds_, flags);
    903 }
    904 
    905 void TabRendererGtk::PaintIcon(GtkWidget* widget, cairo_t* cr) {
    906   if (loading_animation_.animation_state() != ANIMATION_NONE) {
    907     PaintLoadingAnimation(widget, cr);
    908     return;
    909   }
    910 
    911   gfx::CairoCachedSurface* to_display = NULL;
    912   if (should_display_crashed_favicon_) {
    913     to_display = theme_service_->GetImageNamed(IDR_SAD_FAVICON).ToCairo();
    914   } else if (!data_.favicon.isNull()) {
    915     if (data_.is_default_favicon && theme_service_->UsingNativeTheme()) {
    916       to_display = GtkThemeService::GetDefaultFavicon(true).ToCairo();
    917     } else if (data_.cairo_favicon.valid()) {
    918       to_display = &data_.cairo_favicon;
    919     }
    920   }
    921 
    922   if (to_display) {
    923     to_display->SetSource(cr, widget, favicon_bounds_.x(),
    924                           favicon_bounds_.y() + favicon_hiding_offset_);
    925     cairo_paint(cr);
    926   }
    927 }
    928 
    929 void TabRendererGtk::PaintMediaIndicator(GtkWidget* widget, cairo_t* cr) {
    930   if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_)
    931     return;
    932 
    933   double opaqueness = media_indicator_animation_->GetCurrentValue();
    934   if (data_.media_state == TAB_MEDIA_STATE_NONE)
    935     opaqueness = 1.0 - opaqueness;  // Fading out, not in.
    936 
    937   const gfx::Image& media_indicator_image =
    938       chrome::GetTabMediaIndicatorImage(animating_media_state_);
    939   media_indicator_image.ToCairo()->SetSource(
    940       cr, widget, media_indicator_bounds_.x(), media_indicator_bounds_.y());
    941   cairo_paint_with_alpha(cr, opaqueness);
    942 }
    943 
    944 void TabRendererGtk::PaintTabBackground(GtkWidget* widget, cairo_t* cr) {
    945   if (IsActive()) {
    946     PaintActiveTabBackground(widget, cr);
    947   } else {
    948     PaintInactiveTabBackground(widget, cr);
    949 
    950     double throb_value = GetThrobValue();
    951     if (throb_value > 0) {
    952       cairo_push_group(cr);
    953       PaintActiveTabBackground(widget, cr);
    954       cairo_pop_group_to_source(cr);
    955       cairo_paint_with_alpha(cr, throb_value);
    956     }
    957   }
    958 }
    959 
    960 void TabRendererGtk::DrawTabBackground(
    961     cairo_t* cr,
    962     GtkWidget* widget,
    963     const gfx::Image& tab_bg,
    964     int offset_x,
    965     int offset_y) {
    966   tab_bg.ToCairo()->SetSource(cr, widget, -offset_x, -offset_y);
    967   cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
    968 
    969   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    970 
    971   // Draw left edge
    972   gfx::Image& tab_l_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_LEFT);
    973   tab_l_mask.ToCairo()->MaskSource(cr, widget, 0, 0);
    974 
    975   // Draw center
    976   cairo_rectangle(cr,
    977                   tab_active_l_width_, kDropShadowOffset,
    978                   width() - (2 * tab_active_l_width_),
    979                   tab_inactive_l_height_);
    980   cairo_fill(cr);
    981 
    982   // Draw right edge
    983   gfx::Image& tab_r_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_RIGHT);
    984   tab_r_mask.ToCairo()->MaskSource(cr, widget,
    985                                     width() - tab_active_l_width_, 0);
    986 }
    987 
    988 void TabRendererGtk::DrawTabShadow(cairo_t* cr,
    989                                    GtkWidget* widget,
    990                                    int left_idr,
    991                                    int center_idr,
    992                                    int right_idr) {
    993   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    994   gfx::Image& active_image_l = rb.GetNativeImageNamed(left_idr);
    995   gfx::Image& active_image_c = rb.GetNativeImageNamed(center_idr);
    996   gfx::Image& active_image_r = rb.GetNativeImageNamed(right_idr);
    997 
    998   // Draw left drop shadow
    999   active_image_l.ToCairo()->SetSource(cr, widget, 0, 0);
   1000   cairo_paint(cr);
   1001 
   1002   // Draw the center shadow
   1003   active_image_c.ToCairo()->SetSource(cr, widget, 0, 0);
   1004   cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
   1005   cairo_rectangle(cr, tab_active_l_width_, 0,
   1006                   width() - (2 * tab_active_l_width_),
   1007                   height());
   1008   cairo_fill(cr);
   1009 
   1010   // Draw right drop shadow
   1011   active_image_r.ToCairo()->SetSource(
   1012       cr, widget, width() - active_image_r.ToCairo()->Width(), 0);
   1013   cairo_paint(cr);
   1014 }
   1015 
   1016 void TabRendererGtk::PaintInactiveTabBackground(GtkWidget* widget,
   1017                                                 cairo_t* cr) {
   1018   int theme_id = data_.incognito ?
   1019       IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
   1020 
   1021   gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
   1022 
   1023   // If the theme is providing a custom background image, then its top edge
   1024   // should be at the top of the tab. Otherwise, we assume that the background
   1025   // image is a composited foreground + frame image.
   1026   int offset_y = theme_service_->HasCustomImage(theme_id) ?
   1027       0 : background_offset_y_;
   1028 
   1029   DrawTabBackground(cr, widget, tab_bg, background_offset_x_, offset_y);
   1030 
   1031   DrawTabShadow(cr, widget, IDR_TAB_INACTIVE_LEFT, IDR_TAB_INACTIVE_CENTER,
   1032                 IDR_TAB_INACTIVE_RIGHT);
   1033 }
   1034 
   1035 void TabRendererGtk::PaintActiveTabBackground(GtkWidget* widget,
   1036                                               cairo_t* cr) {
   1037   gfx::Image tab_bg = theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
   1038 
   1039   DrawTabBackground(cr, widget, tab_bg, background_offset_x_, 0);
   1040   DrawTabShadow(cr, widget, IDR_TAB_ACTIVE_LEFT, IDR_TAB_ACTIVE_CENTER,
   1041                 IDR_TAB_ACTIVE_RIGHT);
   1042 }
   1043 
   1044 void TabRendererGtk::PaintLoadingAnimation(GtkWidget* widget,
   1045                                            cairo_t* cr) {
   1046   int id = loading_animation_.animation_state() == ANIMATION_WAITING ?
   1047            IDR_THROBBER_WAITING : IDR_THROBBER;
   1048   gfx::Image throbber = theme_service_->GetImageNamed(id);
   1049 
   1050   const int image_size = throbber.ToCairo()->Height();
   1051   const int image_offset = loading_animation_.animation_frame() * image_size;
   1052   DCHECK(image_size == favicon_bounds_.height());
   1053   DCHECK(image_size == favicon_bounds_.width());
   1054 
   1055   throbber.ToCairo()->SetSource(cr, widget, favicon_bounds_.x() - image_offset,
   1056                                  favicon_bounds_.y());
   1057   cairo_rectangle(cr, favicon_bounds_.x(), favicon_bounds_.y(),
   1058                   image_size, image_size);
   1059   cairo_fill(cr);
   1060 }
   1061 
   1062 int TabRendererGtk::IconCapacity() const {
   1063   if (height() < GetMinimumUnselectedSize().height())
   1064     return 0;
   1065   const int available_width =
   1066       std::max(0, width() - kLeftPadding - kRightPadding);
   1067   const int kPaddingBetweenIcons = 2;
   1068   if (available_width >= gfx::kFaviconSize &&
   1069       available_width < (gfx::kFaviconSize + kPaddingBetweenIcons)) {
   1070     return 1;
   1071   }
   1072   return available_width / (gfx::kFaviconSize + kPaddingBetweenIcons);
   1073 }
   1074 
   1075 bool TabRendererGtk::ShouldShowIcon() const {
   1076   return chrome::ShouldTabShowFavicon(
   1077       IconCapacity(), mini(), IsActive(), data_.show_icon,
   1078       animating_media_state_);
   1079 }
   1080 
   1081 bool TabRendererGtk::ShouldShowMediaIndicator() const {
   1082   return chrome::ShouldTabShowMediaIndicator(
   1083       IconCapacity(), mini(), IsActive(), data_.show_icon,
   1084       animating_media_state_);
   1085 }
   1086 
   1087 bool TabRendererGtk::ShouldShowCloseBox() const {
   1088   return chrome::ShouldTabShowCloseButton(IconCapacity(), mini(), IsActive());
   1089 }
   1090 
   1091 CustomDrawButton* TabRendererGtk::MakeCloseButton() {
   1092   CustomDrawButton* button = CustomDrawButton::CloseButtonBar(theme_service_);
   1093   button->ForceChromeTheme();
   1094 
   1095   g_signal_connect(button->widget(), "clicked",
   1096                    G_CALLBACK(OnCloseButtonClickedThunk), this);
   1097   g_signal_connect(button->widget(), "button-release-event",
   1098                    G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
   1099   g_signal_connect(button->widget(), "enter-notify-event",
   1100                    G_CALLBACK(OnEnterNotifyEventThunk), this);
   1101   g_signal_connect(button->widget(), "leave-notify-event",
   1102                    G_CALLBACK(OnLeaveNotifyEventThunk), this);
   1103   gtk_widget_set_can_focus(button->widget(), FALSE);
   1104   gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
   1105 
   1106   return button;
   1107 }
   1108 
   1109 double TabRendererGtk::GetThrobValue() {
   1110   bool is_selected = IsSelected();
   1111   double min = is_selected ? kSelectedTabOpacity : 0;
   1112   double scale = is_selected ? kSelectedTabThrobScale : 1;
   1113 
   1114   if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
   1115     return mini_title_animation_->GetCurrentValue() *
   1116         kMiniTitleChangeThrobOpacity * scale + min;
   1117   }
   1118 
   1119   if (hover_animation_.get())
   1120     return kHoverOpacity * hover_animation_->GetCurrentValue() * scale + min;
   1121 
   1122   return is_selected ? kSelectedTabOpacity : 0;
   1123 }
   1124 
   1125 void TabRendererGtk::CloseButtonClicked() {
   1126   // Nothing to do.
   1127 }
   1128 
   1129 void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
   1130   CloseButtonClicked();
   1131 }
   1132 
   1133 gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
   1134                                                    GdkEventButton* event) {
   1135   if (event->button == 2) {
   1136     CloseButtonClicked();
   1137     return TRUE;
   1138   }
   1139 
   1140   return FALSE;
   1141 }
   1142 
   1143 gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
   1144                                        GdkEventExpose* event) {
   1145   TRACE_EVENT0("ui::gtk", "TabRendererGtk::OnExposeEvent");
   1146 
   1147   PaintTab(widget, event);
   1148   gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
   1149                                  close_button_->widget(), event);
   1150   return TRUE;
   1151 }
   1152 
   1153 void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
   1154                                     GtkAllocation* allocation) {
   1155   gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
   1156                                allocation->width, allocation->height);
   1157 
   1158   // Nothing to do if the bounds are the same.  If we don't catch this, we'll
   1159   // get an infinite loop of size-allocate signals.
   1160   if (bounds_ == bounds)
   1161     return;
   1162 
   1163   bounds_ = bounds;
   1164   Layout();
   1165 }
   1166 
   1167 gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
   1168                                             GdkEventCrossing* event) {
   1169   hover_animation_->SetTweenType(gfx::Tween::EASE_OUT);
   1170   hover_animation_->Show();
   1171   return FALSE;
   1172 }
   1173 
   1174 gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
   1175                                             GdkEventCrossing* event) {
   1176   hover_animation_->SetTweenType(gfx::Tween::EASE_IN);
   1177   hover_animation_->Hide();
   1178   return FALSE;
   1179 }
   1180 
   1181 // static
   1182 void TabRendererGtk::InitResources() {
   1183   if (initialized_)
   1184     return;
   1185 
   1186   // Grab the pixel sizes of our masking images.
   1187   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
   1188   GdkPixbuf* tab_active_l = rb.GetNativeImageNamed(
   1189       IDR_TAB_ACTIVE_LEFT).ToGdkPixbuf();
   1190   tab_active_l_width_ = gdk_pixbuf_get_width(tab_active_l);
   1191   tab_active_l_height_ = gdk_pixbuf_get_height(tab_active_l);
   1192 
   1193   GdkPixbuf* tab_inactive_l = rb.GetNativeImageNamed(
   1194       IDR_TAB_INACTIVE_LEFT).ToGdkPixbuf();
   1195   tab_inactive_l_height_ = gdk_pixbuf_get_height(tab_inactive_l);
   1196 
   1197   GdkPixbuf* tab_close = rb.GetNativeImageNamed(IDR_CLOSE_1).ToGdkPixbuf();
   1198   close_button_width_ = gdk_pixbuf_get_width(tab_close);
   1199   close_button_height_ = gdk_pixbuf_get_height(tab_close);
   1200 
   1201   const gfx::Font& base_font = rb.GetFont(ui::ResourceBundle::BaseFont);
   1202   title_font_ = new gfx::Font(base_font.GetFontName(), kFontPixelSize);
   1203   title_font_height_ = title_font_->GetHeight();
   1204 
   1205   initialized_ = true;
   1206 }
   1207