From 02f58f54e138f44d26743fa1f8bef78fc705ae80 Mon Sep 17 00:00:00 2001 From: "pinkerton@chromium.org" Date: Wed, 29 Apr 2009 20:30:54 +0000 Subject: Implement dropping of tabs into an existing tab strip from another window. Implement dragging and dropping of tabs within a window. Review URL: http://codereview.chromium.org/102010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14879 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/cocoa/browser_window_controller.mm | 48 ++++++++++++++--- chrome/browser/cocoa/tab_strip_controller.h | 10 +++- chrome/browser/cocoa/tab_strip_controller.mm | 64 ++++++++++++++++++++--- chrome/browser/cocoa/tab_view.mm | 53 +++++++++++-------- chrome/browser/cocoa/tab_window_controller.h | 20 +++++-- chrome/browser/cocoa/tab_window_controller.mm | 8 ++- 6 files changed, 162 insertions(+), 41 deletions(-) (limited to 'chrome/browser/cocoa') diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index efc08fc..5c35e2c 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -277,16 +277,52 @@ willPositionSheet:(NSWindow *)sheet return [tabStripController_ selectedTabGrowBoxRect]; } -- (void)dropTabView:(NSView *)view atIndex:(NSUInteger)index { - [tabStripController_ dropTabView:view atIndex:index]; +// Drop a given tab view at the location of the current placeholder. If there +// is no placeholder, it will go at the end. |dragController| is the window +// controller of a tab being dropped from a different window. It will be nil +// if the drag is within the window. The implementation will call +// |-removePlaceholder| since the drag is now complete. This also calls +// |-layoutTabs| internally so clients do not need to call it again. When +// dragging tabs between windows, this should be called *before* +// |-detachTabView| on the source window since it needs to still be in the +// source window's tab model for this method to find the information it needs +// to complete the drop. +- (void)dropTabView:(NSView*)view + fromController:(TabWindowController*)dragController { + if (dragController) { + // Moving between windows. Figure out the TabContents to drop into our tab + // model from the source window's model. + BOOL isBrowser = + [dragController isKindOfClass:[BrowserWindowController class]]; + DCHECK(isBrowser); + if (!isBrowser) return; + BrowserWindowController* dragBWC = (BrowserWindowController*)dragController; + int index = [dragBWC->tabStripController_ indexForTabView:view]; + TabContents* contents = + dragBWC->browser_->tabstrip_model()->GetTabContentsAt(index); + + // Deposit it into our model at the appropriate location (it already knows + // where it should go from tracking the drag). + [tabStripController_ dropTabContents:contents]; + } else { + // Moving within a window. + int index = [tabStripController_ indexForTabView:view]; + [tabStripController_ moveTabFromIndex:index]; + } + + // Remove the placeholder since the drag is now complete. + [self removePlaceholder]; } -- (NSView *)selectedTabView { - return [tabStripController_ selectedTabView]; +// Tells the tab strip to forget about this tab in preparation for it being +// put into a different tab strip, such as during a drop on another window. +- (void)detachTabView:(NSView*)view { + int index = [tabStripController_ indexForTabView:view]; + browser_->tabstrip_model()->DetachTabContentsAt(index); } -- (TabStripController *)tabStripController { - return tabStripController_; +- (NSView *)selectedTabView { + return [tabStripController_ selectedTabView]; } - (void)setIsLoading:(BOOL)isLoading { diff --git a/chrome/browser/cocoa/tab_strip_controller.h b/chrome/browser/cocoa/tab_strip_controller.h index 28e5ff0..35fbc42 100644 --- a/chrome/browser/cocoa/tab_strip_controller.h +++ b/chrome/browser/cocoa/tab_strip_controller.h @@ -69,8 +69,14 @@ class ToolbarModel; // Return the view for the currently selected tab. - (NSView *)selectedTabView; -// Drop a tab view at a new index in the array. -- (void)dropTabView:(NSView *)view atIndex:(NSUInteger)index; +// Move the given tab at index |from| in this window to the location of the +// current placeholder. +- (void)moveTabFromIndex:(NSInteger)from; + +// Drop a given TabContents at the location of the current placeholder. If there +// is no placeholder, it will go at the end. Used when dragging from another +// window when we don't have access to the TabContents as part of our strip. +- (void)dropTabContents:(TabContents*)contents; // Given a tab view in the strip, return its index. Returns -1 if not present. - (NSInteger)indexForTabView:(NSView*)view; diff --git a/chrome/browser/cocoa/tab_strip_controller.mm b/chrome/browser/cocoa/tab_strip_controller.mm index 28de716..b2267d0 100644 --- a/chrome/browser/cocoa/tab_strip_controller.mm +++ b/chrome/browser/cocoa/tab_strip_controller.mm @@ -17,7 +17,6 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/common/l10n_util.h" -#include "grit/chromium_strings.h" #include "grit/generated_resources.h" @implementation TabStripController @@ -136,7 +135,6 @@ } } - - (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame yStretchiness:(CGFloat)yStretchiness { @@ -146,7 +144,6 @@ [self layoutTabs]; } - // Lay out all tabs in the order of their TabContentsControllers, which matches // the ordering in the TabStripModel. This call isn't that expensive, though // it is O(n) in the number of tabs. Tabs will animate to their new position @@ -350,14 +347,69 @@ [updatedController tabDidChange:contents]; } +// Called when a tab is moved (usually by drag&drop). Keep our parallel arrays +// in sync with the tab strip model. +- (void)tabMovedWithContents:(TabContents*)contents + fromIndex:(NSInteger)from + toIndex:(NSInteger)to { + scoped_nsobject movedController( + [[tabContentsArray_ objectAtIndex:from] retain]); + [tabContentsArray_ removeObjectAtIndex:from]; + [tabContentsArray_ insertObject:movedController.get() atIndex:to]; + scoped_nsobject movedView( + [[tabArray_ objectAtIndex:from] retain]); + [tabArray_ removeObjectAtIndex:from]; + [tabArray_ insertObject:movedView.get() atIndex:to]; + + [self layoutTabs]; +} + - (NSView *)selectedTabView { int selectedIndex = tabModel_->selected_index(); return [self viewAtIndex:selectedIndex]; } -- (void)dropTabView:(NSView *)view atIndex:(NSUInteger)index { - // TODO(pinkerton): implement drop - NOTIMPLEMENTED(); +// Find the index based on the x coordinate of the placeholder. If there is +// no placeholder, this returns the end of the tab strip. +- (int)indexOfPlaceholder { + double placeholderX = placeholderFrame_.origin.x; + int index = 0; + int location = 0; + const int count = tabModel_->count(); + while (index < count) { + NSView* curr = [self viewAtIndex:index]; + // The placeholder tab works by changing the frame of the tab being dragged + // to be the bounds of the placeholder, so we need to skip it while we're + // iterating, otherwise we'll end up off by one. Note This only effects + // dragging to the right, not to the left. + if (curr == placeholderTab_) { + index++; + continue; + } + if (placeholderX <= NSMinX([curr frame])) + break; + index++; + location++; + } + return location; +} + +// Move the given tab at index |from| in this window to the location of the +// current placeholder. +- (void)moveTabFromIndex:(NSInteger)from { + int toIndex = [self indexOfPlaceholder]; + tabModel_->MoveTabContentsAt(from, toIndex, true); +} + +// Drop a given TabContents at the location of the current placeholder. If there +// is no placeholder, it will go at the end. Used when dragging from another +// window when we don't have access to the TabContents as part of our strip. +- (void)dropTabContents:(TabContents*)contents { + int index = [self indexOfPlaceholder]; + + // Insert it into this tab strip. We want it in the foreground and to not + // inherit the current tab's group. + tabModel_->InsertTabContentsAt(index, contents, true, false); } // Return the rect, in WebKit coordinates (flipped), of the window's grow box diff --git a/chrome/browser/cocoa/tab_view.mm b/chrome/browser/cocoa/tab_view.mm index fd31652..01f30e6 100644 --- a/chrome/browser/cocoa/tab_view.mm +++ b/chrome/browser/cocoa/tab_view.mm @@ -70,7 +70,7 @@ BOOL isLastRemainingTab = [sourceController numberOfTabs] <= 1; BOOL dragging = YES; - BOOL moved = NO; + BOOL moveBetweenWindows = NO; NSPoint lastPoint = [[theEvent window] convertBaseToScreen:[theEvent locationInWindow]]; @@ -103,7 +103,8 @@ } } - [sourceController removePlaceholder]; + if (dragging) + [sourceController removePlaceholder]; TabWindowController* draggedController = nil; TabWindowController* targetController = nil; @@ -178,7 +179,7 @@ NSEventType type = [theEvent type]; if (type == NSLeftMouseDragged) { - moved = YES; + moveBetweenWindows = YES; if (!draggedController) { if (isLastRemainingTab) { draggedController = sourceController; @@ -257,27 +258,20 @@ // The drag/click is done. If the user dragged the mouse, finalize the drag // and clean up. - if (moved) { - TabWindowController *dropController = targetController; -#if 1 - dropController = nil; // Don't allow drops on other windows for now -#endif + if (moveBetweenWindows) { + // Move between windows. If |targetController| is nil, we're not dropping + // into any existing window. + TabWindowController* dropController = targetController; if (dropController) { - // TODO(alcor/pinkerton): hookup drops on existing windows - NSRect adjustedFrame = [self bounds]; - NSRect dropTabFrame = [[dropController tabStripView] frame]; - adjustedFrame.origin = [self convertPointToBase:NSZeroPoint]; - adjustedFrame.origin = - [sourceWindow convertBaseToScreen:adjustedFrame.origin]; - adjustedFrame.origin.x = adjustedFrame.origin.x - dropTabFrame.origin.x; - //adjustedFrame.origin.y = adjustedFrame.origin.y - dropTabFrame.origin.y; - //adjustedFrame.size.height += adjustedFrame.origin.y; - adjustedFrame.origin.y = 0; - // TODO(alcor): get add tab stuff working - // [dropController addTab:tab_]; - [self setFrame:adjustedFrame]; - [dropController layoutTabs]; - [draggedController close]; + // The ordering here is important. We need to be able to get from the + // TabView in the |draggedController| to whatever is needed by the tab + // model. To do so, it still has to be in the model, so we have to call + // "drop" before we call "detach". + NSView* draggedTabView = [draggedController selectedTabView]; + [draggedController removeOverlay]; + [dropController dropTabView:draggedTabView + fromController:draggedController]; + [draggedController detachTabView:draggedTabView]; [dropController showWindow:nil]; } else { [targetController removePlaceholder]; @@ -291,7 +285,20 @@ [draggedController layoutTabs]; } [sourceController layoutTabs]; + } else { + // Move or click within a window. We need to differentiate between a + // click on the tab and a drag by checking against the initial x position. + NSPoint currentPoint = [NSEvent mouseLocation]; + BOOL wasDrag = fabs(currentPoint.x - lastPoint.x) > kDragStartDistance; + if (wasDrag) { + // Move tab to new location. + TabWindowController* dropController = sourceController; + [dropController dropTabView:[dropController selectedTabView] + fromController:nil]; + } } + + [sourceController removePlaceholder]; } @end diff --git a/chrome/browser/cocoa/tab_window_controller.h b/chrome/browser/cocoa/tab_window_controller.h index e86c829..6311f33 100644 --- a/chrome/browser/cocoa/tab_window_controller.h +++ b/chrome/browser/cocoa/tab_window_controller.h @@ -62,6 +62,23 @@ // Removes the placeholder installed by |-insertPlaceholderForTab:atLocation:|. - (void)removePlaceholder; +// Drop a given tab view at the location of the current placeholder. If there +// is no placeholder, it will go at the end. |controller| is the window +// controller of a tab being dropped from a different window. It will be nil +// if the drag is within the window. The implementation will call +// |-removePlaceholder| since the drag is now complete. This also calls +// |-layoutTabs| internally so clients do not need to call it again. When +// dragging tabs between windows, this should be called *before* +// |-detachTabView| on the source window since it needs to still be in the +// source window's tab model for this method to find the information it needs +// to complete the drop. +- (void)dropTabView:(NSView*)view + fromController:(TabWindowController*)controller; + +// Tells the tab strip to forget about this tab in preparation for it being +// put into a different tab strip, such as during a drop on another window. +- (void)detachTabView:(NSView*)view; + // Number of tabs in the tab strip. Useful, for example, to know if we're // dragging the only tab in the window. - (NSInteger)numberOfTabs; @@ -69,9 +86,6 @@ // Return the view of the selected tab. - (NSView *)selectedTabView; -// Drop a given tab view at a new index. -- (void)dropTabView:(NSView *)view atIndex:(NSUInteger)index; - // The title of the selected tab. - (NSString*)selectedTabTitle; diff --git a/chrome/browser/cocoa/tab_window_controller.mm b/chrome/browser/cocoa/tab_window_controller.mm index f7e6079..4a82940 100644 --- a/chrome/browser/cocoa/tab_window_controller.mm +++ b/chrome/browser/cocoa/tab_window_controller.mm @@ -99,7 +99,8 @@ return overlayWindow_; } -- (void)dropTabView:(NSView *)view atIndex:(NSUInteger)index { +- (void)dropTabView:(NSView*)view + fromController:(TabWindowController*)dragController { NOTIMPLEMENTED(); } @@ -131,6 +132,11 @@ NOTIMPLEMENTED(); } +- (void)detachTabView:(NSView*)view { + // subclass must implement + NOTIMPLEMENTED(); +} + - (NSInteger)numberOfTabs { // subclass must implement NOTIMPLEMENTED(); -- cgit v1.1