diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-30 19:49:53 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-30 19:49:53 +0000 |
commit | 000b6ee0af3d7b362f812352cc2adcdbf4a2c51f (patch) | |
tree | e76a8bebe085b84556072910e5c1a7c168a66822 /chrome | |
parent | 2396f68c0f6f15534ca35497277cd346b9ed0417 (diff) | |
download | chromium_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.scons | 4 | ||||
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/gtk/tab_strip_gtk.cc | 502 | ||||
-rw-r--r-- | chrome/browser/gtk/tab_strip_gtk.h | 142 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_gtk.cc | 63 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_gtk.h | 89 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.cc | 423 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.h | 172 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.cc | 420 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.h | 153 |
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_ |