summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/tabs/tab_strip_gtk.cc
diff options
context:
space:
mode:
authorjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-23 23:03:58 +0000
committerjhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-23 23:03:58 +0000
commit390da186a4be746973d476c5ab23e5c796e80c4f (patch)
treee04330d9bd914e1bfc13066b7f802de7fa44abeb /chrome/browser/gtk/tabs/tab_strip_gtk.cc
parentf9c773362c778437308629ff41d117a1a1cdda81 (diff)
downloadchromium_src-390da186a4be746973d476c5ab23e5c796e80c4f.zip
chromium_src-390da186a4be746973d476c5ab23e5c796e80c4f.tar.gz
chromium_src-390da186a4be746973d476c5ab23e5c796e80c4f.tar.bz2
Add initial support for dragging tabs in the Linux tab strip.
* Tabs cannot be dragged out of the browser window. * Most of this logic needs to be refactored into DraggedTabControllerGtk. Review URL: http://codereview.chromium.org/92028 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14372 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/gtk/tabs/tab_strip_gtk.cc')
-rw-r--r--chrome/browser/gtk/tabs/tab_strip_gtk.cc387
1 files changed, 357 insertions, 30 deletions
diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc
index 82ba974..9c8fd1d 100644
--- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc
+++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc
@@ -20,16 +20,25 @@ namespace {
const int kDefaultAnimationDurationMs = 100;
const int kResizeLayoutAnimationDurationMs = 166;
+const int kReorderAnimationDurationMs = 166;
+const int kAnimateToBoundsDurationMs = 150;
const int kNewTabButtonHOffset = -5;
const int kNewTabButtonVOffset = 5;
+const int kHorizontalMoveThreshold = 16; // pixels
+
// The horizontal offset from one tab to the next,
// which results in overlapping tabs.
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);
}
@@ -63,7 +72,8 @@ class TabStripGtk::TabAnimation : public AnimationDelegate {
INSERT,
REMOVE,
MOVE,
- RESIZE
+ RESIZE,
+ SNAP
};
TabAnimation(TabStripGtk* tabstrip, Type type)
@@ -318,6 +328,60 @@ class RemoveTabAnimation : public TabStripGtk::TabAnimation {
////////////////////////////////////////////////////////////////////////////////
+// Handles the movement of a Tab from one position to another.
+class MoveTabAnimation : public TabStripGtk::TabAnimation {
+ public:
+ MoveTabAnimation(TabStripGtk* tabstrip, int tab_a_index, int tab_b_index)
+ : TabAnimation(tabstrip, MOVE),
+ start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)),
+ start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) {
+ tab_a_ = tabstrip_->GetTabAt(tab_a_index);
+ tab_b_ = tabstrip_->GetTabAt(tab_b_index);
+
+ // Since we don't do a full TabStrip re-layout, we need to force a full
+ // layout upon completion since we're not guaranteed to be in a good state
+ // if for example the animation is canceled.
+ set_layout_on_completion(true);
+ }
+ virtual ~MoveTabAnimation() {}
+
+ // Overridden from AnimationDelegate:
+ virtual void AnimationProgressed(const Animation* animation) {
+ // Position Tab A
+ double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x();
+ double delta = distance * animation_.GetCurrentValue();
+ 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);
+
+ // Position Tab B
+ distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x();
+ delta = distance * animation_.GetCurrentValue();
+ 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);
+ }
+
+ protected:
+ // Overridden from TabStrip::TabAnimation:
+ virtual int GetDuration() const { return kReorderAnimationDurationMs; }
+
+ private:
+ // The two tabs being exchanged.
+ TabGtk* tab_a_;
+ TabGtk* tab_b_;
+
+ // ...and their bounds.
+ gfx::Rect start_tab_a_bounds_;
+ gfx::Rect start_tab_b_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
// Handles the animated resize layout of the entire TabStrip from one width
// to another.
class ResizeLayoutAnimation : public TabStripGtk::TabAnimation {
@@ -372,6 +436,50 @@ 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)
@@ -380,7 +488,10 @@ TabStripGtk::TabStripGtk(TabStripModel* model)
available_width_for_tabs_(-1),
resize_layout_scheduled_(false),
model_(model),
- hover_index_(0) {
+ hover_index_(0),
+ mouse_offset_(-1, -1),
+ last_move_x_(0),
+ is_dragging_(false) {
}
TabStripGtk::~TabStripGtk() {
@@ -413,6 +524,13 @@ void TabStripGtk::Init() {
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",
@@ -425,6 +543,12 @@ void TabStripGtk::Init() {
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-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);
@@ -491,6 +615,11 @@ bool TabStripGtk::IsAnimating() const {
return active_animation_.get() != NULL;
}
+gfx::Rect TabStripGtk::GetIdealBounds(int index) {
+ DCHECK(index >= 0 && index < GetTabCount());
+ return tab_data_.at(index).ideal_bounds;
+}
+
////////////////////////////////////////////////////////////////////////////////
// TabStripGtk, TabStripModelObserver implementation:
@@ -561,8 +690,7 @@ void TabStripGtk::TabMoved(TabContents* contents,
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();
+ StartMoveTabAnimation(from_index, to_index);
}
void TabStripGtk::TabChangedAt(TabContents* contents, int index,
@@ -913,6 +1041,13 @@ void TabStripGtk::StartRemoveTabAnimation(int index, TabContents* contents) {
active_animation_->Start();
}
+void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) {
+ if (active_animation_.get())
+ active_animation_->Stop();
+ active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
+ active_animation_->Start();
+}
+
void TabStripGtk::StartResizeLayoutAnimation() {
if (active_animation_.get())
active_animation_->Stop();
@@ -920,6 +1055,13 @@ 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()) {
@@ -948,6 +1090,10 @@ gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event,
canvas.TileImageInt(*background, 0, 0, tabstrip->bounds_.width(),
tabstrip->bounds_.height());
+ // 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);
+
// Paint the tabs in reverse order, so they stack to the left.
TabGtk* selected_tab = NULL;
int tab_count = tabstrip->GetTabCount();
@@ -967,9 +1113,6 @@ gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event,
if (selected_tab)
selected_tab->Paint(&canvas);
- // Paint the New Tab button.
- tabstrip->newtab_button_.get()->Paint(&canvas);
-
return TRUE;
}
@@ -1000,31 +1143,19 @@ gboolean TabStripGtk::OnConfigure(GtkWidget* widget, GdkEventConfigure* event,
// 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);
- // Get a rough estimate for which tab the mouse is over.
- int index = event->x / (tabstrip->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 = tabstrip->GetTabCount();
- if (index == tab_count &&
- tabstrip->GetTabAt(index - 1)->IsPointInBounds(point)) {
- index--;
- } else if (index >= tab_count) {
- index = -1;
- } else if (index > 0 &&
- tabstrip->GetTabAt(index - 1)->IsPointInBounds(point)) {
- index--;
- } else if (index < tab_count - 1 &&
- tabstrip->GetTabAt(index + 1)->IsPointInBounds(point)) {
- index++;
+ 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
@@ -1036,7 +1167,7 @@ gboolean TabStripGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event,
bool paint = false;
if (old_hover_index != -1 && old_hover_index != index &&
- old_hover_index < tab_count ) {
+ old_hover_index < tabstrip->GetTabCount()) {
// Notify the previously highlighted tab that the mouse has left.
paint = tabstrip->GetTabAt(old_hover_index)->OnLeaveNotify();
}
@@ -1098,12 +1229,30 @@ gboolean TabStripGtk::OnMouseRelease(GtkWidget* widget, GdkEventButton* event,
}
// 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.
@@ -1124,3 +1273,181 @@ gboolean TabStripGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
return TRUE;
}
+
+// static
+void TabStripGtk::OnDragBegin(GtkWidget* widget, GdkDragContext* context,
+ TabStripGtk* tabstrip) {
+ if (tabstrip->hover_index_ == -1)
+ 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) {
+ tabstrip->StartSnapTabAnimation(tabstrip->snap_bounds_);
+ tabstrip->mouse_offset_ = gfx::Point(-1, -1);
+ tabstrip->is_dragging_ = false;
+}
+
+// static
+gboolean TabStripGtk::OnDragMotion(GtkWidget* widget,
+ GdkDragContext* drag_context,
+ guint x, guint y,
+ guint time,
+ TabStripGtk* tabstrip) {
+ 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::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());
+ tab->SetBounds(bounds);
+}
+
+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;
+
+ // 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;
+}
+
+int TabStripGtk::NormalizeIndexToAttachedTabStrip(int index) {
+ if (index >= model_->count())
+ return model_->count() - 1;
+ if (index == TabStripModel::kNoTab)
+ return 0;
+ return index;
+}