Skip to content

Commit

Permalink
Merge pull request #5031 from ales-erjavec/canvas-plugin-drop-handler
Browse files Browse the repository at this point in the history
[ENH] Create nodes on canvas drag/drop
  • Loading branch information
markotoplak authored Aug 5, 2021
2 parents 22f5f91 + e8006ec commit 77f53fe
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 11 deletions.
4 changes: 4 additions & 0 deletions Orange/canvas/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
QFormLayout, QCheckBox, QLineEdit, QWidget, QVBoxLayout, QLabel
)
from orangecanvas.application.settings import UserSettingsDialog, FormLayout
from orangecanvas.document.interactions import PluginDropHandler
from orangecanvas.document.usagestatistics import UsageStatistics
from orangecanvas.utils.overlay import NotificationOverlay

Expand Down Expand Up @@ -107,6 +108,9 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.notification_overlay = NotificationOverlay(self.scheme_widget)
self.notification_server = None
self.scheme_widget.setDropHandlers([
PluginDropHandler("orange.canvas.drophandler")
])

def open_canvas_settings(self):
# type: () -> None
Expand Down
36 changes: 33 additions & 3 deletions Orange/widgets/data/owfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import logging
from itertools import chain
from urllib.parse import urlparse
from typing import List
from typing import List, Dict, Any

import numpy as np
from AnyQt.QtWidgets import \
QStyle, QComboBox, QMessageBox, QGridLayout, QLabel, \
QLineEdit, QSizePolicy as Policy, QCompleter
from AnyQt.QtCore import Qt, QTimer, QSize
from AnyQt.QtCore import Qt, QTimer, QSize, QUrl

from orangewidget.workflow.drophandler import SingleUrlDropHandler

from Orange.data.table import Table, get_sample_datasets_dir
from Orange.data.io import FileFormat, UrlReader, class_from_qualified_name
Expand All @@ -28,7 +30,6 @@
# module's namespace so that old saved settings still work
from Orange.widgets.utils.filedialogs import RecentPath


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -571,5 +572,34 @@ def workflowEnvChanged(self, key, value, oldvalue):
self.update_file_list(key, value, oldvalue)


class OWFileDropHandler(SingleUrlDropHandler):
WIDGET = OWFile

def canDropUrl(self, url: QUrl) -> bool:
if url.isLocalFile():
try:
FileFormat.get_reader(url.toLocalFile())
return True
except Exception: # noqa # pylint:disable=broad-except
return False
else:
return url.scheme().lower() in ("http", "https", "ftp")

def parametersFromUrl(self, url: QUrl) -> Dict[str, Any]:
if url.isLocalFile():
path = url.toLocalFile()
r = RecentPath(os.path.abspath(path), None, None,
os.path.basename(path))
return {
"recent_paths": [r],
"source": OWFile.LOCAL_FILE,
}
else:
return {
"recent_urls": [url.toString()],
"source": OWFile.URL,
}


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWFile).run()
34 changes: 32 additions & 2 deletions Orange/widgets/data/owpythonscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from functools import reduce
from unittest.mock import patch

from typing import Optional, List, TYPE_CHECKING
from typing import Optional, List, Dict, Any, TYPE_CHECKING

from AnyQt.QtWidgets import (
QPlainTextEdit, QListView, QSizePolicy, QMenu, QSplitter, QLineEdit,
Expand All @@ -20,9 +20,12 @@
QSyntaxHighlighter, QTextCharFormat, QTextCursor, QKeySequence,
)
from AnyQt.QtCore import (
Qt, QRegularExpression, QByteArray, QItemSelectionModel, QSize
Qt, QRegularExpression, QByteArray, QItemSelectionModel, QSize,
QMimeDatabase
)

from orangewidget.workflow.drophandler import SingleFileDropHandler

from Orange.data import Table
from Orange.base import Learner, Model
from Orange.util import interleave
Expand Down Expand Up @@ -800,5 +803,32 @@ def migrate_settings(cls, settings, version):
settings["scriptLibrary"] = library


