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] Widget Insertion #3179

Merged
merged 5 commits into from
Aug 8, 2018
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
21 changes: 21 additions & 0 deletions Orange/canvas/document/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ def undo(self):
self.scheme.add_link(self.link)


class InsertNodeCommand(QUndoCommand):
def __init__(self, scheme, new_node, old_link, new_links, parent=None):
QUndoCommand.__init__(self, "Insert widget into link", parent)
self.scheme = scheme
self.inserted_widget = new_node
self.original_link = old_link
self.new_links = new_links

def redo(self):
self.scheme.add_node(self.inserted_widget)
self.scheme.remove_link(self.original_link)
self.scheme.add_link(self.new_links[0])
self.scheme.add_link(self.new_links[1])

def undo(self):
self.scheme.remove_link(self.new_links[0])
self.scheme.remove_link(self.new_links[1])
self.scheme.add_link(self.original_link)
self.scheme.remove_node(self.inserted_widget)


class AddAnnotationCommand(QUndoCommand):
def __init__(self, scheme, annotation, parent=None):
QUndoCommand.__init__(self, "Add annotation", parent)
Expand Down
133 changes: 123 additions & 10 deletions Orange/canvas/document/schemeedit.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
)

from AnyQt.QtCore import (
Qt, QObject, QEvent, QSignalMapper, QRectF, QCoreApplication
)
Qt, QObject, QEvent, QSignalMapper, QRectF, QCoreApplication,
QPoint)

from AnyQt.QtCore import pyqtProperty as Property, pyqtSignal as Signal

from Orange.canvas.registry import WidgetDescription
from .suggestions import Suggestions
from ..registry.qt import whats_this_helper
from ..registry.qt import whats_this_helper, QtWidgetRegistry
from ..gui.quickhelp import QuickHelpTipEvent
from ..gui.utils import message_information, disabled
from ..scheme import (
Expand Down Expand Up @@ -131,6 +132,7 @@ def __init__(self, parent=None, ):
self.__possibleMouseItemsMove = False
self.__itemsMoving = {}
self.__contextMenuTarget = None
self.__dropTarget = None
self.__quickMenu = None
self.__quickTip = ""

Expand Down Expand Up @@ -171,6 +173,8 @@ def __init__(self, parent=None, ):
self.__linkMenu = QMenu(self.tr("Link"), self)
self.__linkMenu.addAction(self.__linkEnableAction)
self.__linkMenu.addSeparator()
self.__linkMenu.addAction(self.__nodeInsertAction)
self.__linkMenu.addSeparator()
self.__linkMenu.addAction(self.__linkRemoveAction)
self.__linkMenu.addAction(self.__linkResetAction)

Expand Down Expand Up @@ -328,6 +332,13 @@ def color_icon(color):
toolTip=self.tr("Remove link."),
)

self.__nodeInsertAction = \
QAction(self.tr("Insert Widget"), self,
objectName="node-insert-action",
triggered=self.__nodeInsert,
toolTip=self.tr("Insert widget."),
)

self.__linkResetAction = \
QAction(self.tr("Reset Signals"), self,
objectName="link-reset-action",
Expand All @@ -346,6 +357,7 @@ def color_icon(color):
self.__newArrowAnnotationAction,
self.__linkEnableAction,
self.__linkRemoveAction,
self.__nodeInsertAction,
self.__linkResetAction,
self.__duplicateSelectedAction])

Expand Down Expand Up @@ -867,6 +879,30 @@ def removeLink(self, link):
command = commands.RemoveLinkCommand(self.__scheme, link)
self.__undoStack.push(command)

def insertNode(self, new_node, old_link):
"""
Insert a node in-between two linked nodes.
"""
source_node = old_link.source_node
sink_node = old_link.sink_node

possible_links = (self.scheme().propose_links(source_node, new_node),
self.scheme().propose_links(new_node, sink_node))

first_link_sink_channel = [l[1] for l in possible_links[0]
if l[0] == old_link.source_channel][0]
second_link_source_channel = [l[0] for l in possible_links[1]
if l[1] == old_link.sink_channel][0]

new_links = (
SchemeLink(source_node, old_link.source_channel,
new_node, first_link_sink_channel),
SchemeLink(new_node, second_link_source_channel,
sink_node, old_link.sink_channel))

command = commands.InsertNodeCommand(self.__scheme, new_node, old_link, new_links)
self.__undoStack.push(command)

