diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-23 21:47:30 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-23 21:47:30 +0000 |
commit | e48211e18675bec9056fb0762ec1c6644b0ff32e (patch) | |
tree | 8925f6392a530f40e43a1569293d8b04a4144d0b /chrome/browser | |
parent | c751b9e0a42759e059321f2daa8c9c4b29d01bfd (diff) | |
download | chromium_src-e48211e18675bec9056fb0762ec1c6644b0ff32e.zip chromium_src-e48211e18675bec9056fb0762ec1c6644b0ff32e.tar.gz chromium_src-e48211e18675bec9056fb0762ec1c6644b0ff32e.tar.bz2 |
Add initial implementation of the Linux TabStrip.
The following are implemented:
* Keyboard shortcuts for next/prev/close tab.
* Tab layout and rendering.
The following need to be implemented:
* Mouse input handling.
* Animations.
* TabGtk, TabRendererGtk.
* Dragging.
* Fix a NOTREACHED in navigation_controller.cc.
Review URL: http://codereview.chromium.org/42457
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12316 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/browser.scons | 1 | ||||
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.cc | 38 | ||||
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.h | 7 | ||||
-rw-r--r-- | chrome/browser/gtk/tab_strip_gtk.cc | 497 | ||||
-rw-r--r-- | chrome/browser/gtk/tab_strip_gtk.h | 142 |
5 files changed, 671 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_ |