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
34namespace {
35
36const int kFontPixelSize = 12;
37const int kLeftPadding = 16;
38const int kTopPadding = 6;
39const int kRightPadding = 15;
40const int kBottomPadding = 5;
41const int kDropShadowHeight = 2;
42const int kFaviconTitleSpacing = 4;
43const int kTitleCloseButtonSpacing = 5;
44const int kStandardTitleWidth = 175;
45const int kDropShadowOffset = 2;
46const 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.
52const 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.
58const int kToolbarOverlap = 1;
59
60// How long the hover state takes.
61const int kHoverDurationMs = 90;
62
63// How opaque to make the hover state (out of 1).
64const double kHoverOpacity = 0.33;
65
66// Max opacity for the mini-tab title change animation.
67const double kMiniTitleChangeThrobOpacity = 0.75;
68
69// Duration for when the title of an inactive mini-tab changes.
70const int kMiniTitleChangeThrobDuration = 1000;
71
72const SkScalar kTabCapWidth = 15;
73const SkScalar kTabTopCurveWidth = 4;
74const 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.
78const int kCloseButtonVertFuzz = 0;
79const int kCloseButtonHorzFuzz = 5;
80
81SkBitmap* crashed_favicon = NULL;
82
83// Gets the bounds of |widget| relative to |parent|.
84gfx::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
95TabRendererGtk::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
127TabRendererGtk::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
136bool TabRendererGtk::initialized_ = false;
137TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0};
138TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0};
139TabRendererGtk::TabImage TabRendererGtk::tab_alpha_ = {0};
140gfx::Font* TabRendererGtk::title_font_ = NULL;
141int TabRendererGtk::title_font_height_ = 0;
142int TabRendererGtk::close_button_width_ = 0;
143int TabRendererGtk::close_button_height_ = 0;
144SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK;
145SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64);
146
147////////////////////////////////////////////////////////////////////////////////
148// TabRendererGtk::LoadingAnimation, public:
149//
150TabRendererGtk::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
161TabRendererGtk::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
169TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
170
171bool 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
200void 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.
212class 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
250TabRendererGtk::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
277TabRendererGtk::~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
285void 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
322void 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
337void 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
344bool TabRendererGtk::is_blocked() const {
345  return data_.blocked;
346}
347
348bool TabRendererGtk::IsSelected() const {
349  return true;
350}
351
352bool TabRendererGtk::IsVisible() const {
353  return GTK_WIDGET_FLAGS(tab_.get()) & GTK_VISIBLE;
354}
355
356void 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
366bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
367  return loading_animation_.ValidateLoadingAnimation(animation_state);
368}
369
370void 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
428bool 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
442gfx::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
454gfx::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
461gfx::Size TabRendererGtk::GetStandardSize() {
462  gfx::Size standard_size = GetMinimumUnselectedSize();
463  standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
464  return standard_size;
465}
466
467// static
468int TabRendererGtk::GetMiniWidth() {
469  return browser_defaults::kMiniTabWidth;
470}
471
472// static
473int 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
481void 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
504void TabRendererGtk::SetSelectedTitleColor(SkColor color) {
505  selected_title_color_ = color;
506}
507
508// static
509void TabRendererGtk::SetUnselectedTitleColor(SkColor color) {
510  unselected_title_color_ = color;
511}
512
513gfx::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
522gfx::Rect TabRendererGtk::GetRequisition() const {
523  return gfx::Rect(requisition_.x(), requisition_.y(),
524                   requisition_.width(), requisition_.height());
525}
526
527void 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
544void TabRendererGtk::StopMiniTabTitleAnimation() {
545  if (mini_title_animation_.get())
546    mini_title_animation_->Stop();
547}
548
549void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
550  requisition_ = bounds;
551  gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
552}
553
554void 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
571string16 TabRendererGtk::GetTitle() const {
572  return data_.title;
573}
574
575///////////////////////////////////////////////////////////////////////////////
576// TabRendererGtk, ui::AnimationDelegate implementation:
577
578void TabRendererGtk::AnimationProgressed(const ui::Animation* animation) {
579  gtk_widget_queue_draw(tab_.get());
580}
581
582void TabRendererGtk::AnimationCanceled(const ui::Animation* animation) {
583  AnimationEnded(animation);
584}
585
586void TabRendererGtk::AnimationEnded(const ui::Animation* animation) {
587  gtk_widget_queue_draw(tab_.get());
588}
589
590////////////////////////////////////////////////////////////////////////////////
591// TabRendererGtk, private:
592
593void TabRendererGtk::StartCrashAnimation() {
594  if (!crash_animation_.get())
595    crash_animation_.reset(new FaviconCrashAnimation(this));
596  crash_animation_->Stop();
597  crash_animation_->Start();
598}
599
600void TabRendererGtk::StopCrashAnimation() {
601  if (!crash_animation_.get())
602    return;
603  crash_animation_->Stop();
604}
605
606bool TabRendererGtk::IsPerformingCrashAnimation() const {
607  return crash_animation_.get() && crash_animation_->is_animating();
608}
609
610void TabRendererGtk::SetFaviconHidingOffset(int offset) {
611  favicon_hiding_offset_ = offset;
612  SchedulePaint();
613}
614
615void TabRendererGtk::DisplayCrashedFavicon() {
616  should_display_crashed_favicon_ = true;
617}
618
619void TabRendererGtk::ResetCrashedFavicon() {
620  should_display_crashed_favicon_ = false;
621}
622
623void 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
645SkBitmap TabRendererGtk::PaintBitmap() {
646  gfx::CanvasSkia canvas(width(), height(), false);
647  Paint(&canvas);
648  return canvas.ExtractBitmap();
649}
650
651cairo_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
657void TabRendererGtk::SchedulePaint() {
658  gtk_widget_queue_draw(tab_.get());
659}
660
661gfx::Rect TabRendererGtk::GetLocalBounds() {
662  return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
663}
664
665void 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
756void 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
766SkBitmap* 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
793void 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
809void 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
827void 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
876void 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
894void 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
935void 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
962void 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
982int TabRendererGtk::IconCapacity() const {
983  if (height() < GetMinimumUnselectedSize().height())
984    return 0;
985  return (width() - kLeftPadding - kRightPadding) / kFaviconSize;
986}
987
988bool TabRendererGtk::ShouldShowCloseBox() const {
989  // The selected tab never clips close button.
990  return !mini() && (IsSelected() || IconCapacity() >= 3);
991}
992
993CustomDrawButton* 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
1014double 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
1023void TabRendererGtk::CloseButtonClicked() {
1024  // Nothing to do.
1025}
1026
1027void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
1028  CloseButtonClicked();
1029}
1030
1031gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
1032                                                   GdkEventButton* event) {
1033  if (event->button == 2) {
1034    CloseButtonClicked();
1035    return TRUE;
1036  }
1037
1038  return FALSE;
1039}
1040
1041gboolean 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
1049void 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
1063gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
1064                                            GdkEventCrossing* event) {
1065  hover_animation_->SetTweenType(ui::Tween::EASE_OUT);
1066  hover_animation_->Show();
1067  return FALSE;
1068}
1069
1070gboolean 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
1078void 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