From 181bf3f360a8b4651d1a75d6bd7c701dcdb2817e Mon Sep 17 00:00:00 2001 From: Abdelbaki Boukerche Date: Thu, 4 Aug 2022 18:20:54 +0100 Subject: [PATCH 1/2] Rounded Rectangle --- gns3/dialogs/style_editor_dialog.py | 4 +++- gns3/dialogs/style_editor_dialog_link.py | 4 +++- gns3/items/rectangle_item.py | 7 +++++-- gns3/items/shape_item.py | 5 +++++ gns3/ui/style_editor_dialog_ui.py | 20 ++++++++++++++++++-- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/gns3/dialogs/style_editor_dialog.py b/gns3/dialogs/style_editor_dialog.py index 83c819244..0c9bf8b4d 100644 --- a/gns3/dialogs/style_editor_dialog.py +++ b/gns3/dialogs/style_editor_dialog.py @@ -70,6 +70,7 @@ def __init__(self, parent, items): self._border_color.green(), self._border_color.blue(), self._border_color.alpha())) + self.uiBorderRadiusSpinBox.setValue(int(first_item._borderRadius)) self.uiRotationSpinBox.setValue(int(first_item.rotation())) self.uiBorderWidthSpinBox.setValue(pen.width()) index = self.uiBorderStyleComboBox.findData(pen.style()) @@ -116,10 +117,11 @@ def _applyPreferencesSlot(self): for item in self._items: item.setPen(pen) - # on multiselection it's possible to select many type of items + # on multi-selection it's possible to select many type of items # but brush can be applied only on ShapeItem, if brush and isinstance(item, ShapeItem): item.setBrush(brush) + item.setBorderRadius(self.uiBorderRadiusSpinBox.value()) item.setRotation(self.uiRotationSpinBox.value()) def done(self, result): diff --git a/gns3/dialogs/style_editor_dialog_link.py b/gns3/dialogs/style_editor_dialog_link.py index beeeeddd5..63bd91b5f 100644 --- a/gns3/dialogs/style_editor_dialog_link.py +++ b/gns3/dialogs/style_editor_dialog_link.py @@ -54,7 +54,9 @@ def __init__(self, link, parent): self.uiColorLabel.hide() self.uiColorPushButton.hide() self._color = None - + + self.uiBorderRadiusLabel.hide() + self.uiBorderRadiusSpinBox.hide() self.uiRotationLabel.hide() self.uiRotationSpinBox.hide() diff --git a/gns3/items/rectangle_item.py b/gns3/items/rectangle_item.py index ba94b04ea..8b8a55480 100644 --- a/gns3/items/rectangle_item.py +++ b/gns3/items/rectangle_item.py @@ -42,8 +42,9 @@ def paint(self, painter, option, widget=None): :param option: QStyleOptionGraphicsItem instance :param widget: QWidget instance """ - - super().paint(painter, option, widget) + painter.setPen(self.pen()) + painter.setBrush(self.brush()) + painter.drawRoundedRect(self.rect(), self._borderRadius, self._borderRadius) self.drawLayerInfo(painter) def toSvg(self): @@ -53,10 +54,12 @@ def toSvg(self): svg = ET.Element("svg") svg.set("width", str(int(self.rect().width()))) svg.set("height", str(int(self.rect().height()))) + svg.set("border_radius", str(int(self._borderRadius))) rect = ET.SubElement(svg, "rect") rect.set("width", str(int(self.rect().width()))) rect.set("height", str(int(self.rect().height()))) + rect.set("border_radius", str(int(self._borderRadius))) rect = self._styleSvg(rect) diff --git a/gns3/items/shape_item.py b/gns3/items/shape_item.py index 4a51ffb26..71cbbcba5 100644 --- a/gns3/items/shape_item.py +++ b/gns3/items/shape_item.py @@ -41,6 +41,7 @@ def __init__(self, width=200, height=200, svg=None, **kws): self._border = 5 self._edge = None self._originally_movable = True + self._borderRadius = 0 if svg is None: self.setRect(0, 0, width, height) @@ -53,6 +54,9 @@ def __init__(self, width=200, height=200, svg=None, **kws): if self._id is None: self.create() + def setBorderRadius(self, radius: int): + self._borderRadius = radius + def mousePressEvent(self, event): """ Handles all mouse press events. @@ -178,6 +182,7 @@ def fromSvg(self, svg): svg = ET.fromstring(svg) width = float(svg.get("width", self.rect().width())) height = float(svg.get("height", self.rect().height())) + self._borderRadius = int(svg.get("border_radius", 0)) self.setRect(0, 0, width, height) pen = QtGui.QPen() diff --git a/gns3/ui/style_editor_dialog_ui.py b/gns3/ui/style_editor_dialog_ui.py index 189e9e441..84745c86a 100644 --- a/gns3/ui/style_editor_dialog_ui.py +++ b/gns3/ui/style_editor_dialog_ui.py @@ -50,9 +50,24 @@ def setupUi(self, StyleEditorDialog): self.uiBorderStyleComboBox = QtWidgets.QComboBox(self.uiStyleSettingsGroupBox) self.uiBorderStyleComboBox.setObjectName("uiBorderStyleComboBox") self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox) + # + self.uiBorderRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox) + self.uiBorderRadiusLabel.setObjectName("uiBorderRadiusLabel") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiBorderRadiusLabel) + self.uiBorderRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.uiBorderRadiusSpinBox.sizePolicy().hasHeightForWidth()) + self.uiBorderRadiusSpinBox.setSizePolicy(sizePolicy) + self.uiBorderRadiusSpinBox.setMinimum(0) + self.uiBorderRadiusSpinBox.setMaximum(100) + self.uiBorderRadiusSpinBox.setObjectName("uiBorderRadiusSpinBox") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiBorderRadiusSpinBox) + # self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox) self.uiRotationLabel.setObjectName("uiRotationLabel") - self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel) + self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel) self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -62,7 +77,7 @@ def setupUi(self, StyleEditorDialog): self.uiRotationSpinBox.setMinimum(-360) self.uiRotationSpinBox.setMaximum(360) self.uiRotationSpinBox.setObjectName("uiRotationSpinBox") - self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox) + self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox) self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox) self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog) self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal) @@ -86,6 +101,7 @@ def retranslateUi(self, StyleEditorDialog): self.uiBorderWidthLabel.setText(_translate("StyleEditorDialog", "Border width:")) self.uiBorderWidthSpinBox.setSuffix(_translate("StyleEditorDialog", " px")) self.uiBorderStyleLabel.setText(_translate("StyleEditorDialog", "Border style:")) + self.uiBorderRadiusLabel.setText(_translate("StyleEditorDialog", "Border Radius:")) self.uiRotationLabel.setText(_translate("StyleEditorDialog", "Rotation:")) self.uiRotationSpinBox.setToolTip(_translate("StyleEditorDialog", "Rotation can be ajusted on the scene for a selected item while\n" "editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)")) From eae9eec15b599308ed4d0e4b46d5296e4714f9e8 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 1 Aug 2023 15:34:30 +1000 Subject: [PATCH 2/2] Support for horizontal and vertical corner radius --- gns3/dialogs/style_editor_dialog.py | 16 ++++++++-- gns3/dialogs/style_editor_dialog_link.py | 4 +-- gns3/items/rectangle_item.py | 39 ++++++++++++++++++++++-- gns3/items/shape_item.py | 7 +---- gns3/ui/style_editor_dialog.ui | 29 ++++++++++++++++-- gns3/ui/style_editor_dialog_ui.py | 36 +++++++++------------- 6 files changed, 95 insertions(+), 36 deletions(-) diff --git a/gns3/dialogs/style_editor_dialog.py b/gns3/dialogs/style_editor_dialog.py index 0c9bf8b4d..45a16c7b5 100644 --- a/gns3/dialogs/style_editor_dialog.py +++ b/gns3/dialogs/style_editor_dialog.py @@ -22,6 +22,7 @@ from ..qt import QtCore, QtWidgets, QtGui from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog from ..items.shape_item import ShapeItem +from ..items.rectangle_item import RectangleItem class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog): @@ -70,7 +71,13 @@ def __init__(self, parent, items): self._border_color.green(), self._border_color.blue(), self._border_color.alpha())) - self.uiBorderRadiusSpinBox.setValue(int(first_item._borderRadius)) + if isinstance(first_item, RectangleItem): + # use the horizontal corner radius first and then the vertical one if it's not set + # maybe we allow configuring them separately in the future + corner_radius = first_item.horizontalCornerRadius() + if not corner_radius: + corner_radius = first_item.verticalCornerRadius() + self.uiCornerRadiusSpinBox.setValue(corner_radius) self.uiRotationSpinBox.setValue(int(first_item.rotation())) self.uiBorderWidthSpinBox.setValue(pen.width()) index = self.uiBorderStyleComboBox.findData(pen.style()) @@ -121,7 +128,12 @@ def _applyPreferencesSlot(self): # but brush can be applied only on ShapeItem, if brush and isinstance(item, ShapeItem): item.setBrush(brush) - item.setBorderRadius(self.uiBorderRadiusSpinBox.value()) + if isinstance(item, RectangleItem): + corner_radius = self.uiCornerRadiusSpinBox.value() + # use the corner radius for both horizontal (rx) and vertical (ry) + # maybe we support setting them separately in the future + item.setHorizontalCornerRadius(corner_radius) + item.setVerticalCornerRadius(corner_radius) item.setRotation(self.uiRotationSpinBox.value()) def done(self, result): diff --git a/gns3/dialogs/style_editor_dialog_link.py b/gns3/dialogs/style_editor_dialog_link.py index 69b431c3d..32e491cb9 100644 --- a/gns3/dialogs/style_editor_dialog_link.py +++ b/gns3/dialogs/style_editor_dialog_link.py @@ -55,8 +55,8 @@ def __init__(self, link, parent): self.uiColorPushButton.hide() self._color = None - self.uiBorderRadiusLabel.hide() - self.uiBorderRadiusSpinBox.hide() + self.uiCornerRadiusLabel.hide() + self.uiCornerRadiusSpinBox.hide() self.uiRotationLabel.hide() self.uiRotationSpinBox.hide() diff --git a/gns3/items/rectangle_item.py b/gns3/items/rectangle_item.py index 8b8a55480..7805b6986 100644 --- a/gns3/items/rectangle_item.py +++ b/gns3/items/rectangle_item.py @@ -32,8 +32,22 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem): """ def __init__(self, width=200, height=100, **kws): + self._rx = 0 + self._ry = 0 super().__init__(width=width, height=height, **kws) + def setHorizontalCornerRadius(self, radius: int): + self._rx = radius + + def horizontalCornerRadius(self): + return self._rx + + def setVerticalCornerRadius(self, radius: int): + self._ry = radius + + def verticalCornerRadius(self): + return self._ry + def paint(self, painter, option, widget=None): """ Paints the contents of an item in local coordinates. @@ -42,9 +56,10 @@ def paint(self, painter, option, widget=None): :param option: QStyleOptionGraphicsItem instance :param widget: QWidget instance """ + painter.setPen(self.pen()) painter.setBrush(self.brush()) - painter.drawRoundedRect(self.rect(), self._borderRadius, self._borderRadius) + painter.drawRoundedRect(self.rect(), self._rx, self._ry) self.drawLayerInfo(painter) def toSvg(self): @@ -54,13 +69,31 @@ def toSvg(self): svg = ET.Element("svg") svg.set("width", str(int(self.rect().width()))) svg.set("height", str(int(self.rect().height()))) - svg.set("border_radius", str(int(self._borderRadius))) rect = ET.SubElement(svg, "rect") rect.set("width", str(int(self.rect().width()))) rect.set("height", str(int(self.rect().height()))) - rect.set("border_radius", str(int(self._borderRadius))) + if self._rx: + rect.set("rx", str(self._rx)) + if self._ry: + rect.set("ry", str(self._ry)) rect = self._styleSvg(rect) return ET.tostring(svg, encoding="utf-8").decode("utf-8") + + def fromSvg(self, svg): + svg_elem = ET.fromstring(svg) + if len(svg_elem): + # handle horizontal corner radius and vertical corner radius (specific to rectangles) + rx = svg_elem[0].get("rx") + ry = svg_elem[0].get("ry") + if rx: + self._rx = int(rx) + elif ry: + self._rx = int(ry) # defaults to ry if it is specified + if ry: + self._ry = int(ry) + elif rx: + self._ry = int(rx) # defaults to rx if it is specified + super().fromSvg(svg) diff --git a/gns3/items/shape_item.py b/gns3/items/shape_item.py index 71cbbcba5..a0f04873d 100644 --- a/gns3/items/shape_item.py +++ b/gns3/items/shape_item.py @@ -41,7 +41,6 @@ def __init__(self, width=200, height=200, svg=None, **kws): self._border = 5 self._edge = None self._originally_movable = True - self._borderRadius = 0 if svg is None: self.setRect(0, 0, width, height) @@ -54,9 +53,6 @@ def __init__(self, width=200, height=200, svg=None, **kws): if self._id is None: self.create() - def setBorderRadius(self, radius: int): - self._borderRadius = radius - def mousePressEvent(self, event): """ Handles all mouse press events. @@ -177,12 +173,11 @@ def hoverLeaveEvent(self, event): def fromSvg(self, svg): """ - Import element informations from an SVG + Import element information from SVG """ svg = ET.fromstring(svg) width = float(svg.get("width", self.rect().width())) height = float(svg.get("height", self.rect().height())) - self._borderRadius = int(svg.get("border_radius", 0)) self.setRect(0, 0, width, height) pen = QtGui.QPen() diff --git a/gns3/ui/style_editor_dialog.ui b/gns3/ui/style_editor_dialog.ui index 34c3b77c6..7cacc2bf6 100755 --- a/gns3/ui/style_editor_dialog.ui +++ b/gns3/ui/style_editor_dialog.ui @@ -2,6 +2,14 @@ StyleEditorDialog + + + 0 + 0 + 270 + 294 + + Style editor @@ -76,14 +84,14 @@ - + Rotation: - + @@ -106,6 +114,23 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M) + + + + Corner radius: + + + + + + + ° + + + 100 + + + diff --git a/gns3/ui/style_editor_dialog_ui.py b/gns3/ui/style_editor_dialog_ui.py index 84745c86a..2c09265af 100644 --- a/gns3/ui/style_editor_dialog_ui.py +++ b/gns3/ui/style_editor_dialog_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/style_editor_dialog.ui' # -# Created by: PyQt5 UI code generator 5.15.2 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,6 +14,7 @@ class Ui_StyleEditorDialog(object): def setupUi(self, StyleEditorDialog): StyleEditorDialog.setObjectName("StyleEditorDialog") + StyleEditorDialog.resize(270, 294) StyleEditorDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog) self.verticalLayout.setObjectName("verticalLayout") @@ -50,24 +51,9 @@ def setupUi(self, StyleEditorDialog): self.uiBorderStyleComboBox = QtWidgets.QComboBox(self.uiStyleSettingsGroupBox) self.uiBorderStyleComboBox.setObjectName("uiBorderStyleComboBox") self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox) - # - self.uiBorderRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox) - self.uiBorderRadiusLabel.setObjectName("uiBorderRadiusLabel") - self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiBorderRadiusLabel) - self.uiBorderRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.uiBorderRadiusSpinBox.sizePolicy().hasHeightForWidth()) - self.uiBorderRadiusSpinBox.setSizePolicy(sizePolicy) - self.uiBorderRadiusSpinBox.setMinimum(0) - self.uiBorderRadiusSpinBox.setMaximum(100) - self.uiBorderRadiusSpinBox.setObjectName("uiBorderRadiusSpinBox") - self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiBorderRadiusSpinBox) - # self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox) self.uiRotationLabel.setObjectName("uiRotationLabel") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel) + self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel) self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -77,7 +63,14 @@ def setupUi(self, StyleEditorDialog): self.uiRotationSpinBox.setMinimum(-360) self.uiRotationSpinBox.setMaximum(360) self.uiRotationSpinBox.setObjectName("uiRotationSpinBox") - self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox) + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox) + self.uiCornerRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox) + self.uiCornerRadiusLabel.setObjectName("uiCornerRadiusLabel") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel) + self.uiCornerRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox) + self.uiCornerRadiusSpinBox.setMaximum(100) + self.uiCornerRadiusSpinBox.setObjectName("uiCornerRadiusSpinBox") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox) self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox) self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog) self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal) @@ -88,8 +81,8 @@ def setupUi(self, StyleEditorDialog): self.verticalLayout.addItem(spacerItem) self.retranslateUi(StyleEditorDialog) - self.uiButtonBox.accepted.connect(StyleEditorDialog.accept) - self.uiButtonBox.rejected.connect(StyleEditorDialog.reject) + self.uiButtonBox.accepted.connect(StyleEditorDialog.accept) # type: ignore + self.uiButtonBox.rejected.connect(StyleEditorDialog.reject) # type: ignore QtCore.QMetaObject.connectSlotsByName(StyleEditorDialog) def retranslateUi(self, StyleEditorDialog): @@ -101,9 +94,10 @@ def retranslateUi(self, StyleEditorDialog): self.uiBorderWidthLabel.setText(_translate("StyleEditorDialog", "Border width:")) self.uiBorderWidthSpinBox.setSuffix(_translate("StyleEditorDialog", " px")) self.uiBorderStyleLabel.setText(_translate("StyleEditorDialog", "Border style:")) - self.uiBorderRadiusLabel.setText(_translate("StyleEditorDialog", "Border Radius:")) self.uiRotationLabel.setText(_translate("StyleEditorDialog", "Rotation:")) self.uiRotationSpinBox.setToolTip(_translate("StyleEditorDialog", "Rotation can be ajusted on the scene for a selected item while\n" "editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)")) self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°")) + self.uiCornerRadiusLabel.setText(_translate("StyleEditorDialog", "Corner radius:")) + self.uiCornerRadiusSpinBox.setSuffix(_translate("StyleEditorDialog", "°")) from . import resources_rc