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
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 kFaviconTitleSpacing = 4;
56const int kTitleCloseButtonSpacing = 5;
57const int kStandardTitleWidth = 175;
58const int kDropShadowOffset = 2;
59const 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.
65const 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.
71const int kToolbarOverlap = 1;
72
73// How long the hover state takes.
74const int kHoverDurationMs = 90;
75
76// How opaque to make the hover state (out of 1).
77const double kHoverOpacity = 0.33;
78
79// Opacity for non-active selected tabs.
80const double kSelectedTabOpacity = 0.45;
81
82// Selected (but not active) tabs have their throb value scaled down by this.
83const double kSelectedTabThrobScale = 0.5;
84
85// Max opacity for the mini-tab title change animation.
86const double kMiniTitleChangeThrobOpacity = 0.75;
87
88// Duration for when the title of an inactive mini-tab changes.
89const int kMiniTitleChangeThrobDuration = 1000;
90
91// The horizontal offset used to position the close button in the tab.
92const int kCloseButtonHorzFuzz = 4;
93
94// Gets the bounds of |widget| relative to |parent|.
95gfx::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.
105GdkPixbuf* 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
134TabRendererGtk::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
167TabRendererGtk::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
174bool TabRendererGtk::initialized_ = false;
175int TabRendererGtk::tab_active_l_width_ = 0;
176int TabRendererGtk::tab_active_l_height_ = 0;
177int TabRendererGtk::tab_inactive_l_height_ = 0;
178gfx::Font* TabRendererGtk::title_font_ = NULL;
179int TabRendererGtk::title_font_height_ = 0;
180int TabRendererGtk::close_button_width_ = 0;
181int TabRendererGtk::close_button_height_ = 0;
182
183////////////////////////////////////////////////////////////////////////////////
184// TabRendererGtk::LoadingAnimation, public:
185//
186TabRendererGtk::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
197TabRendererGtk::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
205TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
206
207bool 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
236void 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
244TabRendererGtk::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
258TabRendererGtk::TabData::~TabData() {}
259
260////////////////////////////////////////////////////////////////////////////////
261// FaviconCrashAnimation
262//
263//  A custom animation subclass to manage the favicon crash animation.
264class 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
302TabRendererGtk::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
336TabRendererGtk::~TabRendererGtk() {
337  tab_.Destroy();
338}
339
340void 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
350void 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    data_.favicon = favicon_tab_helper->GetFavicon().AsBitmap();
375    data_.app = app;
376
377    // Make a cairo cached version of the favicon.
378    if (!data_.favicon.isNull()) {
379      // Instead of resizing the icon during each frame, create our resized
380      // icon resource now, send it to the xserver and use that each frame
381      // instead.
382
383      // For source images smaller than the favicon square, scale them as if
384      // they were padded to fit the favicon square, so we don't blow up tiny
385      // favicons into larger or nonproportional results.
386      GdkPixbuf* pixbuf = GetResizedGdkPixbufFromSkBitmap(data_.favicon,
387          gfx::kFaviconSize, gfx::kFaviconSize);
388      data_.cairo_favicon.UsePixbuf(pixbuf);
389      g_object_unref(pixbuf);
390    } else {
391      data_.cairo_favicon.Reset();
392    }
393
394    // This is kind of a hacky way to determine whether our icon is the default
395    // favicon. But the plumbing that would be necessary to do it right would
396    // be a good bit of work and would sully code for other platforms which
397    // don't care to custom-theme the favicon. Hopefully the default favicon
398    // will eventually be chromium-themable and this code will go away.
399    data_.is_default_favicon =
400        (data_.favicon.pixelRef() ==
401        ui::ResourceBundle::GetSharedInstance().GetImageNamed(
402            IDR_DEFAULT_FAVICON).AsBitmap().pixelRef());
403  }
404
405  // Loading state also involves whether we show the favicon, since that's where
406  // we display the throbber.
407  data_.loading = contents->IsLoading();
408  data_.show_icon = favicon_tab_helper->ShouldDisplayFavicon();
409}
410
411void TabRendererGtk::UpdateFromModel() {
412  // Force a layout, since the tab may have grown a favicon.
413  Layout();
414  SchedulePaint();
415
416  if (data_.crashed) {
417    if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
418      StartCrashAnimation();
419  } else {
420    if (IsPerformingCrashAnimation())
421      StopCrashAnimation();
422    ResetCrashedFavicon();
423  }
424
425  if (data_.media_state != data_.previous_media_state) {
426    data_.previous_media_state = data_.media_state;
427    if (data_.media_state != TAB_MEDIA_STATE_NONE)
428      animating_media_state_ = data_.media_state;
429    StartMediaIndicatorAnimation();
430  }
431}
432
433void TabRendererGtk::SetBlocked(bool blocked) {
434  if (data_.blocked == blocked)
435    return;
436  data_.blocked = blocked;
437  // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
438}
439
440bool TabRendererGtk::is_blocked() const {
441  return data_.blocked;
442}
443
444bool TabRendererGtk::IsActive() const {
445  return is_active_;
446}
447
448bool TabRendererGtk::IsSelected() const {
449  return true;
450}
451
452bool TabRendererGtk::IsVisible() const {
453  return gtk_widget_get_visible(tab_.get());
454}
455
456void TabRendererGtk::SetVisible(bool visible) const {
457  if (visible) {
458    gtk_widget_show(tab_.get());
459    if (data_.mini)
460      gtk_widget_show(close_button_->widget());
461  } else {
462    gtk_widget_hide_all(tab_.get());
463  }
464}
465
466bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
467  return loading_animation_.ValidateLoadingAnimation(animation_state);
468}
469
470void TabRendererGtk::PaintFaviconArea(GtkWidget* widget, cairo_t* cr) {
471  DCHECK(ShouldShowIcon());
472
473  cairo_rectangle(cr,
474                  x() + favicon_bounds_.x(),
475                  y() + favicon_bounds_.y(),
476                  favicon_bounds_.width(),
477                  favicon_bounds_.height());
478  cairo_clip(cr);
479
480  // The tab is rendered into a windowless widget whose offset is at the
481  // coordinate event->area.  Translate by these offsets so we can render at
482  // (0,0) to match Windows' rendering metrics.
483  cairo_matrix_t cairo_matrix;
484  cairo_matrix_init_translate(&cairo_matrix, x(), y());
485  cairo_set_matrix(cr, &cairo_matrix);
486
487  // Which background should we be painting?
488  int theme_id;
489  int offset_y = 0;
490  if (IsActive()) {
491    theme_id = IDR_THEME_TOOLBAR;
492  } else {
493    theme_id = data_.incognito ? IDR_THEME_TAB_BACKGROUND_INCOGNITO :
494               IDR_THEME_TAB_BACKGROUND;
495
496    if (!theme_service_->HasCustomImage(theme_id))
497      offset_y = background_offset_y_;
498  }
499
500  // Paint the background behind the favicon.
501  const gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
502  tab_bg.ToCairo()->SetSource(cr, widget, -x(), -offset_y);
503  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
504  cairo_rectangle(cr,
505                  favicon_bounds_.x(), favicon_bounds_.y(),
506                  favicon_bounds_.width(), favicon_bounds_.height());
507  cairo_fill(cr);
508
509  if (!IsActive()) {
510    double throb_value = GetThrobValue();
511    if (throb_value > 0) {
512      cairo_push_group(cr);
513      gfx::Image active_bg =
514          theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
515      active_bg.ToCairo()->SetSource(cr, widget, -x(), 0);
516      cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
517
518      cairo_rectangle(cr,
519                      favicon_bounds_.x(), favicon_bounds_.y(),
520                      favicon_bounds_.width(), favicon_bounds_.height());
521      cairo_fill(cr);
522
523      cairo_pop_group_to_source(cr);
524      cairo_paint_with_alpha(cr, throb_value);
525    }
526  }
527
528  PaintIcon(widget, cr);
529}
530
531void TabRendererGtk::MaybeAdjustLeftForMiniTab(gfx::Rect* icon_bounds) const {
532  if (!(mini() || data_.animating_mini_change) ||
533      bounds_.width() >= kMiniTabRendererAsNormalTabWidth)
534    return;
535  const int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
536  const int ideal_delta = bounds_.width() - GetMiniWidth();
537  const int ideal_x = (GetMiniWidth() - icon_bounds->width()) / 2;
538  icon_bounds->set_x(icon_bounds->x() + static_cast<int>(
539      (1 - static_cast<float>(ideal_delta) / static_cast<float>(mini_delta)) *
540      (ideal_x - icon_bounds->x())));
541}
542
543// static
544gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
545  InitResources();
546
547  gfx::Size minimum_size;
548  minimum_size.set_width(kLeftPadding + kRightPadding);
549  // Since we use bitmap images, the real minimum height of the image is
550  // defined most accurately by the height of the end cap images.
551  minimum_size.set_height(tab_active_l_height_ - kToolbarOverlap);
552  return minimum_size;
553}
554
555// static
556gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
557  gfx::Size minimum_size = GetMinimumUnselectedSize();
558  minimum_size.set_width(kLeftPadding + gfx::kFaviconSize + kRightPadding);
559  return minimum_size;
560}
561
562// static
563gfx::Size TabRendererGtk::GetStandardSize() {
564  gfx::Size standard_size = GetMinimumUnselectedSize();
565  standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
566  return standard_size;
567}
568
569// static
570int TabRendererGtk::GetMiniWidth() {
571  return browser_defaults::kMiniTabWidth;
572}
573
574// static
575int TabRendererGtk::GetContentHeight() {
576  // The height of the content of the Tab is the largest of the favicon,
577  // the title text and the close button graphic.
578  int content_height = std::max(gfx::kFaviconSize, title_font_height_);
579  return std::max(content_height, close_button_height_);
580}
581
582gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
583  // The tabstrip widget is a windowless widget so the tab widget's allocation
584  // is relative to the browser titlebar.  We need the bounds relative to the
585  // tabstrip.
586  gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
587  bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
588  return bounds;
589}
590
591gfx::Rect TabRendererGtk::GetRequisition() const {
592  return gfx::Rect(requisition_.x(), requisition_.y(),
593                   requisition_.width(), requisition_.height());
594}
595
596void TabRendererGtk::StartMiniTabTitleAnimation() {
597  if (!mini_title_animation_.get()) {
598    mini_title_animation_.reset(new gfx::ThrobAnimation(this));
599    mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
600  }
601
602  if (!mini_title_animation_->is_animating())
603    mini_title_animation_->StartThrobbing(-1);
604}
605
606void TabRendererGtk::StopMiniTabTitleAnimation() {
607  if (mini_title_animation_.get())
608    mini_title_animation_->Stop();
609}
610
611void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
612  requisition_ = bounds;
613  gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
614}
615
616////////////////////////////////////////////////////////////////////////////////
617// TabRendererGtk, protected:
618
619void TabRendererGtk::Raise() const {
620  if (gtk_button_get_event_window(GTK_BUTTON(close_button_->widget())))
621    gdk_window_raise(gtk_button_get_event_window(
622        GTK_BUTTON(close_button_->widget())));
623}
624
625base::string16 TabRendererGtk::GetTitle() const {
626  return data_.title;
627}
628
629///////////////////////////////////////////////////////////////////////////////
630// TabRendererGtk, gfx::AnimationDelegate implementation:
631
632void TabRendererGtk::AnimationProgressed(const gfx::Animation* animation) {
633  gtk_widget_queue_draw(tab_.get());
634}
635
636void TabRendererGtk::AnimationCanceled(const gfx::Animation* animation) {
637  if (media_indicator_animation_ == animation)
638    animating_media_state_ = data_.media_state;
639  AnimationEnded(animation);
640}
641
642void TabRendererGtk::AnimationEnded(const gfx::Animation* animation) {
643  if (media_indicator_animation_ == animation)
644    animating_media_state_ = data_.media_state;
645  gtk_widget_queue_draw(tab_.get());
646}
647
648////////////////////////////////////////////////////////////////////////////////
649// TabRendererGtk, private:
650
651void TabRendererGtk::StartCrashAnimation() {
652  if (!crash_animation_.get())
653    crash_animation_.reset(new FaviconCrashAnimation(this));
654  crash_animation_->Stop();
655  crash_animation_->Start();
656}
657
658void TabRendererGtk::StopCrashAnimation() {
659  if (!crash_animation_.get())
660    return;
661  crash_animation_->Stop();
662}
663
664bool TabRendererGtk::IsPerformingCrashAnimation() const {
665  return crash_animation_.get() && crash_animation_->is_animating();
666}
667
668void TabRendererGtk::StartMediaIndicatorAnimation() {
669  media_indicator_animation_ =
670      chrome::CreateTabMediaIndicatorFadeAnimation(data_.media_state);
671  media_indicator_animation_->set_delegate(this);
672  media_indicator_animation_->Start();
673}
674
675void TabRendererGtk::SetFaviconHidingOffset(int offset) {
676  favicon_hiding_offset_ = offset;
677  SchedulePaint();
678}
679
680void TabRendererGtk::DisplayCrashedFavicon() {
681  should_display_crashed_favicon_ = true;
682}
683
684void TabRendererGtk::ResetCrashedFavicon() {
685  should_display_crashed_favicon_ = false;
686}
687
688void TabRendererGtk::Paint(GtkWidget* widget, cairo_t* cr) {
689  // Don't paint if we're narrower than we can render correctly. (This should
690  // only happen during animations).
691  if (width() < GetMinimumUnselectedSize().width() && !mini())
692    return;
693
694  // See if the model changes whether the icons should be painted.
695  const bool show_icon = ShouldShowIcon();
696  const bool show_media_indicator = ShouldShowMediaIndicator();
697  const bool show_close_button = ShouldShowCloseBox();
698  if (show_icon != showing_icon_ ||
699      show_media_indicator != showing_media_indicator_ ||
700      show_close_button != showing_close_button_)
701    Layout();
702
703  PaintTabBackground(widget, cr);
704
705  if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
706    PaintTitle(widget, cr);
707
708  if (show_icon)
709    PaintIcon(widget, cr);
710
711  if (show_media_indicator)
712    PaintMediaIndicator(widget, cr);
713}
714
715cairo_surface_t* TabRendererGtk::PaintToSurface(GtkWidget* widget,
716                                                cairo_t* cr) {
717  cairo_surface_t* target = cairo_get_target(cr);
718  cairo_surface_t* out_surface = cairo_surface_create_similar(
719      target,
720      CAIRO_CONTENT_COLOR_ALPHA,
721      width(), height());
722
723  cairo_t* out_cr = cairo_create(out_surface);
724  Paint(widget, out_cr);
725  cairo_destroy(out_cr);
726
727  return out_surface;
728}
729
730void TabRendererGtk::SchedulePaint() {
731  gtk_widget_queue_draw(tab_.get());
732}
733
734gfx::Rect TabRendererGtk::GetLocalBounds() {
735  return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
736}
737
738void TabRendererGtk::Layout() {
739  gfx::Rect local_bounds = GetLocalBounds();
740  if (local_bounds.IsEmpty())
741    return;
742  local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
743
744  // Figure out who is tallest.
745  int content_height = GetContentHeight();
746
747  // Size the Favicon.
748  showing_icon_ = ShouldShowIcon();
749  if (showing_icon_) {
750    int favicon_top = kTopPadding + (content_height - gfx::kFaviconSize) / 2;
751    favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
752                            gfx::kFaviconSize, gfx::kFaviconSize);
753    MaybeAdjustLeftForMiniTab(&favicon_bounds_);
754  } else {
755    favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
756  }
757
758  // Size the Close button.
759  showing_close_button_ = ShouldShowCloseBox();
760  if (showing_close_button_) {
761    int close_button_top = kTopPadding +
762        (content_height - close_button_height_) / 2;
763    int close_button_left =
764        local_bounds.right() - close_button_width_ + kCloseButtonHorzFuzz;
765    close_button_bounds_.SetRect(close_button_left,
766                                 close_button_top,
767                                 close_button_width_,
768                                 close_button_height_);
769
770    // If the close button color has changed, generate a new one.
771    if (theme_service_) {
772      SkColor tab_text_color =
773          theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
774      if (!close_button_color_ || tab_text_color != close_button_color_) {
775        close_button_color_ = tab_text_color;
776        ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
777        close_button_->SetBackground(close_button_color_,
778            rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(),
779            rb.GetImageNamed(IDR_CLOSE_1_MASK).AsBitmap());
780      }
781    }
782  } else {
783    close_button_bounds_.SetRect(0, 0, 0, 0);
784  }
785
786  showing_media_indicator_ = ShouldShowMediaIndicator();
787  if (showing_media_indicator_) {
788    const gfx::Image& media_indicator_image =
789        chrome::GetTabMediaIndicatorImage(animating_media_state_);
790    media_indicator_bounds_.set_width(media_indicator_image.Width());
791    media_indicator_bounds_.set_height(media_indicator_image.Height());
792    media_indicator_bounds_.set_y(
793        kTopPadding + (content_height - media_indicator_bounds_.height()) / 2);
794    const int right = showing_close_button_ ?
795        close_button_bounds_.x() : local_bounds.right();
796    media_indicator_bounds_.set_x(std::max(
797        local_bounds.x(), right - media_indicator_bounds_.width()));
798    MaybeAdjustLeftForMiniTab(&media_indicator_bounds_);
799  } else {
800    media_indicator_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
801  }
802
803  if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
804    // Size the Title text to fill the remaining space.
805    int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
806    int title_top = kTopPadding;
807
808    // If the user has big fonts, the title will appear rendered too far down
809    // on the y-axis if we use the regular top padding, so we need to adjust it
810    // so that the text appears centered.
811    gfx::Size minimum_size = GetMinimumUnselectedSize();
812    int text_height = title_top + title_font_height_ + kBottomPadding;
813    if (text_height > minimum_size.height())
814      title_top -= (text_height - minimum_size.height()) / 2;
815
816    int title_width;
817    if (showing_media_indicator_) {
818      title_width = media_indicator_bounds_.x() - kTitleCloseButtonSpacing -
819          title_left;
820    } else if (close_button_bounds_.width() && close_button_bounds_.height()) {
821      title_width = close_button_bounds_.x() - kTitleCloseButtonSpacing -
822          title_left;
823    } else {
824      title_width = local_bounds.width() - title_left;
825    }
826    title_width = std::max(title_width, 0);
827    title_bounds_.SetRect(title_left, title_top, title_width, content_height);
828  }
829
830  favicon_bounds_.set_x(
831      gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
832  media_indicator_bounds_.set_x(
833      gtk_util::MirroredLeftPointForRect(tab_.get(), media_indicator_bounds_));
834  close_button_bounds_.set_x(
835      gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
836  title_bounds_.set_x(
837      gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
838
839  MoveCloseButtonWidget();
840}
841
842void TabRendererGtk::MoveCloseButtonWidget() {
843  if (!close_button_bounds_.IsEmpty()) {
844    gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
845                   close_button_bounds_.x(), close_button_bounds_.y());
846    gtk_widget_show(close_button_->widget());
847  } else {
848    gtk_widget_hide(close_button_->widget());
849  }
850}
851
852void TabRendererGtk::PaintTab(GtkWidget* widget, GdkEventExpose* event) {
853  cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
854  gdk_cairo_rectangle(cr, &event->area);
855  cairo_clip(cr);
856
857  // The tab is rendered into a windowless widget whose offset is at the
858  // coordinate event->area.  Translate by these offsets so we can render at
859  // (0,0) to match Windows' rendering metrics.
860  cairo_matrix_t cairo_matrix;
861  cairo_matrix_init_translate(&cairo_matrix, event->area.x, event->area.y);
862  cairo_set_matrix(cr, &cairo_matrix);
863
864  // Save the original x offset so we can position background images properly.
865  background_offset_x_ = event->area.x;
866
867  Paint(widget, cr);
868  cairo_destroy(cr);
869}
870
871void TabRendererGtk::PaintTitle(GtkWidget* widget, cairo_t* cr) {
872  if (title_bounds_.IsEmpty())
873    return;
874
875  // Paint the Title.
876  base::string16 title = data_.title;
877  if (title.empty()) {
878    title = data_.loading ?
879        l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
880        CoreTabHelper::GetDefaultTitle();
881  } else {
882    Browser::FormatTitleForDisplay(&title);
883  }
884
885  GtkAllocation allocation;
886  gtk_widget_get_allocation(widget, &allocation);
887  gfx::Rect bounds(allocation);
888
889  // Draw the text directly onto the Cairo context. This is necessary for
890  // getting the draw order correct, and automatically applying transformations
891  // such as scaling when a tab is detached.
892  gfx::CanvasSkiaPaintCairo canvas(cr, bounds.size(), true);
893
894  SkColor title_color = IsSelected() ? selected_title_color_
895                                     : unselected_title_color_;
896
897  // Disable subpixel rendering. This does not work because the canvas has a
898  // transparent background.
899  int flags = gfx::Canvas::NO_ELLIPSIS | gfx::Canvas::NO_SUBPIXEL_RENDERING;
900  canvas.DrawFadeTruncatingStringRectWithFlags(
901      title, gfx::Canvas::TruncateFadeTail, gfx::FontList(*title_font_),
902      title_color, title_bounds_, flags);
903}
904
905void TabRendererGtk::PaintIcon(GtkWidget* widget, cairo_t* cr) {
906  if (loading_animation_.animation_state() != ANIMATION_NONE) {
907    PaintLoadingAnimation(widget, cr);
908    return;
909  }
910
911  gfx::CairoCachedSurface* to_display = NULL;
912  if (should_display_crashed_favicon_) {
913    to_display = theme_service_->GetImageNamed(IDR_SAD_FAVICON).ToCairo();
914  } else if (!data_.favicon.isNull()) {
915    if (data_.is_default_favicon && theme_service_->UsingNativeTheme()) {
916      to_display = GtkThemeService::GetDefaultFavicon(true).ToCairo();
917    } else if (data_.cairo_favicon.valid()) {
918      to_display = &data_.cairo_favicon;
919    }
920  }
921
922  if (to_display) {
923    to_display->SetSource(cr, widget, favicon_bounds_.x(),
924                          favicon_bounds_.y() + favicon_hiding_offset_);
925    cairo_paint(cr);
926  }
927}
928
929void TabRendererGtk::PaintMediaIndicator(GtkWidget* widget, cairo_t* cr) {
930  if (media_indicator_bounds_.IsEmpty() || !media_indicator_animation_)
931    return;
932
933  double opaqueness = media_indicator_animation_->GetCurrentValue();
934  if (data_.media_state == TAB_MEDIA_STATE_NONE)
935    opaqueness = 1.0 - opaqueness;  // Fading out, not in.
936
937  const gfx::Image& media_indicator_image =
938      chrome::GetTabMediaIndicatorImage(animating_media_state_);
939  media_indicator_image.ToCairo()->SetSource(
940      cr, widget, media_indicator_bounds_.x(), media_indicator_bounds_.y());
941  cairo_paint_with_alpha(cr, opaqueness);
942}
943
944void TabRendererGtk::PaintTabBackground(GtkWidget* widget, cairo_t* cr) {
945  if (IsActive()) {
946    PaintActiveTabBackground(widget, cr);
947  } else {
948    PaintInactiveTabBackground(widget, cr);
949
950    double throb_value = GetThrobValue();
951    if (throb_value > 0) {
952      cairo_push_group(cr);
953      PaintActiveTabBackground(widget, cr);
954      cairo_pop_group_to_source(cr);
955      cairo_paint_with_alpha(cr, throb_value);
956    }
957  }
958}
959
960void TabRendererGtk::DrawTabBackground(
961    cairo_t* cr,
962    GtkWidget* widget,
963    const gfx::Image& tab_bg,
964    int offset_x,
965    int offset_y) {
966  tab_bg.ToCairo()->SetSource(cr, widget, -offset_x, -offset_y);
967  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
968
969  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
970
971  // Draw left edge
972  gfx::Image& tab_l_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_LEFT);
973  tab_l_mask.ToCairo()->MaskSource(cr, widget, 0, 0);
974
975  // Draw center
976  cairo_rectangle(cr,
977                  tab_active_l_width_, kDropShadowOffset,
978                  width() - (2 * tab_active_l_width_),
979                  tab_inactive_l_height_);
980  cairo_fill(cr);
981
982  // Draw right edge
983  gfx::Image& tab_r_mask = rb.GetNativeImageNamed(IDR_TAB_ALPHA_RIGHT);
984  tab_r_mask.ToCairo()->MaskSource(cr, widget,
985                                    width() - tab_active_l_width_, 0);
986}
987
988void TabRendererGtk::DrawTabShadow(cairo_t* cr,
989                                   GtkWidget* widget,
990                                   int left_idr,
991                                   int center_idr,
992                                   int right_idr) {
993  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
994  gfx::Image& active_image_l = rb.GetNativeImageNamed(left_idr);
995  gfx::Image& active_image_c = rb.GetNativeImageNamed(center_idr);
996  gfx::Image& active_image_r = rb.GetNativeImageNamed(right_idr);
997
998  // Draw left drop shadow
999  active_image_l.ToCairo()->SetSource(cr, widget, 0, 0);
1000  cairo_paint(cr);
1001
1002  // Draw the center shadow
1003  active_image_c.ToCairo()->SetSource(cr, widget, 0, 0);
1004  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
1005  cairo_rectangle(cr, tab_active_l_width_, 0,
1006                  width() - (2 * tab_active_l_width_),
1007                  height());
1008  cairo_fill(cr);
1009
1010  // Draw right drop shadow
1011  active_image_r.ToCairo()->SetSource(
1012      cr, widget, width() - active_image_r.ToCairo()->Width(), 0);
1013  cairo_paint(cr);
1014}
1015
1016void TabRendererGtk::PaintInactiveTabBackground(GtkWidget* widget,
1017                                                cairo_t* cr) {
1018  int theme_id = data_.incognito ?
1019      IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
1020
1021  gfx::Image tab_bg = theme_service_->GetImageNamed(theme_id);
1022
1023  // If the theme is providing a custom background image, then its top edge
1024  // should be at the top of the tab. Otherwise, we assume that the background
1025  // image is a composited foreground + frame image.
1026  int offset_y = theme_service_->HasCustomImage(theme_id) ?
1027      0 : background_offset_y_;
1028
1029  DrawTabBackground(cr, widget, tab_bg, background_offset_x_, offset_y);
1030
1031  DrawTabShadow(cr, widget, IDR_TAB_INACTIVE_LEFT, IDR_TAB_INACTIVE_CENTER,
1032                IDR_TAB_INACTIVE_RIGHT);
1033}
1034
1035void TabRendererGtk::PaintActiveTabBackground(GtkWidget* widget,
1036                                              cairo_t* cr) {
1037  gfx::Image tab_bg = theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
1038
1039  DrawTabBackground(cr, widget, tab_bg, background_offset_x_, 0);
1040  DrawTabShadow(cr, widget, IDR_TAB_ACTIVE_LEFT, IDR_TAB_ACTIVE_CENTER,
1041                IDR_TAB_ACTIVE_RIGHT);
1042}
1043
1044void TabRendererGtk::PaintLoadingAnimation(GtkWidget* widget,
1045                                           cairo_t* cr) {
1046  int id = loading_animation_.animation_state() == ANIMATION_WAITING ?
1047           IDR_THROBBER_WAITING : IDR_THROBBER;
1048  gfx::Image throbber = theme_service_->GetImageNamed(id);
1049
1050  const int image_size = throbber.ToCairo()->Height();
1051  const int image_offset = loading_animation_.animation_frame() * image_size;
1052  DCHECK(image_size == favicon_bounds_.height());
1053  DCHECK(image_size == favicon_bounds_.width());
1054
1055  throbber.ToCairo()->SetSource(cr, widget, favicon_bounds_.x() - image_offset,
1056                                 favicon_bounds_.y());
1057  cairo_rectangle(cr, favicon_bounds_.x(), favicon_bounds_.y(),
1058                  image_size, image_size);
1059  cairo_fill(cr);
1060}
1061
1062int TabRendererGtk::IconCapacity() const {
1063  if (height() < GetMinimumUnselectedSize().height())
1064    return 0;
1065  const int available_width =
1066      std::max(0, width() - kLeftPadding - kRightPadding);
1067  const int kPaddingBetweenIcons = 2;
1068  if (available_width >= gfx::kFaviconSize &&
1069      available_width < (gfx::kFaviconSize + kPaddingBetweenIcons)) {
1070    return 1;
1071  }
1072  return available_width / (gfx::kFaviconSize + kPaddingBetweenIcons);
1073}
1074
1075bool TabRendererGtk::ShouldShowIcon() const {
1076  return chrome::ShouldTabShowFavicon(
1077      IconCapacity(), mini(), IsActive(), data_.show_icon,
1078      animating_media_state_);
1079}
1080
1081bool TabRendererGtk::ShouldShowMediaIndicator() const {
1082  return chrome::ShouldTabShowMediaIndicator(
1083      IconCapacity(), mini(), IsActive(), data_.show_icon,
1084      animating_media_state_);
1085}
1086
1087bool TabRendererGtk::ShouldShowCloseBox() const {
1088  return chrome::ShouldTabShowCloseButton(IconCapacity(), mini(), IsActive());
1089}
1090
1091CustomDrawButton* TabRendererGtk::MakeCloseButton() {
1092  CustomDrawButton* button = CustomDrawButton::CloseButtonBar(theme_service_);
1093  button->ForceChromeTheme();
1094
1095  g_signal_connect(button->widget(), "clicked",
1096                   G_CALLBACK(OnCloseButtonClickedThunk), this);
1097  g_signal_connect(button->widget(), "button-release-event",
1098                   G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
1099  g_signal_connect(button->widget(), "enter-notify-event",
1100                   G_CALLBACK(OnEnterNotifyEventThunk), this);
1101  g_signal_connect(button->widget(), "leave-notify-event",
1102                   G_CALLBACK(OnLeaveNotifyEventThunk), this);
1103  gtk_widget_set_can_focus(button->widget(), FALSE);
1104  gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
1105
1106  return button;
1107}
1108
1109double TabRendererGtk::GetThrobValue() {
1110  bool is_selected = IsSelected();
1111  double min = is_selected ? kSelectedTabOpacity : 0;
1112  double scale = is_selected ? kSelectedTabThrobScale : 1;
1113
1114  if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
1115    return mini_title_animation_->GetCurrentValue() *
1116        kMiniTitleChangeThrobOpacity * scale + min;
1117  }
1118
1119  if (hover_animation_.get())
1120    return kHoverOpacity * hover_animation_->GetCurrentValue() * scale + min;
1121
1122  return is_selected ? kSelectedTabOpacity : 0;
1123}
1124
1125void TabRendererGtk::CloseButtonClicked() {
1126  // Nothing to do.
1127}
1128
1129void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
1130  CloseButtonClicked();
1131}
1132
1133gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
1134                                                   GdkEventButton* event) {
1135  if (event->button == 2) {
1136    CloseButtonClicked();
1137    return TRUE;
1138  }
1139
1140  return FALSE;
1141}
1142
1143gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
1144                                       GdkEventExpose* event) {
1145  TRACE_EVENT0("ui::gtk", "TabRendererGtk::OnExposeEvent");
1146
1147  PaintTab(widget, event);
1148  gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
1149                                 close_button_->widget(), event);
1150  return TRUE;
1151}
1152
1153void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
1154                                    GtkAllocation* allocation) {
1155  gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
1156                               allocation->width, allocation->height);
1157
1158  // Nothing to do if the bounds are the same.  If we don't catch this, we'll
1159  // get an infinite loop of size-allocate signals.
1160  if (bounds_ == bounds)
1161    return;
1162
1163  bounds_ = bounds;
1164  Layout();
1165}
1166
1167gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
1168                                            GdkEventCrossing* event) {
1169  hover_animation_->SetTweenType(gfx::Tween::EASE_OUT);
1170  hover_animation_->Show();
1171  return FALSE;
1172}
1173
1174gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
1175                                            GdkEventCrossing* event) {
1176  hover_animation_->SetTweenType(gfx::Tween::EASE_IN);
1177  hover_animation_->Hide();
1178  return FALSE;
1179}
1180
1181// static
1182void TabRendererGtk::InitResources() {
1183  if (initialized_)
1184    return;
1185
1186  // Grab the pixel sizes of our masking images.
1187  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1188  GdkPixbuf* tab_active_l = rb.GetNativeImageNamed(
1189      IDR_TAB_ACTIVE_LEFT).ToGdkPixbuf();
1190  tab_active_l_width_ = gdk_pixbuf_get_width(tab_active_l);
1191  tab_active_l_height_ = gdk_pixbuf_get_height(tab_active_l);
1192
1193  GdkPixbuf* tab_inactive_l = rb.GetNativeImageNamed(
1194      IDR_TAB_INACTIVE_LEFT).ToGdkPixbuf();
1195  tab_inactive_l_height_ = gdk_pixbuf_get_height(tab_inactive_l);
1196
1197  GdkPixbuf* tab_close = rb.GetNativeImageNamed(IDR_CLOSE_1).ToGdkPixbuf();
1198  close_button_width_ = gdk_pixbuf_get_width(tab_close);
1199  close_button_height_ = gdk_pixbuf_get_height(tab_close);
1200
1201  const gfx::Font& base_font = rb.GetFont(ui::ResourceBundle::BaseFont);
1202  title_font_ = new gfx::Font(base_font.GetFontName(), kFontPixelSize);
1203  title_font_height_ = title_font_->GetHeight();
1204
1205  initialized_ = true;
1206}
1207