def onNewLink(self, func):
"""
Runs function when new link is added to current scheme.
Expand Down Expand Up @@ -1031,30 +1067,56 @@ def changeEvent(self, event):

def eventFilter(self, obj, event):
# Filter the scene's drag/drop events.
MIME_TYPE = "application/vnv.orange-canvas.registry.qualified-name"
if obj is self.scene():
etype = event.type()
if etype == QEvent.GraphicsSceneDragEnter or \
etype == QEvent.GraphicsSceneDragMove:
mime_data = event.mimeData()
if mime_data.hasFormat(
"application/vnv.orange-canvas.registry.qualified-name"
):
drop_target = None
if mime_data.hasFormat(MIME_TYPE):
qname = bytes(mime_data.data(MIME_TYPE)).decode("ascii")
try:
desc = self.__registry.widget(qname)
except KeyError:
pass
else:
item = self.__scene.item_at(event.scenePos(), items.LinkItem)
link = self.scene().link_for_item(item) if item else None
if link is not None and can_insert_node(desc, link):
drop_target = item
drop_target.setHoverState(True)
event.acceptProposedAction()
else:
event.ignore()

if self.__dropTarget is not None and \
self.__dropTarget is not drop_target:
self.__dropTarget.setHoverState(False)
# self.__dropTarget = None

self.__dropTarget = drop_target
return True
elif etype == QEvent.GraphicsSceneDragLeave:
if self.__dropTarget is not None:
self.__dropTarget.setHoverState(False)
self.__dropTarget = None
elif etype == QEvent.GraphicsSceneDrop:
data = event.mimeData()
qname = data.data(
"application/vnv.orange-canvas.registry.qualified-name"
)
qname = data.data(MIME_TYPE)
try:
desc = self.__registry.widget(bytes(qname).decode())
except KeyError:
log.error("Unknown qualified name '%s'", qname)
else:
pos = event.scenePos()
self.createNewNode(desc, position=(pos.x(), pos.y()))
item = self.__scene.item_at(event.scenePos(), items.LinkItem)
link = self.scene().link_for_item(item) if item else None
if link and can_insert_node(desc, link):
node = self.newNodeHelper(desc, position=(pos.x(), pos.y()))
self.insertNode(node, link)
else:
self.createNewNode(desc, position=(pos.x(), pos.y()))
return True

elif etype == QEvent.GraphicsSceneMousePress:
Expand Down Expand Up @@ -1594,6 +1656,50 @@ def __linkReset(self):
)
action.edit_links()

def __nodeInsert(self):
"""
Node insert was requested from the context menu.
"""
if not self.__contextMenuTarget:
return

original_link = self.__contextMenuTarget
source_node = original_link.source_node
sink_node = original_link.sink_node

def filterFunc(index):
desc = index.data(QtWidgetRegistry.WIDGET_DESC_ROLE)
if isinstance(desc, WidgetDescription):
return can_insert_node(desc, original_link)
else:
return False

x = (source_node.position[0] + sink_node.position[0]) / 2
y = (source_node.position[1] + sink_node.position[1]) / 2

menu = self.quickMenu()
menu.setFilterFunc(filterFunc)
menu.setSortingFunc(None)

view = self.view()
try:
action = menu.exec_(view.mapToGlobal(view.mapFromScene(QPoint(x, y))))
finally:
menu.setFilterFunc(None)

if action:
item = action.property("item")
desc = item.data(QtWidgetRegistry.WIDGET_DESC_ROLE)
else:
return

if can_insert_node(desc, original_link):
new_node = self.newNodeHelper(desc, position=(x, y))
self.insertNode(new_node, original_link)
else:
log.info("Cannot insert node: links not possible.")


def __duplicateSelected(self):
"""
Duplicate currently selected nodes.
Expand Down Expand Up @@ -1936,6 +2042,13 @@ def node_properties(scheme):
return [dict(node.properties) for node in scheme.nodes]


def can_insert_node(new_node_desc, original_link):
return any(scheme.compatible_channels(original_link.source_channel, input)
for input in new_node_desc.inputs) and \
any(scheme.compatible_channels(output, original_link.sink_channel)
for output in new_node_desc.outputs)


def uniquify(item, names, pattern="{item}-{_}", start=0):
candidates = (pattern.format(item=item, _=i)
for i in itertools.count(start))
Expand Down