diff options
author | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-04 23:55:52 +0000 |
---|---|---|
committer | viettrungluu@chromium.org <viettrungluu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-04 23:55:52 +0000 |
commit | 920562c8e9bb77cf2c6fa999eeec3a63cab07d7b (patch) | |
tree | baeb69cb6214bc8646b4f4e7e21b10cd70ad5139 | |
parent | 5e4ec9729f5cf5b9cf8ccefad966337ee26b3170 (diff) | |
download | chromium_src-920562c8e9bb77cf2c6fa999eeec3a63cab07d7b.zip chromium_src-920562c8e9bb77cf2c6fa999eeec3a63cab07d7b.tar.gz chromium_src-920562c8e9bb77cf2c6fa999eeec3a63cab07d7b.tar.bz2 |
Mac: implement drag-and-drop of URLs to tabs/tab strip.
Not yet implemented: visual feedback, dropping multiple URLs/files, perhaps key modifiers (?).
Doing the visual feedback right (making the tabs move around, etc.) will require a fairly hefty work on the tabs code, so I'm splitting up the work.
BUG=25405
TEST=Test dragging and dropping URLs/links, files, plain text (with a possibly-poorly-formed URL), etc. on the tab and on the empty part of the tab strip; web page should load in tab or in new tab, and that tab should be selected.
Review URL: http://codereview.chromium.org/337055
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33890 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.h | 10 | ||||
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.mm | 19 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_controller.h | 7 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_controller.mm | 159 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_view.h | 6 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_view.mm | 23 | ||||
-rw-r--r-- | chrome/browser/cocoa/url_drop_target.h | 46 | ||||
-rw-r--r-- | chrome/browser/cocoa/url_drop_target.mm | 99 | ||||
-rwxr-xr-x | chrome/chrome_browser.gypi | 2 |
9 files changed, 361 insertions, 10 deletions
diff --git a/chrome/browser/cocoa/browser_window_controller.h b/chrome/browser/cocoa/browser_window_controller.h index 68cbb82..72fa734 100644 --- a/chrome/browser/cocoa/browser_window_controller.h +++ b/chrome/browser/cocoa/browser_window_controller.h @@ -18,6 +18,7 @@ #import "chrome/browser/cocoa/bookmark_bar_controller.h" #import "chrome/browser/cocoa/bookmark_bubble_controller.h" #import "chrome/browser/cocoa/browser_command_executor.h" +#import "chrome/browser/cocoa/url_drop_target.h" #import "chrome/browser/cocoa/view_resizer.h" #include "chrome/browser/sync/sync_ui_util.h" #import "third_party/GTM/AppKit/GTMTheme.h" @@ -47,7 +48,8 @@ class TabStripModelObserverBridge; BookmarkBarControllerDelegate, BrowserCommandExecutor, ViewResizer, - GTMThemeDelegate> { + GTMThemeDelegate, + URLDropTargetWindowController> { @private // The ordering of these members is important as it determines the order in // which they are destroyed. |browser_| needs to be destroyed last as most of @@ -183,6 +185,12 @@ class TabStripModelObserverBridge; // tab's sheet queue. - (void)removeConstrainedWindow:(ConstrainedWindowMac*)window; +// Implementation of the |URLDropTargetWindowController| protocol, which is +// needed for URL dropping on the tab strip. +- (void)dropURLs:(NSArray*)urls at:(NSPoint)location; +- (void)indicateDropURLsAt:(NSPoint)location; +- (void)hideDropURLsIndicator; + @end // Methods which are either only for testing, or only public for testing. diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index d4189a3..e3682a9 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -14,6 +14,7 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/dock_info.h" #include "chrome/browser/encoding_menu_controller.h" #include "chrome/browser/location_bar.h" @@ -22,6 +23,7 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" #include "chrome/browser/tabs/tab_strip_model.h" +#import "chrome/browser/cocoa/background_gradient_view.h" #import "chrome/browser/cocoa/bookmark_bar_controller.h" #import "chrome/browser/cocoa/bookmark_editor_controller.h" #import "chrome/browser/cocoa/browser_window_cocoa.h" @@ -41,12 +43,11 @@ #import "chrome/browser/cocoa/tab_strip_controller.h" #import "chrome/browser/cocoa/tab_view.h" #import "chrome/browser/cocoa/toolbar_controller.h" -#import "chrome/browser/browser_theme_provider.h" +#include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/sync_ui_util_mac.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" -#import "chrome/browser/cocoa/background_gradient_view.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #import "third_party/GTM/AppKit/GTMTheme.h" @@ -1399,6 +1400,20 @@ willAnimateFromState:(bookmarks::VisualState)oldState isShrinkingFromZoomed_ = NO; } +// Our implementation of the |URLDropTargetWindowController| protocol just +// reflects everything to the |tabStripController_|. +- (void)dropURLs:(NSArray*)urls at:(NSPoint)location { + [tabStripController_ dropURLs:urls at:location]; +} + +- (void)indicateDropURLsAt:(NSPoint)location { + [tabStripController_ indicateDropURLsAt:location]; +} + +- (void)hideDropURLsIndicator { + [tabStripController_ hideDropURLsIndicator]; +} + @end @implementation BrowserWindowController (Private) diff --git a/chrome/browser/cocoa/tab_strip_controller.h b/chrome/browser/cocoa/tab_strip_controller.h index a3b4048..afc16e1 100644 --- a/chrome/browser/cocoa/tab_strip_controller.h +++ b/chrome/browser/cocoa/tab_strip_controller.h @@ -177,6 +177,13 @@ class ToolbarModel; // Default height for tabs. + (CGFloat)defaultTabHeight; +// Effectively an implementation of the |URLDropTargetWindowController| +// protocol, which the |BrowserWindowController| just reflects to us. This +// needed for dropping URLs on the tab strip. +- (void)dropURLs:(NSArray*)urls at:(NSPoint)location; +- (void)indicateDropURLsAt:(NSPoint)location; +- (void)hideDropURLsIndicator; + // Returns the (lazily created) window sheet controller of this window. Used // for the per-tab sheets. - (GTMWindowSheetController*)sheetController; diff --git a/chrome/browser/cocoa/tab_strip_controller.mm b/chrome/browser/cocoa/tab_strip_controller.mm index 54b908a..8b151c0 100644 --- a/chrome/browser/cocoa/tab_strip_controller.mm +++ b/chrome/browser/cocoa/tab_strip_controller.mm @@ -7,6 +7,7 @@ #import <QuartzCore/QuartzCore.h> #include <limits> +#include <string> #include "app/l10n_util.h" #include "app/resource_bundle.h" @@ -27,6 +28,7 @@ #import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h" #import "chrome/browser/cocoa/tab_view.h" #import "chrome/browser/cocoa/throbber_view.h" +#include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents.h" @@ -40,18 +42,24 @@ NSString* const kTabStripNumberOfTabsChanged = @"kTabStripNumberOfTabsChanged"; +namespace { + // A value to indicate tab layout should use the full available width of the // view. -static const CGFloat kUseFullAvailableWidth = -1.0; +const CGFloat kUseFullAvailableWidth = -1.0; // Left-side indent for tab layout so tabs don't overlap with the window // controls. -static const CGFloat kIndentLeavingSpaceForControls = 64.0; +const CGFloat kIndentLeavingSpaceForControls = 64.0; -// Time (in seconds) in which tabs animate to their final position. -static const NSTimeInterval kAnimationDuration = 0.2; +// The amount by which tabs overlap. +const CGFloat kTabOverlap = 20.0; -namespace { +// The amount by which the new tab button is offset (from the tabs). +const CGFloat kNewTabButtonOffset = 8.0; + +// Time (in seconds) in which tabs animate to their final position. +const NSTimeInterval kAnimationDuration = 0.2; // Helper class for doing NSAnimationContext calls that takes a bool to disable // all the work. Useful for code that wants to conditionally animate. @@ -112,6 +120,9 @@ private: - (NSInteger)numberOfOpenUnpinnedTabs; - (void)mouseMoved:(NSEvent*)event; - (void)setTabTrackingAreasEnabled:(BOOL)enabled; +- (void)droppingURLsAt:(NSPoint)location + givesIndex:(NSInteger*)index + disposition:(WindowOpenDisposition*)disposition; @end // A simple view class that prevents the Window Server from dragging the area @@ -289,10 +300,12 @@ private: userInfo:nil]); [newTabButton_ addTrackingArea:newTabTrackingArea_.get()]; targetFrames_.reset([[NSMutableDictionary alloc] init]); + dragBlockingView_.reset( [[TabStripControllerDragBlockingView alloc] initWithFrame:NSZeroRect controller:self]); [self addSubviewToPermanentList:dragBlockingView_]; + newTabTargetFrame_ = NSMakeRect(0, 0, 0, 0); availableResizeWidth_ = kUseFullAvailableWidth; @@ -325,6 +338,7 @@ private: - (void)dealloc { if (trackingArea_.get()) [tabView_ removeTrackingArea:trackingArea_.get()]; + [newTabButton_ removeTrackingArea:newTabTrackingArea_.get()]; // Invalidate all closing animations so they don't call back to us after // we're gone. @@ -600,8 +614,6 @@ private: if (![tabArray_ count]) return; - const CGFloat kTabOverlap = 20.0; - const CGFloat kNewTabButtonOffset = 8.0; const CGFloat kMaxTabWidth = [TabController maxTabWidth]; const CGFloat kMinTabWidth = [TabController minTabWidth]; const CGFloat kMinSelectedTabWidth = [TabController minSelectedTabWidth]; @@ -1401,6 +1413,139 @@ private: [self setTabTrackingAreasEnabled:mouseInside_]; } +// Get the index and disposition for a potential URL(s) drop given a location +// (in window base coordinates). It considers x coordinate of the given +// location. If it's in the "middle" of a tab, it drops on that tab. If it's to +// the left, it inserts to the left, and similarly for the right. +- (void)droppingURLsAt:(NSPoint)location + givesIndex:(NSInteger*)index + disposition:(WindowOpenDisposition*)disposition { + // Proportion of the tab which is considered the "middle" (and causes things + // to drop on that tab). + const double kMiddleProportion = 0.5; + const double kLRProportion = (1.0 - kMiddleProportion) / 2.0; + + DCHECK(index && disposition); + NSInteger i = 0; + for (TabController* tab in tabArray_.get()) { + NSView* view = [tab view]; + DCHECK([view isKindOfClass:[TabView class]]); + + // Recall that |-[NSView frame]| is in its superview's coordinates, so a + // |TabView|'s frame is in the coordinates of the |TabStripView| (which is + // confusingly called |tabView_|). + NSRect frame = [tabView_ convertRectToBase:[view frame]]; + + // Modify the frame to make it "unoverlapped". + frame.origin.x += kTabOverlap / 2.0; + frame.size.width -= kTabOverlap; + if (frame.size.width < 1.0) + frame.size.width = 1.0; // try to avoid complete failure + + // Drop in a new tab to the left of tab |i|? + if (location.x < (frame.origin.x + kLRProportion * frame.size.width)) { + *index = i; + *disposition = NEW_FOREGROUND_TAB; + return; + } + + // Drop on tab |i|? + if (location.x <= (frame.origin.x + + (1.0 - kLRProportion) * frame.size.width)) { + *index = i; + *disposition = CURRENT_TAB; + return; + } + + // (Dropping in a new tab to the right of tab |i| will be taken care of in + // the next iteration.) + i++; + } + + // If we've made it here, we want to append a new tab to the end. + *index = -1; + *disposition = NEW_FOREGROUND_TAB; +} + +// Drop URLs at the given location. +- (void)dropURLs:(NSArray*)urls at:(NSPoint)location { + if ([urls count] < 1) { + NOTREACHED(); + return; + } + + //TODO(viettrungluu): dropping multiple URLs. + if ([urls count] > 1) + NOTIMPLEMENTED(); + + // Get the first URL and fix it up. + GURL url(URLFixerUpper::FixupURL( + base::SysNSStringToUTF8([urls objectAtIndex:0]), std::string())); + + // Get the index and disposition. + NSInteger index; + WindowOpenDisposition disposition; + [self droppingURLsAt:location + givesIndex:&index + disposition:&disposition]; + + // Either insert a new tab or open in a current tab. + switch (disposition) { + case NEW_FOREGROUND_TAB: + browser_->AddTabWithURL(url, GURL(), PageTransition::TYPED, true, index, + true, NULL); + break; + case CURRENT_TAB: + tabModel_->GetTabContentsAt(index)->OpenURL(url, GURL(), CURRENT_TAB, + PageTransition::TYPED); + tabModel_->SelectTabContentsAt(index, true); + break; + default: + NOTIMPLEMENTED(); + } +} + +// Update (possibly showing) the indicator which indicates where an URL drop +// would happen. +- (void)indicateDropURLsAt:(NSPoint)location { +// TODO(viettrungluu): Make the tabs move around, show an indicator, etc. This +// requires a re-work of the tabs code.... +#if 0 + NSInteger index; + WindowOpenDisposition disposition; + [self droppingURLsAt:location + givesIndex:&index + disposition:&disposition]; + + if (index == -1) { + // Append a tab at the end. + DCHECK(disposition == NEW_FOREGROUND_TAB); + // TODO(viettrungluu): ... + } else { + NSRect overRect = [[[tabArray_ objectAtIndex:index] view] frame]; + switch (disposition) { + case NEW_FOREGROUND_TAB: + // Insert tab (to the left of the given tab). + // TODO(viettrungluu): ... + break; + case CURRENT_TAB: + // Overwrite the given tab. + // TODO(viettrungluu): ... + break; + default: + NOTREACHED(); + } + } + + // TODO(viettrungluu): ... +#endif +} + +// Hide the indicator which indicates where an URL drop would happen. +- (void)hideDropURLsIndicator { +// TODO(viettrungluu): See TODO in |-indicateDropURLsAt:| above. +} + - (GTMWindowSheetController*)sheetController { if (!sheetController_.get()) sheetController_.reset([[GTMWindowSheetController alloc] diff --git a/chrome/browser/cocoa/tab_strip_view.h b/chrome/browser/cocoa/tab_strip_view.h index c93ea8c..29aa0eb 100644 --- a/chrome/browser/cocoa/tab_strip_view.h +++ b/chrome/browser/cocoa/tab_strip_view.h @@ -7,12 +7,18 @@ #import <Cocoa/Cocoa.h> +#include "base/scoped_nsobject.h" +#import "chrome/browser/cocoa/url_drop_target.h" + // A view class that handles rendering the tab strip @interface TabStripView : NSView { @private NSTimeInterval lastMouseUp_; + // Handles being a drag-and-drop target. + scoped_nsobject<URLDropTargetHandler> dropHandler_; + // Weak; the following come from the nib. NSButton* newTabButton_; } diff --git a/chrome/browser/cocoa/tab_strip_view.mm b/chrome/browser/cocoa/tab_strip_view.mm index f2638dc..1dcd746 100644 --- a/chrome/browser/cocoa/tab_strip_view.mm +++ b/chrome/browser/cocoa/tab_strip_view.mm @@ -16,6 +16,9 @@ // Set lastMouseUp_ = -1000.0 so that timestamp-lastMouseUp_ is big unless // lastMouseUp_ has been reset. lastMouseUp_ = -1000.0; + + // Register to be an URL drop target. + dropHandler_.reset([[URLDropTargetHandler alloc] initWithView:self]); } return self; } @@ -72,4 +75,24 @@ lastMouseUp_ = (clickCount == 1) ? timestamp : -1000.0; } +// Required by |URLDropTargetHandler|. +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { + return [dropHandler_ draggingEntered:sender]; +} + +// Required by |URLDropTargetHandler|. +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { + return [dropHandler_ draggingUpdated:sender]; +} + +// Required by |URLDropTargetHandler|. +- (void)draggingExited:(id<NSDraggingInfo>)sender { + return [dropHandler_ draggingExited:sender]; +} + +// Required by |URLDropTargetHandler|. +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { + return [dropHandler_ performDragOperation:sender]; +} + @end diff --git a/chrome/browser/cocoa/url_drop_target.h b/chrome/browser/cocoa/url_drop_target.h new file mode 100644 index 0000000..2745cef --- /dev/null +++ b/chrome/browser/cocoa/url_drop_target.h @@ -0,0 +1,46 @@ +// 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_COCOA_URL_DROP_TARGET_H_ +#define CHROME_BROWSER_COCOA_URL_DROP_TARGET_H_ + +#import <Cocoa/Cocoa.h> + +@interface URLDropTargetHandler : NSObject { + @private + NSView* view_; // weak +} + +// Initialize the given view to accept drops of URLs; this requires the view's +// window's controller to implement the |URLDropTargetWindowController| protocol +// (below). +- (id)initWithView:(NSView*)view; + +// The owner view should implement the following methods by calling the +// |URLDropTargetHandler|'s version, and leave the others to the default +// implementation provided by |NSView|/|NSWindow|. +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender; +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender; +- (void)draggingExited:(id<NSDraggingInfo>)sender; +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender; + +@end // @interface URLDropTargetHandler + +@protocol URLDropTargetWindowController + +// The given URLs (an |NSArray| of |NSString|s) were dropped at the given +// location (location in window base coordinates). +- (void)dropURLs:(NSArray*)urls at:(NSPoint)location; + +// Dragging is in progress over the owner view (at the given location, in window +// base coordinates) and any indicator of location -- e.g., an arrow -- should +// be updated/shown. +- (void)indicateDropURLsAt:(NSPoint)location; + +// Dragging is over, and any indicator should be hidden. +- (void)hideDropURLsIndicator; + +@end // @protocol URLDropTargetWindowController + +#endif // CHROME_BROWSER_COCOA_URL_DROP_TARGET_H_ diff --git a/chrome/browser/cocoa/url_drop_target.mm b/chrome/browser/cocoa/url_drop_target.mm new file mode 100644 index 0000000..6a2031d --- /dev/null +++ b/chrome/browser/cocoa/url_drop_target.mm @@ -0,0 +1,99 @@ +// 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. + +#import "chrome/browser/cocoa/url_drop_target.h" + +#include "base/logging.h" +#import "third_party/mozilla/include/NSPasteboard+Utils.h" + +@interface URLDropTargetHandler(Private) + +// Get the window controller. +- (id<URLDropTargetWindowController>)windowController; + +// Gets the appropriate drag operation given the |NSDraggingInfo|. +- (NSDragOperation)getDragOperation:(id<NSDraggingInfo>)sender; + +// Tell the window controller to hide the drop indicator. +- (void)hideIndicator; + +@end // @interface URLDropTargetHandler(Private) + +@implementation URLDropTargetHandler + +- (id)initWithView:(NSView*)view { + if ((self = [super init])) { + view_ = view; + [view_ registerForDraggedTypes: + [NSArray arrayWithObjects:kWebURLsWithTitlesPboardType, + NSURLPboardType, + NSStringPboardType, + NSFilenamesPboardType, + nil]]; + } + return self; +} + +// The following four methods implement parts of the |NSDraggingDestination| +// protocol, which the owner should "forward" to its |URLDropTargetHandler| +// (us). + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { + return [self getDragOperation:sender]; +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { + NSDragOperation dragOp = [self getDragOperation:sender]; + if (dragOp == NSDragOperationCopy) { + // Just tell the window controller to update the indicator. + [[self windowController] indicateDropURLsAt:[sender draggingLocation]]; + } + return dragOp; +} + +- (void)draggingExited:(id<NSDraggingInfo>)sender { + [self hideIndicator]; +} + +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { + [self hideIndicator]; + + NSPasteboard* pboard = [sender draggingPasteboard]; + if ([pboard containsURLData]) { + NSArray* urls = nil; + NSArray* titles; // discarded + [pboard getURLs:&urls andTitles:&titles]; + + if ([urls count]) { + // Tell the window controller about the dropped URL(s). + [[self windowController] dropURLs:urls at:[sender draggingLocation]]; + return YES; + } + } + + return NO; +} + +@end // @implementation URLDropTargetHandler + +@implementation URLDropTargetHandler(Private) + +- (id<URLDropTargetWindowController>)windowController { + id<URLDropTargetWindowController> controller = + [[view_ window] windowController]; + DCHECK([(id)controller conformsToProtocol: + @protocol(URLDropTargetWindowController)]); + return controller; +} + +- (NSDragOperation)getDragOperation:(id<NSDraggingInfo>)sender { + // Only allow the copy operation. + return [sender draggingSourceOperationMask] & NSDragOperationCopy; +} + +- (void)hideIndicator { + [[self windowController] hideDropURLsIndicator]; +} + +@end // @implementation URLDropTargetHandler(Private) diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 7c403d6..8d4807f 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -519,6 +519,8 @@ 'browser/cocoa/toolbar_view.mm', 'browser/cocoa/ui_localizer.h', 'browser/cocoa/ui_localizer.mm', + 'browser/cocoa/url_drop_target.h', + 'browser/cocoa/url_drop_target.mm', 'browser/cocoa/view_resizer.h', 'browser/cocoa/web_drag_source.h', 'browser/cocoa/web_drag_source.mm', |