diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-05 20:27:00 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-05 20:27:00 +0000 |
commit | c12fac94defb54d0a16ca2e1a27fc4023a1acba7 (patch) | |
tree | ec653aa7163c77d08d6333a5e83d0a9c7dce12c4 | |
parent | 6cf2af2efbde465c5ce6c90bd141056ed78ba335 (diff) | |
download | chromium_src-c12fac94defb54d0a16ca2e1a27fc4023a1acba7.zip chromium_src-c12fac94defb54d0a16ca2e1a27fc4023a1acba7.tar.gz chromium_src-c12fac94defb54d0a16ca2e1a27fc4023a1acba7.tar.bz2 |
Implement Linux tabs as widgets. This moves tab input handling into the correct object. This change also adds an initial DraggedTabControllerGtk.
Review URL: http://codereview.chromium.org/99371
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15330 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/gtk/custom_button.h | 10 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc | 368 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h | 177 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_gtk.cc | 204 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_gtk.h | 77 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.cc | 113 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_renderer_gtk.h | 37 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.cc | 602 | ||||
-rw-r--r-- | chrome/browser/gtk/tabs/tab_strip_gtk.h | 132 | ||||
-rw-r--r-- | chrome/chrome.gyp | 2 |
11 files changed, 980 insertions, 744 deletions
diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc index f8887a0..61bc7d8 100644 --- a/chrome/browser/gtk/browser_window_gtk.cc +++ b/chrome/browser/gtk/browser_window_gtk.cc @@ -227,7 +227,7 @@ BrowserWindowGtk::BrowserWindowGtk(Browser* browser) window_vbox_ = gtk_vbox_new(FALSE, 0); tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model())); - tabstrip_->Init(); + tabstrip_->Init(bounds_.width()); tabstrip_->AddTabStripToBox(window_vbox_); // This vbox surrounds the "content": toolbar+page. diff --git a/chrome/browser/gtk/custom_button.h b/chrome/browser/gtk/custom_button.h index aea7717..f5e21ce 100644 --- a/chrome/browser/gtk/custom_button.h +++ b/chrome/browser/gtk/custom_button.h @@ -9,6 +9,7 @@ #include <string> +#include "base/gfx/rect.h" #include "base/scoped_ptr.h" #include "chrome/common/owned_widget_gtk.h" @@ -55,6 +56,15 @@ class CustomDrawButton { GtkWidget* widget() const { return widget_.get(); } + gfx::Rect bounds() const { + return gfx::Rect(widget_.get()->allocation.x, + widget_.get()->allocation.y, + widget_.get()->allocation.width, + widget_.get()->allocation.height); + } + + int width() const { return widget_.get()->allocation.width; } + // This is a convenience function for creating a widget that closes // a bar (find bar, download shelf, info bars). The button will be packed in // |hbox|. diff --git a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc new file mode 100644 index 0000000..3650b27 --- /dev/null +++ b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc @@ -0,0 +1,368 @@ +// 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/dragged_tab_controller_gtk.h" + +#include "chrome/browser/gtk/tabs/tab_strip_gtk.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_service.h" + +namespace { + +// Used to determine how far a tab must obscure another tab in order to swap +// their indexes. +const int kHorizontalMoveThreshold = 16; // pixels + +} // namespace + +DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab, + TabStripGtk* source_tabstrip) + : dragged_contents_(NULL), + original_delegate_(NULL), + source_tab_(source_tab), + source_tabstrip_(source_tabstrip), + source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)), + attached_tabstrip_(source_tabstrip), + last_move_screen_x_(0) { + SetDraggedContents( + source_tabstrip_->model()->GetTabContentsAt(source_model_index_)); +} + +DraggedTabControllerGtk::~DraggedTabControllerGtk() { +} + +void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) { + start_screen_point_ = GetCursorScreenPoint(); + mouse_offset_ = mouse_offset; + snap_bounds_ = source_tab_->bounds(); +} + +void DraggedTabControllerGtk::Drag() { + ContinueDragging(); +} + +bool DraggedTabControllerGtk::EndDrag(bool canceled) { + return EndDragImpl(canceled ? CANCELED : NORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// +// DraggedTabControllerGtk, TabContentsDelegate implementation: + +void DraggedTabControllerGtk::OpenURLFromTab(TabContents* source, + const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + if (original_delegate_) { + if (disposition == CURRENT_TAB) + disposition = NEW_WINDOW; + + original_delegate_->OpenURLFromTab(source, url, referrer, + disposition, transition); + } +} + +void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source, + unsigned changed_flags) { +} + +void DraggedTabControllerGtk::AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + DCHECK(disposition != CURRENT_TAB); + + // Theoretically could be called while dragging if the page tries to + // spawn a window. Route this message back to the browser in most cases. + if (original_delegate_) { + original_delegate_->AddNewContents(source, new_contents, disposition, + initial_pos, user_gesture); + } +} + +void DraggedTabControllerGtk::ActivateContents(TabContents* contents) { + // Ignored. +} + +void DraggedTabControllerGtk::LoadingStateChanged(TabContents* source) { +} + +void DraggedTabControllerGtk::CloseContents(TabContents* source) { + // Theoretically could be called by a window. Should be ignored + // because window.close() is ignored (usually, even though this + // method gets called.) +} + +void DraggedTabControllerGtk::MoveContents(TabContents* source, + const gfx::Rect& pos) { + // Theoretically could be called by a web page trying to move its + // own window. Should be ignored since we're moving the window... +} + +bool DraggedTabControllerGtk::IsPopup(TabContents* source) { + return false; +} + +void DraggedTabControllerGtk::ToolbarSizeChanged(TabContents* source, + bool finished) { + // Dragged tabs don't care about this. +} + +void DraggedTabControllerGtk::URLStarredChanged(TabContents* source, + bool starred) { + // Ignored. +} + +void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source, + const GURL& url) { + // Ignored. +} + +//////////////////////////////////////////////////////////////////////////////// +// DraggedTabControllerGtk, NotificationObserver implementation: + +void DraggedTabControllerGtk::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); + DCHECK(Source<TabContents>(source).ptr() == dragged_contents_); + EndDragImpl(TAB_DESTROYED); +} + +void DraggedTabControllerGtk::SetDraggedContents(TabContents* new_contents) { + if (dragged_contents_) { + registrar_.Remove(this, + NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(dragged_contents_)); + if (original_delegate_) + dragged_contents_->set_delegate(original_delegate_); + } + original_delegate_ = NULL; + dragged_contents_ = new_contents; + if (dragged_contents_) { + registrar_.Add(this, + NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(dragged_contents_)); + + // We need to be the delegate so we receive messages about stuff, + // otherwise our dragged_contents() may be replaced and subsequently + // collected/destroyed while the drag is in process, leading to + // nasty crashes. + original_delegate_ = dragged_contents_->delegate(); + dragged_contents_->set_delegate(this); + } +} + +void DraggedTabControllerGtk::ContinueDragging() { + // TODO(jhawkins): We don't handle the situation where the last tab is dragged + // out of a window, so we'll just go with the way Windows handles dragging for + // now. + gfx::Point screen_point = GetCursorScreenPoint(); + MoveTab(screen_point); +} + +void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) { + gfx::Point dragged_point = GetDraggedPoint(screen_point); + + if (attached_tabstrip_) { + // Determine the horizontal move threshold. This is dependent on the width + // of tabs. The smaller the tabs compared to the standard size, the smaller + // the threshold. + double unselected, selected; + attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); + double ratio = unselected / TabGtk::GetStandardSize().width(); + int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); + + // Update the model, moving the TabContents from one index to another. Do + // this only if we have moved a minimum distance since the last reorder (to + // prevent jitter). + if (abs(screen_point.x() - last_move_screen_x_) > threshold) { + TabStripModel* attached_model = attached_tabstrip_->model(); + int from_index = + attached_model->GetIndexOfTabContents(dragged_contents_); + gfx::Rect bounds = source_tab_->bounds(); + int to_index = GetInsertionIndexForDraggedBounds(bounds); + to_index = NormalizeIndexToAttachedTabStrip(to_index); + if (from_index != to_index) { + last_move_screen_x_ = screen_point.x(); + snap_bounds_ = attached_tabstrip_->GetTabAt(to_index)->bounds(); + attached_model->MoveTabContentsAt(from_index, to_index, true); + } + } + } + + // Move the tab. There are no changes to the model if we're detached. + gfx::Rect bounds = source_tab_->bounds(); + bounds.set_x(dragged_point.x()); + source_tab_->SetBounds(bounds); + gtk_fixed_move(GTK_FIXED(source_tabstrip_->tabstrip_.get()), + source_tab_->widget(), bounds.x(), bounds.y()); +} + +TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( + const gfx::Point& screen_point) { + // TODO(jhawkins): Actually get the correct tabstrip under |screen_point|. + return source_tabstrip_; +} + +int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds( + const gfx::Rect& dragged_bounds) const { + int right_tab_x = 0; + + // TODO(jhawkins): Handle RTL layout. + + // Divides each tab into two halves to see if the dragged tab has crossed + // the halfway boundary necessary to move past the next tab. + for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) { + gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i); + + gfx::Rect left_half = ideal_bounds; + left_half.set_width(left_half.width() / 2); + + gfx::Rect right_half = ideal_bounds; + right_half.set_width(ideal_bounds.width() - left_half.width()); + right_half.set_x(left_half.right()); + + right_tab_x = right_half.right(); + + if (dragged_bounds.x() >= right_half.x() && + dragged_bounds.x() < right_half.right()) { + return i + 1; + } else if (dragged_bounds.x() >= left_half.x() && + dragged_bounds.x() < left_half.right()) { + return i; + } + } + + if (dragged_bounds.right() > right_tab_x) + return attached_tabstrip_->model()->count(); + + return TabStripModel::kNoTab; +} + +gfx::Point DraggedTabControllerGtk::GetDraggedPoint(const gfx::Point& point) { + int x = point.x() - mouse_offset_.x(); + int y = point.y() - mouse_offset_.y(); + + // Snap the dragged tab to the tab strip. + if (x < 0) + x = 0; + + // Make sure the tab can't be dragged off the right side of the tab strip. + int max_x = attached_tabstrip_->bounds_.right() - source_tab_->width(); + if (x > max_x) + x = max_x; + + return gfx::Point(x, y); +} + +int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const { + if (index >= attached_tabstrip_->model_->count()) + return attached_tabstrip_->model_->count() - 1; + if (index == TabStripModel::kNoTab) + return 0; + return index; +} + +TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents( + TabStripGtk* tabstrip) const { + int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_); + return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); +} + +bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) { + // WARNING: this may be invoked multiple times. In particular, if deletion + // occurs after a delay (as it does when the tab is released in the original + // tab strip) and the navigation controller/tab contents is deleted before + // the animation finishes, this is invoked twice. The second time through + // type == TAB_DESTROYED. + + bool destroy_now = true; + if (type != TAB_DESTROYED) { + if (type == CANCELED) { + RevertDrag(); + } else { + destroy_now = CompleteDrag(); + } + + if (dragged_contents_ && dragged_contents_->delegate() == this) + dragged_contents_->set_delegate(original_delegate_); + } else { + // If we get here it means the NavigationController is going down. Don't + // attempt to do any cleanup other than resetting the delegate (if we're + // still the delegate). + if (dragged_contents_ && dragged_contents_->delegate() == this) + dragged_contents_->set_delegate(NULL); + dragged_contents_ = NULL; + } + + // The delegate of the dragged contents should have been reset. Unset the + // original delegate so that we don't attempt to reset the delegate when + // deleted. + DCHECK(!dragged_contents_ || dragged_contents_->delegate() != this); + original_delegate_ = NULL; + + // If we're not destroyed now, we'll be destroyed asynchronously later. + if (destroy_now) + source_tabstrip_->DestroyDragController(); + + return destroy_now; +} + +void DraggedTabControllerGtk::RevertDrag() { + // We save this here because code below will modify |attached_tabstrip_|. + if (attached_tabstrip_) { + int index = attached_tabstrip_->model()->GetIndexOfTabContents( + dragged_contents_); + if (attached_tabstrip_ != source_tabstrip_) { + // The Tab was inserted into another TabStrip. We need to put it back + // into the original one. + attached_tabstrip_->model()->DetachTabContentsAt(index); + // TODO(beng): (Cleanup) seems like we should use Attach() for this + // somehow. + attached_tabstrip_ = source_tabstrip_; + source_tabstrip_->model()->InsertTabContentsAt(source_model_index_, + dragged_contents_, true, false); + } else { + // The Tab was moved within the TabStrip where the drag was initiated. + // Move it back to the starting location. + source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_, + true); + } + } else { + // TODO(beng): (Cleanup) seems like we should use Attach() for this + // somehow. + attached_tabstrip_ = source_tabstrip_; + // The Tab was detached from the TabStrip where the drag began, and has not + // been attached to any other TabStrip. We need to put it back into the + // source TabStrip. + source_tabstrip_->model()->InsertTabContentsAt(source_model_index_, + dragged_contents_, true, false); + } +} + +bool DraggedTabControllerGtk::CompleteDrag() { + // We don't need to do anything other than make the Tab visible again, + // since the dragged tab is going away. + gfx::Rect bounds = source_tab_->bounds(); + bounds.set_x(snap_bounds_.x()); + source_tab_->SetBounds(bounds); + gtk_fixed_move(GTK_FIXED(source_tabstrip_->tabstrip_.get()), + source_tab_->widget(), bounds.x(), bounds.y()); + + return false; +} + +gfx::Point DraggedTabControllerGtk::GetCursorScreenPoint() const { + // Get default display and screen. + GdkDisplay* display = gdk_display_get_default(); + + // Get cursor position. + int x, y; + gdk_display_get_pointer(display, NULL, &x, &y, NULL); + + return gfx::Point(x, y); +} diff --git a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h new file mode 100644 index 0000000..5381b38 --- /dev/null +++ b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h @@ -0,0 +1,177 @@ +// 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_TABS_DRAGGED_TAB_CONTROLLER_GTK_H_ +#define CHROME_BROWSER_GTK_TABS_DRAGGED_TAB_CONTROLLER_GTK_H_ + +#include "base/timer.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/common/notification_registrar.h" + +class TabGtk; +class TabStripGtk; + +class DraggedTabControllerGtk : public NotificationObserver, + public TabContentsDelegate { + public: + DraggedTabControllerGtk(TabGtk* source_tab, TabStripGtk* source_tabstrip); + virtual ~DraggedTabControllerGtk(); + + // Capture information needed to be used during a drag session for this + // controller's associated source Tab and TabStrip. |mouse_offset| is the + // distance of the mouse pointer from the Tab's origin. + void CaptureDragInfo(const gfx::Point& mouse_offset); + + // Responds to drag events subsequent to StartDrag. If the mouse moves a + // sufficient distance before the mouse is released, a drag session is + // initiated. + void Drag(); + + // Complete the current drag session. If the drag session was canceled + // because the user pressed Escape or something interrupted it, |canceled| + // is true so the helper can revert the state to the world before the drag + // begun. Returns whether the tab has been destroyed. + bool EndDrag(bool canceled); + + private: + // Enumeration of the ways a drag session can end. + enum EndDragType { + // Drag session exited normally: the user released the mouse. + NORMAL, + + // The drag session was canceled (alt-tab during drag, escape ...) + CANCELED, + + // The tab (NavigationController) was destroyed during the drag. + TAB_DESTROYED + }; + + // Overridden from TabContentsDelegate: + virtual void OpenURLFromTab(TabContents* source, + const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition); + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags); + virtual void AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + virtual void ActivateContents(TabContents* contents); + virtual void LoadingStateChanged(TabContents* source); + virtual void CloseContents(TabContents* source); + virtual void MoveContents(TabContents* source, const gfx::Rect& pos); + virtual bool IsPopup(TabContents* source); + virtual void ToolbarSizeChanged(TabContents* source, bool is_animating); + virtual void URLStarredChanged(TabContents* source, bool starred); + virtual void UpdateTargetURL(TabContents* source, const GURL& url); + + // Overridden from NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Sets the TabContents being dragged with the specified |new_contents|. + void SetDraggedContents(TabContents* new_contents); + + // Move the DraggedTabView according to the current mouse screen position, + // potentially updating the source and other TabStrips. + void ContinueDragging(); + + // Handles moving the Tab within a TabStrip as well as updating the View. + void MoveTab(const gfx::Point& screen_point); + + // Returns the compatible TabStrip that is under the specified point (screen + // coordinates), or NULL if there is none. + TabStripGtk* GetTabStripForPoint(const gfx::Point& screen_point); + + // Returns the index where the dragged TabContents should be inserted into + // the attached TabStripModel given the DraggedTabView's bounds + // |dragged_bounds| in coordinates relative to the attached TabStrip. + int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const; + + // Get the position of the dragged tab relative to the attached tab strip. + gfx::Point GetDraggedPoint(const gfx::Point& point); + + // Finds the Tab within the specified TabStrip that corresponds to the + // dragged TabContents. + TabGtk* GetTabMatchingDraggedContents(TabStripGtk* tabstrip) const; + + // Does the work for EndDrag. Returns whether the tab has been destroyed. + bool EndDragImpl(EndDragType how_end); + + // If the drag was aborted for some reason, this function is called to un-do + // the changes made during the drag operation. + void RevertDrag(); + + // Finishes the drag operation. Returns true if the drag controller should + // be destroyed immediately, false otherwise. + bool CompleteDrag(); + + // Utility for getting the mouse position in screen coordinates. + gfx::Point GetCursorScreenPoint() const; + + // Utility to convert the specified TabStripModel index to something valid + // for the attached TabStrip. + int NormalizeIndexToAttachedTabStrip(int index) const; + + void BringWindowUnderMouseToFront(); + + // Handles registering for notifications. + NotificationRegistrar registrar_; + + // The TabContents being dragged. + TabContents* dragged_contents_; + + // The original TabContentsDelegate of |dragged_contents_|, before it was + // detached from the browser window. We store this so that we can forward + // certain delegate notifications back to it if we can't handle them locally. + TabContentsDelegate* original_delegate_; + + // The tab that initiated the drag session. + TabGtk* source_tab_; + + // The tab strip |source_tab_| originated from. + TabStripGtk* source_tabstrip_; + + // This is the index of the |source_tab_| in |source_tabstrip_| when the drag + // began. This is used to restore the previous state if the drag is aborted. + int source_model_index_; + + // The TabStrip the dragged Tab is currently attached to, or NULL if the + // dragged Tab is detached. + TabStripGtk* attached_tabstrip_; + + // The position of the mouse (in screen coordinates) at the start of the drag + // operation. This is used to calculate minimum elasticity before a + // DraggedTabView is constructed. + gfx::Point start_screen_point_; + + // This is the offset of the mouse from the top left of the Tab where + // dragging begun. This is used to ensure that the dragged view is always + // positioned at the correct location during the drag, and to ensure that the + // detached window is created at the right location. + gfx::Point mouse_offset_; + + // The horizontal position of the mouse cursor in screen coordinates at the + // time of the last re-order event. + int last_move_screen_x_; + + // The last good tab bounds of the dragged tab. This is the position the tab + // will be snapped back to when the drag is released. + // TODO(jhawkins): We should not be moving the tab itself, but rather a + // stand-in renderer. + gfx::Rect snap_bounds_; + + // Timer used to bring the window under the cursor to front. If the user + // stops moving the mouse for a brief time over a browser window, it is + // brought to front. + base::OneShotTimer<DraggedTabControllerGtk> bring_to_front_timer_; + + DISALLOW_COPY_AND_ASSIGN(DraggedTabControllerGtk); +}; + +#endif // CHROME_BROWSER_GTK_TABS_DRAGGED_TAB_CONTROLLER_GTK_H_ diff --git a/chrome/browser/gtk/tabs/tab_gtk.cc b/chrome/browser/gtk/tabs/tab_gtk.cc index 9966d0f..6db2595 100644 --- a/chrome/browser/gtk/tabs/tab_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_gtk.cc @@ -5,15 +5,21 @@ #include "chrome/browser/gtk/tabs/tab_gtk.h" #include "app/resource_bundle.h" +#include "chrome/browser/gtk/custom_button.h" #include "chrome/browser/gtk/menu_gtk.h" #include "chrome/common/gfx/path.h" #include "chrome/common/l10n_util.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" -static const SkScalar kTabCapWidth = 15; -static const SkScalar kTabTopCurveWidth = 4; -static const SkScalar kTabBottomCurveWidth = 3; +namespace { + +// The targets available for drag n' drop. +GtkTargetEntry target_table[] = { + { const_cast<char*>("application/x-chrome-tab"), GTK_TARGET_SAME_APP, 0 } +}; + +} // namespace class TabGtk::ContextMenuController : public MenuGtk::Delegate { public: @@ -95,17 +101,38 @@ TabGtk::TabGtk(TabDelegate* delegate) : TabRendererGtk(), delegate_(delegate), closing_(false) { - ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - SkBitmap* bitmap = rb.GetBitmapNamed(IDR_TAB_CLOSE); - - close_button_.reset(new TabButtonGtk(this)); - close_button_.get()->SetImage(TabButtonGtk::BS_NORMAL, bitmap); - close_button_.get()->SetImage(TabButtonGtk::BS_HOT, - rb.GetBitmapNamed(IDR_TAB_CLOSE_H)); - close_button_.get()->SetImage(TabButtonGtk::BS_PUSHED, - rb.GetBitmapNamed(IDR_TAB_CLOSE_P)); - close_button_.get()->set_bounds( - gfx::Rect(0, 0, bitmap->width(), bitmap->height())); + event_box_.Own(gtk_event_box_new()); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), FALSE); + gtk_drag_source_set(event_box_.get(), GDK_BUTTON1_MASK, + target_table, G_N_ELEMENTS(target_table), + GDK_ACTION_MOVE); + gtk_drag_dest_set(event_box_.get(), GTK_DEST_DEFAULT_DROP, + target_table, G_N_ELEMENTS(target_table), + GDK_ACTION_MOVE); + gtk_drag_dest_set_track_motion(event_box_.get(), true); + g_signal_connect(G_OBJECT(event_box_.get()), "button-press-event", + G_CALLBACK(OnMousePress), this); + g_signal_connect(G_OBJECT(event_box_.get()), "button-release-event", + G_CALLBACK(OnMouseRelease), this); + g_signal_connect(G_OBJECT(event_box_.get()), "enter-notify-event", + G_CALLBACK(OnEnterNotify), this); + g_signal_connect(G_OBJECT(event_box_.get()), "leave-notify-event", + G_CALLBACK(OnLeaveNotify), this); + g_signal_connect_after(G_OBJECT(event_box_.get()), "drag-begin", + G_CALLBACK(&OnDragBegin), this); + g_signal_connect_after(G_OBJECT(event_box_.get()), "drag-end", + G_CALLBACK(&OnDragEnd), this); + g_signal_connect_after(G_OBJECT(event_box_.get()), "drag-failed", + G_CALLBACK(&OnDragFailed), this); + g_signal_connect_after(G_OBJECT(event_box_.get()), "drag-motion", + G_CALLBACK(&OnDragMotion), this); + gtk_widget_add_events(event_box_.get(), + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_LEAVE_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + gtk_container_add(GTK_CONTAINER(event_box_.get()), TabRendererGtk::widget()); + gtk_widget_show_all(event_box_.get()); + + close_button_.reset(MakeCloseButton()); } TabGtk::~TabGtk() { @@ -116,47 +143,84 @@ TabGtk::~TabGtk() { // Invoke this so that we hide the highlight. ContextMenuClosed(); } + + event_box_.Destroy(); } -bool TabGtk::IsPointInBounds(const gfx::Point& point) { - GdkRegion* region = MakeRegionForTab(); - bool in_bounds = (gdk_region_point_in(region, point.x(), point.y()) == TRUE); - gdk_region_destroy(region); - return in_bounds; +// static +gboolean TabGtk::OnMousePress(GtkWidget* widget, GdkEventButton* event, + TabGtk* tab) { + if (event->button == 1) { + // Store whether or not we were selected just now... we only want to be + // able to drag foreground tabs, so we don't start dragging the tab if + // it was in the background. + bool just_selected = !tab->IsSelected(); + if (just_selected) { + tab->delegate_->SelectTab(tab); + } + } + + return TRUE; } -bool TabGtk::OnMotionNotify(GdkEventMotion* event) { - gfx::Point point(event->x, event->y); - bool paint = false; +// static +gboolean TabGtk::OnMouseRelease(GtkWidget* widget, GdkEventButton* event, + TabGtk* tab) { + if (event->button == 2) { + tab->delegate_->CloseTab(tab); + } else if (event->button == 3) { + tab->ShowContextMenu(); + } - if (!(event->state & GDK_BUTTON1_MASK)) - paint = set_hovering(IsPointInBounds(point)); + return TRUE; +} - paint |= close_button_.get()->OnMotionNotify(event); - return paint; +// static +gboolean TabGtk::OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event, + TabGtk* tab) { + tab->OnMouseEntered(); + return TRUE; } -bool TabGtk::OnMousePress(const gfx::Point& point) { - if (close_button_.get()->IsPointInBounds(point)) - return close_button_.get()->OnMousePress(); +// static +gboolean TabGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, + TabGtk* tab) { + tab->OnMouseExited(); + return TRUE; +} - return false; +// static +void TabGtk::OnDragBegin(GtkWidget* widget, GdkDragContext* context, + TabGtk* tab) { + int x, y; + gdk_window_get_pointer(tab->event_box_.get()->window, &x, &y, NULL); + tab->delegate_->MaybeStartDrag(tab, gfx::Point(x, y)); } -void TabGtk::OnMouseRelease(GdkEventButton* event) { - close_button_.get()->OnMouseRelease(); +// static +void TabGtk::OnDragEnd(GtkWidget* widget, GdkDragContext* context, + TabGtk* tab) { + // Notify the drag helper that we're done with any potential drag operations. + // Clean up the drag helper, which is re-created on the next mouse press. + tab->delegate_->EndDrag(false); +} - if (event->button == 2) { - delegate_->CloseTab(this); - } else if (event->button == 3) { - ShowContextMenu(); - } +// static +gboolean TabGtk::OnDragMotion(GtkWidget* widget, + GdkDragContext* context, + guint x, guint y, + guint time, + TabGtk* tab) { + tab->delegate_->ContinueDrag(context); + return TRUE; } -bool TabGtk::OnLeaveNotify() { - bool paint = set_hovering(false); - paint |= close_button_.get()->OnLeaveNotify(); - return paint; +// static +gboolean TabGtk::OnDragFailed(GtkWidget* widget, GdkDragContext* context, + GtkDragResult result, + TabGtk* tab) { + tab->delegate_->EndDrag(true); + return TRUE; } /////////////////////////////////////////////////////////////////////////////// @@ -167,50 +231,23 @@ bool TabGtk::IsSelected() const { } void TabGtk::CloseButtonResized(const gfx::Rect& bounds) { - close_button_.get()->set_bounds(bounds); -} - -void TabGtk::Paint(ChromeCanvasPaint* canvas) { - TabRendererGtk::Paint(canvas); - close_button_.get()->Paint(canvas); + gtk_fixed_move(GTK_FIXED(TabRendererGtk::widget()), + close_button_.get()->widget(), bounds.x(), bounds.y()); } -//////////////////////////////////////////////////////////////////////////////// -// TabGtk, TabButtonGtk::Delegate implementation: +void TabGtk::Paint(GdkEventExpose* event) { + TabRendererGtk::Paint(event); -GdkRegion* TabGtk::MakeRegionForButton(const TabButtonGtk* button) const { - // Use the close button bounds for hit-testing. - return NULL; -} - -void TabGtk::OnButtonActivate(const TabButtonGtk* button) { - delegate_->CloseTab(this); + gtk_container_propagate_expose(GTK_CONTAINER(TabRendererGtk::widget()), + close_button_.get()->widget(), event); } /////////////////////////////////////////////////////////////////////////////// // TabGtk, private: -GdkRegion* TabGtk::MakeRegionForTab()const { - int w = width(); - int h = height(); - static const int kNumRegionPoints = 9; - - GdkPoint polygon[kNumRegionPoints] = { - { 0, h }, - { kTabBottomCurveWidth, h - kTabBottomCurveWidth }, - { kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth }, - { kTabCapWidth, 0 }, - { w - kTabCapWidth, 0 }, - { w - kTabCapWidth - kTabTopCurveWidth, kTabTopCurveWidth }, - { w - kTabBottomCurveWidth, h - kTabBottomCurveWidth }, - { w, h }, - { 0, h }, - }; - - GdkRegion* region = gdk_region_polygon(polygon, kNumRegionPoints, - GDK_WINDING_RULE); - gdk_region_offset(region, x(), y()); - return region; +// static +void TabGtk::OnCloseButtonClicked(GtkWidget* widget, TabGtk* tab) { + tab->delegate_->CloseTab(tab); } void TabGtk::ShowContextMenu() { @@ -224,3 +261,16 @@ void TabGtk::ContextMenuClosed() { delegate()->StopAllHighlighting(); menu_controller_.reset(); } + +CustomDrawButton* TabGtk::MakeCloseButton() { + CustomDrawButton* button = new CustomDrawButton(IDR_TAB_CLOSE, + IDR_TAB_CLOSE_P, IDR_TAB_CLOSE_H, IDR_TAB_CLOSE); + + g_signal_connect(G_OBJECT(button->widget()), "clicked", + G_CALLBACK(OnCloseButtonClicked), this); + GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS); + gtk_fixed_put(GTK_FIXED(TabRendererGtk::widget()), button->widget(), 0, 0); + gtk_widget_show(button->widget()); + + return button; +} diff --git a/chrome/browser/gtk/tabs/tab_gtk.h b/chrome/browser/gtk/tabs/tab_gtk.h index 3892844..2999564 100644 --- a/chrome/browser/gtk/tabs/tab_gtk.h +++ b/chrome/browser/gtk/tabs/tab_gtk.h @@ -6,7 +6,6 @@ #define CHROME_BROWSER_GTK_TABS_TAB_GTK_H_ #include "base/basictypes.h" -#include "chrome/browser/gtk/tabs/tab_button_gtk.h" #include "chrome/browser/gtk/tabs/tab_renderer_gtk.h" #include "chrome/browser/tabs/tab_strip_model.h" @@ -14,8 +13,9 @@ namespace gfx { class Path; } -class TabGtk : public TabRendererGtk, - public TabButtonGtk::Delegate { +class CustomDrawButton; + +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 @@ -48,6 +48,12 @@ class TabGtk : public TabRendererGtk, TabStripModel::ContextMenuCommand command_id, TabGtk* tab) = 0; virtual void StopAllHighlighting() = 0; + // Potentially starts a drag for the specified Tab. + virtual void MaybeStartDrag(TabGtk* tab, const gfx::Point& point) = 0; + + // Continues dragging a Tab. + virtual void ContinueDrag(GdkDragContext* context) = 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. @@ -66,47 +72,56 @@ class TabGtk : public TabRendererGtk, // Access the delegate. TabDelegate* delegate() const { return delegate_; } + GtkWidget* widget() const { return event_box_.get(); } + // Used to set/check whether this Tab is being animated closed. void set_closing(bool closing) { closing_ = closing; } bool closing() const { return closing_; } - // Checks whether |point| is inside the bounds of the tab. - bool IsPointInBounds(const gfx::Point& point); - // TabRendererGtk overrides: virtual bool IsSelected() const; virtual void CloseButtonResized(const gfx::Rect& bounds); - virtual void Paint(ChromeCanvasPaint* canvas); + virtual void Paint(GdkEventExpose* event); + + // button-press-event handler that handles mouse clicks. + static gboolean OnMousePress(GtkWidget* widget, GdkEventButton* event, + TabGtk* tab); - // Sent by the tabstrip when the mouse moves within this tab. Mouse state is - // in |event|. Returns true if the tabstrip needs to be redrawn as a result - // of the motion. - bool OnMotionNotify(GdkEventMotion* event); + // button-release-event handler that handles mouse click releases. + static gboolean OnMouseRelease(GtkWidget* widget, GdkEventButton* event, + TabGtk* tab); - // Sent by the tabstrip when the mouse clicks within this tab. Returns true - // if the tabstrip needs to be redrawn as a result of the click. - bool OnMousePress(const gfx::Point& point); + // enter-notify-event handler that signals when the mouse enters the tab. + static gboolean OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event, + TabGtk* tab); - // Sent by the tabstrip when the mouse click is released. - void OnMouseRelease(GdkEventButton* event); + // leave-notify-event handler that signals when the mouse enters the tab. + static gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, + TabGtk* tab); - // Sent by the tabstrip when the mouse leaves this tab. Returns true - // if the tabstrip needs to be redrawn as a result of the movement. - bool OnLeaveNotify(); + // drag-begin handler that signals when a drag action begins. + static void OnDragBegin(GtkWidget* widget, GdkDragContext* context, + TabGtk* tab); - protected: - // TabButtonGtk::Delegate implementation: - virtual GdkRegion* MakeRegionForButton(const TabButtonGtk* button) const; - virtual void OnButtonActivate(const TabButtonGtk* button); + // drag-end handler that signals when a drag action ends. + static void OnDragEnd(GtkWidget* widget, GdkDragContext* context, + TabGtk* tab); + + // drag-motion handler that handles drag movements in the tabstrip. + static gboolean OnDragMotion(GtkWidget* widget, GdkDragContext* context, + guint x, guint y, guint time, + TabGtk* tab); + + // drag-failed handler that is emitted when the drag fails. + static gboolean OnDragFailed(GtkWidget* widget, GdkDragContext* context, + GtkDragResult result, TabGtk* tab); private: class ContextMenuController; - friend class ContextMenuController; - // Creates a clickable region of the tab's visual representation. Used for - // hit-testing. Caller is responsible for destroying the region. - GdkRegion* MakeRegionForTab() const; + // Handles the clicked signal for the close button. + static void OnCloseButtonClicked(GtkWidget* widget, TabGtk* tab); // Shows the context menu. void ShowContextMenu(); @@ -114,6 +129,8 @@ class TabGtk : public TabRendererGtk, // Invoked when the context menu closes. void ContextMenuClosed(); + CustomDrawButton* MakeCloseButton(); + // An instance of a delegate object that can perform various actions based on // user gestures. TabDelegate* delegate_; @@ -124,7 +141,11 @@ class TabGtk : public TabRendererGtk, // The context menu controller. scoped_ptr<ContextMenuController> menu_controller_; - scoped_ptr<TabButtonGtk> close_button_; + // The close button. + scoped_ptr<CustomDrawButton> close_button_; + + // The windowless widget used to collect input events for the tab. + OwnedWidgetGtk event_box_; DISALLOW_COPY_AND_ASSIGN(TabGtk); }; diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc index 1ce7aaf..a9a9438 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc @@ -26,14 +26,21 @@ const int kFavIconSize = 16; const int kSelectedTitleColor = SK_ColorBLACK; const int kUnselectedTitleColor = SkColorSetRGB(64, 64, 64); +// How long the hover state takes. +const int kHoverDurationMs = 90; + +// How opaque to make the hover state (out of 1). +const double kHoverOpacity = 0.33; + +const SkScalar kTabCapWidth = 15; +const SkScalar kTabTopCurveWidth = 4; +const SkScalar kTabBottomCurveWidth = 3; + // 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; -// How opaque to make the hover state (out of 1). -const double kHoverOpacity = 0.33; - TabRendererGtk::LoadingAnimation::Data loading_animation_data; // Loads the loading animation images and data. @@ -116,6 +123,11 @@ void TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation( } } +bool TabRendererGtk::IsVisible() { + // TODO(jhawkins): Implement this and SetVisible. + return true; +} + //////////////////////////////////////////////////////////////////////////////// // TabRendererGtk, public: @@ -125,12 +137,21 @@ TabRendererGtk::TabRendererGtk() showing_close_button_(false), fav_icon_hiding_offset_(0), should_display_crashed_favicon_(false), - hovering_(false), loading_animation_(&loading_animation_data) { InitResources(); + + tab_.Own(gtk_fixed_new()); + gtk_widget_set_app_paintable(tab_.get(), TRUE); + g_signal_connect(G_OBJECT(tab_.get()), "expose-event", + G_CALLBACK(OnExpose), this); + gtk_widget_show(tab_.get()); + + hover_animation_.reset(new SlideAnimation(this)); + hover_animation_->SetSlideDuration(kHoverDurationMs); } TabRendererGtk::~TabRendererGtk() { + tab_.Destroy(); } void TabRendererGtk::UpdateData(TabContents* contents, bool loading_only) { @@ -236,6 +257,7 @@ void TabRendererGtk::LoadTabImages() { } void TabRendererGtk::SetBounds(const gfx::Rect& bounds) { + gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height()); bounds_ = bounds; Layout(); } @@ -247,10 +269,29 @@ std::wstring TabRendererGtk::GetTitle() const { return data_.title; } +/////////////////////////////////////////////////////////////////////////////// +// TabRendererGtk, AnimationDelegate implementation: + +void TabRendererGtk::AnimationProgressed(const Animation* animation) { + gtk_widget_queue_draw(tab_.get()); +} + +void TabRendererGtk::AnimationCanceled(const Animation* animation) { + AnimationEnded(animation); +} + +void TabRendererGtk::AnimationEnded(const Animation* animation) { + gtk_widget_queue_draw(tab_.get()); +} + //////////////////////////////////////////////////////////////////////////////// // TabRendererGtk, private: -void TabRendererGtk::Paint(ChromeCanvasPaint* canvas) { +void TabRendererGtk::Paint(GdkEventExpose* event) { + ChromeCanvasPaint canvas(event); + if (canvas.isEmpty()) + return; + // Don't paint if we're narrower than we can render correctly. (This should // only happen during animations). if (width() < GetMinimumUnselectedSize().width()) @@ -265,20 +306,20 @@ void TabRendererGtk::Paint(ChromeCanvasPaint* canvas) { show_close_button != showing_close_button_) Layout(); - PaintTabBackground(canvas); + PaintTabBackground(&canvas); if (show_icon) { if (loading_animation_.animation_state() != ANIMATION_NONE) { - PaintLoadingAnimation(canvas); + PaintLoadingAnimation(&canvas); } else if (!data_.favicon.isNull()) { - canvas->DrawBitmapInt(data_.favicon, favicon_bounds_.x(), - favicon_bounds_.y() + fav_icon_hiding_offset_); + canvas.DrawBitmapInt(data_.favicon, favicon_bounds_.x(), + favicon_bounds_.y() + fav_icon_hiding_offset_); } } if (show_download_icon) { - canvas->DrawBitmapInt(*download_icon_, - download_icon_bounds_.x(), download_icon_bounds_.y()); + canvas.DrawBitmapInt(*download_icon_, + download_icon_bounds_.x(), download_icon_bounds_.y()); } // Paint the Title. @@ -295,15 +336,9 @@ void TabRendererGtk::Paint(ChromeCanvasPaint* canvas) { SkColor title_color = IsSelected() ? kSelectedTitleColor : kUnselectedTitleColor; - canvas->DrawStringInt(title, *title_font_, title_color, title_bounds_.x(), - title_bounds_.y(), title_bounds_.width(), - title_bounds_.height()); -} - -bool TabRendererGtk::set_hovering(bool hovering) { - bool paint = (hovering_ != hovering); - hovering_ = hovering; - return paint; + canvas.DrawStringInt(title, *title_font_, title_color, title_bounds_.x(), + title_bounds_.y(), title_bounds_.width(), + title_bounds_.height()); } void TabRendererGtk::Layout() { @@ -340,10 +375,9 @@ void TabRendererGtk::Layout() { 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_); + close_button_bounds_.SetRect(local_bounds.width() + kCloseButtonHorzFuzz, + close_button_top, close_button_width_, + close_button_height_); } else { close_button_bounds_.SetRect(0, 0, 0, 0); } @@ -364,7 +398,7 @@ void TabRendererGtk::Layout() { int title_width; if (close_button_bounds_.width() && close_button_bounds_.height()) { - title_width = std::max(close_button_bounds_.x() - + title_width = std::max(bounds_.x() + close_button_bounds_.x() - kTitleCloseButtonSpacing - title_left, 0); } else { title_width = std::max(local_bounds.width() - title_left, 0); @@ -384,9 +418,10 @@ void TabRendererGtk::PaintTabBackground(ChromeCanvasPaint* canvas) { PaintActiveTabBackground(canvas); } else { // Draw our hover state. - // TODO(jhawkins): Hover animations. - if (hovering_) { - PaintHoverTabBackground(canvas, kHoverOpacity); + Animation* animation = hover_animation_.get(); + if (animation->GetCurrentValue() > 0) { + PaintHoverTabBackground(canvas, + animation->GetCurrentValue() * kHoverOpacity); } else { PaintInactiveTabBackground(canvas); } @@ -403,8 +438,8 @@ void TabRendererGtk::PaintInactiveTabBackground(ChromeCanvasPaint* canvas) { width() - tab_inactive_.l_width - tab_inactive_.r_width, height()); canvas->DrawBitmapInt(*image.image_r, - bounds_.x() + width() - tab_inactive_.r_width, - bounds_.y()); + bounds_.x() + width() - tab_inactive_.r_width, + bounds_.y()); } void TabRendererGtk::PaintHoverTabBackground(ChromeCanvasPaint* canvas, @@ -453,7 +488,6 @@ void TabRendererGtk::PaintLoadingAnimation(ChromeCanvasPaint* canvas) { // dst_x = x() + width() - kLeftPadding - image_size; int dst_x = x() + kLeftPadding; - canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size, dst_x, dst_y, image_size, image_size, false); @@ -482,6 +516,23 @@ bool TabRendererGtk::ShouldShowCloseBox() const { } // static +gboolean TabRendererGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event, + TabRendererGtk* tab) { + tab->Paint(event); + return TRUE; +} + +void TabRendererGtk::OnMouseEntered() { + hover_animation_->SetTweenType(SlideAnimation::EASE_OUT); + hover_animation_->Show(); +} + +void TabRendererGtk::OnMouseExited() { + hover_animation_->SetTweenType(SlideAnimation::EASE_IN); + hover_animation_->Hide(); +} + +// static void TabRendererGtk::InitResources() { if (initialized_) return; diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.h b/chrome/browser/gtk/tabs/tab_renderer_gtk.h index 3b95189..81f2688 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.h +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.h @@ -7,10 +7,13 @@ #include <gtk/gtk.h> +#include "app/animation.h" +#include "app/slide_animation.h" #include "base/basictypes.h" #include "base/gfx/rect.h" #include "chrome/common/gfx/chrome_canvas.h" #include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/owned_widget_gtk.h" #include "skia/include/SkBitmap.h" namespace gfx { @@ -19,7 +22,7 @@ class Size; class TabContents; -class TabRendererGtk { +class TabRendererGtk : public AnimationDelegate { public: // Possible animation states. enum AnimationState { @@ -84,12 +87,14 @@ class TabRendererGtk { virtual void CloseButtonResized(const gfx::Rect& bounds); // Paints the tab into |canvas|. - virtual void Paint(ChromeCanvasPaint* canvas); + virtual void Paint(GdkEventExpose* event); // Advance the loading animation to the next frame, or hide the animation if // the tab isn't loading. void ValidateLoadingAnimation(AnimationState animation_state); + bool IsVisible(); + // 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 @@ -114,16 +119,22 @@ class TabRendererGtk { // Sets the bounds of the tab. void SetBounds(const gfx::Rect& bounds); + GtkWidget* widget() const { return tab_.get(); } + protected: const gfx::Rect& title_bounds() const { return title_bounds_; } const gfx::Rect& close_button_bounds() const { return close_button_bounds_; } - // Sets the hovering status of the tab. Returns true if a repaint is needed. - bool set_hovering(bool hovering); - // Returns the title of the Tab. std::wstring GetTitle() const; + // Called by TabGtk to notify the renderer that the tab is being hovered. + void OnMouseEntered(); + + // Called by TabGtk to notify the renderer that the tab is no longer being + // hovered. + void OnMouseExited(); + private: // 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 @@ -147,6 +158,11 @@ class TabRendererGtk { int r_width; }; + // Overridden from AnimationDelegate: + virtual void AnimationProgressed(const Animation* animation); + virtual void AnimationCanceled(const Animation* animation); + virtual void AnimationEnded(const Animation* animation); + // Generates the bounds for the interior items of the tab. void Layout(); @@ -170,6 +186,10 @@ class TabRendererGtk { // Returns whether the Tab should display a close button. bool ShouldShowCloseBox() const; + // expose-event handler that redraws the tab. + static gboolean OnExpose(GtkWidget* widget, GdkEventExpose* e, + TabRendererGtk* tab); + // TODO(jhawkins): Move to TabResources. static void InitResources(); static bool initialized_; @@ -197,6 +217,9 @@ class TabRendererGtk { static int close_button_width_; static int close_button_height_; + // The GtkDrawingArea we draw the tab on. + OwnedWidgetGtk tab_; + // Whether we're showing the icon. It is cached so that we can detect when it // changes and layout appropriately. bool showing_icon_; @@ -217,8 +240,8 @@ class TabRendererGtk { // The bounds of this Tab. gfx::Rect bounds_; - // Set when the mouse is hovering over this tab and the tab is not selected. - bool hovering_; + // Hover animation. + scoped_ptr<SlideAnimation> hover_animation_; // Contains the loading animation state. LoadingAnimation loading_animation_; diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc index 75242fb..6631764 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc @@ -9,6 +9,8 @@ #include "base/gfx/gtk_util.h" #include "base/gfx/point.h" #include "chrome/browser/browser.h" +#include "chrome/browser/gtk/custom_button.h" +#include "chrome/browser/gtk/tabs/dragged_tab_controller_gtk.h" #include "chrome/browser/gtk/tabs/tab_button_gtk.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/gfx/chrome_canvas.h" @@ -34,19 +36,10 @@ const int kTabHOffset = -16; SkBitmap* background = NULL; -// The targets available for drag n' drop. -GtkTargetEntry target_table[] = { - { const_cast<char*>("application/x-tabstrip-tab"), GTK_TARGET_SAME_APP, 0 } -}; - inline int Round(double x) { return static_cast<int>(x + 0.5); } -bool IsButtonPressed(guint state) { - return (state & GDK_BUTTON1_MASK); -} - // 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) { @@ -349,7 +342,7 @@ class MoveTabAnimation : public TabStripGtk::TabAnimation { double new_x = start_tab_a_bounds_.x() + delta; gfx::Rect bounds(Round(new_x), tab_a_->y(), tab_a_->width(), tab_a_->height()); - tab_a_->SetBounds(bounds); + tabstrip_->SetTabBounds(tab_a_, bounds); // Position Tab B distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x(); @@ -357,7 +350,7 @@ class MoveTabAnimation : public TabStripGtk::TabAnimation { new_x = start_tab_b_bounds_.x() + delta; bounds = gfx::Rect(Round(new_x), tab_b_->y(), tab_b_->width(), tab_b_->height()); - tab_b_->SetBounds(bounds); + tabstrip_->SetTabBounds(tab_b_, bounds); } protected: @@ -432,50 +425,6 @@ class ResizeLayoutAnimation : public TabStripGtk::TabAnimation { }; //////////////////////////////////////////////////////////////////////////////// - -// Handles the movement of a Tab to it's snapped tab index. -class SnapTabAnimation : public TabStripGtk::TabAnimation { - public: - SnapTabAnimation(TabStripGtk* tabstrip, const gfx::Rect& bounds) - : TabAnimation(tabstrip, SNAP), - tabstrip_(tabstrip) { - tab_ = tabstrip->GetTabAt(tabstrip->hover_index_); - animation_start_bounds_ = tab_->bounds(); - animation_end_bounds_ = bounds; - } - virtual ~SnapTabAnimation() {} - - // Overridden from AnimationDelegate: - virtual void AnimationProgressed(const Animation* animation) { - int delta_x = (animation_end_bounds_.x() - animation_start_bounds_.x()); - int x = animation_start_bounds_.x() + - static_cast<int>(delta_x * animation->GetCurrentValue()); - int y = animation_end_bounds_.y(); - gfx::Rect rect = tab_->bounds(); - rect.set_x(x); - rect.set_y(y); - tab_->SetBounds(rect); - - gtk_widget_queue_draw(tabstrip_->tabstrip_.get()); - } - - protected: - // Overridden from TabStrip::TabAnimation: - virtual int GetDuration() const { return kReorderAnimationDurationMs; } - - private: - TabStripGtk* tabstrip_; - // The tab being snapped. - TabGtk* tab_; - - // The start and end bounds of the animation sequence. - gfx::Rect animation_start_bounds_; - gfx::Rect animation_end_bounds_; - - DISALLOW_COPY_AND_ASSIGN(SnapTabAnimation); -}; - -//////////////////////////////////////////////////////////////////////////////// // TabStripGtk, public: TabStripGtk::TabStripGtk(TabStripModel* model) @@ -483,11 +432,7 @@ TabStripGtk::TabStripGtk(TabStripModel* model) current_selected_width_(TabGtk::GetStandardSize().width()), available_width_for_tabs_(-1), resize_layout_scheduled_(false), - model_(model), - hover_index_(0), - mouse_offset_(-1, -1), - last_move_x_(0), - is_dragging_(false) { + model_(model) { } TabStripGtk::~TabStripGtk() { @@ -505,7 +450,7 @@ TabStripGtk::~TabStripGtk() { tab_data_.clear(); } -void TabStripGtk::Init() { +void TabStripGtk::Init(int width) { ResourceBundle &rb = ResourceBundle::GetSharedInstance(); model_->AddObserver(this); @@ -514,55 +459,21 @@ void TabStripGtk::Init() { background = rb.GetBitmapNamed(IDR_WINDOW_TOP_CENTER); } - tabstrip_.Own(gtk_drawing_area_new()); - gtk_widget_set_size_request(tabstrip_.get(), -1, + tabstrip_.Own(gtk_fixed_new()); + gtk_fixed_set_has_window(GTK_FIXED(tabstrip_.get()), TRUE); + gtk_widget_set_size_request(tabstrip_.get(), width, TabGtk::GetMinimumUnselectedSize().height()); gtk_widget_set_app_paintable(tabstrip_.get(), TRUE); - // ChromeCanvasPaint already effectively double buffers. - gtk_widget_set_double_buffered(tabstrip_.get(), FALSE); - gtk_drag_source_set(tabstrip_.get(), GDK_BUTTON1_MASK, - target_table, G_N_ELEMENTS(target_table), - GDK_ACTION_MOVE); - gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_DROP, - target_table, G_N_ELEMENTS(target_table), - GDK_ACTION_MOVE); - gtk_drag_dest_set_track_motion(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); - g_signal_connect(G_OBJECT(tabstrip_.get()), "motion-notify-event", - G_CALLBACK(OnMotionNotify), this); - g_signal_connect(G_OBJECT(tabstrip_.get()), "button-press-event", - G_CALLBACK(OnMousePress), this); - g_signal_connect(G_OBJECT(tabstrip_.get()), "button-release-event", - G_CALLBACK(OnMouseRelease), this); - g_signal_connect(G_OBJECT(tabstrip_.get()), "leave-notify-event", - G_CALLBACK(OnLeaveNotify), this); - g_signal_connect_after(G_OBJECT(tabstrip_.get()), "drag-begin", - G_CALLBACK(&OnDragBegin), this); - g_signal_connect_after(G_OBJECT(tabstrip_.get()), "drag-end", - G_CALLBACK(&OnDragEnd), this); - g_signal_connect_after(G_OBJECT(tabstrip_.get()), "drag-failed", - G_CALLBACK(&OnDragFailed), this); - g_signal_connect_after(G_OBJECT(tabstrip_.get()), "drag-motion", - G_CALLBACK(&OnDragMotion), this); - gtk_widget_add_events(tabstrip_.get(), - GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK |GDK_LEAVE_NOTIFY_MASK); + g_signal_connect(G_OBJECT(tabstrip_.get()), "size-allocate", + G_CALLBACK(OnSizeAllocate), this); + + newtab_button_.reset(MakeNewTabButton()); + gtk_widget_show_all(tabstrip_.get()); bounds_ = GetInitialWidgetBounds(tabstrip_.get()); - - SkBitmap* bitmap = rb.GetBitmapNamed(IDR_NEWTAB_BUTTON); - newtab_button_.reset(new TabButtonGtk(this)); - newtab_button_.get()->SetImage(TabButtonGtk::BS_NORMAL, bitmap); - newtab_button_.get()->SetImage(TabButtonGtk::BS_HOT, - rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_H)); - newtab_button_.get()->SetImage(TabButtonGtk::BS_PUSHED, - rb.GetBitmapNamed(IDR_NEWTAB_BUTTON_P)); - newtab_button_.get()->set_bounds( - gfx::Rect(0, 0, bitmap->width(), bitmap->height())); } void TabStripGtk::AddTabStripToBox(GtkWidget* box) { @@ -589,7 +500,8 @@ void TabStripGtk::Layout() { int tab_right = 0; for (int i = 0; i < tab_count; ++i) { const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds; - GetTabAt(i)->SetBounds(bounds); + TabGtk* tab = GetTabAt(i); + SetTabBounds(tab, bounds); tab_right = bounds.right() + kTabHOffset; } @@ -597,6 +509,10 @@ void TabStripGtk::Layout() { gtk_widget_queue_draw(tabstrip_.get()); } +void TabStripGtk::SetBounds(const gfx::Rect& bounds) { + bounds_ = bounds; +} + void TabStripGtk::UpdateLoadingAnimations() { for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { TabGtk* current_tab = GetTabAt(i); @@ -621,6 +537,11 @@ bool TabStripGtk::IsAnimating() const { return active_animation_.get() != NULL; } +void TabStripGtk::DestroyDragController() { + if (IsDragSessionActive()) + drag_controller_.reset(NULL); +} + gfx::Rect TabStripGtk::GetIdealBounds(int index) { DCHECK(index >= 0 && index < GetTabCount()); return tab_data_.at(index).ideal_bounds; @@ -651,6 +572,8 @@ void TabStripGtk::TabInsertedAt(TabContents* contents, tab->UpdateData(contents, false); } + gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0); + // Don't animate the first tab; it looks weird. if (GetTabCount() > 1) { StartInsertTabAnimation(index); @@ -788,44 +711,30 @@ void TabStripGtk::StopAllHighlighting() { // TODO(jhawkins): Hook up animations. } -bool TabStripGtk::EndDrag(bool canceled) { - // TODO(jhawkins): Tab dragging. - return true; -} +void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) { + // Don't accidentally start any drag operations during animations if the + // mouse is down. + if (IsAnimating() || tab->closing() || !HasAvailableDragActions()) + return; -bool TabStripGtk::HasAvailableDragActions() const { - return model_->delegate()->GetDragActions() != 0; + drag_controller_.reset(new DraggedTabControllerGtk(tab, this)); + drag_controller_->CaptureDragInfo(point); } -//////////////////////////////////////////////////////////////////////////////// -// TabStripGtk, TabButtonGtk::Delegate implementation: - -GdkRegion* TabStripGtk::MakeRegionForButton(const TabButtonGtk* button) const { - const int w = button->width(); - const int kNumRegionPoints = 8; - - // These values are defined by the shape of the new tab bitmap. Should that - // bitmap ever change, these values will need to be updated. They're so - // custom it's not really worth defining constants for. - GdkPoint polygon[kNumRegionPoints] = { - { 0, 1 }, - { w - 7, 1 }, - { w - 4, 4 }, - { w, 16 }, - { w - 1, 17 }, - { 7, 17 }, - { 4, 13 }, - { 0, 1 }, - }; +void TabStripGtk::ContinueDrag(GdkDragContext* context) { + // We can get called even if |MaybeStartDrag| wasn't called in the event of + // a TabStrip animation when the mouse button is down. In this case we should + // _not_ continue the drag because it can lead to weird bugs. + if (drag_controller_.get()) + drag_controller_->Drag(); +} - GdkRegion* region = gdk_region_polygon(polygon, kNumRegionPoints, - GDK_WINDING_RULE); - gdk_region_offset(region, button->x(), button->y()); - return region; +bool TabStripGtk::EndDrag(bool canceled) { + return drag_controller_.get() ? drag_controller_->EndDrag(canceled) : false; } -void TabStripGtk::OnButtonActivate(const TabButtonGtk* button) { - model_->delegate()->AddBlankTab(true); +bool TabStripGtk::HasAvailableDragActions() const { + return model_->delegate()->GetDragActions() != 0; } //////////////////////////////////////////////////////////////////////////////// @@ -899,16 +808,12 @@ void TabStripGtk::LayoutNewTabButton(double last_tab_right, // We're shrinking tabs, so we need to anchor the New Tab button to the // right edge of the TabStrip's bounds, rather than the right edge of the // right-most Tab, otherwise it'll bounce when animating. - newtab_button_.get()->set_bounds( - gfx::Rect(bounds_.width() - newtab_button_.get()->bounds().width(), - kNewTabButtonVOffset, - newtab_button_.get()->bounds().width(), - newtab_button_.get()->bounds().height())); + gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_.get()->widget(), + bounds_.width() - newtab_button_.get()->width(), kNewTabButtonVOffset); } else { - newtab_button_.get()->set_bounds( - gfx::Rect(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, - kNewTabButtonVOffset, newtab_button_.get()->bounds().width(), - newtab_button_.get()->bounds().height())); + gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_.get()->widget(), + Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset, + kNewTabButtonVOffset); } } @@ -932,7 +837,7 @@ void TabStripGtk::GetDesiredTabWidths(int tab_count, if (available_width_for_tabs_ < 0) { available_width = bounds_.width(); available_width -= - (kNewTabButtonHOffset + newtab_button_.get()->bounds().width()); + (kNewTabButtonHOffset + newtab_button_.get()->width()); } else { // Interesting corner case: if |available_width_for_tabs_| > the result // of the calculation in the conditional arm above, the strip is in @@ -1008,7 +913,7 @@ void TabStripGtk::AnimationLayout(double unselected_width) { TabGtk* tab = GetTabAt(i); gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, tab_height); - tab->SetBounds(bounds); + SetTabBounds(tab, bounds); tab_x = end_of_tab + kTabHOffset; } LayoutNewTabButton(tab_x, unselected_width); @@ -1053,13 +958,6 @@ void TabStripGtk::StartResizeLayoutAnimation() { active_animation_->Start(); } -void TabStripGtk::StartSnapTabAnimation(const gfx::Rect& bounds) { - if (active_animation_.get()) - active_animation_->Stop(); - active_animation_.reset(new SnapTabAnimation(this, bounds)); - active_animation_->Start(); -} - bool TabStripGtk::CanUpdateDisplay() { // Don't bother laying out/painting when we're closing all tabs. if (model_->closing_all()) { @@ -1081,16 +979,27 @@ void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation, // static gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event, TabStripGtk* tabstrip) { - ChromeCanvasPaint canvas(event); - if (canvas.isEmpty()) + + if (gdk_region_empty(event->region)) return TRUE; - canvas.TileImageInt(*background, 0, 0, tabstrip->bounds_.width(), - tabstrip->bounds_.height()); + // TODO(jhawkins): Ideally we'd like to only draw what's needed in the damage + // rect, but the tab widgets overlap each other, and painting on one widget + // will cause an expose-event to be sent to the widgets underneath. The + // underlying widget does not need to be redrawn as we control the order of + // expose-events. Currently we hack it to redraw the entire tabstrip. We + // could change the damage rect to just contain the tabs + the new tab button. + event->area.x = 0; + event->area.y = 0; + event->area.width = tabstrip->bounds_.width(); + event->area.height = tabstrip->bounds_.height(); + gdk_region_union_with_rect(event->region, &event->area); - // Paint the New Tab button. This is painted first because a dragged tab - // should be at the bottom of the z-order. - tabstrip->newtab_button_.get()->Paint(&canvas); + tabstrip->PaintBackground(event); + + // Paint the New Tab button. + gtk_container_propagate_expose(GTK_CONTAINER(tabstrip->tabstrip_.get()), + tabstrip->newtab_button_.get()->widget(), event); // Paint the tabs in reverse order, so they stack to the left. TabGtk* selected_tab = NULL; @@ -1101,29 +1010,39 @@ gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event, // 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(&canvas); + gtk_container_propagate_expose(GTK_CONTAINER(tabstrip->tabstrip_.get()), + tab->widget(), event); } else { selected_tab = tab; } } // Paint the selected tab last, so it overlaps all the others. - if (selected_tab) - selected_tab->Paint(&canvas); + if (selected_tab) { + gtk_container_propagate_expose(GTK_CONTAINER(tabstrip->tabstrip_.get()), + selected_tab->widget(), event); + } 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); +void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation, + TabStripGtk* tabstrip) { + gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y, + allocation->width, allocation->height); + + // Nothing to do if the bounds are the same. If we don't catch this, we'll + // get an infinite loop of size-allocate signals. + if (tabstrip->bounds_ == bounds) + return; + tabstrip->SetBounds(bounds); // No tabs, nothing to layout. This happens when a browser window is created // and shown before tabs are added (as in a popup window). if (tabstrip->GetTabCount() == 0) - return TRUE; + return; // Do a regular layout on the first configure-event so we don't animate // the first tab. @@ -1134,347 +1053,32 @@ gboolean TabStripGtk::OnConfigure(GtkWidget* widget, GdkEventConfigure* event, tabstrip->Layout(); else tabstrip->ResizeLayoutTabs(); - - return TRUE; -} - -// static -gboolean TabStripGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, - TabStripGtk* tabstrip) { - // The dragging code handles moving the tab while the tab is being dragged. - if (tabstrip->is_dragging_) - return TRUE; - - int old_hover_index = tabstrip->hover_index_; - gfx::Point point(event->x, event->y); - - int index; - TabAnimation* animation = tabstrip->active_animation_.get(); - if (animation && animation->type() == TabAnimation::SNAP) { - index = tabstrip->FindTabHoverIndexIterative(point); - } else { - index = tabstrip->FindTabHoverIndexFast(point); - } - - // Hovering does not take place outside of the currently highlighted tab if - // the button is pressed. - if (IsButtonPressed(event->state) && index != tabstrip->hover_index_) - return TRUE; - - tabstrip->hover_index_ = index; - - bool paint = false; - if (old_hover_index != -1 && old_hover_index != index && - old_hover_index < tabstrip->GetTabCount()) { - // Notify the previously highlighted tab that the mouse has left. - paint = tabstrip->GetTabAt(old_hover_index)->OnLeaveNotify(); - } - - if (tabstrip->hover_index_ == -1) { - // If the hover index is out of bounds, try the new tab button. - paint |= tabstrip->newtab_button_.get()->OnMotionNotify(event); - } else { - // Notify the currently highlighted tab where the mouse is. - paint |= tabstrip->GetTabAt(tabstrip->hover_index_)->OnMotionNotify(event); - } - - if (paint) - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - - return TRUE; -} - -// static -gboolean TabStripGtk::OnMousePress(GtkWidget* widget, GdkEventButton* event, - TabStripGtk* tabstrip) { - gfx::Point point(event->x, event->y); - - // Nothing happens on mouse press for middle and right click. - if (event->button != 1) - return TRUE; - - // The hover index is stale if we're in the middle of an animation and the - // mouse is pressed without any movement. - if (tabstrip->active_animation_.get()) - tabstrip->hover_index_ = tabstrip->FindTabHoverIndexIterative(point); - - if (tabstrip->hover_index_ == -1) { - if (tabstrip->newtab_button_.get()->IsPointInBounds(point) && - tabstrip->newtab_button_.get()->OnMousePress()) - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - - return TRUE; - } - - TabGtk* tab = tabstrip->GetTabAt(tabstrip->hover_index_); - - // If a previous tab is closing, the hover index does not match the model - // index. - tabstrip->hover_index_ = tabstrip->GetIndexOfTab(tab); - - if (tab->OnMousePress(point)) { - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - } else if (tabstrip->hover_index_ != tabstrip->model()->selected_index() && - !tab->closing()) { - tabstrip->model()->SelectTabContentsAt(tabstrip->hover_index_, true); - } - - return TRUE; -} - -// static -gboolean TabStripGtk::OnMouseRelease(GtkWidget* widget, GdkEventButton* event, - TabStripGtk* tabstrip) { - gfx::Point point(event->x, event->y); - if (tabstrip->hover_index_ != -1) { - tabstrip->GetTabAt(tabstrip->hover_index_)->OnMouseRelease(event); - } else { - tabstrip->newtab_button_.get()->OnMouseRelease(); - } - - return TRUE; -} - -// static -gboolean TabStripGtk::OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event, - TabStripGtk* tabstrip) { - if (tabstrip->is_dragging_) { - TabGtk* tab = tabstrip->GetTabAt(tabstrip->hover_index_); - tabstrip->MoveTab(tab, gfx::Point(event->x, event->y)); - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - } - - return TRUE; -} - -// static -gboolean TabStripGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, - TabStripGtk* tabstrip) { - // No leave notification if the mouse button is pressed. - if (IsButtonPressed(event->state)) - return TRUE; - - // gtk does not set the button pressed state when a drag is occurring, so - // bail out if we're dragging. Otherwise, the SnapTabAnimation will have - // the wrong tab index (-1) when initiating the snap. - if (tabstrip->is_dragging_) - return TRUE; - - // A leave-notify-event is generated on mouse click, which sets the mode to - // GDK_CROSSING_GRAB. Ignore this event because it doesn't meant the mouse - // has left the tabstrip. - if (event->mode == GDK_CROSSING_GRAB) - return TRUE; - - // There is a race between the remove tab animation and the hover index - // handling in OnMotionNotify. As the mouse leaves the tabstrip, - // OnMotionNotify figures out the hover index, the remove tab animation moves - // a frame, and the hover index becomes stale as OnLeaveNotify is called. - // The check against GetTabCount avoids this scenario. - if (tabstrip->hover_index_ != -1 && - tabstrip->hover_index_ < tabstrip->GetTabCount()) { - tabstrip->GetTabAt(tabstrip->hover_index_)->OnLeaveNotify(); - tabstrip->hover_index_ = -1; - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - } - - return TRUE; -} - -// static -void TabStripGtk::OnDragBegin(GtkWidget* widget, GdkDragContext* context, - TabStripGtk* tabstrip) { - // No dragging should happen if the tab is closing. - if (tabstrip->hover_index_ == -1 || - tabstrip->GetTabAt(tabstrip->hover_index_)->closing()) { - gdk_drop_finish(context, FALSE, 0); - return; - } - - // If we're in the middle of a snap animation, stop the animation. We only - // set the snap bounds if the tab is snapped into a proper index, which is not - // the case if it's being snapped. - if (tabstrip->active_animation_.get()) { - tabstrip->active_animation_->Stop(); - } else { - tabstrip->snap_bounds_ = - tabstrip->GetTabAt(tabstrip->hover_index_)->bounds(); - } - - tabstrip->is_dragging_ = true; } // static -void TabStripGtk::OnDragEnd(GtkWidget* widget, GdkDragContext* context, - TabStripGtk* tabstrip) { - if (tabstrip->is_dragging_) { - tabstrip->StartSnapTabAnimation(tabstrip->snap_bounds_); - tabstrip->mouse_offset_ = gfx::Point(-1, -1); - tabstrip->is_dragging_ = false; - } +void TabStripGtk::OnNewTabClicked(GtkWidget* widget, TabStripGtk* tabstrip) { + tabstrip->model_->delegate()->AddBlankTab(true); } -// static -gboolean TabStripGtk::OnDragFailed(GtkWidget* widget, GdkDragContext* context, - GtkDragResult result, - TabStripGtk* tabstrip) { - tabstrip->mouse_offset_ = gfx::Point(-1, -1); - tabstrip->is_dragging_ = false; - return TRUE; -} - -// static -gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, - GdkDragContext* drag_context, - guint x, guint y, - guint time, - TabStripGtk* tabstrip) { - // gtk sends drag-motion signals even after the drag has failed, but before - // the drag-end signal is emitted. - if (!tabstrip->is_dragging_) - return TRUE; - - TabGtk* tab = tabstrip->GetTabAt(tabstrip->hover_index_); - gfx::Rect bounds = tab->bounds(); - - // For whatever reason, gtk does not give us the coordinates of the mouse in - // the drag-begin event, so set them here once. - if (tabstrip->mouse_offset_ == gfx::Point(-1, -1)) - tabstrip->mouse_offset_.SetPoint(x - bounds.x(), y - bounds.y()); - - tabstrip->MoveTab(tab, gfx::Point(x, y)); - gtk_widget_queue_draw(tabstrip->tabstrip_.get()); - - return TRUE; -} - -int TabStripGtk::FindTabHoverIndexIterative(const gfx::Point& point) { - for (int i = 0; i < GetTabCount(); i++) { - if (GetTabAt(i)->IsPointInBounds(point)) - return i; - } - - return -1; -} - -int TabStripGtk::FindTabHoverIndexFast(const gfx::Point& point) { - // Get a rough estimate for which tab the mouse is over. - int index = point.x() / (current_unselected_width_ + kTabHOffset); - - // Tab hovering calcuation. - // Using the rough estimate tab index, we check the tab bounds in a smart - // order to reduce the number of tabs we need to check. If the tab at the - // estimated index is selected, check it first as it covers both tabs below - // it. Otherwise, check the tab to the left, then the estimated tab, and - // finally the tab to the right (tabs stack to the left.) - - int tab_count = GetTabCount(); - if (index == tab_count && - GetTabAt(index - 1)->IsPointInBounds(point)) { - index--; - } else if (index >= tab_count) { - index = -1; - } else if (index > 0 && - GetTabAt(index - 1)->IsPointInBounds(point)) { - index--; - } else if (index < tab_count - 1 && - GetTabAt(index + 1)->IsPointInBounds(point)) { - index++; - } - - return index; +void TabStripGtk::PaintBackground(GdkEventExpose* event) { + ChromeCanvasPaint canvas(event); + canvas.TileImageInt(*background, 0, 0, bounds_.width(), bounds_.height()); } -void TabStripGtk::MoveTab(TabGtk* tab, const gfx::Point& point) { - // Determine the horizontal move threshold. This is dependent on the width - // of tabs. The smaller the tabs compared to the standard size, the smaller - // the threshold. - double unselected, selected; - GetCurrentTabWidths(&unselected, &selected); - - double ratio = unselected / TabGtk::GetStandardSize().width(); - int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); - - // Update the model, moving the TabContents from one index to another. Do - // this only if we have moved a minimum distance since the last reorder (to - // prevent jitter). - if (abs(point.x() - last_move_x_) > threshold) { - TabStripModel* attached_model = model(); - int from_index = hover_index_; - gfx::Rect bounds = GetTabAt(hover_index_)->bounds(); - - int to_index = GetInsertionIndexForDraggedBounds(bounds); - to_index = NormalizeIndexToAttachedTabStrip(to_index); - if (from_index != to_index) { - last_move_x_ = point.x(); - hover_index_ = to_index; - snap_bounds_ = GetTabAt(to_index)->bounds(); - attached_model->MoveTabContentsAt(from_index, to_index, true); - } - } - - // Move the tab. - gfx::Point dragged_point = GetDraggedPoint(tab, point); - gfx::Rect bounds = tab->bounds(); - bounds.set_x(dragged_point.x()); +void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) { tab->SetBounds(bounds); + gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(), + bounds.x(), bounds.y()); } -gfx::Point TabStripGtk::GetDraggedPoint(TabGtk* tab, const gfx::Point& point) { - int x = point.x() - mouse_offset_.x(); - int y = point.y() - mouse_offset_.y(); - - // Snap the dragged tab to the tab strip. - if (x < 0) - x = 0; - - // Make sure the tab can't be dragged off the right side of the tab strip. - int max_x = bounds_.right() - tab->width(); - if (x > max_x) - x = max_x; - - return gfx::Point(x, y); -} - -int TabStripGtk::GetInsertionIndexForDraggedBounds( - const gfx::Rect& dragged_bounds) { - int right_tab_x = 0; +CustomDrawButton* TabStripGtk::MakeNewTabButton() { + CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON, + IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0); - // TODO(jhawkins): Handle RTL layout. - - // Divides each tab into two halves to see if the dragged tab has crossed - // the halfway boundary necessary to move past the next tab. - for (int i = 0; i < GetTabCount(); i++) { - gfx::Rect ideal_bounds = GetIdealBounds(i); - - gfx::Rect left_half = ideal_bounds; - left_half.set_width(left_half.width() / 2); - - gfx::Rect right_half = ideal_bounds; - right_half.set_width(ideal_bounds.width() - left_half.width()); - right_half.set_x(left_half.right()); - - right_tab_x = right_half.right(); - - if (dragged_bounds.x() >= right_half.x() && - dragged_bounds.x() < right_half.right()) { - return i + 1; - } else if (dragged_bounds.x() >= left_half.x() && - dragged_bounds.x() < left_half.right()) { - return i; - } - } - - if (dragged_bounds.right() > right_tab_x) - return model()->count(); - - return TabStripModel::kNoTab; -} + g_signal_connect(G_OBJECT(button->widget()), "clicked", + G_CALLBACK(OnNewTabClicked), this); + GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS); + gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0); -int TabStripGtk::NormalizeIndexToAttachedTabStrip(int index) { - if (index >= model_->count()) - return model_->count() - 1; - if (index == TabStripModel::kNoTab) - return 0; - return index; + return button; } diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h index 1d4a118..0e0739c 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h @@ -10,14 +10,15 @@ #include "base/basictypes.h" #include "base/gfx/rect.h" -#include "chrome/browser/gtk/tabs/tab_button_gtk.h" #include "chrome/browser/gtk/tabs/tab_gtk.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/common/owned_widget_gtk.h" +class CustomDrawButton; +class DraggedTabControllerGtk; + class TabStripGtk : public TabStripModelObserver, - public TabGtk::TabDelegate, - public TabButtonGtk::Delegate { + public TabGtk::TabDelegate { public: class TabAnimation; @@ -25,7 +26,7 @@ class TabStripGtk : public TabStripModelObserver, virtual ~TabStripGtk(); // Initialize and load the TabStrip into a container. - void Init(); + void Init(int width); void AddTabStripToBox(GtkWidget* box); void Show(); @@ -33,11 +34,14 @@ class TabStripGtk : public TabStripModelObserver, TabStripModel* model() const { return model_; } + // Returns true if there is an active drag session. + bool IsDragSessionActive() const { return drag_controller_.get() != NULL; } + // Sets the bounds of the tabs. void Layout(); // Sets the bounds of the tabstrip. - void SetBounds(const gfx::Rect& bounds) { bounds_ = bounds; } + void SetBounds(const gfx::Rect& bounds); // Updates loading animations for the TabStrip. void UpdateLoadingAnimations(); @@ -46,6 +50,9 @@ class TabStripGtk : public TabStripModelObserver, // position. bool IsAnimating() const; + // Destroys the active drag controller. + void DestroyDragController(); + // Retrieve the ideal bounds for the Tab at the specified index. gfx::Rect GetIdealBounds(int index); @@ -76,18 +83,16 @@ class TabStripGtk : public TabStripModelObserver, virtual void StopHighlightTabsForCommand( TabStripModel::ContextMenuCommand command_id, TabGtk* tab); virtual void StopAllHighlighting(); + virtual void MaybeStartDrag(TabGtk* tab, const gfx::Point& point); + virtual void ContinueDrag(GdkDragContext* context); virtual bool EndDrag(bool canceled); virtual bool HasAvailableDragActions() const; - // TabButtonGtk::Delegate implementation: - virtual GdkRegion* MakeRegionForButton(const TabButtonGtk* button) const; - virtual void OnButtonActivate(const TabButtonGtk* button); - private: + friend class DraggedTabControllerGtk; friend class InsertTabAnimation; friend class RemoveTabAnimation; friend class MoveTabAnimation; - friend class SnapTabAnimation; friend class ResizeLayoutAnimation; friend class TabAnimation; @@ -100,76 +105,21 @@ class TabStripGtk : public TabStripModelObserver, 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); - - // motion-notify-event handler that handles mouse movement in the tabstrip. - static gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, - TabStripGtk* tabstrip); - - // button-press-event handler that handles mouse clicks. - static gboolean OnMousePress(GtkWidget* widget, GdkEventButton* event, - TabStripGtk* tabstrip); - - // button-release-event handler that handles mouse click releases. - static gboolean OnMouseRelease(GtkWidget* widget, GdkEventButton* event, - TabStripGtk* tabstrip); - - // enter-notify-event handler that signals when the mouse enters the tabstrip. - static gboolean OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event, - TabStripGtk* tabstrip); - - // leave-notify-event handler that signals when the mouse leaves the tabstrip. - static gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, - TabStripGtk* tabstrip); - - // drag-begin handler that signals when a drag action begins. - static void OnDragBegin(GtkWidget* widget, GdkDragContext* context, - TabStripGtk* tabstrip); - - // drag-end handler that signals when a drag action ends. - static void OnDragEnd(GtkWidget* widget, GdkDragContext* context, - TabStripGtk* tabstrip); + // size-allocate handler that gets the new bounds of the tabstrip. + static void OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation, + TabStripGtk* tabstrip); - // drag-motion handler that handles drag movements in the tabstrip. - static gboolean OnDragMotion(GtkWidget* widget, GdkDragContext* context, - guint x, guint y, guint time, - TabStripGtk* tabstrip); + // Handles the clicked signal from the new tab button. + static void OnNewTabClicked(GtkWidget* widget, TabStripGtk* tabstrip); - // drag-failed handler that is emitted when the drag fails. - static gboolean OnDragFailed(GtkWidget* widget, GdkDragContext* context, - GtkDragResult result, TabStripGtk* tabstrip); + // Renders the tabstrip background. + void PaintBackground(GdkEventExpose* event); - // Finds the tab that is under |point| by iterating through all of the tabs - // and checking if |point| is in their bounds. This method is only used when - // the state of all the tabs cannot be calculated, as during a SnapTab - // animation. Runs in O(n) time. - int FindTabHoverIndexIterative(const gfx::Point& point); + // Sets the bounds of the tab and moves the tab widget to those bounds. + void SetTabBounds(TabGtk* tab, const gfx::Rect& bounds); - // Finds the tab that is under |point| by estimating the tab index and - // checking if |point| is in the bounds of the surrounding tabs. This method - // is optimal and is used in most cases. Runs in O(1) time. - int FindTabHoverIndexFast(const gfx::Point& point); - - // -- Drag & Drop ------------------------------------------------------------ - // - // TODO(jhawkins): These functions belong in DraggedTabControllerGtk. - - // Returns the index where the dragged TabContents should be inserted into - // the attached TabStripModel given the DraggedTabView's bounds - // |dragged_bounds| in coordinates relative to the attached TabStrip. - int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds); - - // Utility to convert the specified TabStripModel index to something valid - // for the attached TabStrip. - int NormalizeIndexToAttachedTabStrip(int index); - - // Handles moving the Tab within a TabStrip. - void MoveTab(TabGtk* tab, const gfx::Point& point); - - // Get the position of the dragged tab relative to the attached tab strip. - gfx::Point GetDraggedPoint(TabGtk* tab, const gfx::Point& point); + // Initializes the new tab button. + CustomDrawButton* MakeNewTabButton(); // Gets the number of Tabs in the collection. int GetTabCount() const; @@ -226,7 +176,6 @@ class TabStripGtk : public TabStripModelObserver, void StartRemoveTabAnimation(int index, TabContents* contents); void StartResizeLayoutAnimation(); void StartMoveTabAnimation(int from_index, int to_index); - void StartSnapTabAnimation(const gfx::Rect& bounds); // Returns true if detach or select changes in the model should be reflected // in the TabStrip. This returns false if we're closing all tabs in the @@ -262,7 +211,7 @@ class TabStripGtk : public TabStripModelObserver, // TODO(beng): (Cleanup) this would be better named "needs_resize_layout_". bool resize_layout_scheduled_; - // The drawing area widget. + // The GtkFixed widget. OwnedWidgetGtk tabstrip_; // The bounds of the tabstrip. @@ -271,34 +220,15 @@ class TabStripGtk : public TabStripModelObserver, // Our model. TabStripModel* model_; - // The index of the tab the mouse is currently over. -1 if not over a tab. - int hover_index_; - // The currently running animation. scoped_ptr<TabAnimation> active_animation_; // The New Tab button. - scoped_ptr<TabButtonGtk> newtab_button_; - - // =========================================================================== - // TODO(jhawkins): This belongs in DraggedTabControllerGtk. - - // This is the offset of the mouse from the top left of the Tab where - // dragging begun. This is used to ensure that the dragged view is always - // positioned at the correct location during the drag, and to ensure that the - // detached window is created at the right location. - gfx::Point mouse_offset_; - - // The horizontal position of the mouse cursor at the time of the last - // re-order event. - int last_move_x_; - - // The last good tab bounds of the dragged tab. This is the position the tab - // will be snapped back to when the drag is released. - gfx::Rect snap_bounds_; + scoped_ptr<CustomDrawButton> newtab_button_; - // When a tab is being dragged, certain gtk events should be ignored. - bool is_dragging_; + // The controller for a drag initiated from a Tab. Valid for the lifetime of + // the drag session. + scoped_ptr<DraggedTabControllerGtk> drag_controller_; DISALLOW_COPY_AND_ASSIGN(TabStripGtk); }; diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 5fe5813..37308c3 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -880,6 +880,8 @@ 'browser/gtk/status_bubble_gtk.h', 'browser/gtk/tab_contents_container_gtk.cc', 'browser/gtk/tab_contents_container_gtk.h', + 'browser/gtk/tabs/dragged_tab_controller_gtk.cc', + 'browser/gtk/tabs/dragged_tab_controller_gtk.h', 'browser/gtk/tabs/tab_button_gtk.cc', 'browser/gtk/tabs/tab_button_gtk.h', 'browser/gtk/tabs/tab_gtk.cc', |