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/browser/gtk/tabs | |
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/browser/gtk/tabs')
-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 |
6 files changed, 1320 insertions, 0 deletions
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_ |