Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions src/core/layout/qgslayoutitempolyline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,40 @@ void QgsLayoutItemPolyline::drawStartMarker( QPainter *painter )
case MarkerMode::ArrowHead:
{
// calculate angle at start of line
const QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
const double angle = startLine.angle();
drawArrow( painter, mPolygon.at( 0 ), angle );
if ( mVersion == 2 )
{
const QLineF startLine( mPolygon.at( 1 ), mPolygon.at( 0 ) );
const double angle = startLine.angle();

// move start point depending on arrow width
const QVector2D dir = QVector2D( startLine.dx(), startLine.dy() ).normalized();
QPointF startPoint = startLine.p2();
startPoint += ( dir * 0.5 * mArrowHeadWidth ).toPointF();

drawArrow( painter, startPoint, angle );
}
else if ( mVersion == 1 )
{
const QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
const double angle = startLine.angle();
drawArrow( painter, mPolygon.at( 0 ), angle );
}

break;
}

case MarkerMode::SvgMarker:
{
// calculate angle at start of line
const QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
QLineF startLine;
if ( mVersion == 2 )
{
startLine = QLineF( mPolygon.at( 1 ), mPolygon.at( 0 ) );
}
else if ( mVersion == 1 )
{
startLine = QLineF( mPolygon.at( 0 ), mPolygon.at( 1 ) );
}
const double angle = startLine.angle();
drawSvgMarker( painter, mPolygon.at( 0 ), angle, mStartMarkerFile, mStartArrowHeadHeight );
break;
Expand All @@ -171,7 +195,7 @@ void QgsLayoutItemPolyline::drawEndMarker( QPainter *painter )
const QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
const double angle = endLine.angle();

//move end point depending on arrow width
// move end point depending on arrow width
const QVector2D dir = QVector2D( endLine.dx(), endLine.dy() ).normalized();
QPointF endPoint = endLine.p2();
endPoint += ( dir * 0.5 * mArrowHeadWidth ).toPointF();
Expand Down Expand Up @@ -451,6 +475,7 @@ bool QgsLayoutItemPolyline::writePropertiesToElement( QDomElement &elmt, QDomDoc
elmt.setAttribute( u"startMarkerMode"_s, mStartMarker );
elmt.setAttribute( u"startMarkerFile"_s, startMarkerPath );
elmt.setAttribute( u"endMarkerFile"_s, endMarkerPath );
elmt.setAttribute( u"version"_s, mVersion );

return true;
}
Expand All @@ -468,6 +493,7 @@ bool QgsLayoutItemPolyline::readPropertiesFromElement( const QDomElement &elmt,
setEndSvgMarkerPath( QgsSymbolLayerUtils::svgSymbolNameToPath( endMarkerPath, context.pathResolver() ) );
mEndMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( u"markerMode"_s, u"0"_s ).toInt() );
mStartMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( u"startMarkerMode"_s, u"0"_s ).toInt() );
mVersion = elmt.attribute( u"version"_s, u"1"_s ).toInt();

QgsLayoutNodesItem::readPropertiesFromElement( elmt, doc, context );

Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutitempolyline.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ class CORE_EXPORT QgsLayoutItemPolyline : public QgsLayoutNodesItem
QColor mArrowHeadStrokeColor = Qt::black;
QColor mArrowHeadFillColor = Qt::black;

int mVersion = 2;

//! Create a default symbol.
void createDefaultPolylineStyleSymbol();

Expand Down
154 changes: 154 additions & 0 deletions tests/src/python/test_qgslayoutpolyline.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
__copyright__ = "Copyright 2016, The QGIS Project"

import unittest
from pathlib import Path

from qgis.core import (
Qgis,
Expand Down Expand Up @@ -149,6 +150,110 @@ def testEndArrow(self):
)
self.polyline.setEndMarker(QgsLayoutItemPolyline.MarkerMode.NoMarker)

def testStartArrowVersion1(self):
project = QgsProject()
layout = QgsLayout(project)
layout.initializeDefaults()

polygon = QPolygonF()
polygon.append(QPointF(100.0, 50.0))
polygon.append(QPointF(200.0, 100.0))
layout_polyline = QgsLayoutItemPolyline(polygon, layout)

document = QDomDocument("testdoc")
root = document.createElement("test")
self.assertTrue(layout_polyline.writeXml(root, document, QgsReadWriteContext()))
element = root.firstChildElement()
self.assertFalse(element.isNull())
element.setAttribute("version", "1")
layout_polyline_v1 = QgsLayoutItemPolyline(layout)
self.assertTrue(
layout_polyline_v1.readXml(
root.firstChildElement(), document, QgsReadWriteContext()
)
)

layout.addLayoutItem(layout_polyline_v1)

properties = {"color": "0,0,0,255", "width": "10.0", "capstyle": "square"}
style = QgsLineSymbol.createSimple(properties)
layout_polyline_v1.setSymbol(style)

layout_polyline_v1.setStartMarker(QgsLayoutItemPolyline.MarkerMode.ArrowHead)
layout_polyline_v1.setArrowHeadWidth(30.0)

