summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-30 19:49:53 +0000
committerjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-30 19:49:53 +0000
commit000b6ee0af3d7b362f812352cc2adcdbf4a2c51f (patch)
treee76a8bebe085b84556072910e5c1a7c168a66822 /chrome
parent2396f68c0f6f15534ca35497277cd346b9ed0417 (diff)
downloadchromium_src-000b6ee0af3d7b362f812352cc2adcdbf4a2c51f.zip
chromium_src-000b6ee0af3d7b362f812352cc2adcdbf4a2c51f.tar.gz
chromium_src-000b6ee0af3d7b362f812352cc2adcdbf4a2c51f.tar.bz2
Break out the tab rendering logic into TabGtk and TabRendererGtk.
Review URL: http://codereview.chromium.org/56030 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12795 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser.scons4
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc2
-rw-r--r--chrome/browser/gtk/tab_strip_gtk.cc502
-rw-r--r--chrome/browser/gtk/tab_strip_gtk.h142
-rw-r--r--chrome/browser/gtk/tabs/tab_gtk.cc63
-rw-r--r--chrome/browser/gtk/tabs/tab_gtk.h89
-rw-r--r--chrome/browser/gtk/tabs/tab_renderer_gtk.cc423
-rw-r--r--chrome/browser/gtk/tabs/tab_renderer_gtk.h172
-rw-r--r--chrome/browser/gtk/tabs/tab_strip_gtk.cc420
-rw-r--r--chrome/browser/gtk/tabs/tab_strip_gtk.h153
10 files changed, 1324 insertions, 646 deletions
diff --git a/chrome/browser/browser.scons b/chrome/browser/browser.scons
index f6c1cc4..21a35f3 100644
--- a/chrome/browser/browser.scons
+++ b/chrome/browser/browser.scons
@@ -773,7 +773,9 @@ if env.Bit('linux'):
'gtk/standard_menus.cc',
'gtk/status_bubble_gtk.cc',
'gtk/tab_contents_container_gtk.cc',
- 'gtk/tab_strip_gtk.cc',
+ 'gtk/tabs/tab_gtk.cc',
+ 'gtk/tabs/tab_renderer_gtk.cc',
+ 'gtk/tabs/tab_strip_gtk.cc',
'process_singleton_linux.cc',
'renderer_host/render_widget_host_view_gtk.cc',
'tab_contents/web_contents_view_gtk.cc',
diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc
index aca7897..3f878dc 100644
--- a/chrome/browser/gtk/browser_window_gtk.cc
+++ b/chrome/browser/gtk/browser_window_gtk.cc
@@ -19,7 +19,7 @@
#include "chrome/browser/gtk/find_bar_gtk.h"
#include "chrome/browser/gtk/status_bubble_gtk.h"
#include "chrome/browser/gtk/tab_contents_container_gtk.h"
-#include "chrome/browser/gtk/tab_strip_gtk.h"
+#include "chrome/browser/gtk/tabs/tab_strip_gtk.h"
#include "chrome/browser/location_bar.h"
#include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
#include "chrome/browser/tab_contents/web_contents.h"
diff --git a/chrome/browser/gtk/tab_strip_gtk.cc b/chrome/browser/gtk/tab_strip_gtk.cc
deleted file mode 100644
index 20b5308..0000000
--- a/chrome/browser/gtk/tab_strip_gtk.cc
+++ /dev/null
@@ -1,502 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/gtk/tab_strip_gtk.h"
-
-#include "base/gfx/gtk_util.h"
-#include "chrome/browser/browser.h"
-#include "chrome/browser/tab_contents/tab_contents.h"
-#include "chrome/common/l10n_util.h"
-#include "chrome/common/resource_bundle.h"
-#include "grit/generated_resources.h"
-#include "grit/theme_resources.h"
-
-namespace {
-
-static const int kLeftPadding = 16;
-static const int kTopPadding = 6;
-static const int kRightPadding = 15;
-static const int kBottomPadding = 5;
-static const int kFavIconTitleSpacing = 4;
-static const int kTitleCloseButtonSpacing = 5;
-static const int kStandardTitleWidth = 175;
-static const int kFavIconSize = 16;
-static const int kNewTabButtonHOffset = -5;
-static const int kNewTabButtonVOffset = 5;
-static const GdkColor kSelectedTitleColor = GDK_COLOR_RGB(0, 0, 0);
-static const GdkColor kUnselectedTitleColor = GDK_COLOR_RGB(64, 64, 64);
-
-// The horizontal offset from one tab to the next,
-// which results in overlapping tabs.
-static const int kTabHOffset = -16;
-
-// The vertical and horizontal offset used to position the close button
-// in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about.
-static const int kCloseButtonVertFuzz = 0;
-static const int kCloseButtonHorzFuzz = 5;
-
-// TODO(jhawkins): Move this code into ChromeFont and allow pulling out a
-// GdkFont*.
-static GdkFont* load_default_font() {
- GtkSettings* settings = gtk_settings_get_default();
-
- GValue value = { 0 };
- g_value_init(&value, G_TYPE_STRING);
- g_object_get_property(G_OBJECT(settings), "gtk-font-name", &value);
-
- gchar* font_name = g_strdup_value_contents(&value);
- PangoFontDescription* font_description = pango_font_description_from_string(
- font_name);
-
- GdkFont* font = gdk_font_from_description(font_description);
- g_free(font_name);
- return font;
-}
-
-} // namespace
-
-bool TabStripGtk::initialized_ = false;
-TabStripGtk::TabImage TabStripGtk::tab_active_ = {0};
-TabStripGtk::TabImage TabStripGtk::tab_inactive_ = {0};
-TabStripGtk::TabImage TabStripGtk::tab_inactive_otr_ = {0};
-TabStripGtk::TabImage TabStripGtk::tab_hover_ = {0};
-TabStripGtk::ButtonImage TabStripGtk::close_button_ = {0};
-TabStripGtk::ButtonImage TabStripGtk::newtab_button_ = {0};
-GdkFont* TabStripGtk::title_font_ = NULL;
-int TabStripGtk::title_font_height_ = 0;
-GdkPixbuf* TabStripGtk::download_icon_ = NULL;
-int TabStripGtk::download_icon_width_ = 0;
-int TabStripGtk::download_icon_height_ = 0;
-
-static inline int Round(double x) {
- return static_cast<int>(floor(x + 0.5));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TabStripGtk, public:
-
-TabStripGtk::TabStripGtk(TabStripModel* model)
- : model_(model) {
-
-}
-
-TabStripGtk::~TabStripGtk() {
- model_->RemoveObserver(this);
- tabstrip_.Destroy();
-}
-
-void TabStripGtk::Init() {
- model_->AddObserver(this);
-
- InitResources();
-
- ResourceBundle& rb = ResourceBundle::GetSharedInstance();
- GdkPixbuf* tab_center = rb.LoadPixbuf(IDR_TAB_ACTIVE_CENTER);
-
- tabstrip_.Own(gtk_drawing_area_new());
- gtk_widget_set_size_request(tabstrip_.get(), -1,
- gdk_pixbuf_get_height(tab_center));
- gtk_widget_set_app_paintable(tabstrip_.get(), TRUE);
- g_signal_connect(G_OBJECT(tabstrip_.get()), "expose-event",
- G_CALLBACK(OnExpose), this);
- gtk_widget_show_all(tabstrip_.get());
-}
-
-void TabStripGtk::AddTabStripToBox(GtkWidget* box) {
- gtk_box_pack_start(GTK_BOX(box), tabstrip_.get(), FALSE, FALSE, 0);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TabStripGtk, TabStripModelObserver implementation:
-
-void TabStripGtk::TabInsertedAt(TabContents* contents,
- int index,
- bool foreground) {
- TabData tabdata;
- UpdateTabData(contents, &tabdata);
- tab_data_.push_back(tabdata);
-
- gtk_widget_queue_draw(tabstrip_.get());
-}
-
-void TabStripGtk::TabDetachedAt(TabContents* contents, int index) {
- RemoveTabAt(index);
- gtk_widget_queue_draw(tabstrip_.get());
-}
-
-void TabStripGtk::TabSelectedAt(TabContents* old_contents,
- TabContents* new_contents,
- int index,
- bool user_gesture) {
- gtk_widget_queue_draw(tabstrip_.get());
-}
-
-void TabStripGtk::TabMoved(TabContents* contents,
- int from_index,
- int to_index) {
- gtk_widget_queue_draw(tabstrip_.get());
-}
-
-void TabStripGtk::TabChangedAt(TabContents* contents, int index) {
- UpdateTabData(contents, &tab_data_.at(index));
-
- gtk_widget_queue_draw(tabstrip_.get());
-}
-
-void TabStripGtk::LayoutTab(TabData* tab) {
- gfx::Rect bounds = tab->bounds;
- if (bounds.IsEmpty())
- return;
-
- bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
-
- // Figure out who is tallest.
- int content_height = GetContentHeight();
-
- // Size the FavIcon.
- if (tab->show_icon) {
- int favicon_top = kTopPadding + (content_height - kFavIconSize) / 2;
- tab->favicon_bounds.SetRect(bounds.x(), favicon_top,
- kFavIconSize, kFavIconSize);
- } else {
- tab->favicon_bounds.SetRect(bounds.x(), bounds.y(), 0, 0);
- }
-
- // Size the download icon.
- if (tab->show_download_icon) {
- int icon_top = kTopPadding + (content_height - download_icon_height_) / 2;
- tab->download_icon_bounds.SetRect(bounds.width() - download_icon_width_,
- icon_top, download_icon_width_,
- download_icon_height_);
- }
-
- int close_button_top = kTopPadding + kCloseButtonVertFuzz +
- (content_height - close_button_.height) / 2;
- tab->close_button_bounds.SetRect(tab->bounds.x() + bounds.width() +
- kCloseButtonHorzFuzz, close_button_top, close_button_.width,
- close_button_.height);
-
- // Size the title text to fill the remaining space.
- int title_left = tab->favicon_bounds.right() + kFavIconTitleSpacing;
- int title_top = kTopPadding + (content_height - title_font_height_) / 2;
-
- // If the user has big fonts, the title will appear rendered too far down on
- // the y-axis if we use the regular top padding, so we need to adjust it so
- // that the text appears centered.
- gfx::Size minimum_size = GetMinimumUnselectedSize();
- int text_height = title_top + title_font_height_ + kBottomPadding;
- if (text_height > minimum_size.height())
- title_top -= (text_height - minimum_size.height()) / 2;
-
- int title_width = std::max(tab->close_button_bounds.x() -
- kTitleCloseButtonSpacing - title_left, 0);
- if (tab->show_download_icon)
- title_width = std::max(title_width - download_icon_width_, 0);
- title_top = tab->bounds.height() - title_top;
- tab->title_bounds.SetRect(title_left, title_top,
- title_width, title_font_height_);
-}
-
-void TabStripGtk::Layout() {
- GenerateIdealBounds();
-
- for (std::vector<TabData>::iterator iter = tab_data_.begin();
- iter != tab_data_.end(); ++iter) {
- LayoutTab(&*iter);
- }
-}
-
-void TabStripGtk::DrawImageInt(GdkPixbuf* pixbuf, int x, int y) {
- GdkGC* gc = tabstrip_.get()->style->fg_gc[GTK_WIDGET_STATE(tabstrip_.get())];
-
- gdk_draw_pixbuf(tabstrip_.get()->window, // The destination drawable.
- gc, // Graphics context.
- pixbuf, // Source image.
- 0, 0, // Source x, y.
- x, y, -1, -1, // Destination x, y, width, height.
- GDK_RGB_DITHER_NONE, 0, 0); // Dithering mode, x,y offsets.
-}
-
-void TabStripGtk::TileImageInt(GdkPixbuf* pixbuf,
- int x, int y, int w, int h) {
- GdkGC* gc = tabstrip_.get()->style->fg_gc[GTK_WIDGET_STATE(tabstrip_.get())];
- int image_width = gdk_pixbuf_get_width(pixbuf);
- int slices = w / image_width;
- int remaining = w - slices * image_width;
-
- for (int i = 0; i < slices; i++) {
- gdk_draw_pixbuf(tabstrip_.get()->window, gc,
- pixbuf, 0, 0, x + image_width * i, y, -1, -1,
- GDK_RGB_DITHER_NONE, 0, 0);
- }
-
- if (remaining) {
- gdk_draw_pixbuf(tabstrip_.get()->window, gc,
- pixbuf, 0, 0, x + image_width * slices, y, remaining, -1,
- GDK_RGB_DITHER_NONE, 0, 0);
- }
-}
-
-void TabStripGtk::PaintTab(int index, bool selected) {
- GdkGC* gc = gdk_gc_new(tabstrip_.get()->window);
- TabImage& image = (selected) ? tab_active_ : tab_inactive_;
- TabData& data = tab_data_.at(index);
- gfx::Rect bounds = data.bounds;
-
- DrawImageInt(image.image_l, bounds.x(), bounds.y());
-
- TileImageInt(image.image_c, bounds.x() + image.l_width, bounds.y(),
- bounds.width() - image.l_width - image.r_width,
- gdk_pixbuf_get_height(image.image_c));
-
- DrawImageInt(image.image_r,
- bounds.x() + bounds.width() - image.r_width, bounds.y());
-
- if (data.show_icon && !data.favicon.empty()) {
- GdkPixbuf* favicon = gfx::GdkPixbufFromSkBitmap(&data.favicon);
- DrawImageInt(favicon, bounds.x() + image.l_width, kTopPadding);
- }
-
- std::wstring title = data.title;
- if (title.empty()) {
- if (data.loading) {
- title = l10n_util::GetString(IDS_TAB_LOADING_TITLE);
- } else {
- title = l10n_util::GetString(IDS_TAB_UNTITLED_TITLE);
- }
- } else {
- Browser::FormatTitleForDisplay(&title);
- }
-
- if (selected) {
- gdk_gc_set_rgb_fg_color(gc, &kSelectedTitleColor);
- } else {
- gdk_gc_set_rgb_fg_color(gc, &kUnselectedTitleColor);
- }
-
- // TODO(jhawkins): Clip the title.
- gdk_draw_text(tabstrip_.get()->window, title_font_, gc,
- data.title_bounds.x(), data.title_bounds.y(),
- WideToUTF8(title).c_str(), title.length());
-
- DrawImageInt(close_button_.normal,
- data.close_button_bounds.x(), data.close_button_bounds.y());
-
- g_object_unref(gc);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TabStrip, private:
-
-size_t TabStripGtk::GetTabCount() const {
- return tab_data_.size();
-}
-
-void TabStripGtk::RemoveTabAt(int index) {
- tab_data_.erase(tab_data_.begin() + index);
-}
-
-void TabStripGtk::GenerateIdealBounds() {
- size_t tab_count = GetTabCount();
- double unselected, selected;
- GetDesiredTabWidths(tab_count, &unselected, &selected);
-
- int tab_height = GetStandardSize().height();
- double tab_x = 0;
- int selected_index = model_->selected_index();
-
- std::vector<TabData>::iterator iter = tab_data_.begin();
- for (int i = 0; iter != tab_data_.end(); ++iter, ++i) {
- double tab_width = unselected;
- if (i == selected_index)
- tab_width = selected;
- double end_of_tab = tab_x + tab_width;
- int rounded_tab_x = Round(tab_x);
- gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
- tab_height);
- iter->bounds = state;
- tab_x = end_of_tab + kTabHOffset;
- }
-}
-
-// static
-int TabStripGtk::GetContentHeight() {
- // The height of the content of the Tab is the largest of the favicon,
- // the title text and the close button graphic.
- int content_height = std::max(kFavIconSize, title_font_height_);
- return std::max(content_height, close_button_.height);
-}
-
-// static
-gfx::Size TabStripGtk::GetMinimumUnselectedSize() {
- InitResources();
-
- gfx::Size minimum_size;
- minimum_size.set_width(kLeftPadding + kRightPadding);
- // Since we use bitmap images, the real minimum height of the image is
- // defined most accurately by the height of the end cap images.
- minimum_size.set_height(gdk_pixbuf_get_height(tab_active_.image_l));
- return minimum_size;
-}
-
-// static
-gfx::Size TabStripGtk::GetMinimumSelectedSize() {
- gfx::Size minimum_size = GetMinimumUnselectedSize();
- minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding);
- return minimum_size;
-}
-
-// static
-gfx::Size TabStripGtk::GetStandardSize() {
- gfx::Size standard_size = GetMinimumUnselectedSize();
- standard_size.set_width(
- standard_size.width() + kFavIconTitleSpacing + kStandardTitleWidth);
- return standard_size;
-}
-
-void TabStripGtk::GetDesiredTabWidths(size_t tab_count,
- double* unselected_width,
- double* selected_width) const {
- const double min_unselected_width = GetMinimumUnselectedSize().width();
- const double min_selected_width = GetMinimumSelectedSize().width();
- if (tab_count == 0) {
- // Return immediately to avoid divide-by-zero below.
- *unselected_width = min_unselected_width;
- *selected_width = min_selected_width;
- return;
- }
-
- // Determine how much space we can actually allocate to tabs.
- int available_width = tabstrip_.get()->allocation.width;
- available_width -= (kNewTabButtonHOffset + newtab_button_.width);
-
- // Calculate the desired tab widths by dividing the available space into equal
- // portions. Don't let tabs get larger than the "standard width" or smaller
- // than the minimum width for each type, respectively.
- const int total_offset = kTabHOffset * (tab_count - 1);
- const double desired_tab_width = std::min((static_cast<double>(
- available_width - total_offset) / static_cast<double>(tab_count)),
- static_cast<double>(GetStandardSize().width()));
- *unselected_width = std::max(desired_tab_width, min_unselected_width);
- *selected_width = std::max(desired_tab_width, min_selected_width);
-
- // When there are multiple tabs, we'll have one selected and some unselected
- // tabs. If the desired width was between the minimum sizes of these types,
- // try to shrink the tabs with the smaller minimum. For example, if we have
- // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
- // selected tabs have a minimum width of 4 and unselected tabs have a minimum
- // width of 1, the above code would set *unselected_width = 2.5,
- // *selected_width = 4, which results in a total width of 11.5. Instead, we
- // want to set *unselected_width = 2, *selected_width = 4, for a total width
- // of 10.
- if (tab_count > 1) {
- if ((min_unselected_width < min_selected_width) &&
- (desired_tab_width < min_selected_width)) {
- *unselected_width = std::max(static_cast<double>(
- available_width - total_offset - min_selected_width) /
- static_cast<double>(tab_count - 1), min_unselected_width);
- } else if ((min_unselected_width > min_selected_width) &&
- (desired_tab_width < min_unselected_width)) {
- *selected_width = std::max(available_width - total_offset -
- (min_unselected_width * (tab_count - 1)), min_selected_width);
- }
- }
-}
-
-void TabStripGtk::UpdateTabData(TabContents* contents, TabData* tab) {
- tab->favicon = contents->GetFavIcon();
- tab->show_icon = contents->ShouldDisplayFavIcon();
- tab->show_download_icon = contents->IsDownloadShelfVisible();
- tab->title = UTF16ToWideHack(contents->GetTitle());
- tab->loading = contents->is_loading();
-}
-
-// static
-gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e,
- TabStripGtk* tabstrip) {
- TabStripModel* model = tabstrip->model();
- int selected = model->selected_index();
-
- // TODO(jhawkins): Move the Layout call out of OnExpose and into methods
- // that actually affect the layout.
- tabstrip->Layout();
-
- ResourceBundle& rb = ResourceBundle::GetSharedInstance();
- GdkPixbuf* background = rb.LoadPixbuf(IDR_WINDOW_TOP_CENTER);
- tabstrip->TileImageInt(background, 0, 0,
- tabstrip->tabstrip_.get()->allocation.width,
- tabstrip->tabstrip_.get()->allocation.height);
-
- if (model->count() == 0)
- return TRUE;
-
- for (int i = 0; i < model->count(); i++) {
- if (i != selected) {
- tabstrip->PaintTab(i, false);
- }
- }
-
- tabstrip->PaintTab(selected, true);
-
- return TRUE;
-}
-
-// static
-void TabStripGtk::LoadTabImages() {
- ResourceBundle& rb = ResourceBundle::GetSharedInstance();
-
- tab_active_.image_l = rb.LoadPixbuf(IDR_TAB_ACTIVE_LEFT);
- tab_active_.image_c = rb.LoadPixbuf(IDR_TAB_ACTIVE_CENTER);
- tab_active_.image_r = rb.LoadPixbuf(IDR_TAB_ACTIVE_RIGHT);
- tab_active_.l_width = gdk_pixbuf_get_width(tab_active_.image_l);
- tab_active_.r_width = gdk_pixbuf_get_width(tab_active_.image_r);
-
- tab_inactive_.image_l = rb.LoadPixbuf(IDR_TAB_INACTIVE_LEFT);
- tab_inactive_.image_c = rb.LoadPixbuf(IDR_TAB_INACTIVE_CENTER);
- tab_inactive_.image_r = rb.LoadPixbuf(IDR_TAB_INACTIVE_RIGHT);
- tab_inactive_.l_width = gdk_pixbuf_get_width(tab_inactive_.image_l);
- tab_inactive_.r_width = gdk_pixbuf_get_width(tab_inactive_.image_r);
-
- tab_hover_.image_l = rb.LoadPixbuf(IDR_TAB_HOVER_LEFT);
- tab_hover_.image_c = rb.LoadPixbuf(IDR_TAB_HOVER_CENTER);
- tab_hover_.image_r = rb.LoadPixbuf(IDR_TAB_HOVER_RIGHT);
-
- tab_inactive_otr_.image_l = rb.LoadPixbuf(IDR_TAB_INACTIVE_LEFT_OTR);
- tab_inactive_otr_.image_c = rb.LoadPixbuf(IDR_TAB_INACTIVE_CENTER_OTR);
- tab_inactive_otr_.image_r = rb.LoadPixbuf(IDR_TAB_INACTIVE_RIGHT_OTR);
-
- // tab_[hover,inactive_otr] width are not used and are initialized to 0
- // during static initialization.
-
- close_button_.normal = rb.LoadPixbuf(IDR_TAB_CLOSE);
- close_button_.hot = rb.LoadPixbuf(IDR_TAB_CLOSE_H);
- close_button_.pushed = rb.LoadPixbuf(IDR_TAB_CLOSE_P);
- close_button_.width = gdk_pixbuf_get_width(close_button_.normal);
- close_button_.height = gdk_pixbuf_get_height(close_button_.normal);
-
- newtab_button_.normal = rb.LoadPixbuf(IDR_NEWTAB_BUTTON);
- newtab_button_.hot = rb.LoadPixbuf(IDR_NEWTAB_BUTTON_H);
- newtab_button_.pushed = rb.LoadPixbuf(IDR_NEWTAB_BUTTON_P);
- newtab_button_.width = gdk_pixbuf_get_width(newtab_button_.normal);
- newtab_button_.height = gdk_pixbuf_get_height(newtab_button_.normal);
-
- download_icon_ = rb.LoadPixbuf(IDR_DOWNLOAD_ICON);
- download_icon_width_ = gdk_pixbuf_get_width(download_icon_);
- download_icon_height_ = gdk_pixbuf_get_height(download_icon_);
-}
-
-// static
-void TabStripGtk::InitResources() {
- if (initialized_)
- return;
-
- LoadTabImages();
-
- // TODO(jhawkins): Move this into ChromeFont. Also, my default gtk font
- // is really ugly compared to the other fonts being used in the UI.
- title_font_ = load_default_font();
- DCHECK(title_font_);
- title_font_height_ = gdk_char_height(title_font_, 'X');
- initialized_ = true;
-}
diff --git a/chrome/browser/gtk/tab_strip_gtk.h b/chrome/browser/gtk/tab_strip_gtk.h
deleted file mode 100644
index b147fd0..0000000
--- a/chrome/browser/gtk/tab_strip_gtk.h
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_
-#define CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_
-
-#include <gtk/gtk.h>
-
-#include "base/gfx/rect.h"
-#include "chrome/browser/tabs/tab_strip_model.h"
-#include "chrome/common/owned_widget_gtk.h"
-#include "skia/include/SkBitmap.h"
-
-class TabStripGtk : public TabStripModelObserver {
- public:
- TabStripGtk(TabStripModel* model);
- virtual ~TabStripGtk();
-
- // Initialize and load the TabStrip into a container.
- void Init();
- void AddTabStripToBox(GtkWidget* box);
-
- TabStripModel* model() const { return model_; }
-
- // Sets the bounds of the tabs.
- void Layout();
-
- // Paints the tab at |index|.
- void PaintTab(int index, bool selected);
-
- protected:
- // TabStripModelObserver implementation:
- virtual void TabInsertedAt(TabContents* contents,
- int index,
- bool foreground);
- virtual void TabDetachedAt(TabContents* contents, int index);
- virtual void TabSelectedAt(TabContents* old_contents,
- TabContents* contents,
- int index,
- bool user_gesture);
- virtual void TabMoved(TabContents* contents, int from_index, int to_index);
- virtual void TabChangedAt(TabContents* contents, int index);
-
- private:
- // expose-event handler that redraws the tabstrip
- static gboolean OnExpose(GtkWidget* widget, GdkEventExpose* e,
- TabStripGtk* tabstrip);
-
- // Gets the number of Tabs in the collection.
- size_t GetTabCount() const;
-
- // Cleans up the tab from the TabStrip at the specified |index|.
- void RemoveTabAt(int index);
-
- struct TabData;
- // Sets the bounds for each tab based on that TabStrip state.
- void LayoutTab(TabData* tab);
- void GenerateIdealBounds();
-
- // Returns the largest of the favicon, title text, and the close button.
- static int GetContentHeight();
-
- // Tab layout helpers.
- static gfx::Size GetMinimumUnselectedSize();
- static gfx::Size GetMinimumSelectedSize();
- static gfx::Size GetStandardSize();
- void GetDesiredTabWidths(size_t tab_count,
- double* unselected_width,
- double* selected_width) const;
-
- // Updates the state of |tab| based on the current state of |contents|.
- void UpdateTabData(TabContents* contents, TabData* tab);
-
- // Loads the images and font used by the TabStrip.
- static void LoadTabImages();
- static void InitResources();
- static bool initialized_;
-
- // Draws |pixbuf| at |x|,|y| in the TabStrip.
- void DrawImageInt(GdkPixbuf* pixbuf, int x, int y);
-
- // Tiles |pixbuf| at |x|,|y| in the TabStrip, filling up |w|x|h| area.
- void TileImageInt(GdkPixbuf* pixbuf,
- int x, int y, int w, int h);
-
- // TODO(jhawkins): Move this data to TabRendererGtk.
- struct TabImage {
- GdkPixbuf* image_l;
- GdkPixbuf* image_c;
- GdkPixbuf* image_r;
- int l_width;
- int r_width;
- };
- static TabImage tab_active_;
- static TabImage tab_inactive_;
- static TabImage tab_inactive_otr_;
- static TabImage tab_hover_;
-
- struct ButtonImage {
- GdkPixbuf* normal;
- GdkPixbuf* hot;
- GdkPixbuf* pushed;
- int width;
- int height;
- };
- static ButtonImage close_button_;
- static ButtonImage newtab_button_;
-
- static GdkFont* title_font_;
- static int title_font_height_;
-
- static GdkPixbuf* download_icon_;
- static int download_icon_width_;
- static int download_icon_height_;
-
- // Model data. We store this here so that we don't need to ask the underlying
- // model, which is tricky since instances of this object can outlive the
- // corresponding objects in the underlying model.
- // TODO(jhawkins): Move this data into TabGtk/TabRendererGtk.
- struct TabData {
- SkBitmap favicon;
- bool show_icon;
- bool show_download_icon;
- std::wstring title;
- bool loading;
- gfx::Rect bounds;
- gfx::Rect close_button_bounds;
- gfx::Rect download_icon_bounds;
- gfx::Rect favicon_bounds;
- gfx::Rect title_bounds;
- };
- std::vector<TabData> tab_data_;
-
- // The drawing area widget.
- OwnedWidgetGtk tabstrip_;
-
- // Our model.
- TabStripModel* model_;
-};
-
-#endif // CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_
diff --git a/chrome/browser/gtk/tabs/tab_gtk.cc b/chrome/browser/gtk/tabs/tab_gtk.cc
new file mode 100644
index 0000000..bd555f4
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_gtk.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/tabs/tab_gtk.h"
+
+#include "chrome/common/gfx/path.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/resource_bundle.h"
+#include "grit/generated_resources.h"
+
+static const SkScalar kTabCapWidth = 15;
+static const SkScalar kTabTopCurveWidth = 4;
+static const SkScalar kTabBottomCurveWidth = 3;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TabGtk, public:
+
+TabGtk::TabGtk(TabDelegate* delegate)
+ : TabRendererGtk(),
+ delegate_(delegate),
+ closing_(false) {
+}
+
+TabGtk::~TabGtk() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabGtk, TabRendererGtk overrides:
+
+bool TabGtk::IsSelected() const {
+ return delegate_->IsTabSelected(this);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabGtk, private:
+
+void TabGtk::MakePathForTab(gfx::Path* path) const {
+ DCHECK(path);
+
+ SkScalar h = SkIntToScalar(height());
+ SkScalar w = SkIntToScalar(width());
+
+ path->moveTo(0, h);
+
+ // Left end cap.
+ path->lineTo(kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(kTabCapWidth, 0);
+
+ // Connect to the right cap.
+ path->lineTo(w - kTabCapWidth, 0);
+
+ // Right end cap.
+ path->lineTo(w - kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth);
+ path->lineTo(w - kTabBottomCurveWidth, h - kTabBottomCurveWidth);
+ path->lineTo(w, h);
+
+ // Close out the path.
+ path->lineTo(0, h);
+ path->close();
+}
diff --git a/chrome/browser/gtk/tabs/tab_gtk.h b/chrome/browser/gtk/tabs/tab_gtk.h
new file mode 100644
index 0000000..6dda29c
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_gtk.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VIEWS_GTK_TABS_TAB_GTK_H_
+#define CHROME_BROWSER_VIEWS_GTK_TABS_TAB_GTK_H_
+
+#include "base/basictypes.h"
+#include "chrome/browser/gtk/tabs/tab_renderer_gtk.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+
+namespace gfx {
+class Path;
+}
+
+class TabGtk : public TabRendererGtk {
+ public:
+ // An interface implemented by an object that can help this Tab complete
+ // various actions. The index parameter is the index of this Tab in the
+ // TabRenderer::Model.
+ class TabDelegate {
+ public:
+ // Returns true if the specified Tab is selected.
+ virtual bool IsTabSelected(const TabGtk* tab) const = 0;
+
+ // Selects the specified Tab.
+ virtual void SelectTab(TabGtk* tab) = 0;
+
+ // Closes the specified Tab.
+ virtual void CloseTab(TabGtk* tab) = 0;
+
+ // Returns true if the specified command is enabled for the specified Tab.
+ virtual bool IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id,
+ const TabGtk* tab) const = 0;
+
+ // Executes the specified command for the specified Tab.
+ virtual void ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) = 0;
+
+ // Starts/Stops highlighting the tabs that will be affected by the
+ // specified command for the specified Tab.
+ virtual void StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) = 0;
+ virtual void StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) = 0;
+ virtual void StopAllHighlighting() = 0;
+
+ // Ends dragging a Tab. |canceled| is true if the drag was aborted in a way
+ // other than the user releasing the mouse. Returns whether the tab has been
+ // destroyed.
+ virtual bool EndDrag(bool canceled) = 0;
+
+ // Returns true if the associated TabStrip's delegate supports tab moving or
+ // detaching. Used by the Frame to determine if dragging on the Tab
+ // itself should move the window in cases where there's only one
+ // non drag-able Tab.
+ virtual bool HasAvailableDragActions() const = 0;
+ };
+
+ explicit TabGtk(TabDelegate* delegate);
+ virtual ~TabGtk();
+
+ // Access the delegate.
+ TabDelegate* delegate() const { return delegate_; }
+
+ // Used to set/check whether this Tab is being animated closed.
+ void set_closing(bool closing) { closing_ = closing; }
+ bool closing() const { return closing_; }
+
+ // TabRenderer overrides:
+ virtual bool IsSelected() const;
+
+ private:
+ // Creates a path that contains the clickable region of the tab's visual
+ // representation. Used by GetViewForPoint for hit-testing.
+ void MakePathForTab(gfx::Path* path) const;
+
+ // An instance of a delegate object that can perform various actions based on
+ // user gestures.
+ TabDelegate* delegate_;
+
+ // True if the tab is being animated closed.
+ bool closing_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabGtk);
+};
+
+#endif // CHROME_BROWSER_VIEWS_GTK_TABS_TAB_GTK_H_
diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc
new file mode 100644
index 0000000..c75e982
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc
@@ -0,0 +1,423 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/tabs/tab_renderer_gtk.h"
+
+#include "base/gfx/gtk_util.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/resource_bundle.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+
+namespace {
+
+const int kLeftPadding = 16;
+const int kTopPadding = 6;
+const int kRightPadding = 15;
+const int kBottomPadding = 5;
+const int kFavIconTitleSpacing = 4;
+const int kTitleCloseButtonSpacing = 5;
+const int kStandardTitleWidth = 175;
+const int kFavIconSize = 16;
+const GdkColor kSelectedTitleColor = GDK_COLOR_RGB(0, 0, 0);
+const GdkColor kUnselectedTitleColor = GDK_COLOR_RGB(64, 64, 64);
+
+// The vertical and horizontal offset used to position the close button
+// in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about.
+const int kCloseButtonVertFuzz = 0;
+const int kCloseButtonHorzFuzz = 5;
+
+// TODO(jhawkins): Move this code into ChromeFont and allow pulling out a
+// GdkFont*.
+GdkFont* load_default_font() {
+ GtkSettings* settings = gtk_settings_get_default();
+
+ GValue value = {0};
+ g_value_init(&value, G_TYPE_STRING);
+ g_object_get_property(G_OBJECT(settings), "gtk-font-name", &value);
+
+ gchar* font_name = g_strdup_value_contents(&value);
+ PangoFontDescription* font_description =
+ pango_font_description_from_string(font_name);
+
+ GdkFont* font = gdk_font_from_description(font_description);
+ g_free(font_name);
+ return font;
+}
+
+} // namespace
+
+bool TabRendererGtk::initialized_ = false;
+TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0};
+TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0};
+TabRendererGtk::TabImage TabRendererGtk::tab_inactive_otr_ = {0};
+TabRendererGtk::TabImage TabRendererGtk::tab_hover_ = {0};
+TabRendererGtk::ButtonImage TabRendererGtk::close_button_ = {0};
+TabRendererGtk::ButtonImage TabRendererGtk::newtab_button_ = {0};
+GdkFont* TabRendererGtk::title_font_ = NULL;
+int TabRendererGtk::title_font_height_ = 0;
+GdkPixbuf* TabRendererGtk::download_icon_ = NULL;
+int TabRendererGtk::download_icon_width_ = 0;
+int TabRendererGtk::download_icon_height_ = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, public:
+
+TabRendererGtk::TabRendererGtk()
+ : showing_icon_(false),
+ showing_download_icon_(false),
+ showing_close_button_(false),
+ fav_icon_hiding_offset_(0),
+ should_display_crashed_favicon_(false) {
+ InitResources();
+}
+
+TabRendererGtk::~TabRendererGtk() {
+}
+
+void TabRendererGtk::UpdateData(TabContents* contents) {
+ DCHECK(contents);
+ data_.favicon = contents->GetFavIcon();
+ data_.title = UTF16ToWideHack(contents->GetTitle());
+ data_.loading = contents->is_loading();
+ data_.off_the_record = contents->profile()->IsOffTheRecord();
+ data_.show_icon = contents->ShouldDisplayFavIcon();
+ data_.show_download_icon = contents->IsDownloadShelfVisible();
+ data_.crashed = contents->is_crashed();
+}
+
+void TabRendererGtk::UpdateFromModel() {
+ // Force a layout, since the tab may have grown a favicon.
+ Layout();
+}
+
+bool TabRendererGtk::IsSelected() const {
+ return true;
+}
+
+// static
+gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
+ InitResources();
+
+ gfx::Size minimum_size;
+ minimum_size.set_width(kLeftPadding + kRightPadding);
+ // Since we use bitmap images, the real minimum height of the image is
+ // defined most accurately by the height of the end cap images.
+ minimum_size.set_height(gdk_pixbuf_get_height(tab_active_.image_l));
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
+ gfx::Size minimum_size = GetMinimumUnselectedSize();
+ minimum_size.set_width(kLeftPadding + kFavIconSize + kRightPadding);
+ return minimum_size;
+}
+
+// static
+gfx::Size TabRendererGtk::GetStandardSize() {
+ gfx::Size standard_size = GetMinimumUnselectedSize();
+ standard_size.Enlarge(kFavIconTitleSpacing + kStandardTitleWidth, 0);
+ return standard_size;
+}
+
+// static
+int TabRendererGtk::GetContentHeight() {
+ // The height of the content of the Tab is the largest of the favicon,
+ // the title text and the close button graphic.
+ int content_height = std::max(kFavIconSize, title_font_height_);
+ return std::max(content_height, close_button_.height);
+}
+
+// static
+void TabRendererGtk::LoadTabImages() {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+
+ tab_active_.image_l = rb.LoadPixbuf(IDR_TAB_ACTIVE_LEFT);
+ tab_active_.image_c = rb.LoadPixbuf(IDR_TAB_ACTIVE_CENTER);
+ tab_active_.image_r = rb.LoadPixbuf(IDR_TAB_ACTIVE_RIGHT);
+ tab_active_.l_width = gdk_pixbuf_get_width(tab_active_.image_l);
+ tab_active_.r_width = gdk_pixbuf_get_width(tab_active_.image_r);
+
+ tab_inactive_.image_l = rb.LoadPixbuf(IDR_TAB_INACTIVE_LEFT);
+ tab_inactive_.image_c = rb.LoadPixbuf(IDR_TAB_INACTIVE_CENTER);
+ tab_inactive_.image_r = rb.LoadPixbuf(IDR_TAB_INACTIVE_RIGHT);
+ tab_inactive_.l_width = gdk_pixbuf_get_width(tab_inactive_.image_l);
+ tab_inactive_.r_width = gdk_pixbuf_get_width(tab_inactive_.image_r);
+
+ tab_hover_.image_l = rb.LoadPixbuf(IDR_TAB_HOVER_LEFT);
+ tab_hover_.image_c = rb.LoadPixbuf(IDR_TAB_HOVER_CENTER);
+ tab_hover_.image_r = rb.LoadPixbuf(IDR_TAB_HOVER_RIGHT);
+
+ tab_inactive_otr_.image_l = rb.LoadPixbuf(IDR_TAB_INACTIVE_LEFT_OTR);
+ tab_inactive_otr_.image_c = rb.LoadPixbuf(IDR_TAB_INACTIVE_CENTER_OTR);
+ tab_inactive_otr_.image_r = rb.LoadPixbuf(IDR_TAB_INACTIVE_RIGHT_OTR);
+
+ // tab_[hover,inactive_otr] width are not used and are initialized to 0
+ // during static initialization.
+
+ close_button_.normal = rb.LoadPixbuf(IDR_TAB_CLOSE);
+ close_button_.hot = rb.LoadPixbuf(IDR_TAB_CLOSE_H);
+ close_button_.pushed = rb.LoadPixbuf(IDR_TAB_CLOSE_P);
+ close_button_.width = gdk_pixbuf_get_width(close_button_.normal);
+ close_button_.height = gdk_pixbuf_get_height(close_button_.normal);
+
+ newtab_button_.normal = rb.LoadPixbuf(IDR_NEWTAB_BUTTON);
+ newtab_button_.hot = rb.LoadPixbuf(IDR_NEWTAB_BUTTON_H);
+ newtab_button_.pushed = rb.LoadPixbuf(IDR_NEWTAB_BUTTON_P);
+ newtab_button_.width = gdk_pixbuf_get_width(newtab_button_.normal);
+ newtab_button_.height = gdk_pixbuf_get_height(newtab_button_.normal);
+
+ download_icon_ = rb.LoadPixbuf(IDR_DOWNLOAD_ICON);
+ download_icon_width_ = gdk_pixbuf_get_width(download_icon_);
+ download_icon_height_ = gdk_pixbuf_get_height(download_icon_);
+}
+
+void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
+ bounds_ = bounds;
+ Layout();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, protected:
+
+std::wstring TabRendererGtk::GetTitle() const {
+ return data_.title;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabRendererGtk, private:
+
+void TabRendererGtk::Paint(GtkWidget* canvas) {
+ GdkGC* gc = gdk_gc_new(canvas->window);
+ // Don't paint if we're narrower than we can render correctly. (This should
+ // only happen during animations).
+ if (width() < GetMinimumUnselectedSize().width())
+ return;
+
+ // See if the model changes whether the icons should be painted.
+ const bool show_icon = ShouldShowIcon();
+ const bool show_download_icon = data_.show_download_icon;
+ const bool show_close_button = ShouldShowCloseBox();
+ if (show_icon != showing_icon_ ||
+ show_download_icon != showing_download_icon_ ||
+ show_close_button != showing_close_button_)
+ Layout();
+
+ PaintTabBackground(canvas);
+
+ if (show_icon && !data_.favicon.isNull()) {
+ GdkPixbuf* favicon = gfx::GdkPixbufFromSkBitmap(&data_.favicon);
+ DrawImageInt(canvas, favicon, favicon_bounds_.x(),
+ favicon_bounds_.y() + fav_icon_hiding_offset_);
+ }
+
+ if (show_download_icon) {
+ DrawImageInt(canvas, download_icon_,
+ download_icon_bounds_.x(), download_icon_bounds_.y());
+ }
+
+ // Paint the Title.
+ std::wstring title = data_.title;
+ if (title.empty()) {
+ if (data_.loading) {
+ title = l10n_util::GetString(IDS_TAB_LOADING_TITLE);
+ } else {
+ title = l10n_util::GetString(IDS_TAB_UNTITLED_TITLE);
+ }
+ } else {
+ Browser::FormatTitleForDisplay(&title);
+ }
+
+ if (IsSelected()) {
+ gdk_gc_set_rgb_fg_color(gc, &kSelectedTitleColor);
+ } else {
+ gdk_gc_set_rgb_fg_color(gc, &kUnselectedTitleColor);
+ }
+
+ // TODO(jhawkins): Clip the title.
+ gdk_draw_text(canvas->window, title_font_, gc,
+ title_bounds_.x(), title_bounds_.y(),
+ WideToUTF8(title).c_str(), title.length());
+
+ DrawImageInt(canvas, close_button_.normal,
+ close_button_bounds_.x(), close_button_bounds_.y());
+
+ g_object_unref(gc);
+}
+
+void TabRendererGtk::Layout() {
+ gfx::Rect local_bounds = bounds_;
+ if (local_bounds.IsEmpty())
+ return;
+ local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
+
+ // Figure out who is tallest.
+ int content_height = GetContentHeight();
+
+ // Size the Favicon.
+ showing_icon_ = ShouldShowIcon();
+ if (showing_icon_) {
+ int favicon_top = kTopPadding + (content_height - kFavIconSize) / 2;
+ favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
+ kFavIconSize, kFavIconSize);
+ } else {
+ favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
+ }
+
+ // Size the download icon.
+ showing_download_icon_ = data_.show_download_icon;
+ if (showing_download_icon_) {
+ int icon_top = kTopPadding + (content_height - download_icon_height_) / 2;
+ download_icon_bounds_.SetRect(local_bounds.width() - download_icon_width_,
+ icon_top, download_icon_width_,
+ download_icon_height_);
+ }
+
+ // Size the Close button.
+ showing_close_button_ = ShouldShowCloseBox();
+ if (showing_close_button_) {
+ int close_button_top =
+ kTopPadding + kCloseButtonVertFuzz +
+ (content_height - close_button_.height) / 2;
+ close_button_bounds_.SetRect(bounds_.x() +
+ local_bounds.width() + kCloseButtonHorzFuzz,
+ bounds_.y() + close_button_top,
+ close_button_.width, close_button_.height);
+ } else {
+ close_button_bounds_.SetRect(0, 0, 0, 0);
+ }
+
+ // Size the Title text to fill the remaining space.
+ int title_left = favicon_bounds_.right() + kFavIconTitleSpacing;
+ int title_top = bounds_.y() + kTopPadding +
+ (content_height - title_font_height_) / 2;
+ title_top = bounds_.height() - title_top;
+
+ // If the user has big fonts, the title will appear rendered too far down on
+ // the y-axis if we use the regular top padding, so we need to adjust it so
+ // that the text appears centered.
+ gfx::Size minimum_size = GetMinimumUnselectedSize();
+ int text_height = title_top + title_font_height_ + kBottomPadding;
+ if (text_height > minimum_size.height()) {
+ title_top -= (text_height - minimum_size.height()) / 2;
+ }
+
+ int title_width;
+ if (close_button_bounds_.width() && close_button_bounds_.height()) {
+ title_width = std::max(close_button_bounds_.x() -
+ kTitleCloseButtonSpacing - title_left, 0);
+ } else {
+ title_width = std::max(local_bounds.width() - title_left, 0);
+ }
+ if (data_.show_download_icon)
+ title_width = std::max(title_width - download_icon_width_, 0);
+ title_bounds_.SetRect(title_left, title_top, title_width, title_font_height_);
+
+ // TODO(jhawkins): Handle RTL layout.
+}
+
+void TabRendererGtk::DrawImageInt(GtkWidget* canvas, GdkPixbuf* pixbuf,
+ int x, int y) {
+ GdkGC* gc = canvas->style->fg_gc[GTK_WIDGET_STATE(canvas)];
+
+ gdk_draw_pixbuf(canvas->window, // The destination drawable.
+ gc, // Graphics context.
+ pixbuf, // Source image.
+ 0, 0, // Source x, y.
+ x, y, -1, -1, // Destination x, y, width, height.
+ GDK_RGB_DITHER_NONE, 0, 0); // Dithering mode, x,y offsets.
+}
+
+void TabRendererGtk::TileImageInt(GtkWidget* canvas, GdkPixbuf* pixbuf,
+ int x, int y, int w, int h) {
+ GdkGC* gc = canvas->style->fg_gc[GTK_WIDGET_STATE(canvas)];
+ int image_width = gdk_pixbuf_get_width(pixbuf);
+ int slices = w / image_width;
+ int remaining = w - slices * image_width;
+
+ for (int i = 0; i < slices; i++) {
+ gdk_draw_pixbuf(canvas->window, gc,
+ pixbuf, 0, 0, x + image_width * i, y, -1, -1,
+ GDK_RGB_DITHER_NONE, 0, 0);
+ }
+
+ if (remaining) {
+ gdk_draw_pixbuf(canvas->window, gc,
+ pixbuf, 0, 0, x + image_width * slices, y, remaining, -1,
+ GDK_RGB_DITHER_NONE, 0, 0);
+ }
+}
+
+void TabRendererGtk::PaintTabBackground(GtkWidget* canvas) {
+ if (IsSelected()) {
+ // Sometimes detaching a tab quickly can result in the model reporting it
+ // as not being selected, so is_drag_clone_ ensures that we always paint
+ // the active representation for the dragged tab.
+ PaintActiveTabBackground(canvas);
+ } else {
+ PaintInactiveTabBackground(canvas);
+ }
+}
+
+void TabRendererGtk::PaintInactiveTabBackground(GtkWidget* canvas) {
+ bool is_otr = data_.off_the_record;
+ const TabImage& image = is_otr ? tab_inactive_otr_ : tab_inactive_;
+
+ DrawImageInt(canvas, image.image_l, bounds_.x(), bounds_.y());
+ TileImageInt(canvas, image.image_c,
+ bounds_.x() + tab_inactive_.l_width, bounds_.y(),
+ width() - tab_inactive_.l_width - tab_inactive_.r_width,
+ height());
+ DrawImageInt(canvas, image.image_r,
+ bounds_.x() + width() - tab_inactive_.r_width, bounds_.y());
+}
+
+void TabRendererGtk::PaintActiveTabBackground(GtkWidget* canvas) {
+ DrawImageInt(canvas, tab_active_.image_l, bounds_.x(), bounds_.y());
+ TileImageInt(canvas, tab_active_.image_c,
+ bounds_.x() + tab_active_.l_width, bounds_.y(),
+ width() - tab_active_.l_width - tab_active_.r_width, height());
+ DrawImageInt(canvas, tab_active_.image_r,
+ bounds_.x() + width() - tab_active_.r_width, bounds_.y());
+}
+
+int TabRendererGtk::IconCapacity() const {
+ if (height() < GetMinimumUnselectedSize().height())
+ return 0;
+ return (width() - kLeftPadding - kRightPadding) / kFavIconSize;
+}
+
+bool TabRendererGtk::ShouldShowIcon() const {
+ if (!data_.show_icon) {
+ return false;
+ } else if (IsSelected()) {
+ // The selected tab clips favicon before close button.
+ return IconCapacity() >= 2;
+ }
+ // Non-selected tabs clip close button before favicon.
+ return IconCapacity() >= 1;
+}
+
+bool TabRendererGtk::ShouldShowCloseBox() const {
+ // The selected tab never clips close button.
+ return IsSelected() || IconCapacity() >= 3;
+}
+
+// static
+void TabRendererGtk::InitResources() {
+ if (initialized_)
+ return;
+
+ LoadTabImages();
+
+ // TODO(jhawkins): Move this into ChromeFont. Also, my default gtk font
+ // is really ugly compared to the other fonts being used in the UI.
+ title_font_ = load_default_font();
+ DCHECK(title_font_);
+ title_font_height_ = gdk_char_height(title_font_, 'X');
+ initialized_ = true;
+}
diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.h b/chrome/browser/gtk/tabs/tab_renderer_gtk.h
new file mode 100644
index 0000000..80d3084
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_VIEWS_GTK_TABS_TAB_RENDERER_GTK_H_
+#define CHROME_BROWSER_VIEWS_GTK_TABS_TAB_RENDERER_GTK_H_
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+#include "skia/include/SkBitmap.h"
+
+namespace gfx {
+class Size;
+}
+
+class TabContents;
+
+class TabRendererGtk {
+ public:
+ TabRendererGtk();
+ virtual ~TabRendererGtk();
+
+ // Updates the data the Tab uses to render itself from the specified
+ // TabContents.
+ void UpdateData(TabContents* contents);
+
+ // Updates the display to reflect the contents of this TabRenderer's model.
+ void UpdateFromModel();
+
+ // Returns true if the Tab is selected, false otherwise.
+ virtual bool IsSelected() const;
+
+ // Returns the minimum possible size of a single unselected Tab.
+ static gfx::Size GetMinimumUnselectedSize();
+ // Returns the minimum possible size of a selected Tab. Selected tabs must
+ // always show a close button and have a larger minimum size than unselected
+ // tabs.
+ static gfx::Size GetMinimumSelectedSize();
+ // Returns the preferred size of a single Tab, assuming space is
+ // available.
+ static gfx::Size GetStandardSize();
+
+ // Loads the images to be used for the tab background.
+ static void LoadTabImages();
+
+ // Returns the bounds of the Tab.
+ int x() const { return bounds_.x(); }
+ int y() const { return bounds_.y(); }
+ int width() const { return bounds_.width(); }
+ int height() const { return bounds_.height(); }
+
+ // Sets the bounds of the tab.
+ void SetBounds(const gfx::Rect& bounds);
+
+ // Paints the tab into |canvas|.
+ void Paint(GtkWidget* canvas);
+
+ protected:
+ const gfx::Rect& title_bounds() const { return title_bounds_; }
+
+ // Returns the title of the Tab.
+ std::wstring GetTitle() const;
+
+ private:
+ // Generates the bounds for the interior items of the tab.
+ void Layout();
+
+ // Returns the largest of the favicon, title text, and the close button.
+ static int GetContentHeight();
+
+ // TODO(jhawkins): Use a NineBox.
+ void DrawImageInt(GtkWidget* tabstrip, GdkPixbuf* pixbuf, int x, int y);
+ void TileImageInt(GtkWidget* tabstrip, GdkPixbuf* pixbuf,
+ int x, int y, int w, int h);
+
+ // Paint various portions of the Tab
+ // TODO(jhawkins): Paint hover tab.
+ void PaintTabBackground(GtkWidget* canvas);
+ void PaintInactiveTabBackground(GtkWidget* canvas);
+ void PaintActiveTabBackground(GtkWidget* canvas);
+ void PaintLoadingAnimation(GtkWidget* canvas);
+
+ // Returns the number of favicon-size elements that can fit in the tab's
+ // current size.
+ int IconCapacity() const;
+
+ // Returns whether the Tab should display a favicon.
+ bool ShouldShowIcon() const;
+
+ // Returns whether the Tab should display a close button.
+ bool ShouldShowCloseBox() const;
+
+ // TODO(jhawkins): Move to TabResources.
+ static void InitResources();
+ static bool initialized_;
+
+ // The bounds of various sections of the display.
+ gfx::Rect favicon_bounds_;
+ gfx::Rect download_icon_bounds_;
+ gfx::Rect title_bounds_;
+ gfx::Rect close_button_bounds_;
+
+ // Model data. We store this here so that we don't need to ask the underlying
+ // model, which is tricky since instances of this object can outlive the
+ // corresponding objects in the underlying model.
+ struct TabData {
+ SkBitmap favicon;
+ std::wstring title;
+ bool loading;
+ bool crashed;
+ bool off_the_record;
+ bool show_icon;
+ bool show_download_icon;
+ };
+ TabData data_;
+
+ // TODO(jhawkins): Move into TabResources class.
+ struct TabImage {
+ GdkPixbuf* image_l;
+ GdkPixbuf* image_c;
+ GdkPixbuf* image_r;
+ int l_width;
+ int r_width;
+ };
+ static TabImage tab_active_;
+ static TabImage tab_inactive_;
+ static TabImage tab_inactive_otr_;
+ static TabImage tab_hover_;
+
+ struct ButtonImage {
+ GdkPixbuf* normal;
+ GdkPixbuf* hot;
+ GdkPixbuf* pushed;
+ int width;
+ int height;
+ };
+ static ButtonImage close_button_;
+ static ButtonImage newtab_button_;
+
+ static GdkFont* title_font_;
+ static int title_font_height_;
+
+ static GdkPixbuf* download_icon_;
+ static int download_icon_width_;
+ static int download_icon_height_;
+
+ // Whether we're showing the icon. It is cached so that we can detect when it
+ // changes and layout appropriately.
+ bool showing_icon_;
+
+ // Whether we are showing the download icon. Comes from the model.
+ bool showing_download_icon_;
+
+ // Whether we are showing the close button. It is cached so that we can
+ // detect when it changes and layout appropriately.
+ bool showing_close_button_;
+
+ // The offset used to animate the favicon location.
+ int fav_icon_hiding_offset_;
+
+ // Set when the crashed favicon should be displayed.
+ bool should_display_crashed_favicon_;
+
+ // The bounds of this Tab.
+ gfx::Rect bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabRendererGtk);
+};
+
+#endif // CHROME_BROWSER_VIEWS_GTK_TABS_TAB_RENDERER_GTK_H_
diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc
new file mode 100644
index 0000000..ba369c8
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/tabs/tab_strip_gtk.h"
+
+#include "base/gfx/gtk_util.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/resource_bundle.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+
+namespace {
+
+const int kNewTabButtonHOffset = -5;
+const int kNewTabButtonVOffset = 5;
+
+// The horizontal offset from one tab to the next,
+// which results in overlapping tabs.
+const int kTabHOffset = -16;
+
+inline int Round(double x) {
+ return static_cast<int>(floor(x + 0.5));
+}
+
+// widget->allocation is not guaranteed to be set. After window creation,
+// we pick up the normal bounds by connecting to the configure-event signal.
+gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) {
+ GtkRequisition request;
+ gtk_widget_size_request(widget, &request);
+ return gfx::Rect(0, 0, request.width, request.height);
+}
+
+} // namespace
+
+NineBox* TabStripGtk::background_ = NULL;
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStripGtk, public:
+
+TabStripGtk::TabStripGtk(TabStripModel* model)
+ : current_unselected_width_(TabGtk::GetStandardSize().width()),
+ current_selected_width_(TabGtk::GetStandardSize().width()),
+ available_width_for_tabs_(-1),
+ resize_layout_scheduled_(false),
+ model_(model) {
+}
+
+TabStripGtk::~TabStripGtk() {
+ model_->RemoveObserver(this);
+ tabstrip_.Destroy();
+}
+
+void TabStripGtk::Init() {
+ model_->AddObserver(this);
+
+ InitBackgroundNineBox();
+
+ tabstrip_.Own(gtk_drawing_area_new());
+ gtk_widget_set_size_request(tabstrip_.get(), -1,
+ TabGtk::GetMinimumUnselectedSize().height());
+ gtk_widget_set_app_paintable(tabstrip_.get(), TRUE);
+ g_signal_connect(G_OBJECT(tabstrip_.get()), "expose-event",
+ G_CALLBACK(OnExpose), this);
+ g_signal_connect(G_OBJECT(tabstrip_.get()), "configure-event",
+ G_CALLBACK(OnConfigure), this);
+ gtk_widget_show_all(tabstrip_.get());
+
+ bounds_ = GetInitialWidgetBounds(tabstrip_.get());
+}
+
+void TabStripGtk::AddTabStripToBox(GtkWidget* box) {
+ gtk_box_pack_start(GTK_BOX(box), tabstrip_.get(), FALSE, FALSE, 0);
+}
+
+void TabStripGtk::Layout() {
+ // Called from:
+ // - window resize
+ GenerateIdealBounds();
+ int tab_count = GetTabCount();
+ for (int i = 0; i < tab_count; ++i) {
+ const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds;
+ GetTabAt(i)->SetBounds(bounds);
+ }
+
+ gtk_widget_queue_draw(tabstrip_.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStripGtk, TabStripModelObserver implementation:
+
+void TabStripGtk::TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground) {
+ DCHECK(contents);
+ DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));
+
+ TabGtk* tab = new TabGtk(this);
+
+ // Only insert if we're not already in the list.
+ if (index == TabStripModel::kNoTab) {
+ TabData d = { tab, gfx::Rect() };
+ tab_data_.push_back(d);
+ tab->UpdateData(contents);
+ } else {
+ TabData d = { tab, gfx::Rect() };
+ tab_data_.insert(tab_data_.begin() + index, d);
+ tab->UpdateData(contents);
+ }
+
+ Layout();
+}
+
+void TabStripGtk::TabDetachedAt(TabContents* contents, int index) {
+ GetTabAt(index)->set_closing(true);
+ // TODO(jhawkins): Remove erase call when animations are hooked up.
+ tab_data_.erase(tab_data_.begin() + index);
+ GenerateIdealBounds();
+ // TODO(jhawkins): Remove layout call when animations are hooked up.
+ Layout();
+}
+
+void TabStripGtk::TabSelectedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index,
+ bool user_gesture) {
+ DCHECK(index >= 0 && index < static_cast<int>(GetTabCount()));
+
+ // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
+ // a different size to the selected ones.
+ bool tiny_tabs = current_unselected_width_ != current_selected_width_;
+ if (!resize_layout_scheduled_ || tiny_tabs) {
+ Layout();
+ } else {
+ gtk_widget_queue_draw(tabstrip_.get());
+ }
+}
+
+void TabStripGtk::TabMoved(TabContents* contents,
+ int from_index,
+ int to_index) {
+ TabGtk* tab = GetTabAt(from_index);
+ tab_data_.erase(tab_data_.begin() + from_index);
+ TabData data = {tab, gfx::Rect()};
+ tab_data_.insert(tab_data_.begin() + to_index, data);
+ GenerateIdealBounds();
+ // TODO(jhawkins): Remove layout call when animations are hooked up.
+ Layout();
+}
+
+void TabStripGtk::TabChangedAt(TabContents* contents, int index) {
+ // Index is in terms of the model. Need to make sure we adjust that index in
+ // case we have an animation going.
+ TabGtk* tab = GetTabAt(index);
+ tab->UpdateData(contents);
+ tab->UpdateFromModel();
+ gtk_widget_queue_draw(tabstrip_.get());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TabStripGtk, TabGtk::Delegate implementation:
+
+bool TabStripGtk::IsTabSelected(const TabGtk* tab) const {
+ if (tab->closing())
+ return false;
+
+ int tab_count = GetTabCount();
+ for (int i = 0, index = 0; i < tab_count; ++i, ++index) {
+ TabGtk* current_tab = GetTabAt(i);
+ if (current_tab->closing())
+ --index;
+ if (current_tab == tab)
+ return index == model_->selected_index();
+ }
+ return false;
+}
+
+void TabStripGtk::GetCurrentTabWidths(double* unselected_width,
+ double* selected_width) const {
+ *unselected_width = current_unselected_width_;
+ *selected_width = current_selected_width_;
+}
+
+void TabStripGtk::SelectTab(TabGtk* tab) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ model_->SelectTabContentsAt(index, true);
+}
+
+void TabStripGtk::CloseTab(TabGtk* tab) {
+ int tab_index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(tab_index)) {
+ TabGtk* last_tab = GetTabAt(GetTabCount() - 1);
+ // Limit the width available to the TabStrip for laying out Tabs, so that
+ // Tabs are not resized until a later time (when the mouse pointer leaves
+ // the TabStrip).
+ available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab);
+ resize_layout_scheduled_ = true;
+ model_->CloseTabContentsAt(tab_index);
+ }
+}
+
+bool TabStripGtk::IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ return model_->IsContextMenuCommandEnabled(index, command_id);
+ return false;
+}
+
+void TabStripGtk::ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index))
+ model_->ExecuteContextMenuCommand(index, command_id);
+}
+
+void TabStripGtk::StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
+ if (command_id == TabStripModel::CommandCloseTabsOpenedBy) {
+ int index = GetIndexOfTab(tab);
+ if (model_->ContainsIndex(index)) {
+ std::vector<int> indices = model_->GetIndexesOpenedBy(index);
+ std::vector<int>::const_iterator iter = indices.begin();
+ for (; iter != indices.end(); ++iter) {
+ int current_index = *iter;
+ DCHECK(current_index >= 0 && current_index < GetTabCount());
+ }
+ }
+ }
+}
+
+void TabStripGtk::StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
+ if (command_id == TabStripModel::CommandCloseTabsOpenedBy ||
+ command_id == TabStripModel::CommandCloseTabsToRight ||
+ command_id == TabStripModel::CommandCloseOtherTabs) {
+ // Just tell all Tabs to stop pulsing - it's safe.
+ StopAllHighlighting();
+ }
+}
+
+void TabStripGtk::StopAllHighlighting() {
+ // TODO(jhawkins): Hook up animations.
+}
+
+bool TabStripGtk::EndDrag(bool canceled) {
+ // TODO(jhawkins): Tab dragging.
+ return true;
+}
+
+bool TabStripGtk::HasAvailableDragActions() const {
+ return model_->delegate()->GetDragActions() != 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabStripGtk, private:
+
+int TabStripGtk::GetTabCount() const {
+ return static_cast<int>(tab_data_.size());
+}
+
+int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const {
+ return last_tab->x() + last_tab->width();
+}
+
+int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const {
+ for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
+ TabGtk* current_tab = GetTabAt(i);
+ if (current_tab->closing()) {
+ --index;
+ } else if (current_tab == tab) {
+ return index;
+ }
+ }
+ return -1;
+}
+
+TabGtk* TabStripGtk::GetTabAt(int index) const {
+ DCHECK(index >= 0 && index < GetTabCount());
+ return tab_data_.at(index).tab;
+}
+
+void TabStripGtk::RemoveTabAt(int index) {
+ tab_data_.erase(tab_data_.begin() + index);
+ Layout();
+}
+
+void TabStripGtk::GenerateIdealBounds() {
+ int tab_count = GetTabCount();
+ double unselected, selected;
+ GetDesiredTabWidths(tab_count, &unselected, &selected);
+
+ current_unselected_width_ = unselected;
+ current_selected_width_ = selected;
+
+ // NOTE: This currently assumes a tab's height doesn't differ based on
+ // selected state or the number of tabs in the strip!
+ int tab_height = TabGtk::GetStandardSize().height();
+ double tab_x = 0;
+ for (int i = 0; i < tab_count; ++i) {
+ TabGtk* tab = GetTabAt(i);
+ double tab_width = unselected;
+ if (tab->IsSelected())
+ tab_width = selected;
+ double end_of_tab = tab_x + tab_width;
+ int rounded_tab_x = Round(tab_x);
+ gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
+ tab_height);
+ tab_data_.at(i).ideal_bounds = state;
+ tab_x = end_of_tab + kTabHOffset;
+ }
+}
+
+void TabStripGtk::GetDesiredTabWidths(int tab_count,
+ double* unselected_width,
+ double* selected_width) const {
+ const double min_unselected_width =
+ TabGtk::GetMinimumUnselectedSize().width();
+ const double min_selected_width =
+ TabGtk::GetMinimumSelectedSize().width();
+
+ if (tab_count == 0) {
+ // Return immediately to avoid divide-by-zero below.
+ *unselected_width = min_unselected_width;
+ *selected_width = min_selected_width;
+ return;
+ }
+
+ // Determine how much space we can actually allocate to tabs.
+ int available_width = tabstrip_.get()->allocation.width;
+ // TODO(jhawkins): Implement new tab button.
+
+ // Calculate the desired tab widths by dividing the available space into equal
+ // portions. Don't let tabs get larger than the "standard width" or smaller
+ // than the minimum width for each type, respectively.
+ const int total_offset = kTabHOffset * (tab_count - 1);
+ const double desired_tab_width = std::min(
+ (static_cast<double>(available_width - total_offset) /
+ static_cast<double>(tab_count)),
+ static_cast<double>(TabGtk::GetStandardSize().width()));
+ *unselected_width = std::max(desired_tab_width, min_unselected_width);
+ *selected_width = std::max(desired_tab_width, min_selected_width);
+
+ // When there are multiple tabs, we'll have one selected and some unselected
+ // tabs. If the desired width was between the minimum sizes of these types,
+ // try to shrink the tabs with the smaller minimum. For example, if we have
+ // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
+ // selected tabs have a minimum width of 4 and unselected tabs have a minimum
+ // width of 1, the above code would set *unselected_width = 2.5,
+ // *selected_width = 4, which results in a total width of 11.5. Instead, we
+ // want to set *unselected_width = 2, *selected_width = 4, for a total width
+ // of 10.
+ if (tab_count > 1) {
+ if ((min_unselected_width < min_selected_width) &&
+ (desired_tab_width < min_selected_width)) {
+ double calc_width =
+ static_cast<double>(
+ available_width - total_offset - min_selected_width) /
+ static_cast<double>(tab_count - 1);
+ *unselected_width = std::max(calc_width, min_unselected_width);
+ } else if ((min_unselected_width > min_selected_width) &&
+ (desired_tab_width < min_unselected_width)) {
+ *selected_width = std::max(available_width - total_offset -
+ (min_unselected_width * (tab_count - 1)), min_selected_width);
+ }
+ }
+}
+
+// static
+gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e,
+ TabStripGtk* tabstrip) {
+ background_->RenderToWidget(widget);
+
+ // Paint the tabs in reverse order, so they stack to the left.
+ TabGtk* selected_tab = NULL;
+ int tab_count = tabstrip->GetTabCount();
+ for (int i = tab_count - 1; i >= 0; --i) {
+ TabGtk* tab = tabstrip->GetTabAt(i);
+ // We must ask the _Tab's_ model, not ourselves, because in some situations
+ // the model will be different to this object, e.g. when a Tab is being
+ // removed after its TabContents has been destroyed.
+ if (!tab->IsSelected()) {
+ tab->Paint(widget);
+ } else {
+ selected_tab = tab;
+ }
+ }
+
+ // Paint the selected tab last, so it overlaps all the others.
+ if (selected_tab)
+ selected_tab->Paint(widget);
+
+ return TRUE;
+}
+
+// static
+gboolean TabStripGtk::OnConfigure(GtkWidget* widget, GdkEventConfigure* event,
+ TabStripGtk* tabstrip) {
+ gfx::Rect bounds = gfx::Rect(event->x, event->y, event->width, event->height);
+ tabstrip->SetBounds(bounds);
+ tabstrip->Layout();
+ return TRUE;
+}
+
+// static
+void TabStripGtk::InitBackgroundNineBox() {
+ if (background_)
+ return;
+
+ ResourceBundle &rb = ResourceBundle::GetSharedInstance();
+
+ GdkPixbuf* images[9] = {0};
+ images[0] = rb.LoadPixbuf(IDR_WINDOW_TOP_CENTER);
+ images[1] = rb.LoadPixbuf(IDR_WINDOW_TOP_CENTER);
+ images[2] = rb.LoadPixbuf(IDR_WINDOW_TOP_CENTER);
+ background_ = new NineBox(images);
+}
diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h
new file mode 100644
index 0000000..695cd1a
--- /dev/null
+++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_
+#define CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_
+
+#include <gtk/gtk.h>
+#include <vector>
+
+#include "base/gfx/rect.h"
+#include "chrome/browser/gtk/nine_box.h"
+#include "chrome/browser/gtk/tabs/tab_gtk.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/common/owned_widget_gtk.h"
+#include "skia/include/SkBitmap.h"
+
+class TabStripGtk : public TabStripModelObserver,
+ public TabGtk::TabDelegate {
+ public:
+ explicit TabStripGtk(TabStripModel* model);
+ virtual ~TabStripGtk();
+
+ // Initialize and load the TabStrip into a container.
+ void Init();
+ void AddTabStripToBox(GtkWidget* box);
+
+ TabStripModel* model() const { return model_; }
+
+ // Sets the bounds of the tabs.
+ void Layout();
+
+ // Sets the bounds of the tabstrip.
+ void SetBounds(const gfx::Rect& bounds) { bounds_ = bounds; }
+
+ protected:
+ // TabStripModelObserver implementation:
+ virtual void TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground);
+ virtual void TabDetachedAt(TabContents* contents, int index);
+ virtual void TabSelectedAt(TabContents* old_contents,
+ TabContents* contents,
+ int index,
+ bool user_gesture);
+ virtual void TabMoved(TabContents* contents, int from_index, int to_index);
+ virtual void TabChangedAt(TabContents* contents, int index);
+
+ // TabGtk::Delegate implementation:
+ virtual bool IsTabSelected(const TabGtk* tab) const;
+ virtual void SelectTab(TabGtk* tab);
+ virtual void CloseTab(TabGtk* tab);
+ virtual bool IsCommandEnabledForTab(
+ TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const;
+ virtual void ExecuteCommandForTab(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab);
+ virtual void StartHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab);
+ virtual void StopHighlightTabsForCommand(
+ TabStripModel::ContextMenuCommand command_id, TabGtk* tab);
+ virtual void StopAllHighlighting();
+ virtual bool EndDrag(bool canceled);
+ virtual bool HasAvailableDragActions() const;
+
+ private:
+ // expose-event handler that redraws the tabstrip
+ static gboolean OnExpose(GtkWidget* widget, GdkEventExpose* e,
+ TabStripGtk* tabstrip);
+
+ // configure-event handler that gets the new bounds of the tabstrip.
+ static gboolean OnConfigure(GtkWidget* widget, GdkEventConfigure* event,
+ TabStripGtk* tabstrip);
+
+ // Gets the number of Tabs in the collection.
+ int GetTabCount() const;
+
+ // Retrieves the Tab at the specified index.
+ TabGtk* GetTabAt(int index) const;
+
+ // Returns the exact (unrounded) current width of each tab.
+ void GetCurrentTabWidths(double* unselected_width,
+ double* selected_width) const;
+
+ // Returns the exact (unrounded) desired width of each tab, based on the
+ // desired strip width and number of tabs. If
+ // |width_of_tabs_for_mouse_close_| is nonnegative we use that value in
+ // calculating the desired strip width; otherwise we use the current width.
+ void GetDesiredTabWidths(int tab_count,
+ double* unselected_width,
+ double* selected_width) const;
+
+ // Calculates the available width for tabs, assuming a Tab is to be closed.
+ int GetAvailableWidthForTabs(TabGtk* last_tab) const;
+
+ // Finds the index of the TabContents corresponding to |tab| in our
+ // associated TabStripModel, or -1 if there is none (e.g. the specified |tab|
+ // is being animated closed).
+ int GetIndexOfTab(const TabGtk* tab) const;
+
+ // Cleans up the tab from the TabStrip at the specified |index|.
+ void RemoveTabAt(int index);
+
+ // Generates the ideal bounds of the TabStrip when all Tabs have finished
+ // animating to their desired position/bounds. This is used by the standard
+ // Layout method and other callers like the DraggedTabController that need
+ // stable representations of Tab positions.
+ void GenerateIdealBounds();
+
+ // Loads the background resource into a NineBox.
+ static void InitBackgroundNineBox();
+
+ // The Tabs we contain, and their last generated "good" bounds.
+ struct TabData {
+ TabGtk* tab;
+ gfx::Rect ideal_bounds;
+ };
+ std::vector<TabData> tab_data_;
+
+ // The current widths of various types of tabs. We save these so that, as
+ // users close tabs while we're holding them at the same size, we can lay out
+ // tabs exactly and eliminate the "pixel jitter" we'd get from just leaving
+ // them all at their existing, rounded widths.
+ double current_unselected_width_;
+ double current_selected_width_;
+
+ // If this value is nonnegative, it is used in GetDesiredTabWidths() to
+ // calculate how much space in the tab strip to use for tabs. Most of the
+ // time this will be -1, but while we're handling closing a tab via the mouse,
+ // we'll set this to the edge of the last tab before closing, so that if we
+ // are closing the last tab and need to resize immediately, we'll resize only
+ // back to this width, thus once again placing the last tab under the mouse
+ // cursor.
+ int available_width_for_tabs_;
+
+ // True if a resize layout animation should be run a short delay after the
+ // mouse exits the TabStrip.
+ // TODO(beng): (Cleanup) this would be better named "needs_resize_layout_".
+ bool resize_layout_scheduled_;
+
+ // The NineBox that renders the tabstrip background.
+ static NineBox* background_;
+
+ // The drawing area widget.
+ OwnedWidgetGtk tabstrip_;
+
+ // The bounds of the tabstrip.
+ gfx::Rect bounds_;
+
+ // Our model.
+ TabStripModel* model_;
+};
+
+#endif // CHROME_BROWSER_GTK_TAB_STRIP_GTK_H_