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