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