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