summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/tabs/tab_renderer_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/tabs/tab_renderer_gtk.cc')
-rw-r--r--chrome/browser/gtk/tabs/tab_renderer_gtk.cc1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc
new file mode 100644
index 0000000..0ae3023
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc
@@ -0,0 +1,1086 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/tabs/tab_renderer_gtk.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "app/throb_animation.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/gtk/bookmark_utils_gtk.h"
+#include "chrome/browser/gtk/custom_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/notification_service.h"
+#include "gfx/canvas_skia_paint.h"
+#include "gfx/favicon_size.h"
+#include "gfx/skbitmap_operations.h"
+#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+
+namespace {
+
+const int kLeftPadding = 16;
+const int kTopPadding = 6;
+const int kRightPadding = 15;
+const int kBottomPadding = 5;
+const int kDropShadowHeight = 2;
+const int kFavIconTitleSpacing = 4;
+const int kTitleCloseButtonSpacing = 5;
+const int kStandardTitleWidth = 175;
+const int kDropShadowOffset = 2;
+const int kInactiveTabBackgroundOffsetY = 15;
+
+// When a non-mini-tab becomes a mini-tab the width of the tab animates. If
+// the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
+// is rendered as a normal tab. This is done to avoid having the title
+// immediately disappear when transitioning a tab from normal to mini-tab.
+const int kMiniTabRendererAsNormalTabWidth =
+ browser_defaults::kMiniTabWidth + 30;
+
+// The tab images are designed to overlap the toolbar by 1 pixel. For now we
+// don't actually overlap the toolbar, so this is used to know how many pixels
+// at the bottom of the tab images are to be ignored.
+const int kToolbarOverlap = 1;
+
+// How long the hover state takes.
+const int kHoverDurationMs = 90;
+
+// How opaque to make the hover state (out of 1).
+const double kHoverOpacity = 0.33;
+
+// Max opacity for the mini-tab title change animation.
+const double kMiniTitleChangeThrobOpacity = 0.75;
+
+// Duration for when the title of an inactive mini-tab changes.
+const int kMiniTitleChangeThrobDuration = 1000;
+
+const SkScalar kTabCapWidth = 15;
+const SkScalar kTabTopCurveWidth = 4;
+const SkScalar kTabBottomCurveWidth = 3;
+
+// The vertical and horizontal offset used to position the close button
+// in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about.
+const int kCloseButtonVertFuzz = 0;
+const int kCloseButtonHorzFuzz = 5;
+
+SkBitmap* crashed_fav_icon = NULL;
+
+// Gets the bounds of |widget| relative to |parent|.
+gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent,
+ GtkWidget* widget) {
+ gfx::Point parent_pos = gtk_util::GetWidgetScreenPosition(parent);
+ gfx::Point widget_pos = gtk_util::GetWidgetScreenPosition(widget);
+ return gfx::Rect(widget_pos.x() - parent_pos.x(),
+ widget_pos.y() - parent_pos.y(),
+ widget->allocation.width, widget->allocation.height);
+}
+
+} // namespace
+
+TabRendererGtk::LoadingAnimation::Data::Data(ThemeProvider* theme_provider) {
+ // The loading animation image is a strip of states. Each state must be
+ // square, so the height must divide the width evenly.
+ loading_animation_frames = theme_provider->GetBitmapNamed(IDR_THROBBER);
+ DCHECK(loading_animation_frames);
+ DCHECK_EQ(loading_animation_frames->width() %
+ loading_animation_frames->height(), 0);
+ loading_animation_frame_count =
+ loading_animation_frames->width() /
+ loading_animation_frames->height();
+
+ waiting_animation_frames =
+ theme_provider->GetBitmapNamed(IDR_THROBBER_WAITING);
+ DCHECK(waiting_animation_frames);
+ DCHECK_EQ(waiting_animation_frames->width() %
+ waiting_animation_frames->height(), 0);
+ waiting_animation_frame_count =
+ waiting_animation_frames->width() /
+ waiting_animation_frames->height();
+
+ waiting_to_loading_frame_count_ratio =
+ waiting_animation_frame_count /
+ loading_animation_frame_count;
+ // TODO(beng): eventually remove this when we have a proper themeing system.
+ // themes not supporting IDR_THROBBER_WAITING are causing this
+ // value to be 0 which causes DIV0 crashes. The value of 5
+ // matches the current bitmaps in our source.
+ if (waiting_to_loading_frame_count_ratio == 0)
+ waiting_to_loading_frame_count_ratio = 5;
+}
+
+TabRendererGtk::LoadingAnimation::Data::Data(
+ int loading, int waiting, int waiting_to_loading)
+ : waiting_animation_frames(NULL),
+ loading_animation_frames(NULL),
+ loading_animation_frame_count(loading),
+ waiting_animation_frame_count(waiting),
+ waiting_to_loading_frame_count_ratio(waiting_to_loading) {
+}
+
+bool TabRendererGtk::initialized_ = false;
+TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0};
+TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0};
+TabRendererGtk::TabImage TabRendererGtk::tab_alpha_ = {0};
+gfx::Font* TabRendererGtk::title_font_ = NULL;
+int TabRendererGtk::title_font_height_ = 0;
+int TabRendererGtk::close_button_width_ = 0;
+int TabRendererGtk::close_button_height_ = 0;
+SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK;
+SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64);
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk::LoadingAnimation, public:
+//
+TabRendererGtk::LoadingAnimation::LoadingAnimation(
+ ThemeProvider* theme_provider)
+ : data_(new Data(theme_provider)),
+ theme_provider_(theme_provider),
+ animation_state_(ANIMATION_NONE),
+ animation_frame_(0) {
+ registrar_.Add(this,
+ NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+}
+
+TabRendererGtk::LoadingAnimation::LoadingAnimation(
+ const LoadingAnimation::Data& data)
+ : data_(new Data(data)),
+ theme_provider_(NULL),
+ animation_state_(ANIMATION_NONE),
+ animation_frame_(0) {
+}
+
+bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation(
+ AnimationState animation_state) {
+ bool has_changed = false;
+ if (animation_state_ != animation_state) {
+ // The waiting animation is the reverse of the loading animation, but at a
+ // different rate - the following reverses and scales the animation_frame_
+ // so that the frame is at an equivalent position when going from one
+ // animation to the other.
+ if (animation_state_ == ANIMATION_WAITING &&
+ animation_state == ANIMATION_LOADING) {
+ animation_frame_ = data_->loading_animation_frame_count -
+ (animation_frame_ / data_->waiting_to_loading_frame_count_ratio);
+ }
+ animation_state_ = animation_state;
+ has_changed = true;
+ }
+
+ if (animation_state_ != ANIMATION_NONE) {
+ animation_frame_ = (animation_frame_ + 1) %
+ ((animation_state_ == ANIMATION_WAITING) ?
+ data_->waiting_animation_frame_count :
+ data_->loading_animation_frame_count);
+ has_changed = true;
+ } else {
+ animation_frame_ = 0;
+ }
+ return has_changed;
+}
+
+void TabRendererGtk::LoadingAnimation::Observe(
+ NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
+ data_.reset(new Data(theme_provider_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FaviconCrashAnimation
+//
+// A custom animation subclass to manage the favicon crash animation.
+class TabRendererGtk::FavIconCrashAnimation : public LinearAnimation,
+ public AnimationDelegate {
+ public:
+ explicit FavIconCrashAnimation(TabRendererGtk* target)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(LinearAnimation(1000, 25, this)),
+ target_(target) {
+ }
+ virtual ~FavIconCrashAnimation() {}
+
+ // Animation overrides:
+ virtual void AnimateToState(double state) {
+ const double kHidingOffset = 27;
+
+ if (state < .5) {
+ target_->SetFavIconHidingOffset(
+ static_cast<int>(floor(kHidingOffset * 2.0 * state)));
+ } else {
+ target_->DisplayCrashedFavIcon();
+ target_->SetFavIconHidingOffset(
+ static_cast<int>(
+ floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
+ }
+ }
+
+ // AnimationDelegate overrides:
+ virtual void AnimationCanceled(const Animation* animation) {
+ target_->SetFavIconHidingOffset(0);
+ }
+
+ private:
+ TabRendererGtk* target_;
+
+ DISALLOW_COPY_AND_ASSIGN(FavIconCrashAnimation);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, public:
+
+TabRendererGtk::TabRendererGtk(ThemeProvider* theme_provider)
+ : showing_icon_(false),
+ showing_close_button_(false),
+ fav_icon_hiding_offset_(0),
+ should_display_crashed_favicon_(false),
+ loading_animation_(theme_provider),
+ background_offset_x_(0),
+ background_offset_y_(kInactiveTabBackgroundOffsetY),
+ close_button_color_(0) {
+ InitResources();
+
+ tab_.Own(gtk_fixed_new());
+ gtk_widget_set_app_paintable(tab_.get(), TRUE);
+ g_signal_connect(tab_.get(), "expose-event",
+ G_CALLBACK(OnExposeEventThunk), this);
+ g_signal_connect(tab_.get(), "size-allocate",
+ G_CALLBACK(OnSizeAllocateThunk), this);
+ close_button_.reset(MakeCloseButton());
+ gtk_widget_show(tab_.get());
+
+ hover_animation_.reset(new SlideAnimation(this));
+ hover_animation_->SetSlideDuration(kHoverDurationMs);
+
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+}
+
+TabRendererGtk::~TabRendererGtk() {
+ tab_.Destroy();
+ for (BitmapCache::iterator it = cached_bitmaps_.begin();
+ it != cached_bitmaps_.end(); ++it) {
+ delete it->second.bitmap;
+ }
+}
+
+void TabRendererGtk::UpdateData(TabContents* contents,
+ bool phantom,
+ bool app,
+ bool loading_only) {
+ DCHECK(contents);
+ theme_provider_ = GtkThemeProvider::GetFrom(contents->profile());
+
+ if (!loading_only) {
+ data_.title = contents->GetTitle();
+ data_.off_the_record = contents->profile()->IsOffTheRecord();
+ data_.crashed = contents->is_crashed();
+
+ SkBitmap* app_icon = contents->GetExtensionAppIcon();
+ if (app_icon)
+ data_.favicon = *app_icon;
+ else
+ data_.favicon = contents->GetFavIcon();
+
+ data_.phantom = phantom;
+ data_.app = app;
+ // This is kind of a hacky way to determine whether our icon is the default
+ // favicon. But the plumbing that would be necessary to do it right would
+ // be a good bit of work and would sully code for other platforms which
+ // don't care to custom-theme the favicon. Hopefully the default favicon
+ // will eventually be chromium-themable and this code will go away.
+ data_.is_default_favicon =
+ (data_.favicon.pixelRef() ==
+ ResourceBundle::GetSharedInstance().GetBitmapNamed(
+ IDR_DEFAULT_FAVICON)->pixelRef());
+ }
+
+ // Loading state also involves whether we show the favicon, since that's where
+ // we display the throbber.
+ data_.loading = contents->is_loading();
+ data_.show_icon = contents->ShouldDisplayFavIcon();
+}
+
+void TabRendererGtk::UpdateFromModel() {
+ // Force a layout, since the tab may have grown a favicon.
+ Layout();
+ SchedulePaint();
+
+ if (data_.crashed) {
+ if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
+ StartCrashAnimation();
+ } else {
+ if (IsPerformingCrashAnimation())
+ StopCrashAnimation();
+ ResetCrashedFavIcon();
+ }
+}
+
+void TabRendererGtk::SetBlocked(bool blocked) {
+ if (data_.blocked == blocked)
+ return;
+ data_.blocked = blocked;
+ // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
+}
+
+bool TabRendererGtk::is_blocked() const {
+ return data_.blocked;
+}
+
+bool TabRendererGtk::IsSelected() const {
+ return true;
+}
+
+bool TabRendererGtk::IsVisible() const {
+ return GTK_WIDGET_FLAGS(tab_.get()) & GTK_VISIBLE;
+}
+
+void TabRendererGtk::SetVisible(bool visible) const {
+ if (visible) {
+ gtk_widget_show(tab_.get());
+ if (data_.mini)
+ gtk_widget_show(close_button_->widget());
+ } else {
+ gtk_widget_hide_all(tab_.get());
+ }
+}
+
+bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
+ return loading_animation_.ValidateLoadingAnimation(animation_state);
+}
+
+void TabRendererGtk::PaintFavIconArea(GdkEventExpose* event) {
+ DCHECK(ShouldShowIcon());
+
+ // The paint area is the favicon bounds, but we're painting into the gdk
+ // window belonging to the tabstrip. So the coordinates are relative to the
+ // top left of the tab strip.
+ event->area.x = x() + favicon_bounds_.x();
+ event->area.y = y() + favicon_bounds_.y();
+ event->area.width = favicon_bounds_.width();
+ event->area.height = favicon_bounds_.height();
+ gfx::CanvasSkiaPaint canvas(event, false);
+
+ // The actual paint methods expect 0, 0 to be the tab top left (see
+ // PaintTab).
+ canvas.TranslateInt(x(), y());
+
+ // Paint the background behind the favicon.
+ int theme_id;
+ int offset_y = 0;
+ if (IsSelected()) {
+ theme_id = IDR_THEME_TOOLBAR;
+ } else {
+ if (!data_.off_the_record) {
+ theme_id = IDR_THEME_TAB_BACKGROUND;
+ } else {
+ theme_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
+ }
+ if (!theme_provider_->HasCustomImage(theme_id))
+ offset_y = background_offset_y_;
+ }
+ SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(theme_id);
+ canvas.TileImageInt(*tab_bg,
+ x() + favicon_bounds_.x(), offset_y + favicon_bounds_.y(),
+ favicon_bounds_.x(), favicon_bounds_.y(),
+ favicon_bounds_.width(), favicon_bounds_.height());
+
+ if (!IsSelected()) {
+ double throb_value = GetThrobValue();
+ if (throb_value > 0) {
+ SkRect bounds;
+ bounds.set(favicon_bounds_.x(), favicon_bounds_.y(),
+ favicon_bounds_.right(), favicon_bounds_.bottom());
+ canvas.saveLayerAlpha(&bounds, static_cast<int>(throb_value * 0xff),
+ SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
+ SkBitmap* active_bg = theme_provider_->GetBitmapNamed(IDR_THEME_TOOLBAR);
+ canvas.TileImageInt(*active_bg,
+ x() + favicon_bounds_.x(), favicon_bounds_.y(),
+ favicon_bounds_.x(), favicon_bounds_.y(),
+ favicon_bounds_.width(), favicon_bounds_.height());
+ canvas.restore();
+ }
+ }
+
+ // Now paint the icon.
+ PaintIcon(&canvas);
+}
+
+// static
+gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
+ InitResources();
+
+ gfx::Size minimum_size;
+ minimum_size.set_width(kLeftPadding + kRightPadding);
+ // Since we use bitmap images, the real minimum height of the image is
+ // defined most accurately by the height of the end cap images.
+ minimum_size.set_height(tab_active_.image_l->height() - kToolbarOverlap);
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
+ gfx::Size minimum_size = GetMinimumUnselectedSize();
+ minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding);
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRendererGtk::GetStandardSize() {
+ gfx::Size standard_size = GetMinimumUnselectedSize();
+ standard_size.Enlarge(kFavIconTitleSpacing + kStandardTitleWidth, 0);
+ return standard_size;
+}
+
+// static
+int TabRendererGtk::GetMiniWidth() {
+ return browser_defaults::kMiniTabWidth;
+}
+
+// static
+int TabRendererGtk::GetContentHeight() {
+ // The height of the content of the Tab is the largest of the favicon,
+ // the title text and the close button graphic.
+ int content_height = std::max(kFavIconSize, title_font_height_);
+ return std::max(content_height, close_button_height_);
+}
+
+// static
+void TabRendererGtk::LoadTabImages() {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+
+ tab_alpha_.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT);
+ tab_alpha_.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT);
+
+ tab_active_.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT);
+ tab_active_.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER);
+ tab_active_.image_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT);
+ tab_active_.l_width = tab_active_.image_l->width();
+ tab_active_.r_width = tab_active_.image_r->width();
+
+ tab_inactive_.image_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT);
+ tab_inactive_.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER);
+ tab_inactive_.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT);
+ tab_inactive_.l_width = tab_inactive_.image_l->width();
+ tab_inactive_.r_width = tab_inactive_.image_r->width();
+
+ close_button_width_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->width();
+ close_button_height_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->height();
+}
+
+// static
+void TabRendererGtk::SetSelectedTitleColor(SkColor color) {
+ selected_title_color_ = color;
+}
+
+// static
+void TabRendererGtk::SetUnselectedTitleColor(SkColor color) {
+ unselected_title_color_ = color;
+}
+
+gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
+ // The tabstrip widget is a windowless widget so the tab widget's allocation
+ // is relative to the browser titlebar. We need the bounds relative to the
+ // tabstrip.
+ gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
+ bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
+ return bounds;
+}
+
+gfx::Rect TabRendererGtk::GetRequisition() const {
+ return gfx::Rect(requisition_.x(), requisition_.y(),
+ requisition_.width(), requisition_.height());
+}
+
+void TabRendererGtk::StartMiniTabTitleAnimation() {
+ if (!mini_title_animation_.get()) {
+ mini_title_animation_.reset(new ThrobAnimation(this));
+ mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
+ }
+
+ if (!mini_title_animation_->is_animating()) {
+ mini_title_animation_->StartThrobbing(2);
+ } else if (mini_title_animation_->cycles_remaining() <= 2) {
+ // The title changed while we're already animating. Add at most one more
+ // cycle. This is done in an attempt to smooth out pages that continuously
+ // change the title.
+ mini_title_animation_->set_cycles_remaining(
+ mini_title_animation_->cycles_remaining() + 2);
+ }
+}
+
+void TabRendererGtk::StopMiniTabTitleAnimation() {
+ if (mini_title_animation_.get())
+ mini_title_animation_->Stop();
+}
+
+void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
+ requisition_ = bounds;
+ gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
+}
+
+void TabRendererGtk::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
+
+ // Clear our cache when we receive a theme change notification because it
+ // contains cached bitmaps based off the previous theme.
+ for (BitmapCache::iterator it = cached_bitmaps_.begin();
+ it != cached_bitmaps_.end(); ++it) {
+ delete it->second.bitmap;
+ }
+ cached_bitmaps_.clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, protected:
+
+std::wstring TabRendererGtk::GetTitle() const {
+ return UTF16ToWideHack(data_.title);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, AnimationDelegate implementation:
+
+void TabRendererGtk::AnimationProgressed(const Animation* animation) {
+ gtk_widget_queue_draw(tab_.get());
+}
+
+void TabRendererGtk::AnimationCanceled(const Animation* animation) {
+ AnimationEnded(animation);
+}
+
+void TabRendererGtk::AnimationEnded(const Animation* animation) {
+ gtk_widget_queue_draw(tab_.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, private:
+
+void TabRendererGtk::StartCrashAnimation() {
+ if (!crash_animation_.get())
+ crash_animation_.reset(new FavIconCrashAnimation(this));
+ crash_animation_->Stop();
+ crash_animation_->Start();
+}
+
+void TabRendererGtk::StopCrashAnimation() {
+ if (!crash_animation_.get())
+ return;
+ crash_animation_->Stop();
+}
+
+bool TabRendererGtk::IsPerformingCrashAnimation() const {
+ return crash_animation_.get() && crash_animation_->is_animating();
+}
+
+void TabRendererGtk::SetFavIconHidingOffset(int offset) {
+ fav_icon_hiding_offset_ = offset;
+ SchedulePaint();
+}
+
+void TabRendererGtk::DisplayCrashedFavIcon() {
+ should_display_crashed_favicon_ = true;
+}
+
+void TabRendererGtk::ResetCrashedFavIcon() {
+ should_display_crashed_favicon_ = false;
+}
+
+void TabRendererGtk::Paint(gfx::Canvas* canvas) {
+ // Don't paint if we're narrower than we can render correctly. (This should
+ // only happen during animations).
+ if (width() < GetMinimumUnselectedSize().width() && !mini())
+ return;
+
+ // See if the model changes whether the icons should be painted.
+ const bool show_icon = ShouldShowIcon();
+ const bool show_close_button = ShouldShowCloseBox();
+ if (show_icon != showing_icon_ ||
+ show_close_button != showing_close_button_)
+ Layout();
+
+ if (!phantom()) {
+ // TODO: this isn't quite right. To match the Windows side we need to render
+ // phantom tabs to a separate layer than alpha composite that. This will do
+ // for now though.
+ PaintTabBackground(canvas);
+ }
+
+ if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
+ PaintTitle(canvas);
+
+ if (show_icon)
+ PaintIcon(canvas);
+}
+
+SkBitmap TabRendererGtk::PaintBitmap() {
+ gfx::CanvasSkia canvas(width(), height(), false);
+ Paint(&canvas);
+ return canvas.ExtractBitmap();
+}
+
+cairo_surface_t* TabRendererGtk::PaintToSurface() {
+ gfx::CanvasSkia canvas(width(), height(), false);
+ Paint(&canvas);
+ return cairo_surface_reference(cairo_get_target(canvas.beginPlatformPaint()));
+}
+
+void TabRendererGtk::SchedulePaint() {
+ gtk_widget_queue_draw(tab_.get());
+}
+
+gfx::Rect TabRendererGtk::GetLocalBounds() {
+ return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
+}
+
+void TabRendererGtk::Layout() {
+ gfx::Rect local_bounds = GetLocalBounds();
+ if (local_bounds.IsEmpty())
+ return;
+ local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
+
+ // Figure out who is tallest.
+ int content_height = GetContentHeight();
+
+ // Size the Favicon.
+ showing_icon_ = ShouldShowIcon();
+ if (showing_icon_) {
+ int favicon_top = kTopPadding + (content_height - kFavIconSize) / 2;
+ favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
+ kFavIconSize, kFavIconSize);
+ if ((mini() || data_.animating_mini_change) &&
+ bounds_.width() < kMiniTabRendererAsNormalTabWidth) {
+ int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
+ int ideal_delta = bounds_.width() - GetMiniWidth();
+ if (ideal_delta < mini_delta) {
+ int ideal_x = (GetMiniWidth() - kFavIconSize) / 2;
+ int x = favicon_bounds_.x() + static_cast<int>(
+ (1 - static_cast<float>(ideal_delta) /
+ static_cast<float>(mini_delta)) *
+ (ideal_x - favicon_bounds_.x()));
+ favicon_bounds_.set_x(x);
+ }
+ }
+ } else {
+ favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
+ }
+
+ // Size the Close button.
+ showing_close_button_ = ShouldShowCloseBox();
+ if (showing_close_button_) {
+ int close_button_top =
+ kTopPadding + kCloseButtonVertFuzz +
+ (content_height - close_button_height_) / 2;
+ close_button_bounds_.SetRect(local_bounds.width() + kCloseButtonHorzFuzz,
+ close_button_top, close_button_width_,
+ close_button_height_);
+
+ // If the close button color has changed, generate a new one.
+ if (theme_provider_) {
+ SkColor tab_text_color =
+ theme_provider_->GetColor(BrowserThemeProvider::COLOR_TAB_TEXT);
+ if (!close_button_color_ || tab_text_color != close_button_color_) {
+ close_button_color_ = tab_text_color;
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ close_button_->SetBackground(close_button_color_,
+ rb.GetBitmapNamed(IDR_TAB_CLOSE),
+ rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK));
+ }
+ }
+ } else {
+ close_button_bounds_.SetRect(0, 0, 0, 0);
+ }
+
+ if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
+ // Size the Title text to fill the remaining space.
+ int title_left = favicon_bounds_.right() + kFavIconTitleSpacing;
+ int title_top = kTopPadding;
+
+ // If the user has big fonts, the title will appear rendered too far down
+ // on the y-axis if we use the regular top padding, so we need to adjust it
+ // so that the text appears centered.
+ gfx::Size minimum_size = GetMinimumUnselectedSize();
+ int text_height = title_top + title_font_height_ + kBottomPadding;
+ if (text_height > minimum_size.height())
+ title_top -= (text_height - minimum_size.height()) / 2;
+
+ int title_width;
+ if (close_button_bounds_.width() && close_button_bounds_.height()) {
+ title_width = std::max(close_button_bounds_.x() -
+ kTitleCloseButtonSpacing - title_left, 0);
+ } else {
+ title_width = std::max(local_bounds.width() - title_left, 0);
+ }
+ title_bounds_.SetRect(title_left, title_top, title_width, content_height);
+ }
+
+ favicon_bounds_.set_x(
+ gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
+ close_button_bounds_.set_x(
+ gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
+ title_bounds_.set_x(
+ gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
+
+ MoveCloseButtonWidget();
+}
+
+void TabRendererGtk::MoveCloseButtonWidget() {
+ if (!close_button_bounds_.IsEmpty()) {
+ gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
+ close_button_bounds_.x(), close_button_bounds_.y());
+ gtk_widget_show(close_button_->widget());
+ } else {
+ gtk_widget_hide(close_button_->widget());
+ }
+}
+
+SkBitmap* TabRendererGtk::GetMaskedBitmap(const SkBitmap* mask,
+ const SkBitmap* background, int bg_offset_x, int bg_offset_y) {
+ // We store a bitmap for each mask + background pair (4 total bitmaps). We
+ // replace the cached image if the tab has moved relative to the background.
+ BitmapCache::iterator it = cached_bitmaps_.find(std::make_pair(mask,
+ background));
+ if (it != cached_bitmaps_.end()) {
+ if (it->second.bg_offset_x == bg_offset_x &&
+ it->second.bg_offset_y == bg_offset_y) {
+ return it->second.bitmap;
+ }
+ // The background offset changed so we should re-render with the new
+ // offsets.
+ delete it->second.bitmap;
+ }
+ SkBitmap image = SkBitmapOperations::CreateTiledBitmap(
+ *background, bg_offset_x, bg_offset_y, mask->width(),
+ height() + kToolbarOverlap);
+ CachedBitmap bitmap = {
+ bg_offset_x,
+ bg_offset_y,
+ new SkBitmap(SkBitmapOperations::CreateMaskedBitmap(image, *mask))
+ };
+ cached_bitmaps_[std::make_pair(mask, background)] = bitmap;
+ return bitmap.bitmap;
+}
+
+void TabRendererGtk::PaintTab(GdkEventExpose* event) {
+ gfx::CanvasSkiaPaint canvas(event, false);
+ if (canvas.is_empty())
+ return;
+
+ // The tab is rendered into a windowless widget whose offset is at the
+ // coordinate event->area. Translate by these offsets so we can render at
+ // (0,0) to match Windows' rendering metrics.
+ canvas.TranslateInt(event->area.x, event->area.y);
+
+ // Save the original x offset so we can position background images properly.
+ background_offset_x_ = event->area.x;
+
+ Paint(&canvas);
+}
+
+void TabRendererGtk::PaintTitle(gfx::Canvas* canvas) {
+ // Paint the Title.
+ string16 title = data_.title;
+ if (title.empty()) {
+ title = data_.loading ?
+ l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
+ TabContents::GetDefaultTitle();
+ } else {
+ Browser::FormatTitleForDisplay(&title);
+ }
+
+ SkColor title_color = IsSelected() ? selected_title_color_
+ : unselected_title_color_;
+ canvas->DrawStringInt(UTF16ToWideHack(title), *title_font_, title_color,
+ title_bounds_.x(), title_bounds_.y(),
+ title_bounds_.width(), title_bounds_.height());
+}
+
+void TabRendererGtk::PaintIcon(gfx::Canvas* canvas) {
+ if (loading_animation_.animation_state() != ANIMATION_NONE) {
+ PaintLoadingAnimation(canvas);
+ } else {
+ canvas->Save();
+ canvas->ClipRectInt(0, 0, width(), height() - kFavIconTitleSpacing);
+ if (should_display_crashed_favicon_) {
+ canvas->DrawBitmapInt(*crashed_fav_icon, 0, 0,
+ crashed_fav_icon->width(),
+ crashed_fav_icon->height(),
+ favicon_bounds_.x(),
+ favicon_bounds_.y() + fav_icon_hiding_offset_,
+ kFavIconSize, kFavIconSize,
+ true);
+ } else {
+ if (!data_.favicon.isNull()) {
+ if (data_.is_default_favicon && theme_provider_->UseGtkTheme()) {
+ GdkPixbuf* favicon = GtkThemeProvider::GetDefaultFavicon(true);
+ canvas->AsCanvasSkia()->DrawGdkPixbuf(
+ favicon, favicon_bounds_.x(),
+ favicon_bounds_.y() + fav_icon_hiding_offset_);
+ } else {
+ // If the favicon is an app icon, it is allowed to be drawn slightly
+ // larger than the standard favicon.
+ int favIconHeightOffset = data_.app ? -2 : 0;
+ int favIconWidthDelta = data_.app ?
+ data_.favicon.width() - kFavIconSize : 0;
+ int favIconHeightDelta = data_.app ?
+ data_.favicon.height() - kFavIconSize : 0;
+
+ // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch
+ // to using that class to render the favicon).
+ canvas->DrawBitmapInt(data_.favicon, 0, 0,
+ data_.favicon.width(),
+ data_.favicon.height(),
+ favicon_bounds_.x() - favIconWidthDelta/2,
+ favicon_bounds_.y() + favIconHeightOffset
+ - favIconHeightDelta/2
+ + fav_icon_hiding_offset_,
+ kFavIconSize + favIconWidthDelta,
+ kFavIconSize + favIconHeightDelta,
+ true);
+ }
+ }
+ }
+ canvas->Restore();
+ }
+}
+
+void TabRendererGtk::PaintTabBackground(gfx::Canvas* canvas) {
+ if (IsSelected()) {
+ PaintActiveTabBackground(canvas);
+ } else {
+ PaintInactiveTabBackground(canvas);
+
+ double throb_value = GetThrobValue();
+ if (throb_value > 0) {
+ canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
+ gfx::Rect(width(), height()));
+ canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
+ SkXfermode::kClear_Mode);
+ PaintActiveTabBackground(canvas);
+ canvas->Restore();
+ }
+ }
+}
+
+void TabRendererGtk::PaintInactiveTabBackground(gfx::Canvas* canvas) {
+ bool is_otr = data_.off_the_record;
+
+ // The tab image needs to be lined up with the background image
+ // so that it feels partially transparent.
+ int offset_x = background_offset_x_;
+
+ int tab_id = is_otr ?
+ IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
+
+ SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(tab_id);
+
+ // If the theme is providing a custom background image, then its top edge
+ // should be at the top of the tab. Otherwise, we assume that the background
+ // image is a composited foreground + frame image.
+ int offset_y = theme_provider_->HasCustomImage(tab_id) ?
+ 0 : background_offset_y_;
+
+ // Draw left edge.
+ SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x,
+ offset_y);
+ canvas->DrawBitmapInt(*theme_l, 0, 0);
+
+ // Draw right edge.
+ SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
+ offset_x + width() - tab_active_.r_width, offset_y);
+
+ canvas->DrawBitmapInt(*theme_r, width() - theme_r->width(), 0);
+
+ // Draw center.
+ canvas->TileImageInt(*tab_bg,
+ offset_x + tab_active_.l_width, kDropShadowOffset + offset_y,
+ tab_active_.l_width, 2,
+ width() - tab_active_.l_width - tab_active_.r_width, height() - 2);
+
+ canvas->DrawBitmapInt(*tab_inactive_.image_l, 0, 0);
+ canvas->TileImageInt(*tab_inactive_.image_c, tab_inactive_.l_width, 0,
+ width() - tab_inactive_.l_width - tab_inactive_.r_width, height());
+ canvas->DrawBitmapInt(*tab_inactive_.image_r,
+ width() - tab_inactive_.r_width, 0);
+}
+
+void TabRendererGtk::PaintActiveTabBackground(gfx::Canvas* canvas) {
+ int offset_x = background_offset_x_;
+
+ SkBitmap* tab_bg = theme_provider_->GetBitmapNamed(IDR_THEME_TOOLBAR);
+
+ // Draw left edge.
+ SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x, 0);
+ canvas->DrawBitmapInt(*theme_l, 0, 0);
+
+ // Draw right edge.
+ SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
+ offset_x + width() - tab_active_.r_width, 0);
+ canvas->DrawBitmapInt(*theme_r, width() - tab_active_.r_width, 0);
+
+ // Draw center.
+ canvas->TileImageInt(*tab_bg,
+ offset_x + tab_active_.l_width, kDropShadowHeight,
+ tab_active_.l_width, kDropShadowHeight,
+ width() - tab_active_.l_width - tab_active_.r_width,
+ height() - kDropShadowHeight);
+
+ canvas->DrawBitmapInt(*tab_active_.image_l, 0, 0);
+ canvas->TileImageInt(*tab_active_.image_c, tab_active_.l_width, 0,
+ width() - tab_active_.l_width - tab_active_.r_width, height());
+ canvas->DrawBitmapInt(*tab_active_.image_r, width() - tab_active_.r_width, 0);
+}
+
+void TabRendererGtk::PaintLoadingAnimation(gfx::Canvas* canvas) {
+ const SkBitmap* frames =
+ (loading_animation_.animation_state() == ANIMATION_WAITING) ?
+ loading_animation_.waiting_animation_frames() :
+ loading_animation_.loading_animation_frames();
+ const int image_size = frames->height();
+ const int image_offset = loading_animation_.animation_frame() * image_size;
+ DCHECK(image_size == favicon_bounds_.height());
+ DCHECK(image_size == favicon_bounds_.width());
+
+ canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size,
+ favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size,
+ false);
+}
+
+int TabRendererGtk::IconCapacity() const {
+ if (height() < GetMinimumUnselectedSize().height())
+ return 0;
+ return (width() - kLeftPadding - kRightPadding) / kFavIconSize;
+}
+
+bool TabRendererGtk::ShouldShowIcon() const {
+ if (mini() && height() >= GetMinimumUnselectedSize().height()) {
+ return true;
+ } else if (!data_.show_icon) {
+ return false;
+ } else if (IsSelected()) {
+ // The selected tab clips favicon before close button.
+ return IconCapacity() >= 2;
+ }
+ // Non-selected tabs clip close button before favicon.
+ return IconCapacity() >= 1;
+}
+
+bool TabRendererGtk::ShouldShowCloseBox() const {
+ // The selected tab never clips close button.
+ return !mini() && (IsSelected() || IconCapacity() >= 3);
+}
+
+CustomDrawButton* TabRendererGtk::MakeCloseButton() {
+ CustomDrawButton* button = new CustomDrawButton(IDR_TAB_CLOSE,
+ IDR_TAB_CLOSE_P, IDR_TAB_CLOSE_H, IDR_TAB_CLOSE);
+
+ gtk_widget_set_tooltip_text(button->widget(),
+ l10n_util::GetStringUTF8(IDS_TOOLTIP_CLOSE_TAB).c_str());
+
+ g_signal_connect(button->widget(), "clicked",
+ G_CALLBACK(OnCloseButtonClickedThunk), this);
+ g_signal_connect(button->widget(), "button-release-event",
+ G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
+ g_signal_connect(button->widget(), "enter-notify-event",
+ G_CALLBACK(OnEnterNotifyEventThunk), this);
+ g_signal_connect(button->widget(), "leave-notify-event",
+ G_CALLBACK(OnLeaveNotifyEventThunk), this);
+ GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS);
+ gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
+
+ return button;
+}
+
+double TabRendererGtk::GetThrobValue() {
+ if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
+ return mini_title_animation_->GetCurrentValue() *
+ kMiniTitleChangeThrobOpacity;
+ }
+ return hover_animation_.get() ?
+ kHoverOpacity * hover_animation_->GetCurrentValue() : 0;
+}
+
+void TabRendererGtk::CloseButtonClicked() {
+ // Nothing to do.
+}
+
+void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
+ CloseButtonClicked();
+}
+
+gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
+ GdkEventButton* event) {
+ if (event->button == 2) {
+ CloseButtonClicked();
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
+ GdkEventExpose* event) {
+ PaintTab(event);
+ gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
+ close_button_->widget(), event);
+ return TRUE;
+}
+
+void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ // Nothing to do if the bounds are the same. If we don't catch this, we'll
+ // get an infinite loop of size-allocate signals.
+ if (bounds_ == bounds)
+ return;
+
+ bounds_ = bounds;
+ Layout();
+}
+
+gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ hover_animation_->SetTweenType(Tween::EASE_OUT);
+ hover_animation_->Show();
+ return FALSE;
+}
+
+gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ hover_animation_->SetTweenType(Tween::EASE_IN);
+ hover_animation_->Hide();
+ return FALSE;
+}
+
+// static
+void TabRendererGtk::InitResources() {
+ if (initialized_)
+ return;
+
+ LoadTabImages();
+
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ // Force the font size to 9pt, which matches Windows' default font size
+ // (taken from the system).
+ const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
+ title_font_ = new gfx::Font(gfx::Font::CreateFont(base_font.FontName(), 9));
+ title_font_height_ = title_font_->height();
+
+ crashed_fav_icon = rb.GetBitmapNamed(IDR_SAD_FAVICON);
+
+ initialized_ = true;
+}