class OWPythonScriptDropHandler(SingleFileDropHandler):
WIDGET = OWPythonScript

def canDropFile(self, path: str) -> bool:
md = QMimeDatabase()
mt = md.mimeTypeForFile(path)
return mt.inherits("text/x-python")

def parametersFromFile(self, path: str) -> Dict[str, Any]:
with open(path, "rt") as f:
content = f.read()

item: '_ScriptData' = {
"name": os.path.basename(path),
"script": content,
"filename": path,
}
params = {
"__version__": OWPythonScript.settings_version,
"scriptLibrary": [
item,
],
"scriptText": content
}
return params


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWPythonScript).run()
18 changes: 17 additions & 1 deletion Orange/widgets/data/tests/test_owfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from Orange.data.io import TabReader
from Orange.tests import named_file
from Orange.widgets.data.owfile import OWFile
from Orange.widgets.data.owfile import OWFile, OWFileDropHandler
from Orange.widgets.utils.filedialogs import dialog_formats, format_filter, RecentPath
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.utils.domaineditor import ComboDelegate, VarTypeDelegate, VarTableModel
Expand Down Expand Up @@ -666,5 +666,21 @@ def read():
self.assertIn(WARNING_MSG, str(self.widget.Warning.load_warning))


class TestOWFileDropHandler(unittest.TestCase):
def test_canDropUrl(self):
handler = OWFileDropHandler()
self.assertTrue(handler.canDropUrl(QUrl("https://example.com/test.tab")))
self.assertTrue(handler.canDropUrl(QUrl.fromLocalFile("test.tab")))

def test_parametersFromUrl(self):
handler = OWFileDropHandler()
r = handler.parametersFromUrl(QUrl("https://example.com/test.tab"))
self.assertEqual(r["source"], OWFile.URL)
self.assertEqual(r["recent_urls"], ["https://example.com/test.tab"])
r = handler.parametersFromUrl(QUrl.fromLocalFile("test.tab"))
self.assertEqual(r["source"], OWFile.LOCAL_FILE)
self.assertEqual(r["recent_paths"][0].basename, "test.tab")


if __name__ == "__main__":
unittest.main()
17 changes: 16 additions & 1 deletion Orange/widgets/data/tests/test_owpythonscript.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring
import sys
import unittest

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.tests import named_file
from Orange.widgets.data.owpythonscript import OWPythonScript, read_file_content, Script
from Orange.widgets.data.owpythonscript import OWPythonScript, \
read_file_content, Script, OWPythonScriptDropHandler
from Orange.widgets.tests.base import WidgetTest, DummySignalManager
from Orange.widgets.widget import OWWidget

Expand Down Expand Up @@ -260,3 +262,16 @@ def test_restore(self):
"__version__": 2
})
self.assertEqual(w.libraryListSource[0].name, "A")


class TestOWPythonScriptDropHandler(unittest.TestCase):
def test_canDropFile(self):
handler = OWPythonScriptDropHandler()
self.assertTrue(handler.canDropFile(__file__))
self.assertFalse(handler.canDropFile("test.tab"))

def test_parametersFromFile(self):
handler = OWPythonScriptDropHandler()
r = handler.parametersFromFile(__file__)
item = r["scriptLibrary"][0]
self.assertEqual(item["filename"], __file__)
18 changes: 17 additions & 1 deletion Orange/widgets/model/owloadmodel.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import os
import pickle
from typing import Any, Dict

from AnyQt.QtWidgets import QSizePolicy, QStyle, QFileDialog
from AnyQt.QtCore import QTimer

from orangewidget.workflow.drophandler import SingleFileDropHandler

from Orange.base import Model
from Orange.widgets import widget, gui
from Orange.widgets.model import owsavemodel
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin, RecentPath
from Orange.widgets.utils import stdpaths
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Msg, Output
Expand Down Expand Up @@ -88,5 +91,18 @@ def open_file(self):
self.Outputs.model.send(model)


class OWLoadModelDropHandler(SingleFileDropHandler):
WIDGET = OWLoadModel

