Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/tabs/tab_renderer_gtk.h"
      6 
      7 #include <algorithm>
      8 #include <utility>
      9 
     10 #include "base/utf_string_conversions.h"
     11 #include "chrome/browser/defaults.h"
     12 #include "chrome/browser/extensions/extension_tab_helper.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/browser/ui/browser.h"
     15 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
     16 #include "chrome/browser/ui/gtk/custom_button.h"
     17 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     18 #include "chrome/browser/ui/gtk/gtk_util.h"
     19 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     20 #include "content/browser/tab_contents/tab_contents.h"
     21 #include "content/common/notification_service.h"
     22 #include "grit/app_resources.h"
     23 #include "grit/generated_resources.h"
     24 #include "grit/theme_resources.h"
     25 #include "ui/base/animation/slide_animation.h"
     26 #include "ui/base/animation/throb_animation.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 #include "ui/base/resource/resource_bundle.h"
     29 #include "ui/gfx/canvas_skia_paint.h"
     30 #include "ui/gfx/favicon_size.h"
     31 #include "ui/gfx/platform_font_gtk.h"
     32 #include "ui/gfx/skbitmap_operations.h"
     33 
     34 namespace {
     35 
     36 const int kFontPixelSize = 12;
     37 const int kLeftPadding = 16;
     38 const int kTopPadding = 6;
     39 const int kRightPadding = 15;
     40 const int kBottomPadding = 5;
     41 const int kDropShadowHeight = 2;
     42 const int kFaviconTitleSpacing = 4;
     43 const int kTitleCloseButtonSpacing = 5;
     44 const int kStandardTitleWidth = 175;
     45 const int kDropShadowOffset = 2;
     46 const int kInactiveTabBackgroundOffsetY = 15;
     47 
     48 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
     49 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
     50 // is rendered as a normal tab. This is done to avoid having the title
     51 // immediately disappear when transitioning a tab from normal to mini-tab.
     52 const int kMiniTabRendererAsNormalTabWidth =
     53     browser_defaults::kMiniTabWidth + 30;
     54 
     55 // The tab images are designed to overlap the toolbar by 1 pixel. For now we
     56 // don't actually overlap the toolbar, so this is used to know how many pixels
     57 // at the bottom of the tab images are to be ignored.
     58 const int kToolbarOverlap = 1;
     59 
     60 // How long the hover state takes.
     61 const int kHoverDurationMs = 90;
     62 
     63 // How opaque to make the hover state (out of 1).
     64 const double kHoverOpacity = 0.33;
     65 
     66 // Max opacity for the mini-tab title change animation.
     67 const double kMiniTitleChangeThrobOpacity = 0.75;
     68 
     69 // Duration for when the title of an inactive mini-tab changes.
     70 const int kMiniTitleChangeThrobDuration = 1000;
     71 
     72 const SkScalar kTabCapWidth = 15;
     73 const SkScalar kTabTopCurveWidth = 4;
     74 const SkScalar kTabBottomCurveWidth = 3;
     75 
     76 // The vertical and horizontal offset used to position the close button
     77 // in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about.
     78 const int kCloseButtonVertFuzz = 0;
     79 const int kCloseButtonHorzFuzz = 5;
     80 
     81 SkBitmap* crashed_favicon = NULL;
     82 
     83 // Gets the bounds of |widget| relative to |parent|.
     84 gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent,
     85                                           GtkWidget* widget) {
     86   gfx::Point parent_pos = gtk_util::GetWidgetScreenPosition(parent);
     87   gfx::Point widget_pos = gtk_util::GetWidgetScreenPosition(widget);
     88   return gfx::Rect(widget_pos.x() - parent_pos.x(),
     89                    widget_pos.y() - parent_pos.y(),
     90                    widget->allocation.width, widget->allocation.height);
     91 }
     92 
     93 }  // namespace
     94 
     95 TabRendererGtk::LoadingAnimation::Data::Data(
     96     ui::ThemeProvider* theme_provider) {
     97   // The loading animation image is a strip of states. Each state must be
     98   // square, so the height must divide the width evenly.
     99   loading_animation_frames = theme_provider->GetBitmapNamed(IDR_THROBBER);
    100   DCHECK(loading_animation_frames);
    101   DCHECK_EQ(loading_animation_frames->width() %
    102             loading_animation_frames->height(), 0);
    103   loading_animation_frame_count =
    104       loading_animation_frames->width() /
    105       loading_animation_frames->height();
    106 
    107   waiting_animation_frames =
    108       theme_provider->GetBitmapNamed(IDR_THROBBER_WAITING);
    109   DCHECK(waiting_animation_frames);
    110   DCHECK_EQ(waiting_animation_frames->width() %
    111             waiting_animation_frames->height(), 0);
    112   waiting_animation_frame_count =
    113       waiting_animation_frames->width() /
    114       waiting_animation_frames->height();
    115 
    116   waiting_to_loading_frame_count_ratio =
    117       waiting_animation_frame_count /
    118       loading_animation_frame_count;
    119   // TODO(beng): eventually remove this when we have a proper themeing system.
    120   //             themes not supporting IDR_THROBBER_WAITING are causing this
    121   //             value to be 0 which causes DIV0 crashes. The value of 5
    122   //             matches the current bitmaps in our source.
    123   if (waiting_to_loading_frame_count_ratio == 0)
    124     waiting_to_loading_frame_count_ratio = 5;
    125 }
    126 
    127 TabRendererGtk::LoadingAnimation::Data::Data(
    128     int loading, int waiting, int waiting_to_loading)
    129     : waiting_animation_frames(NULL),
    130       loading_animation_frames(NULL),
    131       loading_animation_frame_count(loading),
    132       waiting_animation_frame_count(waiting),
    133       waiting_to_loading_frame_count_ratio(waiting_to_loading) {
    134 }
    135 
    136 bool TabRendererGtk::initialized_ = false;
    137 TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0};
    138 TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0};
    139 TabRendererGtk::TabImage TabRendererGtk::tab_alpha_ = {0};
    140 gfx::Font* TabRendererGtk::title_font_ = NULL;
    141 int TabRendererGtk::title_font_height_ = 0;
    142 int TabRendererGtk::close_button_width_ = 0;
    143 int TabRendererGtk::close_button_height_ = 0;
    144 SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK;
    145 SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64);
    146 
    147 ////////////////////////////////////////////////////////////////////////////////
    148 // TabRendererGtk::LoadingAnimation, public:
    149 //
    150 TabRendererGtk::LoadingAnimation::LoadingAnimation(
    151     ui::ThemeProvider* theme_provider)
    152     : data_(new Data(theme_provider)),
    153       theme_service_(theme_provider),
    154       animation_state_(ANIMATION_NONE),
    155       animation_frame_(0) {
    156   registrar_.Add(this,
    157                  NotificationType::BROWSER_THEME_CHANGED,
    158                  NotificationService::AllSources());
    159 }
    160 
    161 TabRendererGtk::LoadingAnimation::LoadingAnimation(
    162     const LoadingAnimation::Data& data)
    163     : data_(new Data(data)),
    164       theme_service_(NULL),
    165       animation_state_(ANIMATION_NONE),
    166       animation_frame_(0) {
    167 }
    168 
    169 TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
    170 
    171 bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation(
    172     AnimationState animation_state) {
    173   bool has_changed = false;
    174   if (animation_state_ != animation_state) {
    175     // The waiting animation is the reverse of the loading animation, but at a
    176     // different rate - the following reverses and scales the animation_frame_
    177     // so that the frame is at an equivalent position when going from one
    178     // animation to the other.
    179     if (animation_state_ == ANIMATION_WAITING &&
    180         animation_state == ANIMATION_LOADING) {
    181       animation_frame_ = data_->loading_animation_frame_count -
    182           (animation_frame_ / data_->waiting_to_loading_frame_count_ratio);
    183     }
    184     animation_state_ = animation_state;
    185     has_changed = true;
    186   }
    187 
    188   if (animation_state_ != ANIMATION_NONE) {
    189     animation_frame_ = (animation_frame_ + 1) %
    190                        ((animation_state_ == ANIMATION_WAITING) ?
    191                          data_->waiting_animation_frame_count :
    192                          data_->loading_animation_frame_count);
    193     has_changed = true;
    194   } else {
    195     animation_frame_ = 0;
    196   }
    197   return has_changed;
    198 }
    199 
    200 void TabRendererGtk::LoadingAnimation::Observe(
    201     NotificationType type,
    202     const NotificationSource& source,
    203     const NotificationDetails& details) {
    204   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
    205   data_.reset(new Data(theme_service_));
    206 }
    207 
    208 ////////////////////////////////////////////////////////////////////////////////
    209 // FaviconCrashAnimation
    210 //
    211 //  A custom animation subclass to manage the favicon crash animation.
    212 class TabRendererGtk::FaviconCrashAnimation : public ui::LinearAnimation,
    213                                               public ui::AnimationDelegate {
    214  public:
    215   explicit FaviconCrashAnimation(TabRendererGtk* target)
    216       : ALLOW_THIS_IN_INITIALIZER_LIST(ui::LinearAnimation(1000, 25, this)),
    217         target_(target) {
    218   }
    219   virtual ~FaviconCrashAnimation() {}
    220 
    221   // ui::Animation overrides:
    222   virtual void AnimateToState(double state) {
    223     const double kHidingOffset = 27;
    224 
    225     if (state < .5) {
    226       target_->SetFaviconHidingOffset(
    227           static_cast<int>(floor(kHidingOffset * 2.0 * state)));
    228     } else {
    229       target_->DisplayCrashedFavicon();
    230       target_->SetFaviconHidingOffset(
    231           static_cast<int>(
    232               floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
    233     }
    234   }
    235 
    236   // ui::AnimationDelegate overrides:
    237   virtual void AnimationCanceled(const ui::Animation* animation) {
    238     target_->SetFaviconHidingOffset(0);
    239   }
    240 
    241  private:
    242   TabRendererGtk* target_;
    243 
    244   DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
    245 };
    246 
    247 ////////////////////////////////////////////////////////////////////////////////
    248 // TabRendererGtk, public:
    249 
    250 TabRendererGtk::TabRendererGtk(ui::ThemeProvider* theme_provider)
    251     : showing_icon_(false),
    252       showing_close_button_(false),
    253       favicon_hiding_offset_(0),
    254       should_display_crashed_favicon_(false),
    255       loading_animation_(theme_provider),
    256       background_offset_x_(0),
    257       background_offset_y_(kInactiveTabBackgroundOffsetY),
    258       close_button_color_(0) {
    259   InitResources();
    260 
    261   tab_.Own(gtk_fixed_new());
    262   gtk_widget_set_app_paintable(tab_.get(), TRUE);
    263   g_signal_connect(tab_.get(), "expose-event",
    264                    G_CALLBACK(OnExposeEventThunk), this);
    265   g_signal_connect(tab_.get(), "size-allocate",
    266                    G_CALLBACK(OnSizeAllocateThunk), this);
    267   close_button_.reset(MakeCloseButton());
    268   gtk_widget_show(tab_.get());
    269 
    270   hover_animation_.reset(new ui::SlideAnimation(this));
    271   hover_animation_->SetSlideDuration(kHoverDurationMs);
    272 
    273   registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
    274                  NotificationService::AllSources());
    275 }
    276 
    277 TabRendererGtk::~TabRendererGtk() {
    278   tab_.Destroy();
    279   for (BitmapCache::iterator it = cached_bitmaps_.begin();
    280        it != cached_bitmaps_.end(); ++it) {
    281     delete it->second.bitmap;
    282   }
    283 }
    284 
    285 void TabRendererGtk::UpdateData(TabContents* contents,
    286                                 bool app,
    287                                 bool loading_only) {
    288   DCHECK(contents);
    289   theme_service_ = GtkThemeService::GetFrom(contents->profile());
    290 
    291   if (!loading_only) {
    292     data_.title = contents->GetTitle();
    293     data_.incognito = contents->profile()->IsOffTheRecord();
    294     data_.crashed = contents->is_crashed();
    295 
    296     SkBitmap* app_icon =
    297         TabContentsWrapper::GetCurrentWrapperForContents(contents)->
    298             extension_tab_helper()->GetExtensionAppIcon();
    299     if (app_icon)
    300       data_.favicon = *app_icon;
    301     else
    302       data_.favicon = contents->GetFavicon();
    303 
    304     data_.app = app;
    305     // This is kind of a hacky way to determine whether our icon is the default
    306     // favicon. But the plumbing that would be necessary to do it right would
    307     // be a good bit of work and would sully code for other platforms which
    308     // don't care to custom-theme the favicon. Hopefully the default favicon
    309     // will eventually be chromium-themable and this code will go away.
    310     data_.is_default_favicon =
    311         (data_.favicon.pixelRef() ==
    312         ResourceBundle::GetSharedInstance().GetBitmapNamed(
    313             IDR_DEFAULT_FAVICON)->pixelRef());
    314   }
    315 
    316   // Loading state also involves whether we show the favicon, since that's where
    317   // we display the throbber.
    318   data_.loading = contents->is_loading();
    319   data_.show_icon = contents->ShouldDisplayFavicon();
    320 }
    321 
    322 void TabRendererGtk::UpdateFromModel() {
    323   // Force a layout, since the tab may have grown a favicon.
    324   Layout();
    325   SchedulePaint();
    326 
    327   if (data_.crashed) {
    328     if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
    329       StartCrashAnimation();
    330   } else {
    331     if (IsPerformingCrashAnimation())
    332       StopCrashAnimation();
    333     ResetCrashedFavicon();
    334   }
    335 }
    336 
    337 void TabRendererGtk::SetBlocked(bool blocked) {
    338   if (data_.blocked == blocked)
    339     return;
    340   data_.blocked = blocked;
    341   // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
    342 }
    343 
    344 bool TabRendererGtk::is_blocked() const {
    345   return data_.blocked;
    346 }
    347 
    348 bool TabRendererGtk::IsSelected() const {
    349   return true;
    350 }
    351 
    352 bool TabRendererGtk::IsVisible() const {
    353   return GTK_WIDGET_FLAGS(tab_.get()) & GTK_VISIBLE;
    354 }
    355 
    356 void TabRendererGtk::SetVisible(bool visible) const {
    357   if (visible) {
    358     gtk_widget_show(tab_.get());
    359     if (data_.mini)
    360       gtk_widget_show(close_button_->widget());
    361   } else {
    362     gtk_widget_hide_all(tab_.get());
    363   }
    364 }
    365 
    366 bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
    367   return loading_animation_.ValidateLoadingAnimation(animation_state);
    368 }
    369 
    370 void TabRendererGtk::PaintFaviconArea(GdkEventExpose* event) {
    371   DCHECK(ShouldShowIcon());
    372 
    373   // The paint area is the favicon bounds, but we're painting into the gdk
    374   // window belonging to the tabstrip.  So the coordinates are relative to the
    375   // top left of the tab strip.
    376   event->area.x = x() + favicon_bounds_.x();
    377   event->area.y = y() + favicon_bounds_.y();
    378   event->area.width = favicon_bounds_.width();
    379   event->area.height = favicon_bounds_.height();
    380   gfx::CanvasSkiaPaint canvas(event, false);
    381 
    382   // The actual paint methods expect 0, 0 to be the tab top left (see
    383   // PaintTab).
    384   canvas.TranslateInt(x(), y());
    385 
    386   // Paint the background behind the favicon.
    387   int theme_id;
    388   int offset_y = 0;
    389   if (IsSelected()) {
    390     theme_id = IDR_THEME_TOOLBAR;
    391   } else {
    392     if (!data_.incognito) {
    393       theme_id = IDR_THEME_TAB_BACKGROUND;
    394     } else {
    395       theme_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
    396     }
    397     if (!theme_service_->HasCustomImage(theme_id))
    398       offset_y = background_offset_y_;
    399   }
    400   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(theme_id);
    401   canvas.TileImageInt(*tab_bg,
    402       x() + favicon_bounds_.x(), offset_y + favicon_bounds_.y(),
    403       favicon_bounds_.x(), favicon_bounds_.y(),
    404       favicon_bounds_.width(), favicon_bounds_.height());
    405 
    406   if (!IsSelected()) {
    407     double throb_value = GetThrobValue();
    408     if (throb_value > 0) {
    409       SkRect bounds;
    410       bounds.set(favicon_bounds_.x(), favicon_bounds_.y(),
    411           favicon_bounds_.right(), favicon_bounds_.bottom());
    412       canvas.saveLayerAlpha(&bounds, static_cast<int>(throb_value * 0xff),
    413                             SkCanvas::kARGB_ClipLayer_SaveFlag);
    414       canvas.drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
    415       SkBitmap* active_bg = theme_service_->GetBitmapNamed(IDR_THEME_TOOLBAR);
    416       canvas.TileImageInt(*active_bg,
    417           x() + favicon_bounds_.x(), favicon_bounds_.y(),
    418           favicon_bounds_.x(), favicon_bounds_.y(),
    419           favicon_bounds_.width(), favicon_bounds_.height());
    420       canvas.restore();
    421     }
    422   }
    423 
    424   // Now paint the icon.
    425   PaintIcon(&canvas);
    426 }
    427 
    428 bool TabRendererGtk::ShouldShowIcon() const {
    429   if (mini() && height() >= GetMinimumUnselectedSize().height()) {
    430     return true;
    431   } else if (!data_.show_icon) {
    432     return false;
    433   } else if (IsSelected()) {
    434     // The selected tab clips favicon before close button.
    435     return IconCapacity() >= 2;
    436   }
    437   // Non-selected tabs clip close button before favicon.
    438   return IconCapacity() >= 1;
    439 }
    440 
    441 // static
    442 gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
    443   InitResources();
    444 
    445   gfx::Size minimum_size;
    446   minimum_size.set_width(kLeftPadding + kRightPadding);
    447   // Since we use bitmap images, the real minimum height of the image is
    448   // defined most accurately by the height of the end cap images.
    449   minimum_size.set_height(tab_active_.image_l->height() - kToolbarOverlap);
    450   return minimum_size;
    451 }
    452 
    453 // static
    454 gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
    455   gfx::Size minimum_size = GetMinimumUnselectedSize();
    456   minimum_size.set_width(kLeftPadding + kFaviconSize + kRightPadding);
    457   return minimum_size;
    458 }
    459 
    460 // static
    461 gfx::Size TabRendererGtk::GetStandardSize() {
    462   gfx::Size standard_size = GetMinimumUnselectedSize();
    463   standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
    464   return standard_size;
    465 }
    466 
    467 // static
    468 int TabRendererGtk::GetMiniWidth() {
    469   return browser_defaults::kMiniTabWidth;
    470 }
    471 
    472 // static
    473 int TabRendererGtk::GetContentHeight() {
    474   // The height of the content of the Tab is the largest of the favicon,
    475   // the title text and the close button graphic.
    476   int content_height = std::max(kFaviconSize, title_font_height_);
    477   return std::max(content_height, close_button_height_);
    478 }
    479 
    480 // static
    481 void TabRendererGtk::LoadTabImages() {
    482   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    483 
    484   tab_alpha_.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT);
    485   tab_alpha_.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT);
    486 
    487   tab_active_.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT);
    488   tab_active_.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER);
    489   tab_active_.image_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT);
    490   tab_active_.l_width = tab_active_.image_l->width();
    491   tab_active_.r_width = tab_active_.image_r->width();
    492 
    493   tab_inactive_.image_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT);
    494   tab_inactive_.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER);
    495   tab_inactive_.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT);
    496   tab_inactive_.l_width = tab_inactive_.image_l->width();
    497   tab_inactive_.r_width = tab_inactive_.image_r->width();
    498 
    499   close_button_width_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->width();
    500   close_button_height_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->height();
    501 }
    502 
    503 // static
    504 void TabRendererGtk::SetSelectedTitleColor(SkColor color) {
    505   selected_title_color_ = color;
    506 }
    507 
    508 // static
    509 void TabRendererGtk::SetUnselectedTitleColor(SkColor color) {
    510   unselected_title_color_ = color;
    511 }
    512 
    513 gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
    514   // The tabstrip widget is a windowless widget so the tab widget's allocation
    515   // is relative to the browser titlebar.  We need the bounds relative to the
    516   // tabstrip.
    517   gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
    518   bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
    519   return bounds;
    520 }
    521 
    522 gfx::Rect TabRendererGtk::GetRequisition() const {
    523   return gfx::Rect(requisition_.x(), requisition_.y(),
    524                    requisition_.width(), requisition_.height());
    525 }
    526 
    527 void TabRendererGtk::StartMiniTabTitleAnimation() {
    528   if (!mini_title_animation_.get()) {
    529     mini_title_animation_.reset(new ui::ThrobAnimation(this));
    530     mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
    531   }
    532 
    533   if (!mini_title_animation_->is_animating()) {
    534     mini_title_animation_->StartThrobbing(2);
    535   } else if (mini_title_animation_->cycles_remaining() <= 2) {
    536     // The title changed while we're already animating. Add at most one more
    537     // cycle. This is done in an attempt to smooth out pages that continuously
    538     // change the title.
    539     mini_title_animation_->set_cycles_remaining(
    540         mini_title_animation_->cycles_remaining() + 2);
    541   }
    542 }
    543 
    544 void TabRendererGtk::StopMiniTabTitleAnimation() {
    545   if (mini_title_animation_.get())
    546     mini_title_animation_->Stop();
    547 }
    548 
    549 void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
    550   requisition_ = bounds;
    551   gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
    552 }
    553 
    554 void TabRendererGtk::Observe(NotificationType type,
    555                              const NotificationSource& source,
    556                              const NotificationDetails& details) {
    557   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
    558 
    559   // Clear our cache when we receive a theme change notification because it
    560   // contains cached bitmaps based off the previous theme.
    561   for (BitmapCache::iterator it = cached_bitmaps_.begin();
    562        it != cached_bitmaps_.end(); ++it) {
    563     delete it->second.bitmap;
    564   }
    565   cached_bitmaps_.clear();
    566 }
    567 
    568 ////////////////////////////////////////////////////////////////////////////////
    569 // TabRendererGtk, protected:
    570 
    571 string16 TabRendererGtk::GetTitle() const {
    572   return data_.title;
    573 }
    574 
    575 ///////////////////////////////////////////////////////////////////////////////
    576 // TabRendererGtk, ui::AnimationDelegate implementation:
    577 
    578 void TabRendererGtk::AnimationProgressed(const ui::Animation* animation) {
    579   gtk_widget_queue_draw(tab_.get());
    580 }
    581 
    582 void TabRendererGtk::AnimationCanceled(const ui::Animation* animation) {
    583   AnimationEnded(animation);
    584 }
    585 
    586 void TabRendererGtk::AnimationEnded(const ui::Animation* animation) {
    587   gtk_widget_queue_draw(tab_.get());
    588 }
    589 
    590 ////////////////////////////////////////////////////////////////////////////////
    591 // TabRendererGtk, private:
    592 
    593 void TabRendererGtk::StartCrashAnimation() {
    594   if (!crash_animation_.get())
    595     crash_animation_.reset(new FaviconCrashAnimation(this));
    596   crash_animation_->Stop();
    597   crash_animation_->Start();
    598 }
    599 
    600 void TabRendererGtk::StopCrashAnimation() {
    601   if (!crash_animation_.get())
    602     return;
    603   crash_animation_->Stop();
    604 }
    605 
    606 bool TabRendererGtk::IsPerformingCrashAnimation() const {
    607   return crash_animation_.get() && crash_animation_->is_animating();
    608 }
    609 
    610 void TabRendererGtk::SetFaviconHidingOffset(int offset) {
    611   favicon_hiding_offset_ = offset;
    612   SchedulePaint();
    613 }
    614 
    615 void TabRendererGtk::DisplayCrashedFavicon() {
    616   should_display_crashed_favicon_ = true;
    617 }
    618 
    619 void TabRendererGtk::ResetCrashedFavicon() {
    620   should_display_crashed_favicon_ = false;
    621 }
    622 
    623 void TabRendererGtk::Paint(gfx::Canvas* canvas) {
    624   // Don't paint if we're narrower than we can render correctly. (This should
    625   // only happen during animations).
    626   if (width() < GetMinimumUnselectedSize().width() && !mini())
    627     return;
    628 
    629   // See if the model changes whether the icons should be painted.
    630   const bool show_icon = ShouldShowIcon();
    631   const bool show_close_button = ShouldShowCloseBox();
    632   if (show_icon != showing_icon_ ||
    633       show_close_button != showing_close_button_)
    634     Layout();
    635 
    636   PaintTabBackground(canvas);
    637 
    638   if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
    639     PaintTitle(canvas);
    640 
    641   if (show_icon)
    642     PaintIcon(canvas);
    643 }
    644 
    645 SkBitmap TabRendererGtk::PaintBitmap() {
    646   gfx::CanvasSkia canvas(width(), height(), false);
    647   Paint(&canvas);
    648   return canvas.ExtractBitmap();
    649 }
    650 
    651 cairo_surface_t* TabRendererGtk::PaintToSurface() {
    652   gfx::CanvasSkia canvas(width(), height(), false);
    653   Paint(&canvas);
    654   return cairo_surface_reference(cairo_get_target(canvas.beginPlatformPaint()));
    655 }
    656 
    657 void TabRendererGtk::SchedulePaint() {
    658   gtk_widget_queue_draw(tab_.get());
    659 }
    660 
    661 gfx::Rect TabRendererGtk::GetLocalBounds() {
    662   return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
    663 }
    664 
    665 void TabRendererGtk::Layout() {
    666   gfx::Rect local_bounds = GetLocalBounds();
    667   if (local_bounds.IsEmpty())
    668     return;
    669   local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
    670 
    671   // Figure out who is tallest.
    672   int content_height = GetContentHeight();
    673 
    674   // Size the Favicon.
    675   showing_icon_ = ShouldShowIcon();
    676   if (showing_icon_) {
    677     int favicon_top = kTopPadding + (content_height - kFaviconSize) / 2;
    678     favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
    679                             kFaviconSize, kFaviconSize);
    680     if ((mini() || data_.animating_mini_change) &&
    681         bounds_.width() < kMiniTabRendererAsNormalTabWidth) {
    682       int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
    683       int ideal_delta = bounds_.width() - GetMiniWidth();
    684       if (ideal_delta < mini_delta) {
    685         int ideal_x = (GetMiniWidth() - kFaviconSize) / 2;
    686         int x = favicon_bounds_.x() + static_cast<int>(
    687             (1 - static_cast<float>(ideal_delta) /
    688              static_cast<float>(mini_delta)) *
    689             (ideal_x - favicon_bounds_.x()));
    690         favicon_bounds_.set_x(x);
    691       }
    692     }
    693   } else {
    694     favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
    695   }
    696 
    697   // Size the Close button.
    698   showing_close_button_ = ShouldShowCloseBox();
    699   if (showing_close_button_) {
    700     int close_button_top =
    701         kTopPadding + kCloseButtonVertFuzz +
    702         (content_height - close_button_height_) / 2;
    703     close_button_bounds_.SetRect(local_bounds.width() + kCloseButtonHorzFuzz,
    704                                  close_button_top, close_button_width_,
    705                                  close_button_height_);
    706 
    707     // If the close button color has changed, generate a new one.
    708     if (theme_service_) {
    709       SkColor tab_text_color =
    710         theme_service_->GetColor(ThemeService::COLOR_TAB_TEXT);
    711       if (!close_button_color_ || tab_text_color != close_button_color_) {
    712         close_button_color_ = tab_text_color;
    713         ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    714         close_button_->SetBackground(close_button_color_,
    715             rb.GetBitmapNamed(IDR_TAB_CLOSE),
    716             rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK));
    717       }
    718     }
    719   } else {
    720     close_button_bounds_.SetRect(0, 0, 0, 0);
    721   }
    722 
    723   if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
    724     // Size the Title text to fill the remaining space.
    725     int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
    726     int title_top = kTopPadding;
    727 
    728     // If the user has big fonts, the title will appear rendered too far down
    729     // on the y-axis if we use the regular top padding, so we need to adjust it
    730     // so that the text appears centered.
    731     gfx::Size minimum_size = GetMinimumUnselectedSize();
    732     int text_height = title_top + title_font_height_ + kBottomPadding;
    733     if (text_height > minimum_size.height())
    734       title_top -= (text_height - minimum_size.height()) / 2;
    735 
    736     int title_width;
    737     if (close_button_bounds_.width() && close_button_bounds_.height()) {
    738       title_width = std::max(close_button_bounds_.x() -
    739                              kTitleCloseButtonSpacing - title_left, 0);
    740     } else {
    741       title_width = std::max(local_bounds.width() - title_left, 0);
    742     }
    743     title_bounds_.SetRect(title_left, title_top, title_width, content_height);
    744   }
    745 
    746   favicon_bounds_.set_x(
    747       gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
    748   close_button_bounds_.set_x(
    749       gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
    750   title_bounds_.set_x(
    751       gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
    752 
    753   MoveCloseButtonWidget();
    754 }
    755 
    756 void TabRendererGtk::MoveCloseButtonWidget() {
    757   if (!close_button_bounds_.IsEmpty()) {
    758     gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
    759                    close_button_bounds_.x(), close_button_bounds_.y());
    760     gtk_widget_show(close_button_->widget());
    761   } else {
    762     gtk_widget_hide(close_button_->widget());
    763   }
    764 }
    765 
    766 SkBitmap* TabRendererGtk::GetMaskedBitmap(const SkBitmap* mask,
    767     const SkBitmap* background, int bg_offset_x, int bg_offset_y) {
    768   // We store a bitmap for each mask + background pair (4 total bitmaps).  We
    769   // replace the cached image if the tab has moved relative to the background.
    770   BitmapCache::iterator it = cached_bitmaps_.find(std::make_pair(mask,
    771                                                                  background));
    772   if (it != cached_bitmaps_.end()) {
    773     if (it->second.bg_offset_x == bg_offset_x &&
    774         it->second.bg_offset_y == bg_offset_y) {
    775       return it->second.bitmap;
    776     }
    777     // The background offset changed so we should re-render with the new
    778     // offsets.
    779     delete it->second.bitmap;
    780   }
    781   SkBitmap image = SkBitmapOperations::CreateTiledBitmap(
    782       *background, bg_offset_x, bg_offset_y, mask->width(),
    783       height() + kToolbarOverlap);
    784   CachedBitmap bitmap = {
    785     bg_offset_x,
    786     bg_offset_y,
    787     new SkBitmap(SkBitmapOperations::CreateMaskedBitmap(image, *mask))
    788   };
    789   cached_bitmaps_[std::make_pair(mask, background)] = bitmap;
    790   return bitmap.bitmap;
    791 }
    792 
    793 void TabRendererGtk::PaintTab(GdkEventExpose* event) {
    794   gfx::CanvasSkiaPaint canvas(event, false);
    795   if (canvas.is_empty())
    796     return;
    797 
    798   // The tab is rendered into a windowless widget whose offset is at the
    799   // coordinate event->area.  Translate by these offsets so we can render at
    800   // (0,0) to match Windows' rendering metrics.
    801   canvas.TranslateInt(event->area.x, event->area.y);
    802 
    803   // Save the original x offset so we can position background images properly.
    804   background_offset_x_ = event->area.x;
    805 
    806   Paint(&canvas);
    807 }
    808 
    809 void TabRendererGtk::PaintTitle(gfx::Canvas* canvas) {
    810   // Paint the Title.
    811   string16 title = data_.title;
    812   if (title.empty()) {
    813     title = data_.loading ?
    814         l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
    815         TabContentsWrapper::GetDefaultTitle();
    816   } else {
    817     Browser::FormatTitleForDisplay(&title);
    818   }
    819 
    820   SkColor title_color = IsSelected() ? selected_title_color_
    821                                      : unselected_title_color_;
    822   canvas->DrawStringInt(title, *title_font_, title_color,
    823                         title_bounds_.x(), title_bounds_.y(),
    824                         title_bounds_.width(), title_bounds_.height());
    825 }
    826 
    827 void TabRendererGtk::PaintIcon(gfx::Canvas* canvas) {
    828   if (loading_animation_.animation_state() != ANIMATION_NONE) {
    829     PaintLoadingAnimation(canvas);
    830   } else {
    831     canvas->Save();
    832     canvas->ClipRectInt(0, 0, width(), height() - kFaviconTitleSpacing);
    833     if (should_display_crashed_favicon_) {
    834       canvas->DrawBitmapInt(*crashed_favicon, 0, 0,
    835                             crashed_favicon->width(),
    836                             crashed_favicon->height(),
    837                             favicon_bounds_.x(),
    838                             favicon_bounds_.y() + favicon_hiding_offset_,
    839                             kFaviconSize, kFaviconSize,
    840                             true);
    841     } else {
    842       if (!data_.favicon.isNull()) {
    843         if (data_.is_default_favicon && theme_service_->UseGtkTheme()) {
    844           GdkPixbuf* favicon = GtkThemeService::GetDefaultFavicon(true);
    845           canvas->AsCanvasSkia()->DrawGdkPixbuf(
    846               favicon, favicon_bounds_.x(),
    847               favicon_bounds_.y() + favicon_hiding_offset_);
    848         } else {
    849           // If the favicon is an app icon, it is allowed to be drawn slightly
    850           // larger than the standard favicon.
    851           int faviconHeightOffset = data_.app ? -2 : 0;
    852           int faviconWidthDelta = data_.app ?
    853               data_.favicon.width() - kFaviconSize : 0;
    854           int faviconHeightDelta = data_.app ?
    855               data_.favicon.height() - kFaviconSize : 0;
    856 
    857           // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch
    858           // to using that class to render the favicon).
    859           canvas->DrawBitmapInt(data_.favicon, 0, 0,
    860                                 data_.favicon.width(),
    861                                 data_.favicon.height(),
    862                                 favicon_bounds_.x() - faviconWidthDelta/2,
    863                                 favicon_bounds_.y() + faviconHeightOffset
    864                                     - faviconHeightDelta/2
    865                                     + favicon_hiding_offset_,
    866                                 kFaviconSize + faviconWidthDelta,
    867                                 kFaviconSize + faviconHeightDelta,
    868                                 true);
    869         }
    870       }
    871     }
    872     canvas->Restore();
    873   }
    874 }
    875 
    876 void TabRendererGtk::PaintTabBackground(gfx::Canvas* canvas) {
    877   if (IsSelected()) {
    878     PaintActiveTabBackground(canvas);
    879   } else {
    880     PaintInactiveTabBackground(canvas);
    881 
    882     double throb_value = GetThrobValue();
    883     if (throb_value > 0) {
    884       canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
    885                              gfx::Rect(width(), height()));
    886       canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
    887                                        SkXfermode::kClear_Mode);
    888       PaintActiveTabBackground(canvas);
    889       canvas->Restore();
    890     }
    891   }
    892 }
    893 
    894 void TabRendererGtk::PaintInactiveTabBackground(gfx::Canvas* canvas) {
    895 
    896   // The tab image needs to be lined up with the background image
    897   // so that it feels partially transparent.
    898   int offset_x = background_offset_x_;
    899 
    900   int tab_id = data_.incognito ?
    901       IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
    902 
    903   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(tab_id);
    904 
    905   // If the theme is providing a custom background image, then its top edge
    906   // should be at the top of the tab. Otherwise, we assume that the background
    907   // image is a composited foreground + frame image.
    908   int offset_y = theme_service_->HasCustomImage(tab_id) ?
    909       0 : background_offset_y_;
    910 
    911   // Draw left edge.
    912   SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x,
    913                                       offset_y);
    914   canvas->DrawBitmapInt(*theme_l, 0, 0);
    915 
    916   // Draw right edge.
    917   SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
    918       offset_x + width() - tab_active_.r_width, offset_y);
    919 
    920   canvas->DrawBitmapInt(*theme_r, width() - theme_r->width(), 0);
    921 
    922   // Draw center.
    923   canvas->TileImageInt(*tab_bg,
    924       offset_x + tab_active_.l_width, kDropShadowOffset + offset_y,
    925       tab_active_.l_width, 2,
    926       width() - tab_active_.l_width - tab_active_.r_width, height() - 2);
    927 
    928   canvas->DrawBitmapInt(*tab_inactive_.image_l, 0, 0);
    929   canvas->TileImageInt(*tab_inactive_.image_c, tab_inactive_.l_width, 0,
    930       width() - tab_inactive_.l_width - tab_inactive_.r_width, height());
    931   canvas->DrawBitmapInt(*tab_inactive_.image_r,
    932       width() - tab_inactive_.r_width, 0);
    933 }
    934 
    935 void TabRendererGtk::PaintActiveTabBackground(gfx::Canvas* canvas) {
    936   int offset_x = background_offset_x_;
    937 
    938   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(IDR_THEME_TOOLBAR);
    939 
    940   // Draw left edge.
    941   SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x, 0);
    942   canvas->DrawBitmapInt(*theme_l, 0, 0);
    943 
    944   // Draw right edge.
    945   SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
    946       offset_x + width() - tab_active_.r_width, 0);
    947   canvas->DrawBitmapInt(*theme_r, width() - tab_active_.r_width, 0);
    948 
    949   // Draw center.
    950   canvas->TileImageInt(*tab_bg,
    951       offset_x + tab_active_.l_width, kDropShadowHeight,
    952       tab_active_.l_width, kDropShadowHeight,
    953       width() - tab_active_.l_width - tab_active_.r_width,
    954       height() - kDropShadowHeight);
    955 
    956   canvas->DrawBitmapInt(*tab_active_.image_l, 0, 0);
    957   canvas->TileImageInt(*tab_active_.image_c, tab_active_.l_width, 0,
    958       width() - tab_active_.l_width - tab_active_.r_width, height());
    959   canvas->DrawBitmapInt(*tab_active_.image_r, width() - tab_active_.r_width, 0);
    960 }
    961 
    962 void TabRendererGtk::PaintLoadingAnimation(gfx::Canvas* canvas) {
    963   const SkBitmap* frames =
    964       (loading_animation_.animation_state() == ANIMATION_WAITING) ?
    965       loading_animation_.waiting_animation_frames() :
    966       loading_animation_.loading_animation_frames();
    967   const int image_size = frames->height();
    968   const int image_offset = loading_animation_.animation_frame() * image_size;
    969   DCHECK(image_size == favicon_bounds_.height());
    970   DCHECK(image_size == favicon_bounds_.width());
    971 
    972   // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
    973   canvas->Save();
    974   canvas->ClipRectInt(
    975       favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size);
    976   canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size,
    977       favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size,
    978       false);
    979   canvas->Restore();
    980 }
    981 
    982 int TabRendererGtk::IconCapacity() const {
    983   if (height() < GetMinimumUnselectedSize().height())
    984     return 0;
    985   return (width() - kLeftPadding - kRightPadding) / kFaviconSize;
    986 }
    987 
    988 bool TabRendererGtk::ShouldShowCloseBox() const {
    989   // The selected tab never clips close button.
    990   return !mini() && (IsSelected() || IconCapacity() >= 3);
    991 }
    992 
    993 CustomDrawButton* TabRendererGtk::MakeCloseButton() {
    994   CustomDrawButton* button = new CustomDrawButton(IDR_TAB_CLOSE,
    995       IDR_TAB_CLOSE_P, IDR_TAB_CLOSE_H, IDR_TAB_CLOSE);
    996 
    997   gtk_widget_set_tooltip_text(button->widget(),
    998       l10n_util::GetStringUTF8(IDS_TOOLTIP_CLOSE_TAB).c_str());
    999 
   1000   g_signal_connect(button->widget(), "clicked",
   1001                    G_CALLBACK(OnCloseButtonClickedThunk), this);
   1002   g_signal_connect(button->widget(), "button-release-event",
   1003                    G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
   1004   g_signal_connect(button->widget(), "enter-notify-event",
   1005                    G_CALLBACK(OnEnterNotifyEventThunk), this);
   1006   g_signal_connect(button->widget(), "leave-notify-event",
   1007                    G_CALLBACK(OnLeaveNotifyEventThunk), this);
   1008   GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS);
   1009   gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
   1010 
   1011   return button;
   1012 }
   1013 
   1014 double TabRendererGtk::GetThrobValue() {
   1015   if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
   1016     return mini_title_animation_->GetCurrentValue() *
   1017         kMiniTitleChangeThrobOpacity;
   1018   }
   1019   return hover_animation_.get() ?
   1020       kHoverOpacity * hover_animation_->GetCurrentValue() : 0;
   1021 }
   1022 
   1023 void TabRendererGtk::CloseButtonClicked() {
   1024   // Nothing to do.
   1025 }
   1026 
   1027 void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
   1028   CloseButtonClicked();
   1029 }
   1030 
   1031 gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
   1032                                                    GdkEventButton* event) {
   1033   if (event->button == 2) {
   1034     CloseButtonClicked();
   1035     return TRUE;
   1036   }
   1037 
   1038   return FALSE;
   1039 }
   1040 
   1041 gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
   1042                                        GdkEventExpose* event) {
   1043   PaintTab(event);
   1044   gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
   1045                                  close_button_->widget(), event);
   1046   return TRUE;
   1047 }
   1048 
   1049 void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
   1050                                     GtkAllocation* allocation) {
   1051   gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
   1052                                allocation->width, allocation->height);
   1053 
   1054   // Nothing to do if the bounds are the same.  If we don't catch this, we'll
   1055   // get an infinite loop of size-allocate signals.
   1056   if (bounds_ == bounds)
   1057     return;
   1058 
   1059   bounds_ = bounds;
   1060   Layout();
   1061 }
   1062 
   1063 gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
   1064                                             GdkEventCrossing* event) {
   1065   hover_animation_->SetTweenType(ui::Tween::EASE_OUT);
   1066   hover_animation_->Show();
   1067   return FALSE;
   1068 }
   1069 
   1070 gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
   1071                                             GdkEventCrossing* event) {
   1072   hover_animation_->SetTweenType(ui::Tween::EASE_IN);
   1073   hover_animation_->Hide();
   1074   return FALSE;
   1075 }
   1076 
   1077 // static
   1078 void TabRendererGtk::InitResources() {
   1079   if (initialized_)
   1080     return;
   1081 
   1082   LoadTabImages();
   1083 
   1084   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
   1085   const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
   1086   title_font_ = new gfx::Font(base_font.GetFontName(), kFontPixelSize);
   1087   title_font_height_ = title_font_->GetHeight();
   1088 
   1089   crashed_favicon = rb.GetBitmapNamed(IDR_SAD_FAVICON);
   1090 
   1091   initialized_ = true;
   1092 }
   1093