Browse Source

Merge pull request #6333 from electron/drag-item

Add `webContents.startDrag(item)` API
Cheng Zhao 8 years ago
parent
commit
83ae14f2ed

+ 31 - 0
atom/browser/api/atom_api_web_contents.cc

@@ -17,6 +17,7 @@
 #include "atom/browser/lib/bluetooth_chooser.h"
 #include "atom/browser/native_window.h"
 #include "atom/browser/net/atom_network_delegate.h"
+#include "atom/browser/ui/drag_util.h"
 #include "atom/browser/web_contents_permission_helper.h"
 #include "atom/browser/web_contents_preferences.h"
 #include "atom/browser/web_view_guest_delegate.h"
@@ -1205,6 +1206,35 @@ void WebContents::EndFrameSubscription() {
     view->EndFrameSubscription();
 }
 
+void WebContents::StartDrag(const mate::Dictionary& item,
+                            mate::Arguments* args) {
+  base::FilePath file;
+  std::vector<base::FilePath> files;
+  if (!item.Get("files", &files) && item.Get("file", &file)) {
+    files.push_back(file);
+  }
+
+  mate::Handle<NativeImage> icon;
+  if (!item.Get("icon", &icon) && !file.empty()) {
+    // TODO(zcbenz): Set default icon from file.
+  }
+
+  // Error checking.
+  if (icon.IsEmpty()) {
+    args->ThrowError("icon must be set");
+    return;
+  }
+
+  // Start dragging.
+  if (!files.empty()) {
+    base::MessageLoop::ScopedNestableTaskAllower allow(
+        base::MessageLoop::current());
+    DragFileItems(files, icon->image(), web_contents()->GetNativeView());
+  } else {
+    args->ThrowError("There is nothing to drag");
+  }
+}
+
 void WebContents::OnCursorChange(const content::WebCursor& cursor) {
   content::WebCursor::CursorInfo info;
   cursor.GetCursorInfo(&info);
@@ -1324,6 +1354,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("beginFrameSubscription",
                  &WebContents::BeginFrameSubscription)
       .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription)
+      .SetMethod("startDrag", &WebContents::StartDrag)
       .SetMethod("setSize", &WebContents::SetSize)
       .SetMethod("isGuest", &WebContents::IsGuest)
       .SetMethod("getType", &WebContents::GetType)

+ 3 - 0
atom/browser/api/atom_api_web_contents.h

@@ -142,6 +142,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
   void BeginFrameSubscription(mate::Arguments* args);
   void EndFrameSubscription();
 
+  // Dragging native items.
+  void StartDrag(const mate::Dictionary& item, mate::Arguments* args);
+
   // Methods for creating <webview>.
   void SetSize(const SetSizeParams& params);
   bool IsGuest() const;

+ 24 - 0
atom/browser/ui/drag_util.h

@@ -0,0 +1,24 @@
+// Copyright (c) 2016 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_UI_DRAG_UTIL_H_
+#define ATOM_BROWSER_UI_DRAG_UTIL_H_
+
+#include <vector>
+
+#include "ui/gfx/image/image.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace atom {
+
+void DragFileItems(const std::vector<base::FilePath>& files,
+                   const gfx::Image& icon,
+                   gfx::NativeView view);
+
+}  // namespace atom
+
+#endif  // ATOM_BROWSER_UI_DRAG_UTIL_H_

+ 58 - 0
atom/browser/ui/drag_util_mac.mm

@@ -0,0 +1,58 @@
+// Copyright (c) 2016 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "atom/browser/ui/drag_util.h"
+#include "base/files/file_path.h"
+#include "base/strings/sys_string_conversions.h"
+
+namespace atom {
+
+namespace {
+
+// Write information about the file being dragged to the pasteboard.
+void AddFilesToPasteboard(NSPasteboard* pasteboard,
+                          const std::vector<base::FilePath>& files) {
+  NSMutableArray* fileList = [NSMutableArray array];
+  for (const base::FilePath& file : files)
+    [fileList addObject:base::SysUTF8ToNSString(file.value())];
+  [pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
+                     owner:nil];
+  [pasteboard setPropertyList:fileList forType:NSFilenamesPboardType];
+}
+
+}  // namespace
+
+void DragFileItems(const std::vector<base::FilePath>& files,
+                   const gfx::Image& icon,
+                   gfx::NativeView view) {
+  NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+  AddFilesToPasteboard(pasteboard, files);
+
+  // Synthesize a drag event, since we don't have access to the actual event
+  // that initiated a drag (possibly consumed by the Web UI, for example).
+  NSPoint position = [[view window] mouseLocationOutsideOfEventStream];
+  NSTimeInterval eventTime = [[NSApp currentEvent] timestamp];
+  NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
+                                          location:position
+                                     modifierFlags:NSLeftMouseDraggedMask
+                                         timestamp:eventTime
+                                      windowNumber:[[view window] windowNumber]
+                                           context:nil
+                                       eventNumber:0
+                                        clickCount:1
+                                          pressure:1.0];
+
+  // Run the drag operation.
+  [[view window] dragImage:icon.ToNSImage()
+                        at:position
+                    offset:NSZeroSize
+                     event:dragEvent
+                pasteboard:pasteboard
+                    source:view
+                 slideBack:YES];
+}
+
+}  // namespace atom

+ 48 - 0
atom/browser/ui/drag_util_views.cc

@@ -0,0 +1,48 @@
+// Copyright (c) 2016 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "atom/browser/ui/drag_util.h"
+
+#include "ui/aura/window.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/drag_utils.h"
+#include "ui/base/dragdrop/file_info.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+#include "ui/wm/public/drag_drop_client.h"
+
+namespace atom {
+
+void DragFileItems(const std::vector<base::FilePath>& files,
+                   const gfx::Image& icon,
+                   gfx::NativeView view) {
+  // Set up our OLE machinery
+  ui::OSExchangeData data;
+
+  drag_utils::CreateDragImageForFile(files[0], icon.AsImageSkia(), &data);
+
+  std::vector<ui::FileInfo> file_infos;
+  for (const base::FilePath& file : files) {
+    file_infos.push_back(ui::FileInfo(file, base::FilePath()));
+  }
+  data.SetFilenames(file_infos);
+
+  aura::Window* root_window = view->GetRootWindow();
+  if (!root_window || !aura::client::GetDragDropClient(root_window))
+    return;
+
+  gfx::Point location = gfx::Screen::GetScreen()->GetCursorScreenPoint();
+  // TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below.
+  aura::client::GetDragDropClient(root_window)->StartDragAndDrop(
+      data,
+      root_window,
+      view,
+      location,
+      ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK,
+      ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
+}
+
+}  // namespace atom

+ 10 - 0
docs/api/web-contents.md

@@ -943,6 +943,16 @@ defaults to `false`.
 
 End subscribing for frame presentation events.
 
+### `webContents.startDrag(item)`
+
+* `item` object
+  * `file` String
+  * `icon` [NativeImage](native-image.md)
+
+Sets the `item` as dragging item for current drag-drop operation, `file` is the
+absolute path of the file to be dragged, and `icon` is the image showing under
+the cursor when dragging.
+
 ### `webContents.savePage(fullPath, saveType, callback)`
 
 * `fullPath` String - The full file path.

+ 29 - 0
docs/tutorial/desktop-environment-integration.md

@@ -321,6 +321,35 @@ win.setRepresentedFilename('/etc/passwd');
 win.setDocumentEdited(true);
 ```
 
+## Dragging files out of the window
+
+For certain kinds of apps that manipulate on files, it is important to be able
+to drag files from Electron to other apps. To implement this feature in your
+app, you need to call `webContents.startDrag(item)` API on `ondragstart` event.
+
+In web page:
+
+```html
+<a href="#" id="drag">item</a>
+<script type="text/javascript" charset="utf-8">
+  document.getElementById('drag').ondragstart = (event) => {
+    event.preventDefault()
+    ipcRenderer.send('ondragstart', '/path/to/item')
+  }
+</script>
+```
+
+In the main process:
+
+```javascript
+ipcMain.on('ondragstart', (event, filePath) => {
+  event.sender.startDrag({
+    file: filePath,
+    icon: '/path/to/icon.png'
+  })
+})
+```
+
 [addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows
 [clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows
 [setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows

+ 3 - 0
filenames.gypi

@@ -245,6 +245,9 @@
       'atom/browser/ui/atom_menu_model.h',
       'atom/browser/ui/cocoa/atom_menu_controller.h',
       'atom/browser/ui/cocoa/atom_menu_controller.mm',
+      'atom/browser/ui/drag_util_mac.mm',
+      'atom/browser/ui/drag_util_views.cc',
+      'atom/browser/ui/drag_util.h',
       'atom/browser/ui/file_dialog.h',
       'atom/browser/ui/file_dialog_gtk.cc',
       'atom/browser/ui/file_dialog_mac.mm',