self.assertTrue(
self.render_layout_check("composerpolyline_startArrowVersion1", layout)
)

def testStartArrowVersion2(self):
project = QgsProject()
layout = QgsLayout(project)
layout.initializeDefaults()

polygon = QPolygonF()
polygon.append(QPointF(100.0, 50.0))
polygon.append(QPointF(200.0, 100.0))
layout_polyline = QgsLayoutItemPolyline(polygon, layout)
layout.addLayoutItem(layout_polyline)

properties = {"color": "0,0,0,255", "width": "10.0", "capstyle": "square"}
style = QgsLineSymbol.createSimple(properties)
layout_polyline.setSymbol(style)

layout_polyline.setStartMarker(QgsLayoutItemPolyline.MarkerMode.ArrowHead)
layout_polyline.setArrowHeadWidth(30.0)

self.assertTrue(
self.render_layout_check("composerpolyline_startArrowVersion2", layout)
)

def testBothArrows(self):
project = QgsProject()
layout = QgsLayout(project)
layout.initializeDefaults()

polygon = QPolygonF()
polygon.append(QPointF(100.0, 50.0))
polygon.append(QPointF(200.0, 100.0))
layout_polyline = QgsLayoutItemPolyline(polygon, layout)
layout.addLayoutItem(layout_polyline)

properties = {"color": "0,0,0,255", "width": "10.0", "capstyle": "square"}
style = QgsLineSymbol.createSimple(properties)
layout_polyline.setSymbol(style)

layout_polyline.setStartMarker(QgsLayoutItemPolyline.MarkerMode.ArrowHead)
layout_polyline.setEndMarker(QgsLayoutItemPolyline.MarkerMode.ArrowHead)
layout_polyline.setArrowHeadWidth(30.0)

self.assertTrue(self.render_layout_check("composerpolyline_bothArrows", layout))

def testBothSvgMarkers(self):
project = QgsProject()
layout = QgsLayout(project)
layout.initializeDefaults()

polygon = QPolygonF()
polygon.append(QPointF(100.0, 50.0))
polygon.append(QPointF(200.0, 100.0))
layout_polyline = QgsLayoutItemPolyline(polygon, layout)
layout.addLayoutItem(layout_polyline)

properties = {"color": "0,0,0,255", "width": "0.5", "capstyle": "square"}
style = QgsLineSymbol.createSimple(properties)
layout_polyline.setSymbol(style)

svg_path = Path(TEST_DATA_DIR) / "test_symbol_svg.svg"
layout_polyline.setStartMarker(QgsLayoutItemPolyline.MarkerMode.SvgMarker)
layout_polyline.setStartSvgMarkerPath(svg_path.as_posix())
layout_polyline.setEndMarker(QgsLayoutItemPolyline.MarkerMode.SvgMarker)
layout_polyline.setEndSvgMarkerPath(svg_path.as_posix())

self.assertTrue(
self.render_layout_check("composerpolyline_bothSvgMarkers", layout)
)

def testRemoveNode(self):
"""Test removeNode method"""

Expand Down Expand Up @@ -274,6 +379,55 @@ def testReadWriteXml(self):
self.assertEqual(shape2.nodes(), shape.nodes())
self.assertEqual(shape2.symbol().symbolLayer(0).color().name(), "#ff0000")

def testSavingLoadingArrowVersions(self):
project = QgsProject()
layout = QgsLayout(project)
polygon = QPolygonF()
polygon.append(QPointF(0.0, 0.0))
polygon.append(QPointF(100.0, 0.0))

def _write_xml(polyline):
document = QDomDocument("testdoc")
root = document.createElement("test")
self.assertTrue(polyline.writeXml(root, document, QgsReadWriteContext()))
element = root.firstChildElement()
self.assertFalse(element.isNull())
return element, document

def _check_version(polyline, version):
element, document = _write_xml(polyline)
self.assertEqual(element.attribute("version"), version)
return element, document

# new polylines have version 2 by default
polyline_v2 = QgsLayoutItemPolyline(polygon, layout)
element, document = _check_version(polyline_v2, "2")

# load polyline from xml
restored_v2 = QgsLayoutItemPolyline(layout)
self.assertTrue(restored_v2.readXml(element, document, QgsReadWriteContext()))
_check_version(restored_v2, "2")

# save/load polyline version 1
element, document = _write_xml(restored_v2)
element.setAttribute("version", "1")
self.assertEqual(element.attribute("version"), "1")

polyline_v1 = QgsLayoutItemPolyline(layout)
self.assertTrue(polyline_v1.readXml(element, document, QgsReadWriteContext()))
_check_version(polyline_v1, "1")

# loading from an XML without "version" should result in version 1
element, document = _write_xml(polyline_v2)
element.removeAttribute("version")
self.assertFalse(element.hasAttribute("version"))

polyline_no_version = QgsLayoutItemPolyline(layout)
self.assertTrue(
polyline_no_version.readXml(element, document, QgsReadWriteContext())
)
_check_version(polyline_no_version, "1")

def testBounds(self):
pr = QgsProject()
l = QgsLayout(pr)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading