Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] OWPythonScript: dropping and pasting of python scripts #3611

Merged
merged 2 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Orange/widgets/data/owpythonscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from AnyQt.QtCore import Qt, QRegExp, QByteArray, QItemSelectionModel

from Orange.canvas.gui.utils import OSX_NSURL_toLocalFile
from Orange.data import Table
from Orange.base import Learner, Model
from Orange.widgets import widget, gui
Expand All @@ -35,6 +36,19 @@ def text_format(foreground=Qt.black, weight=QFont.Normal):
return fmt


def to_local_file(url):
return OSX_NSURL_toLocalFile(url) or url.toLocalFile()


def read_file_content(filename, limit=None):
try:
with open(filename, encoding="utf-8", errors='strict') as f:
text = f.read(limit)
return text
except (OSError, UnicodeDecodeError):
return None


class PythonSyntaxHighlighter(QSyntaxHighlighter):
def __init__(self, parent=None):

Expand Down Expand Up @@ -138,6 +152,24 @@ def keyPressEvent(self, event):
else:
super().keyPressEvent(event)

def insertFromMimeData(self, source):
"""
Reimplemented from QPlainTextEdit.insertFromMimeData.
"""
urls = source.urls()
if urls:
self.pasteFile(urls[0])
else:
super().insertFromMimeData(source)

def pasteFile(self, url):
new = read_file_content(to_local_file(url))
if new:
# inserting text like this allows undo
cursor = QTextCursor(self.document())
cursor.select(QTextCursor.Document)
cursor.insertText(new)


class PythonConsole(QPlainTextEdit, code.InteractiveConsole):
def __init__(self, locals=None, parent=None):
Expand Down Expand Up @@ -524,6 +556,8 @@ def __init__(self):
if self.splitterState is not None:
self.splitCanvas.restoreState(QByteArray(self.splitterState))

self.setAcceptDrops(True)

self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
self.controlArea.layout().addStretch(1)
self.resize(800, 600)
Expand Down Expand Up @@ -708,6 +742,20 @@ def commit(self):
out_var = None
getattr(self.Outputs, signal).send(out_var)

def dragEnterEvent(self, event):
urls = event.mimeData().urls()
if urls:
# try reading the file as text
c = read_file_content(to_local_file(urls[0]), limit=1000)
if c is not None:
event.acceptProposedAction()

def dropEvent(self, event):
"""Handle file drops"""
urls = event.mimeData().urls()
if urls:
self.text.pasteFile(urls[0])


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWPythonScript).run()
78 changes: 77 additions & 1 deletion Orange/widgets/data/tests/test_owpythonscript.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring
from AnyQt.QtCore import QMimeData, QUrl, QPoint, Qt
from AnyQt.QtGui import QDragEnterEvent, QDropEvent

from Orange.data import Table
from Orange.classification import LogisticRegressionLearner
from Orange.widgets.data.owpythonscript import OWPythonScript
from Orange.tests import named_file
from Orange.widgets.data.owpythonscript import OWPythonScript, read_file_content
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.widget import OWWidget

Expand Down Expand Up @@ -134,3 +138,75 @@ def test_store_current_script(self):
self.widget = self.create_widget(OWPythonScript, stored_settings=settings)
script = self.widget.text.toPlainText()
self.assertEqual("42", script)

def test_read_file_content(self):
with named_file("Content", suffix=".42") as fn:
# valid file opens
content = read_file_content(fn)
self.assertEqual("Content", content)
# invalid utf-8 file does not
with open(fn, "wb") as f:
f.write(b"\xc3\x28")
content = read_file_content(fn)
self.assertIsNone(content)

def test_script_insert_mime_text(self):
current = self.widget.text.toPlainText()
insert = "test\n"
cursor = self.widget.text.cursor()
cursor.setPos(0, 0)
mime = QMimeData()
mime.setText(insert)
self.widget.text.insertFromMimeData(mime)
self.assertEqual(insert + current, self.widget.text.toPlainText())

def test_script_insert_mime_file(self):
with named_file("test", suffix=".42") as fn:
previous = self.widget.text.toPlainText()
mime = QMimeData()
url = QUrl.fromLocalFile(fn)
mime.setUrls([url])
self.widget.text.insertFromMimeData(mime)
self.assertEqual("test", self.widget.text.toPlainText())
self.widget.text.undo()
self.assertEqual(previous, self.widget.text.toPlainText())

def test_dragEnterEvent_accepts_text(self):
with named_file("Content", suffix=".42") as fn:
event = self._drag_enter_event(QUrl.fromLocalFile(fn))
self.widget.dragEnterEvent(event)
self.assertTrue(event.isAccepted())

def test_dragEnterEvent_rejects_binary(self):
with named_file("", suffix=".42") as fn:
with open(fn, "wb") as f:
f.write(b"\xc3\x28")
event = self._drag_enter_event(QUrl.fromLocalFile(fn))
self.widget.dragEnterEvent(event)
self.assertFalse(event.isAccepted())

def _drag_enter_event(self, url):
# make sure data does not get garbage collected before it used
self.event_data = data = QMimeData()
data.setUrls([QUrl(url)])
return QDragEnterEvent(
QPoint(0, 0), Qt.MoveAction, data,
Qt.NoButton, Qt.NoModifier)

def test_dropEvent_replaces_file(self):
with named_file("test", suffix=".42") as fn:
previous = self.widget.text.toPlainText()
event = self._drop_event(QUrl.fromLocalFile(fn))
self.widget.dropEvent(event)
self.assertEqual("test", self.widget.text.toPlainText())
self.widget.text.undo()
self.assertEqual(previous, self.widget.text.toPlainText())

def _drop_event(self, url):
# make sure data does not get garbage collected before it used
self.event_data = data = QMimeData()
data.setUrls([QUrl(url)])

return QDropEvent(
QPoint(0, 0), Qt.MoveAction, data,
Qt.NoButton, Qt.NoModifier, QDropEvent.Drop)