Repository: ipython/ipython

Issue:
qtconsole drag target?
Having recently switched from IPython on osx terminal to qtconsole, I miss the "drag a file from finder to get full path in terminal" feature. Is something along these lines planned? More generally is some kind of drag and drop from/to qtconsole planned?

Thanks



Discussion:
No, I don't think anyone's working on that.  Such a thing would definitely be nice.  I don't know what the cross-platform situation is for working with the clipboard and drag/drop via Qt, but if you want to investigate, that would certainly be welcome.

I've got a preliminary implementation which allows files to be dragged from Finder and dropped onto the Qt Console in OS X.

[bfroehle/ipython@drag_n_drop](https://github.com/bfroehle/ipython/compare/drag_n_drop)

However, I'm not a Qt expert and it is likely that I may have messed up some of the other drag and drop logic (which seems to use a different mechanism).

Also the interface is somewhat clunky, as it is hard to know where you are dragging to since the cursor doesn't update as you move the file around.


Resolving patch:
diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -268,7 +268,6 @@ def __init__(self, parent=None, **kw):
         self._continuation_prompt = '> '
         self._continuation_prompt_html = None
         self._executing = False
-        self._filter_drag = False
         self._filter_resize = False
         self._html_exporter = HtmlExporter(self._control)
         self._input_buffer_executing = ''
@@ -343,7 +342,51 @@ def __init__(self, parent=None, **kw):
                 triggered=self.reset_font)
         self.addAction(self.reset_font_size)
 
+        # Accept drag and drop events here. Drops were already turned off
+        # in self._control when that widget was created.
+        self.setAcceptDrops(True)
 
+    #---------------------------------------------------------------------------
+    # Drag and drop support
+    #---------------------------------------------------------------------------
+
+    def dragEnterEvent(self, e):
+        if e.mimeData().hasUrls():
+            # The link action should indicate to that the drop will insert
+            # the file anme.
+            e.setDropAction(QtCore.Qt.LinkAction)
+            e.accept()
+        elif e.mimeData().hasText():
+            # By changing the action to copy we don't need to worry about
+            # the user accidentally moving text around in the widget.
+            e.setDropAction(QtCore.Qt.CopyAction)
+            e.accept()
+
+    def dragMoveEvent(self, e):
+        if e.mimeData().hasUrls():
+            pass
+        elif e.mimeData().hasText():
+            cursor = self._control.cursorForPosition(e.pos())
+            if self._in_buffer(cursor.position()):
+                e.setDropAction(QtCore.Qt.CopyAction)
+                self._control.setTextCursor(cursor)
+            else:
+                e.setDropAction(QtCore.Qt.IgnoreAction)
+            e.accept()
+
+    def dropEvent(self, e):
+        if e.mimeData().hasUrls():
+            self._keep_cursor_in_buffer()
+            cursor = self._control.textCursor()
+            filenames = [url.toLocalFile() for url in e.mimeData().urls()]
+            text = ', '.join("'" + f.replace("'", "'\"'\"'") + "'"
+                             for f in filenames)
+            self._insert_plain_text_into_buffer(cursor, text)
+        elif e.mimeData().hasText():
+            cursor = self._control.cursorForPosition(e.pos())
+            if self._in_buffer(cursor.position()):
+                text = e.mimeData().text()
+                self._insert_plain_text_into_buffer(cursor, text)
 
     def eventFilter(self, obj, event):
         """ Reimplemented to ensure a console-like behavior in the underlying
@@ -392,39 +435,6 @@ def eventFilter(self, obj, event):
                 event.key() in self._shortcuts:
             event.accept()
 
-        # Ensure that drags are safe. The problem is that the drag starting
-        # logic, which determines whether the drag is a Copy or Move, is locked
-        # down in QTextControl. If the widget is editable, which it must be if
-        # we're not executing, the drag will be a Move. The following hack
-        # prevents QTextControl from deleting the text by clearing the selection
-        # when a drag leave event originating from this widget is dispatched.
-        # The fact that we have to clear the user's selection is unfortunate,
-        # but the alternative--trying to prevent Qt from using its hardwired
-        # drag logic and writing our own--is worse.
-        elif etype == QtCore.QEvent.DragEnter and \
-                obj == self._control.viewport() and \
-                event.source() == self._control.viewport():
-            self._filter_drag = True
-        elif etype == QtCore.QEvent.DragLeave and \
-                obj == self._control.viewport() and \
-                self._filter_drag:
-            cursor = self._control.textCursor()
-            cursor.clearSelection()
-            self._control.setTextCursor(cursor)
-            self._filter_drag = False
-
-        # Ensure that drops are safe.
-        elif etype == QtCore.QEvent.Drop and obj == self._control.viewport():
-            cursor = self._control.cursorForPosition(event.pos())
-            if self._in_buffer(cursor.position()):
-                text = event.mimeData().text()
-                self._insert_plain_text_into_buffer(cursor, text)
-
-            # Qt is expecting to get something here--drag and drop occurs in its
-            # own event loop. Send a DragLeave event to end it.
-            QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent())
-            return True
-
         # Handle scrolling of the vsplit pager. This hack attempts to solve
         # problems with tearing of the help text inside the pager window.  This
         # happens only on Mac OS X with both PySide and PyQt. This fix isn't
@@ -1035,8 +1045,12 @@ def _create_control(self):
             control.setAcceptRichText(False)
             control.setMouseTracking(True)
 
+        # Prevent the widget from handling drops, as we already provide
+        # the logic in this class.
+        control.setAcceptDrops(False)
+
         # Install event filters. The filter on the viewport is needed for
-        # mouse events and drag events.
+        # mouse events.
         control.installEventFilter(self)
         control.viewport().installEventFilter(self)
 


Test patch: