Skip to content
Permalink
Browse files

Move logic for calculation of linkage points for model items to C++

  • Loading branch information
nyalldawson committed Mar 2, 2020
1 parent 997ac4e commit b624d3d5d6b62cb9c885286aa45a635e55131e7a
@@ -82,6 +82,8 @@ Sets the ``font`` used to render text in the item.

virtual QVariant itemChange( GraphicsItemChange change, const QVariant &value );

virtual QRectF boundingRect() const;


QRectF itemRect() const;
%Docstring
@@ -107,6 +109,41 @@ Returns the item's ``label`` text.
Returns the item's current state.
%End

virtual int linkPointCount( Qt::Edge edge ) const;
%Docstring
Returns the number of link points associated with the component on the specified ``edge``.
%End

virtual QString linkPointText( Qt::Edge edge, int index ) const;
%Docstring
Returns the text to use for the link point with the specified ``index`` on the specified ``edge``.
%End

QPointF linkPoint( Qt::Edge edge, int index ) const;
%Docstring
Returns the location of the link point with the specified ``index`` on the specified ``edge``.
%End

QPointF calculateAutomaticLinkPoint( QgsModelComponentGraphicItem *other, Qt::Edge &edge /Out/ ) const;
%Docstring
Returns the best link point to use for a link originating at a specified ``other`` item.

:param other: item at other end of link

:return: - calculated link point in item coordinates.
- edge: item edge for calculated best link point
%End

QPointF calculateAutomaticLinkPoint( const QPointF &point, Qt::Edge &edge /Out/ ) const;
%Docstring
Returns the best link point to use for a link originating at a specified ``other`` point.

:param other: point for other end of link (in scene coordinates)

:return: - calculated link point in item coordinates.
- edge: item edge for calculated best link point
%End

signals:


@@ -46,8 +46,6 @@
***************************************************************************
"""

from qgis.core import (QgsProcessingModelChildAlgorithm,
QgsProcessingModelParameter)
from qgis.gui import (
QgsModelGraphicsScene,
QgsModelComponentGraphicItem
@@ -59,19 +57,21 @@

class ModelerArrowItem(QGraphicsPathItem):

def __init__(self, startItem, startIndex, endItem, endIndex,
def __init__(self, startItem, start_edge, startIndex, endItem, end_edge, endIndex,
parent=None):
super(ModelerArrowItem, self).__init__(parent)
self.arrowHead = QPolygonF()
self.endIndex = endIndex
self.startIndex = startIndex
self.start_edge = start_edge
self.startItem = startItem
self.endItem = endItem
self.end_edge = end_edge
self.endPoints = []
self.setFlag(QGraphicsItem.ItemIsSelectable, False)
self.myColor = QApplication.palette().color(QPalette.WindowText)
self.myColor.setAlpha(150)
self.setPen(QPen(self.myColor, 1, Qt.SolidLine,
self.setPen(QPen(self.myColor, 4, Qt.SolidLine,
Qt.RoundCap, Qt.RoundJoin))
self.setZValue(QgsModelGraphicsScene.ArrowLink)

@@ -84,44 +84,59 @@ def setPenStyle(self, style):
def updatePath(self):
self.endPoints = []
controlPoints = []
endPt = self.endItem.getLinkPointForParameter(self.endIndex)
if isinstance(self.startItem.component(), QgsProcessingModelParameter):
startPt = self.startItem.getLinkPointForParameter(self.startIndex)
else:
startPt = self.startItem.getLinkPointForOutput(self.startIndex)
if isinstance(self.endItem.component(), QgsProcessingModelParameter):
endPt = self.endItem.getLinkPointForParameter(self.startIndex)

if isinstance(self.startItem.component(), QgsProcessingModelChildAlgorithm):
if self.startIndex != -1:
controlPoints.append(self.startItem.pos() + startPt)
controlPoints.append(self.startItem.pos() + startPt +
QPointF(self.startItem.component().size().width() / 3, 0))
controlPoints.append(self.endItem.pos() + endPt -
QPointF(self.endItem.component().size().width() / 3, 0))
controlPoints.append(self.endItem.pos() + endPt)
pt = QPointF(self.startItem.pos() + startPt + QPointF(-3, -3))
self.endPoints.append(pt)
pt = QPointF(self.endItem.pos() + endPt + QPointF(-3, -3))
self.endPoints.append(pt)

# is there a fixed start or end point?
startPt = None
if self.start_edge is not None and self.startIndex is not None:
startPt = self.startItem.linkPoint(self.start_edge, self.startIndex)
endPt = None
if self.end_edge is not None and self.endIndex is not None:
endPt = self.endItem.linkPoint(self.end_edge, self.endIndex)

if startPt is None:
# find closest edge
if endPt is None:
pt, edge = self.startItem.calculateAutomaticLinkPoint(self.endItem)
else:
# Case where there is a dependency on an algorithm not
# on an output
controlPoints.append(self.startItem.pos() + startPt)
controlPoints.append(self.startItem.pos() + startPt +
QPointF(self.startItem.component().size().width() / 3, 0))
controlPoints.append(self.endItem.pos() + endPt -
QPointF(self.endItem.component().size().height() / 3, 0))
controlPoints.append(self.endItem.pos() + endPt)
pt, edge = self.startItem.calculateAutomaticLinkPoint(endPt + self.endItem.pos())
controlPoints.append(pt)
self.endPoints.append(pt)
if edge == Qt.LeftEdge:
controlPoints.append(pt - QPointF(50, 0))
elif edge == Qt.RightEdge:
controlPoints.append(pt + QPointF(50, 0))
elif edge == Qt.BottomEdge:
controlPoints.append(pt + QPointF(0, 30))
else:
controlPoints.append(pt + QPointF(0, -30))
else:
controlPoints.append(self.startItem.pos())
controlPoints.append(self.startItem.pos() +
self.endPoints.append(self.startItem.pos() + startPt)
controlPoints.append(self.startItem.pos() + startPt)
controlPoints.append(self.startItem.pos() + startPt +
QPointF(self.startItem.component().size().width() / 3, 0))

if endPt is None:
# find closest edge
if startPt is None:
pt, edge = self.endItem.calculateAutomaticLinkPoint(self.startItem)
else:
pt, edge = self.endItem.calculateAutomaticLinkPoint(startPt + self.startItem.pos())
if edge == Qt.LeftEdge:
controlPoints.append(pt - QPointF(50, 0))
elif edge == Qt.RightEdge:
controlPoints.append(pt + QPointF(50, 0))
elif edge == Qt.BottomEdge:
controlPoints.append(pt + QPointF(0, 30))
else:
controlPoints.append(pt + QPointF(0, -30))
controlPoints.append(pt)
self.endPoints.append(pt)
else:
self.endPoints.append(self.endItem.pos() + endPt)
controlPoints.append(self.endItem.pos() + endPt -
QPointF(self.endItem.component().size().width() / 3, 0))
controlPoints.append(self.endItem.pos() + endPt)
pt = QPointF(self.endItem.pos() + endPt + QPointF(-3, -3))
self.endPoints.append(pt)

path = QPainterPath()
path.moveTo(controlPoints[0])
path.cubicTo(*controlPoints[1:])
@@ -139,12 +154,13 @@ def paint(self, painter, option, widget=None):

myPen = self.pen()
myPen.setColor(color)
myPen.setWidth(1)
painter.setPen(myPen)
painter.setBrush(color)
painter.setRenderHint(QPainter.Antialiasing)

for point in self.endPoints:
painter.drawEllipse(point.x(), point.y(), 6, 6)
painter.drawEllipse(point, 3.0, 3.0)

painter.setBrush(Qt.NoBrush)
painter.drawPath(self.path())
@@ -23,13 +23,12 @@

import os

from qgis.PyQt.QtCore import Qt, QPointF, QRectF, pyqtSignal
from qgis.PyQt.QtGui import QFont, QFontMetricsF, QPen, QBrush, QColor, QPicture, QPainter, QPalette
from qgis.PyQt.QtWidgets import QApplication, QGraphicsItem, QMessageBox, QMenu
from qgis.PyQt.QtCore import Qt, QPointF, QRectF
from qgis.PyQt.QtGui import QFontMetricsF, QPen, QBrush, QColor, QPicture, QPainter, QPalette
from qgis.PyQt.QtWidgets import QApplication, QMessageBox, QMenu
from qgis.PyQt.QtSvg import QSvgRenderer
from qgis.core import (QgsProcessingParameterDefinition,
QgsProcessingModelParameter,
QgsProcessingModelOutput,
QgsProcessingModelChildAlgorithm,
QgsProject)
from qgis.gui import (
@@ -53,24 +52,6 @@ def __init__(self, element, model):
self.pixmap = None
self.picture = None

def boundingRect(self):
fm = QFontMetricsF(self.font())
unfolded = isinstance(self.component(),
QgsProcessingModelChildAlgorithm) and not self.component().parametersCollapsed()
numParams = len([a for a in self.component().algorithm().parameterDefinitions() if
not a.isDestination()]) if unfolded else 0
unfolded = isinstance(self.component(),
QgsProcessingModelChildAlgorithm) and not self.component().outputsCollapsed()
numOutputs = len(self.component().algorithm().outputDefinitions()) if unfolded else 0

hUp = fm.height() * 1.2 * (numParams + 2)
hDown = fm.height() * 1.2 * (numOutputs + 2)
rect = QRectF(-(self.component().size().width() + 2) / 2,
-(self.component().size().height() + 2) / 2 - hUp,
self.component().size().width() + 2,
self.component().size().height() + hDown + hUp)
return rect

def paint(self, painter, option, widget=None):
rect = self.itemRect()

@@ -106,32 +87,34 @@ def paint(self, painter, option, widget=None):
pt = QPointF(-self.component().size().width() / 2 + 25, self.component().size().height() / 2.0 - h + 1)
painter.drawText(pt, text)
painter.setPen(QPen(QApplication.palette().color(QPalette.WindowText)))
if isinstance(self.component(), QgsProcessingModelChildAlgorithm):

if self.linkPointCount(Qt.TopEdge) or self.linkPointCount(Qt.BottomEdge):
h = -(fm.height() * 1.2)
h = h - self.component().size().height() / 2.0 + 5
pt = QPointF(-self.component().size().width() / 2 + 25, h)
painter.drawText(pt, 'In')
i = 1
if not self.component().parametersCollapsed():
for param in [p for p in self.component().algorithm().parameterDefinitions() if not p.isDestination()]:
if not param.flags() & QgsProcessingParameterDefinition.FlagHidden:
text = self.truncatedTextForItem(param.description())
h = -(fm.height() * 1.2) * (i + 1)
h = h - self.component().size().height() / 2.0 + 5
pt = QPointF(-self.component().size().width() / 2 + 33, h)
painter.drawText(pt, text)
i += 1
if not self.component().linksCollapsed(Qt.TopEdge):
for idx in range(self.linkPointCount(Qt.TopEdge)):
text = self.linkPointText(Qt.TopEdge, idx)
h = -(fm.height() * 1.2) * (i + 1)
h = h - self.component().size().height() / 2.0 + 5
pt = QPointF(-self.component().size().width() / 2 + 33, h)
painter.drawText(pt, text)
i += 1

h = fm.height() * 1.1
h = h + self.component().size().height() / 2.0
pt = QPointF(-self.component().size().width() / 2 + 25, h)
painter.drawText(pt, 'Out')
if not self.component().outputsCollapsed():
for i, out in enumerate(self.component().algorithm().outputDefinitions()):
text = self.truncatedTextForItem(out.description())
h = fm.height() * 1.2 * (i + 2)
if not self.component().linksCollapsed(Qt.BottomEdge):
for idx in range(self.linkPointCount(Qt.BottomEdge)):
text = self.linkPointText(Qt.BottomEdge, idx)
h = fm.height() * 1.2 * (idx + 2)
h = h + self.component().size().height() / 2.0
pt = QPointF(-self.component().size().width() / 2 + 33, h)
painter.drawText(pt, text)

if self.pixmap:
painter.drawPixmap(-(self.component().size().width() / 2.0) + 3, -8,
self.pixmap)
@@ -141,7 +124,8 @@ def paint(self, painter, option, widget=None):

def getLinkPointForParameter(self, paramIndex):
offsetX = 25
if isinstance(self.component(), QgsProcessingModelChildAlgorithm) and self.component().parametersCollapsed():
if isinstance(self.component(), QgsProcessingModelChildAlgorithm) and self.component().linksCollapsed(
Qt.TopEdge):
paramIndex = -1
offsetX = 17
if isinstance(self.component(), QgsProcessingModelParameter):
@@ -155,22 +139,6 @@ def getLinkPointForParameter(self, paramIndex):
h = 0
return QPointF(-self.component().size().width() / 2 + offsetX, h)

def getLinkPointForOutput(self, outputIndex):
if isinstance(self.component(),
QgsProcessingModelChildAlgorithm) and self.component().algorithm().outputDefinitions():
outputIndex = (outputIndex if not self.component().outputsCollapsed() else -1)
text = self.truncatedTextForItem(self.component().algorithm().outputDefinitions()[outputIndex].description())
fm = QFontMetricsF(self.font())
w = fm.width(text)
h = fm.height() * 1.2 * (outputIndex + 1) + fm.height() / 2.0
y = h + self.component().size().height() / 2.0 + 5
x = (-self.component().size().width() / 2 + 33 + w + 5
if not self.component().outputsCollapsed()
else 10)
return QPointF(x, y)
else:
return QPointF(0, 0)


class ModelerInputGraphicItem(ModelerGraphicItem):

@@ -273,31 +241,50 @@ def __init__(self, element, model):
if [a for a in alg.parameterDefinitions() if not a.isDestination()]:
pt = self.getLinkPointForParameter(-1)
pt = QPointF(0, pt.y())
self.inButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().parametersCollapsed(), pt)
self.inButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().linksCollapsed(Qt.TopEdge), pt)
self.inButton.folded.connect(self.foldInput)
if alg.outputDefinitions():
pt = self.getLinkPointForOutput(-1)
pt = self.linkPoint(Qt.BottomEdge, -1)
pt = QPointF(0, pt.y())
self.outButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().outputsCollapsed(), pt)
self.outButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().linksCollapsed(Qt.BottomEdge),
pt)
self.outButton.folded.connect(self.foldOutput)

def linkPointCount(self, edge):
if edge == Qt.BottomEdge:
return len(self.component().algorithm().outputDefinitions())
elif edge == Qt.TopEdge:
return len([p for p in self.component().algorithm().parameterDefinitions() if
not p.isDestination() and not p.flags() & QgsProcessingParameterDefinition.FlagHidden])

return 0

def linkPointText(self, edge, index):
if edge == Qt.TopEdge:
param = [p for p in self.component().algorithm().parameterDefinitions() if
not p.isDestination() and not p.flags() & QgsProcessingParameterDefinition.FlagHidden][index]
return self.truncatedTextForItem(param.description())
elif edge == Qt.BottomEdge:
out = self.component().algorithm().outputDefinitions()[index]
return self.truncatedTextForItem(out.description())

def foldInput(self, folded):
self.component().setParametersCollapsed(folded)
self.component().setLinksCollapsed(Qt.TopEdge, folded)
# also need to update the model's stored component
self.model().childAlgorithm(self.component().childId()).setParametersCollapsed(folded)
self.model().childAlgorithm(self.component().childId()).setLinksCollapsed(Qt.TopEdge, folded)
self.prepareGeometryChange()
if self.component().algorithm().outputDefinitions():
pt = self.getLinkPointForOutput(-1)
pt = self.linkPoint(Qt.BottomEdge, -1)
pt = QPointF(0, pt.y())
self.outButton.position = pt

self.updateArrowPaths.emit()
self.update()

def foldOutput(self, folded):
self.component().setOutputsCollapsed(folded)
self.component().setLinksCollapsed(Qt.BottomEdge, folded)
# also need to update the model's stored component
self.model().childAlgorithm(self.component().childId()).setOutputsCollapsed(folded)
self.model().childAlgorithm(self.component().childId()).setLinksCollapsed(Qt.BottomEdge, folded)
self.prepareGeometryChange()
self.updateArrowPaths.emit()
self.update()
@@ -315,11 +302,11 @@ def editComponent(self):
def updateAlgorithm(self, alg):
existing_child = self.model().childAlgorithm(alg.childId())
alg.setPosition(existing_child.position())
alg.setParametersCollapsed(existing_child.parametersCollapsed())
alg.setOutputsCollapsed(existing_child.outputsCollapsed())
alg.setLinksCollapsed(Qt.TopEdge, existing_child.linksCollapsed(Qt.TopEdge))
alg.setLinksCollapsed(Qt.BottomEdge, existing_child.linksCollapsed(Qt.BottomEdge))
for i, out in enumerate(alg.modelOutputs().keys()):
alg.modelOutput(out).setPosition(alg.modelOutput(out).position() or
alg.position() + QPointF(
alg.modelOutput(out).setPosition(alg.modelOutput(out).position()
or alg.position() + QPointF(
self.component().size().width(),
(i + 1.5) * self.component().size().height()))
self.model().setChildAlgorithm(alg)

0 comments on commit b624d3d

Please sign in to comment.
You can’t perform that action at this time.