| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "chrome/browser/cocoa/web_drag_source.h" |
| 6 |
| 7 #include "base/file_util.h" |
| 8 #include "base/string_util.h" |
| 9 #include "base/sys_string_conversions.h" |
| 10 #include "base/task.h" |
| 11 #include "base/thread.h" |
| 12 #include "chrome/browser/browser_process.h" |
| 13 #include "chrome/browser/cocoa/nsimage_cache.h" |
| 14 #include "chrome/browser/renderer_host/render_view_host.h" |
| 15 #include "chrome/browser/tab_contents/tab_contents.h" |
| 16 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" |
| 17 #include "net/base/file_stream.h" |
| 18 #include "net/base/net_errors.h" |
| 19 #include "net/base/net_util.h" |
| 20 #import "third_party/mozilla/include/NSPasteboard+Utils.h" |
| 21 #include "webkit/glue/webdropdata.h" |
| 22 |
| 23 using base::SysNSStringToUTF8; |
| 24 using base::SysUTF8ToNSString; |
| 25 using base::SysUTF16ToNSString; |
| 26 using net::FileStream; |
| 27 |
| 28 |
| 29 namespace { |
| 30 |
| 31 // Make a drag image from the drop data. |
| 32 // TODO(viettrungluu@gmail.com): Move this somewhere more sensible. |
| 33 NSImage* MakeDragImage(const WebDropData* drop_data) { |
| 34 // TODO(viettrungluu@gmail.com): Just a stub for now. Make it do something. |
| 35 |
| 36 // Default to returning a generic image. |
| 37 return nsimage_cache::ImageNamed(@"nav.pdf"); |
| 38 } |
| 39 |
| 40 // Returns a filename appropriate for the drop data (of form "FILENAME-seq.EXT" |
| 41 // if seq > 0). |
| 42 // TODO(viettrungluu@gmail.com): Refactor to make it common across platforms, |
| 43 // and move it somewhere sensible. |
| 44 FilePath GetFileNameFromDragData( |
| 45 const WebDropData& drop_data, unsigned seq) { |
| 46 // Images without ALT text will only have a file extension so we need to |
| 47 // synthesize one from the provided extension and URL. |
| 48 FilePath file_name([SysUTF16ToNSString(drop_data.file_description_filename) |
| 49 fileSystemRepresentation]); |
| 50 file_name = file_name.BaseName().RemoveExtension(); |
| 51 |
| 52 if (file_name.empty()) { |
| 53 // Retrieve the name from the URL. |
| 54 file_name = FilePath::FromWStringHack( |
| 55 net::GetSuggestedFilename(drop_data.url, "", "", L"")); |
| 56 } |
| 57 |
| 58 file_name = file_name.ReplaceExtension([SysUTF16ToNSString( |
| 59 drop_data.file_extension) fileSystemRepresentation]); |
| 60 |
| 61 if (seq > 0) { |
| 62 file_name = |
| 63 file_name.InsertBeforeExtension(std::string("-")+UintToString(seq)); |
| 64 } |
| 65 |
| 66 return file_name; |
| 67 } |
| 68 |
| 69 // This class's sole task is to write out data for a promised file; the caller |
| 70 // is responsible for opening the file. |
| 71 class PromiseWriterTask : public Task { |
| 72 public: |
| 73 // Assumes ownership of file_stream. |
| 74 PromiseWriterTask(const WebDropData& drop_data, |
| 75 FileStream* file_stream); |
| 76 virtual ~PromiseWriterTask(); |
| 77 virtual void Run(); |
| 78 |
| 79 private: |
| 80 WebDropData drop_data_; |
| 81 |
| 82 // This class takes ownership of file_stream_ and will close and delete it. |
| 83 scoped_ptr<FileStream> file_stream_; |
| 84 }; |
| 85 |
| 86 // Takes the drop data and an open file stream (which it takes ownership of and |
| 87 // will close and delete). |
| 88 PromiseWriterTask::PromiseWriterTask(const WebDropData& drop_data, |
| 89 FileStream* file_stream) : |
| 90 drop_data_(drop_data) { |
| 91 file_stream_.reset(file_stream); |
| 92 DCHECK(file_stream_.get()); |
| 93 } |
| 94 |
| 95 PromiseWriterTask::~PromiseWriterTask() { |
| 96 DCHECK(file_stream_.get()); |
| 97 if (file_stream_.get()) |
| 98 file_stream_->Close(); |
| 99 } |
| 100 |
| 101 void PromiseWriterTask::Run() { |
| 102 CHECK(file_stream_.get()); |
| 103 file_stream_->Write(drop_data_.file_contents.data(), |
| 104 drop_data_.file_contents.length(), |
| 105 NULL); |
| 106 |
| 107 // Let our destructor take care of business. |
| 108 } |
| 109 |
| 110 } // namespace |
| 111 |
| 112 |
| 113 @interface WebDragSource(Private) |
| 114 |
| 115 - (void)fillPasteboard; |
| 116 - (NSImage*)dragImage; |
| 117 |
| 118 @end // @interface WebDragSource(Private) |
| 119 |
| 120 |
| 121 @implementation WebDragSource |
| 122 |
| 123 - (id)initWithContentsView:(TabContentsViewCocoa*)contentsView |
| 124 dropData:(const WebDropData*)dropData |
| 125 pasteboard:(NSPasteboard*)pboard { |
| 126 if ((self = [super init])) { |
| 127 contentsView_ = contentsView; |
| 128 DCHECK(contentsView_); |
| 129 |
| 130 dropData_.reset(new WebDropData(*dropData)); |
| 131 DCHECK(dropData_.get()); |
| 132 |
| 133 pasteboard_.reset([pboard retain]); |
| 134 DCHECK(pasteboard_.get()); |
| 135 |
| 136 [self fillPasteboard]; |
| 137 } |
| 138 |
| 139 return self; |
| 140 } |
| 141 |
| 142 - (void)lazyWriteToPasteboard:(NSPasteboard*)pboard forType:(NSString*)type { |
| 143 // Be extra paranoid; avoid crashing. |
| 144 if (!dropData_.get()) { |
| 145 NOTREACHED() << "No drag-and-drop data available for lazy write."; |
| 146 return; |
| 147 } |
| 148 |
| 149 // HTML. |
| 150 if ([type isEqualToString:NSHTMLPboardType]) { |
| 151 DCHECK(!dropData_->text_html.empty()); |
| 152 [pboard setString:SysUTF16ToNSString(dropData_->text_html) |
| 153 forType:NSHTMLPboardType]; |
| 154 |
| 155 // URL. |
| 156 } else if ([type isEqualToString:NSURLPboardType]) { |
| 157 DCHECK(dropData_->url.is_valid()); |
| 158 [pboard setURLs:[NSArray |
| 159 arrayWithObject:SysUTF8ToNSString(dropData_->url.spec())] |
| 160 withTitles:[NSArray arrayWithObject: |
| 161 SysUTF16ToNSString(dropData_->url_title)]]; |
| 162 |
| 163 // File contents. |
| 164 } else if ([type isEqualToString:NSFileContentsPboardType] || |
| 165 [type isEqualToString:NSCreateFileContentsPboardType( |
| 166 SysUTF16ToNSString(dropData_->file_extension))]) { |
| 167 // TODO(viettrungluu@gmail.com): find something which is known to accept |
| 168 // NSFileContentsPboardType to check that this actually works! |
| 169 scoped_nsobject<NSFileWrapper> file_wrapper( |
| 170 [[NSFileWrapper alloc] initRegularFileWithContents:[NSData |
| 171 dataWithBytes:dropData_->file_contents.data() |
| 172 length:dropData_->file_contents.length()]]); |
| 173 [file_wrapper setPreferredFilename:SysUTF8ToNSString( |
| 174 GetFileNameFromDragData(*dropData_, 0).value())]; |
| 175 [pboard writeFileWrapper:file_wrapper]; |
| 176 |
| 177 // TIFF. |
| 178 } else if ([type isEqualToString:NSTIFFPboardType]) { |
| 179 // FIXME(viettrungluu@gmail.com): This is a bit odd since we rely on Cocoa |
| 180 // to render our image into a TIFF. This is also suboptimal since this is |
| 181 // all done synchronously. I'm not sure there's much we can easily do about |
| 182 // it. |
| 183 scoped_nsobject<NSImage> image( |
| 184 [[NSImage alloc] initWithData:[NSData |
| 185 dataWithBytes:dropData_->file_contents.data() |
| 186 length:dropData_->file_contents.length()]]); |
| 187 [pboard setData:[image TIFFRepresentation] forType:NSTIFFPboardType]; |
| 188 |
| 189 // Plain text. |
| 190 } else if ([type isEqualToString:NSStringPboardType]) { |
| 191 DCHECK(!dropData_->plain_text.empty()); |
| 192 [pboard setString:SysUTF16ToNSString(dropData_->plain_text) |
| 193 forType:NSStringPboardType]; |
| 194 |
| 195 // Oops! |
| 196 } else { |
| 197 NOTREACHED() << "Asked for a drag pasteboard type we didn't offer."; |
| 198 } |
| 199 } |
| 200 |
| 201 - (void)startDrag { |
| 202 NSEvent* currentEvent = [NSApp currentEvent]; |
| 203 [contentsView_ dragImage:[self dragImage] |
| 204 at:[contentsView_ |
| 205 convertPoint:[currentEvent locationInWindow] |
| 206 fromView:nil] |
| 207 offset:NSZeroSize |
| 208 event:currentEvent |
| 209 pasteboard:pasteboard_ |
| 210 source:contentsView_ |
| 211 slideBack:YES]; |
| 212 } |
| 213 |
| 214 - (void)endDragAt:(NSPoint)screenPoint |
| 215 isCancelled:(BOOL)cancelled { |
| 216 RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host(); |
| 217 if (rvh) { |
| 218 rvh->DragSourceSystemDragEnded(); |
| 219 |
| 220 // Convert |screenPoint| to view coordinates and flip it. |
| 221 NSPoint localPoint = [contentsView_ convertPointFromBase:screenPoint]; |
| 222 NSRect viewFrame = [contentsView_ frame]; |
| 223 localPoint.y = viewFrame.size.height - localPoint.y; |
| 224 // Flip |screenPoint|. |
| 225 NSRect screenFrame = [[[contentsView_ window] screen] frame]; |
| 226 screenPoint.y = screenFrame.size.height - screenPoint.y; |
| 227 |
| 228 if (cancelled) { |
| 229 rvh->DragSourceCancelledAt(localPoint.x, localPoint.y, |
| 230 screenPoint.x, screenPoint.y); |
| 231 } else { |
| 232 rvh->DragSourceEndedAt(localPoint.x, localPoint.y, |
| 233 screenPoint.x, screenPoint.y); |
| 234 } |
| 235 } |
| 236 |
| 237 // Make sure the pasteboard owner isn't us. |
| 238 [pasteboard_ declareTypes:[NSArray array] owner:nil]; |
| 239 } |
| 240 |
| 241 - (void)moveDragTo:(NSPoint)screenPoint { |
| 242 RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host(); |
| 243 if (rvh) { |
| 244 // Convert |screenPoint| to view coordinates and flip it. |
| 245 NSPoint localPoint = [contentsView_ convertPointFromBase:screenPoint]; |
| 246 NSRect viewFrame = [contentsView_ frame]; |
| 247 localPoint.y = viewFrame.size.height - localPoint.y; |
| 248 // Flip |screenPoint|. |
| 249 NSRect screenFrame = [[[contentsView_ window] screen] frame]; |
| 250 screenPoint.y = screenFrame.size.height - screenPoint.y; |
| 251 |
| 252 rvh->DragSourceMovedTo(localPoint.x, localPoint.y, |
| 253 screenPoint.x, screenPoint.y); |
| 254 } |
| 255 } |
| 256 |
| 257 - (NSString*)dragPromisedFileTo:(NSString*)path { |
| 258 // Be extra paranoid; avoid crashing. |
| 259 if (!dropData_.get()) { |
| 260 NOTREACHED() << "No drag-and-drop data available for promised file."; |
| 261 return nil; |
| 262 } |
| 263 |
| 264 FileStream* file_stream = new FileStream; |
| 265 DCHECK(file_stream); |
| 266 if (!file_stream) |
| 267 return nil; |
| 268 |
| 269 FilePath path_name(SysNSStringToUTF8(path)); |
| 270 FilePath file_name; |
| 271 unsigned seq; |
| 272 const unsigned k_max_seq = 99; |
| 273 for (seq = 0; seq <= k_max_seq; seq++) { |
| 274 file_name = GetFileNameFromDragData(*dropData_, seq); |
| 275 FilePath file_path = path_name.Append(file_name); |
| 276 |
| 277 // Explicitly (and redundantly check) for file -- despite the fact that our |
| 278 // open won't overwrite -- just to avoid log spew. |
| 279 if (!file_util::PathExists(file_path) && |
| 280 file_stream->Open(path_name.Append(file_name), |
| 281 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE) == net::OK) |
| 282 break; |
| 283 } |
| 284 if (seq > k_max_seq) { |
| 285 delete file_stream; |
| 286 return nil; |
| 287 } |
| 288 |
| 289 // The writer will take care of closing and deletion. |
| 290 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, |
| 291 new PromiseWriterTask(*dropData_, file_stream)); |
| 292 |
| 293 // Once we've created the file, we should return the file name. |
| 294 return SysUTF8ToNSString(file_name.value()); |
| 295 } |
| 296 |
| 297 @end // @implementation WebDragSource |
| 298 |
| 299 |
| 300 @implementation WebDragSource (Private) |
| 301 |
| 302 - (void)fillPasteboard { |
| 303 DCHECK(pasteboard_.get()); |
| 304 |
| 305 [pasteboard_ declareTypes:[NSArray array] owner:contentsView_]; |
| 306 |
| 307 // HTML. |
| 308 if (!dropData_->text_html.empty()) |
| 309 [pasteboard_ addTypes:[NSArray arrayWithObject:NSHTMLPboardType] |
| 310 owner:contentsView_]; |
| 311 |
| 312 // URL. |
| 313 if (dropData_->url.is_valid()) |
| 314 [pasteboard_ addTypes:[NSArray arrayWithObject:NSURLPboardType] |
| 315 owner:contentsView_]; |
| 316 |
| 317 // File. |
| 318 if (!dropData_->file_contents.empty()) { |
| 319 NSString* file_ext = SysUTF16ToNSString(dropData_->file_extension); |
| 320 |
| 321 // File contents (with and without specific type), file (HFS) promise, TIFF. |
| 322 // TODO(viettrungluu@gmail.com): others? |
| 323 [pasteboard_ addTypes:[NSArray arrayWithObjects: |
| 324 NSFileContentsPboardType, |
| 325 NSCreateFileContentsPboardType(file_ext), |
| 326 NSFilesPromisePboardType, |
| 327 NSTIFFPboardType, |
| 328 nil] |
| 329 owner:contentsView_]; |
| 330 |
| 331 // For the file promise, we need to specify the extension. |
| 332 [pasteboard_ setPropertyList:[NSArray arrayWithObject:file_ext] |
| 333 forType:NSFilesPromisePboardType]; |
| 334 } |
| 335 |
| 336 // Plain text. |
| 337 if (!dropData_->plain_text.empty()) |
| 338 [pasteboard_ addTypes:[NSArray arrayWithObject:NSStringPboardType] |
| 339 owner:contentsView_]; |
| 340 } |
| 341 |
| 342 - (NSImage*)dragImage { |
| 343 return MakeDragImage(dropData_.get()); |
| 344 } |
| 345 |
| 346 @end // @implementation WebDragSource (Private) |
| OLD | NEW |