def canDropFile(self, path: str) -> bool:
return path.endswith(".pkcls")

def parametersFromFile(self, path: str) -> Dict[str, Any]:
r = RecentPath(os.path.abspath(path), None, None,
os.path.basename(path))
parameters = {"recent_paths": [r]}
return parameters


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWLoadModel).run()
14 changes: 13 additions & 1 deletion Orange/widgets/model/tests/test_owloadmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from orangewidget.utils.filedialogs import RecentPath
from Orange.data import Table
from Orange.classification.naive_bayes import NaiveBayesLearner
from Orange.widgets.model.owloadmodel import OWLoadModel
from Orange.widgets.model.owloadmodel import OWLoadModel, OWLoadModelDropHandler
from Orange.widgets.tests.base import WidgetTest


Expand Down Expand Up @@ -136,5 +136,17 @@ def test_open_moved_workflow(self, load):
os.remove(file_name)


class TestOWLoadModelDropHandler(unittest.TestCase):
def test_canDropFile(self):
handler = OWLoadModelDropHandler()
self.assertTrue(handler.canDropFile("test.pkcls"))
self.assertFalse(handler.canDropFile("test.txt"))

def test_parametersFromFile(self):
handler = OWLoadModelDropHandler()
res = handler.parametersFromFile("test.pkcls")
self.assertEqual(res["recent_paths"][0].basename, "test.pkcls")


if __name__ == "__main__":
unittest.main()
16 changes: 15 additions & 1 deletion Orange/widgets/unsupervised/owdistancefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from AnyQt.QtWidgets import QSizePolicy, QStyle, QMessageBox, QFileDialog
from AnyQt.QtCore import QTimer

from orangewidget.workflow.drophandler import SingleFileDropHandler

from Orange.misc import DistMatrix
from Orange.widgets import widget, gui
from Orange.data import get_sample_datasets_dir
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin, RecentPath
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Output

Expand Down Expand Up @@ -144,5 +146,17 @@ def send_report(self):
self.report_items([("File name", self.loaded_file)])


class OWDistanceFileDropHandler(SingleFileDropHandler):
WIDGET = OWDistanceFile

def parametersFromFile(self, path):
r = RecentPath(os.path.abspath(path), None, None,
os.path.basename(path))
return {"recent_paths": [r]}

def canDropFile(self, path: str) -> bool:
return os.path.splitext(path)[1].lower() == ".dst"


if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWDistanceFile).run()
15 changes: 15 additions & 0 deletions Orange/widgets/unsupervised/tests/test_owdistancefile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import unittest

from Orange.widgets.unsupervised.owdistancefile import OWDistanceFileDropHandler


class TestOWDistanceFileDropHandler(unittest.TestCase):
def test_canDropFile(self):
handler = OWDistanceFileDropHandler()
self.assertTrue(handler.canDropFile("test.dst"))
self.assertFalse(handler.canDropFile("test.bin"))

def test_parametersFromFile(self):
handler = OWDistanceFileDropHandler()
r = handler.parametersFromFile("test.dst")
self.assertEqual(r["recent_paths"][0].basename, "test.dst")
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
"orange.canvas.help": (
"html-index = Orange.widgets:WIDGET_HELP_PATH",
),
"orange.canvas.drophandler": (
"File = Orange.widgets.data.owfile:OWFileDropHandler",
"Load Model = Orange.widgets.model.owloadmodel:OWLoadModelDropHandler",
"Distance File = Orange.widgets.unsupervised.owdistancefile:OWDistanceFileDropHandler",
"Python Script = Orange.widgets.data.owpythonscript:OWPythonScriptDropHandler",
),
"gui_scripts": (
"orange-canvas = Orange.canvas.__main__:main",
),
Expand Down
6 changes: 5 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ commands =
changedir = {toxinidir}
skip_install = true
whitelist_externals = bash
deps = pylint
deps =
orange-widget-base
anyqt
PyQt5==5.12.*
pylint
commands =
bash .github/workflows/check_pylint_diff.sh

Expand Down

0 comments on commit 77f53fe

Please sign in to comment.