summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser.scons1
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc38
-rw-r--r--chrome/browser/gtk/browser_window_gtk.h7
-rw-r--r--chrome/browser/gtk/tab_strip_gtk.cc497
-rw-r--r--chrome/browser/gtk/tab_strip_gtk.h142
-rw-r--r--chrome/chrome.gyp2
6 files changed, 673 insertions, 14 deletions
diff --git a/chrome/browser/browser.scons b/chrome/browser/browser.scons
index d65875c..8df9881 100644
--- a/chrome/browser/browser.scons
+++ b/chrome/browser/browser.scons
@@ -766,6 +766,7 @@ if env.Bit('linux'):
'gtk/standard_menus.cc',
'gtk/status_bubble_gtk.cc',
'gtk/tab_contents_container_gtk.cc',
+ 'gtk/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 1ae6ade..e2960f8 100644
--- a/chrome/browser/gtk/browser_window_gtk.cc
+++ b/chrome/browser/gtk/browser_window_gtk.cc
@@ -20,6 +20,7 @@
#include "chrome/browser/gtk/nine_box.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/location_bar.h"
#include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
#include "chrome/browser/tab_contents/web_contents.h"
@@ -120,6 +121,8 @@ const struct AcceleratorMapping {
{ GDK_k, IDC_FOCUS_SEARCH },
{ GDK_l, IDC_FOCUS_LOCATION },
{ GDK_o, IDC_OPEN_FILE },
+ { GDK_Page_Down, IDC_SELECT_NEXT_TAB },
+ { GDK_Page_Up, IDC_SELECT_PREVIOUS_TAB },
{ GDK_w, IDC_CLOSE_TAB },
};
@@ -188,12 +191,19 @@ BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
};
content_area_ninebox_.reset(new NineBox(images));
- // This vbox is intended to surround the "content": toolbar+page.
- // When we add the tab strip, it should go in a vbox surrounding this one.
- vbox_ = gtk_vbox_new(FALSE, 0);
- gtk_widget_set_app_paintable(vbox_, TRUE);
- gtk_widget_set_double_buffered(vbox_, FALSE);
- g_signal_connect(G_OBJECT(vbox_), "expose-event",
+ // This vbox encompasses all of the widgets within the browser, including
+ // the tabstrip and the content vbox.
+ window_vbox_ = gtk_vbox_new(FALSE, 0);
+
+ tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model()));
+ tabstrip_->Init();
+ tabstrip_->AddTabStripToBox(window_vbox_);
+
+ // This vbox surrounds the "content": toolbar+page.
+ content_vbox_ = gtk_vbox_new(FALSE, 0);
+ gtk_widget_set_app_paintable(content_vbox_, TRUE);
+ gtk_widget_set_double_buffered(content_vbox_, FALSE);
+ g_signal_connect(G_OBJECT(content_vbox_), "expose-event",
G_CALLBACK(&OnContentAreaExpose), this);
// Temporary hack hidden behind a command line option to add one of the
@@ -205,14 +215,14 @@ BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
experimental_widget_->SetContentsView(
new views::TextButton(new DummyButtonListener, L"Button"));
- gtk_box_pack_start(GTK_BOX(vbox_),
+ gtk_box_pack_start(GTK_BOX(content_vbox_),
experimental_widget_->GetNativeView(),
false, false, 2);
}
toolbar_.reset(new BrowserToolbarGtk(browser_.get()));
toolbar_->Init(browser_->profile(), window_);
- toolbar_->AddToolbarToBox(vbox_);
+ toolbar_->AddToolbarToBox(content_vbox_);
FindBarGtk* find_bar_gtk = new FindBarGtk();
find_bar_controller_.reset(new FindBarController(find_bar_gtk));
@@ -221,7 +231,7 @@ BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
contents_container_.reset(
new TabContentsContainerGtk(find_bar_gtk->widget()));
- contents_container_->AddContainerToBox(vbox_);
+ contents_container_->AddContainerToBox(content_vbox_);
// Note that calling this the first time is necessary to get the
// proper control layout.
@@ -230,8 +240,10 @@ BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
status_bubble_.reset(new StatusBubbleGtk(window_));
- gtk_container_add(GTK_CONTAINER(window_), vbox_);
- gtk_widget_show(vbox_);
+ gtk_container_add(GTK_CONTAINER(window_vbox_), content_vbox_);
+ gtk_container_add(GTK_CONTAINER(window_), window_vbox_);
+ gtk_widget_show(content_vbox_);
+ gtk_widget_show(window_vbox_);
browser_->tabstrip_model()->AddObserver(this);
}
@@ -538,11 +550,11 @@ void BrowserWindowGtk::ConnectAccelerators() {
void BrowserWindowGtk::SetCustomFrame(bool custom_frame) {
custom_frame_ = custom_frame;
if (custom_frame_) {
- gtk_container_set_border_width(GTK_CONTAINER(vbox_), 2);
+ gtk_container_set_border_width(GTK_CONTAINER(window_vbox_), 2);
// TODO(port): all the crazy blue title bar, etc.
NOTIMPLEMENTED();
} else {
- gtk_container_set_border_width(GTK_CONTAINER(vbox_), 0);
+ gtk_container_set_border_width(GTK_CONTAINER(window_vbox_), 0);
}
}
diff --git a/chrome/browser/gtk/browser_window_gtk.h b/chrome/browser/gtk/browser_window_gtk.h
index e092d74..301c5ec 100644
--- a/chrome/browser/gtk/browser_window_gtk.h
+++ b/chrome/browser/gtk/browser_window_gtk.h
@@ -20,6 +20,7 @@ class LocationBar;
class NineBox;
class StatusBubbleGtk;
class TabContentsContainerGtk;
+class TabStripGtk;
// An implementation of BrowserWindow for GTK.
// Cross-platform code will interact with this object when
@@ -86,7 +87,8 @@ class BrowserWindowGtk : public BrowserWindow,
protected:
virtual void DestroyBrowser();
GtkWindow* window_;
- GtkWidget* vbox_;
+ GtkWidget* window_vbox_;
+ GtkWidget* content_vbox_;
scoped_ptr<Browser> browser_;
@@ -140,6 +142,9 @@ class BrowserWindowGtk : public BrowserWindow,
// to move among windows as tabs are dragged around.
scoped_ptr<FindBarController> find_bar_controller_;
+ // The tab strip. Always non-NULL.
+ scoped_ptr<TabStripGtk> tabstrip_;
+
// When it goes out of scope during our destruction, |method_factory_| will
// cancel its pending tasks (which depend on us still existing).
ScopedRunnableMethodFactory<BrowserWindowGtk> method_factory_;
diff --git a/chrome/browser/gtk/tab_strip_gtk.cc b/chrome/browser/gtk/tab_strip_gtk.cc
new file mode 100644
index 0000000..2e7b4fc
--- /dev/null
+++ b/chrome/browser/gtk/tab_strip_gtk.cc
@@ -0,0 +1,497 @@
+// 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);
+ 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 = tabstrip_.get()->style->fg_gc[GTK_WIDGET_STATE(tabstrip_.get())];
+ 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());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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);
+
+ 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
new file mode 100644
index 0000000..b147fd0
--- /dev/null
+++ b/chrome/browser/gtk/tab_strip_gtk.h
@@ -0,0 +1,142 @@
+// 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/chrome.gyp b/chrome/chrome.gyp
index 3b1eef6..372622d 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -672,6 +672,8 @@
'browser/gtk/status_bubble_gtk.h',
'browser/gtk/tab_contents_container_gtk.cc',
'browser/gtk/tab_contents_container_gtk.h',
+ 'browser/gtk/tab_strip_gtk.cc',
+ 'browser/gtk/tab_strip_gtk.h',
'browser/hang_monitor/hung_plugin_action.cc',
'browser/hang_monitor/hung_plugin_action.h',
'browser/hang_monitor/hung_window_detector.cc',