summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authorthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-31 20:53:47 +0000
committerthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-31 20:53:47 +0000
commit7418edfa1ad3eada8a12a225579fae3f45f382d5 (patch)
tree5a0387088ef18bc9a261853964fdf4a801976c08 /chrome/browser/cocoa
parentbca8899ca1e49c182dcfcf17b6b94202e67f5e4b (diff)
downloadchromium_src-7418edfa1ad3eada8a12a225579fae3f45f382d5.zip
chromium_src-7418edfa1ad3eada8a12a225579fae3f45f382d5.tar.gz
chromium_src-7418edfa1ad3eada8a12a225579fae3f45f382d5.tar.bz2
Enable dragging of images to desktop (Finder), Preview, etc. (on Mac).
This hooks up drag-and-drop of file promises, lazy writing to the drag pasteboard, drag-sourcing of TIFF images (via Cocoa) and file contents. Patch Set 5 improvements: Adds asynchronous writing of promised files. Patch Set 4 improvements: Big refactoring -- drag source stuff is now handled by the WebDragSource (Cocoa) object, with messages proxied through the TabContentsViewCocoa object. The WebDragSource object carries a weak reference to the TCVC, owns the WebDropData, and keeps track of the drag pasteboard types/promises. Patch Set 3 improvements over Patch Set 2: It shouldn't crash anymore. Made drop_data_ reference counted, in anticipation of asynchronous file writing. TODO #1: Testing. Still need a unit test, maybe. Should make sure that dragging by file contents actually works. TODO #2 (in some other patch): Refactor some of the WebDropData extraction code out, e.g., file name extraction should be made common with other platforms. TODO #3 (in some other patch): We really should make WebDropData cheaper to copy around and retain. I'm not convinced it's a good idea to push out the entire thing over IPC, especially since the data pushed could be very big and may not even be used. BUG=15640 TEST=drag images to various applications Patch by viettrungluu@gmail.com git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22187 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/web_drag_source.h57
-rw-r--r--chrome/browser/cocoa/web_drag_source.mm346
2 files changed, 403 insertions, 0 deletions
diff --git a/chrome/browser/cocoa/web_drag_source.h b/chrome/browser/cocoa/web_drag_source.h
new file mode 100644
index 0000000..6532878
--- /dev/null
+++ b/chrome/browser/cocoa/web_drag_source.h
@@ -0,0 +1,57 @@
+// 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 <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#include "base/scoped_ptr.h"
+
+struct WebDropData;
+@class TabContentsViewCocoa;
+
+// A class that handles tracking and event processing for a drag and drop
+// originating from the content area.
+@interface WebDragSource : NSObject {
+ @private
+ // Our tab. Weak reference (owns or co-owns us).
+ TabContentsViewCocoa* contentsView_;
+
+ // Our drop data. Should only be initialized once.
+ scoped_ptr<WebDropData> dropData_;
+
+ // Our pasteboard.
+ scoped_nsobject<NSPasteboard> pasteboard_;
+}
+
+// Initialize a WebDragSource object for a drag (originating on the given
+// contentsView and with the given dropData and pboard). Fill the pasteboard
+// with data types appropriate for dropData.
+- (id)initWithContentsView:(TabContentsViewCocoa*)contentsView
+ dropData:(const WebDropData*)dropData
+ pasteboard:(NSPasteboard*)pboard;
+
+// Call when asked to do a lazy write to the pasteboard; hook up to
+// -pasteboard:provideDataForType: (on the contentsView).
+- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard
+ forType:(NSString*)type;
+
+// Start the drag (on the originally provided contentsView); can do this right
+// after -initWithContentsView:....
+- (void)startDrag;
+
+// End the drag and clear the pasteboard; hook up to
+// -draggedImage:endedAt:operation:.
+- (void)endDragAt:(NSPoint)screenPoint
+ isCancelled:(BOOL)cancelled;
+
+// Drag moved; hook up to -draggedImage:movedTo:.
+- (void)moveDragTo:(NSPoint)screenPoint;
+
+// Call to drag a promised file to the given path (should be called before
+// -endDragAt:...); hook up to -namesOfPromisedFilesDroppedAtDestination:.
+// Returns the file name (not including path) of the file deposited (or which
+// will be deposited).
+- (NSString*)dragPromisedFileTo:(NSString*)path;
+
+@end
diff --git a/chrome/browser/cocoa/web_drag_source.mm b/chrome/browser/cocoa/web_drag_source.mm
new file mode 100644
index 0000000..6fd44a7
--- /dev/null
+++ b/chrome/browser/cocoa/web_drag_source.mm
@@ -0,0 +1,346 @@
+// 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/web_drag_source.h"
+
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/cocoa/nsimage_cache.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_view_mac.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#import "third_party/mozilla/include/NSPasteboard+Utils.h"
+#include "webkit/glue/webdropdata.h"
+
+using base::SysNSStringToUTF8;
+using base::SysUTF8ToNSString;
+using base::SysUTF16ToNSString;
+using net::FileStream;
+
+
+namespace {
+
+// Make a drag image from the drop data.
+// TODO(viettrungluu@gmail.com): Move this somewhere more sensible.
+NSImage* MakeDragImage(const WebDropData* drop_data) {
+ // TODO(viettrungluu@gmail.com): Just a stub for now. Make it do something.
+
+ // Default to returning a generic image.
+ return nsimage_cache::ImageNamed(@"nav.pdf");
+}
+
+// Returns a filename appropriate for the drop data (of form "FILENAME-seq.EXT"
+// if seq > 0).
+// TODO(viettrungluu@gmail.com): Refactor to make it common across platforms,
+// and move it somewhere sensible.
+FilePath GetFileNameFromDragData(
+ const WebDropData& drop_data, unsigned seq) {
+ // Images without ALT text will only have a file extension so we need to
+ // synthesize one from the provided extension and URL.
+ FilePath file_name([SysUTF16ToNSString(drop_data.file_description_filename)
+ fileSystemRepresentation]);
+ file_name = file_name.BaseName().RemoveExtension();
+
+ if (file_name.empty()) {
+ // Retrieve the name from the URL.
+ file_name = FilePath::FromWStringHack(
+ net::GetSuggestedFilename(drop_data.url, "", "", L""));
+ }
+
+ file_name = file_name.ReplaceExtension([SysUTF16ToNSString(
+ drop_data.file_extension) fileSystemRepresentation]);
+
+ if (seq > 0) {
+ file_name =
+ file_name.InsertBeforeExtension(std::string("-")+UintToString(seq));
+ }
+
+ return file_name;
+}
+
+// This class's sole task is to write out data for a promised file; the caller
+// is responsible for opening the file.
+class PromiseWriterTask : public Task {
+ public:
+ // Assumes ownership of file_stream.
+ PromiseWriterTask(const WebDropData& drop_data,
+ FileStream* file_stream);
+ virtual ~PromiseWriterTask();
+ virtual void Run();
+
+ private:
+ WebDropData drop_data_;
+
+ // This class takes ownership of file_stream_ and will close and delete it.
+ scoped_ptr<FileStream> file_stream_;
+};
+
+// Takes the drop data and an open file stream (which it takes ownership of and
+// will close and delete).
+PromiseWriterTask::PromiseWriterTask(const WebDropData& drop_data,
+ FileStream* file_stream) :
+ drop_data_(drop_data) {
+ file_stream_.reset(file_stream);
+ DCHECK(file_stream_.get());
+}
+
+PromiseWriterTask::~PromiseWriterTask() {
+ DCHECK(file_stream_.get());
+ if (file_stream_.get())
+ file_stream_->Close();
+}
+
+void PromiseWriterTask::Run() {
+ CHECK(file_stream_.get());
+ file_stream_->Write(drop_data_.file_contents.data(),
+ drop_data_.file_contents.length(),
+ NULL);
+
+ // Let our destructor take care of business.
+}
+
+} // namespace
+
+
+@interface WebDragSource(Private)
+
+- (void)fillPasteboard;
+- (NSImage*)dragImage;
+
+@end // @interface WebDragSource(Private)
+
+
+@implementation WebDragSource
+
+- (id)initWithContentsView:(TabContentsViewCocoa*)contentsView
+ dropData:(const WebDropData*)dropData
+ pasteboard:(NSPasteboard*)pboard {
+ if ((self = [super init])) {
+ contentsView_ = contentsView;
+ DCHECK(contentsView_);
+
+ dropData_.reset(new WebDropData(*dropData));
+ DCHECK(dropData_.get());
+
+ pasteboard_.reset([pboard retain]);
+ DCHECK(pasteboard_.get());
+
+ [self fillPasteboard];
+ }
+
+ return self;
+}
+
+- (void)lazyWriteToPasteboard:(NSPasteboard*)pboard forType:(NSString*)type {
+ // Be extra paranoid; avoid crashing.
+ if (!dropData_.get()) {
+ NOTREACHED() << "No drag-and-drop data available for lazy write.";
+ return;
+ }
+
+ // HTML.
+ if ([type isEqualToString:NSHTMLPboardType]) {
+ DCHECK(!dropData_->text_html.empty());
+ [pboard setString:SysUTF16ToNSString(dropData_->text_html)
+ forType:NSHTMLPboardType];
+
+ // URL.
+ } else if ([type isEqualToString:NSURLPboardType]) {
+ DCHECK(dropData_->url.is_valid());
+ [pboard setURLs:[NSArray
+ arrayWithObject:SysUTF8ToNSString(dropData_->url.spec())]
+ withTitles:[NSArray arrayWithObject:
+ SysUTF16ToNSString(dropData_->url_title)]];
+
+ // File contents.
+ } else if ([type isEqualToString:NSFileContentsPboardType] ||
+ [type isEqualToString:NSCreateFileContentsPboardType(
+ SysUTF16ToNSString(dropData_->file_extension))]) {
+ // TODO(viettrungluu@gmail.com): find something which is known to accept
+ // NSFileContentsPboardType to check that this actually works!
+ scoped_nsobject<NSFileWrapper> file_wrapper(
+ [[NSFileWrapper alloc] initRegularFileWithContents:[NSData
+ dataWithBytes:dropData_->file_contents.data()
+ length:dropData_->file_contents.length()]]);
+ [file_wrapper setPreferredFilename:SysUTF8ToNSString(
+ GetFileNameFromDragData(*dropData_, 0).value())];
+ [pboard writeFileWrapper:file_wrapper];
+
+ // TIFF.
+ } else if ([type isEqualToString:NSTIFFPboardType]) {
+ // FIXME(viettrungluu@gmail.com): This is a bit odd since we rely on Cocoa
+ // to render our image into a TIFF. This is also suboptimal since this is
+ // all done synchronously. I'm not sure there's much we can easily do about
+ // it.
+ scoped_nsobject<NSImage> image(
+ [[NSImage alloc] initWithData:[NSData
+ dataWithBytes:dropData_->file_contents.data()
+ length:dropData_->file_contents.length()]]);
+ [pboard setData:[image TIFFRepresentation] forType:NSTIFFPboardType];
+
+ // Plain text.
+ } else if ([type isEqualToString:NSStringPboardType]) {
+ DCHECK(!dropData_->plain_text.empty());
+ [pboard setString:SysUTF16ToNSString(dropData_->plain_text)
+ forType:NSStringPboardType];
+
+ // Oops!
+ } else {
+ NOTREACHED() << "Asked for a drag pasteboard type we didn't offer.";
+ }
+}
+
+- (void)startDrag {
+ NSEvent* currentEvent = [NSApp currentEvent];
+ [contentsView_ dragImage:[self dragImage]
+ at:[contentsView_
+ convertPoint:[currentEvent locationInWindow]
+ fromView:nil]
+ offset:NSZeroSize
+ event:currentEvent
+ pasteboard:pasteboard_
+ source:contentsView_
+ slideBack:YES];
+}
+
+- (void)endDragAt:(NSPoint)screenPoint
+ isCancelled:(BOOL)cancelled {
+ RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host();
+ if (rvh) {
+ rvh->DragSourceSystemDragEnded();
+
+ // Convert |screenPoint| to view coordinates and flip it.
+ NSPoint localPoint = [contentsView_ convertPointFromBase:screenPoint];
+ NSRect viewFrame = [contentsView_ frame];
+ localPoint.y = viewFrame.size.height - localPoint.y;
+ // Flip |screenPoint|.
+ NSRect screenFrame = [[[contentsView_ window] screen] frame];
+ screenPoint.y = screenFrame.size.height - screenPoint.y;
+
+ if (cancelled) {
+ rvh->DragSourceCancelledAt(localPoint.x, localPoint.y,
+ screenPoint.x, screenPoint.y);
+ } else {
+ rvh->DragSourceEndedAt(localPoint.x, localPoint.y,
+ screenPoint.x, screenPoint.y);
+ }
+ }
+
+ // Make sure the pasteboard owner isn't us.
+ [pasteboard_ declareTypes:[NSArray array] owner:nil];
+}
+
+- (void)moveDragTo:(NSPoint)screenPoint {
+ RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host();
+ if (rvh) {
+ // Convert |screenPoint| to view coordinates and flip it.
+ NSPoint localPoint = [contentsView_ convertPointFromBase:screenPoint];
+ NSRect viewFrame = [contentsView_ frame];
+ localPoint.y = viewFrame.size.height - localPoint.y;
+ // Flip |screenPoint|.
+ NSRect screenFrame = [[[contentsView_ window] screen] frame];
+ screenPoint.y = screenFrame.size.height - screenPoint.y;
+
+ rvh->DragSourceMovedTo(localPoint.x, localPoint.y,
+ screenPoint.x, screenPoint.y);
+ }
+}
+
+- (NSString*)dragPromisedFileTo:(NSString*)path {
+ // Be extra paranoid; avoid crashing.
+ if (!dropData_.get()) {
+ NOTREACHED() << "No drag-and-drop data available for promised file.";
+ return nil;
+ }
+
+ FileStream* file_stream = new FileStream;
+ DCHECK(file_stream);
+ if (!file_stream)
+ return nil;
+
+ FilePath path_name(SysNSStringToUTF8(path));
+ FilePath file_name;
+ unsigned seq;
+ const unsigned k_max_seq = 99;
+ for (seq = 0; seq <= k_max_seq; seq++) {
+ file_name = GetFileNameFromDragData(*dropData_, seq);
+ FilePath file_path = path_name.Append(file_name);
+
+ // Explicitly (and redundantly check) for file -- despite the fact that our
+ // open won't overwrite -- just to avoid log spew.
+ if (!file_util::PathExists(file_path) &&
+ file_stream->Open(path_name.Append(file_name),
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE) == net::OK)
+ break;
+ }
+ if (seq > k_max_seq) {
+ delete file_stream;
+ return nil;
+ }
+
+ // The writer will take care of closing and deletion.
+ g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
+ new PromiseWriterTask(*dropData_, file_stream));
+
+ // Once we've created the file, we should return the file name.
+ return SysUTF8ToNSString(file_name.value());
+}
+
+@end // @implementation WebDragSource
+
+
+@implementation WebDragSource (Private)
+
+- (void)fillPasteboard {
+ DCHECK(pasteboard_.get());
+
+ [pasteboard_ declareTypes:[NSArray array] owner:contentsView_];
+
+ // HTML.
+ if (!dropData_->text_html.empty())
+ [pasteboard_ addTypes:[NSArray arrayWithObject:NSHTMLPboardType]
+ owner:contentsView_];
+
+ // URL.
+ if (dropData_->url.is_valid())
+ [pasteboard_ addTypes:[NSArray arrayWithObject:NSURLPboardType]
+ owner:contentsView_];
+
+ // File.
+ if (!dropData_->file_contents.empty()) {
+ NSString* file_ext = SysUTF16ToNSString(dropData_->file_extension);
+
+ // File contents (with and without specific type), file (HFS) promise, TIFF.
+ // TODO(viettrungluu@gmail.com): others?
+ [pasteboard_ addTypes:[NSArray arrayWithObjects:
+ NSFileContentsPboardType,
+ NSCreateFileContentsPboardType(file_ext),
+ NSFilesPromisePboardType,
+ NSTIFFPboardType,
+ nil]
+ owner:contentsView_];
+
+ // For the file promise, we need to specify the extension.
+ [pasteboard_ setPropertyList:[NSArray arrayWithObject:file_ext]
+ forType:NSFilesPromisePboardType];
+ }
+
+ // Plain text.
+ if (!dropData_->plain_text.empty())
+ [pasteboard_ addTypes:[NSArray arrayWithObject:NSStringPboardType]
+ owner:contentsView_];
+}
+
+- (NSImage*)dragImage {
+ return MakeDragImage(dropData_.get());
+}
+
+@end // @implementation WebDragSource (Private)