Files
gns3-gui/gns3/items/drawing_item.py

317 lines
11 KiB
Python

#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..qt import QtCore, QtWidgets, qslot, QtGui
from .utils import colorFromSvg
import uuid
import logging
import binascii
log = logging.getLogger(__name__)
class DrawingItem:
# Map QT stroke to SVG style
QT_DASH_TO_SVG = {
QtCore.Qt.PenStyle.SolidLine: "",
QtCore.Qt.PenStyle.NoPen: None,
QtCore.Qt.PenStyle.DashLine: "25, 25",
QtCore.Qt.PenStyle.DotLine: "5, 25",
QtCore.Qt.PenStyle.DashDotLine: "5, 25, 25",
QtCore.Qt.PenStyle.DashDotDotLine: "25, 25, 5, 25, 5"
}
show_layer = False
"""
Base class for non emulation item
"""
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
self._id = drawing_id
self._deleting = False
self._allow_snap_to_grid = True
self._locked = locked
if self._id is None:
self._id = str(uuid.uuid4())
self.setFlags(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsFocusable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
from ..main_window import MainWindow
self._graphics_view = MainWindow.instance().uiGraphicsView
self._main_window = MainWindow.instance()
self._project = project
# Store a hash of the SVG to avoid him
# to be sent if he doesn't change
self._hash_svg = None
if pos:
self.setPos(pos)
if z:
self.setZValue(z)
if rotation:
self.setRotation(rotation)
self.setLocked(locked)
def drawing_id(self):
return self._id
def create(self):
if self._project:
self._project.post("/drawings", self._createDrawingCallback, body=self.__json__())
def _createDrawingCallback(self, result, error=False, **kwargs):
"""
Callback for create.
:param result: server response
:param error: indicates an error (boolean)
:returns: Boolean success or not
"""
if error:
log.error("Error while creating drawing: {}".format(result["message"]))
return False
self._id = result["drawing_id"]
self.updateDrawingCallback(result)
def updateDrawing(self):
if self._id and not self.deleting() and self._project:
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), showProgress=False)
@qslot
def updateDrawingCallback(self, result, error=False, **kwargs):
"""
Callback for update.
:param result: server response
:param error: indicates an error (boolean)
:returns: Boolean success or not
"""
if error:
if "doesn't exist" in result.get("message", ""):
log.warning("Drawing not found on server, recreating: {}".format(self._id))
self._id = None
self.create()
return True
log.error("Error while updating drawing: {}".format(result["message"]))
return False
self.setPos(QtCore.QPointF(result["x"], result["y"]))
self.setZValue(result["z"])
self.setLocked(result["locked"])
self.setRotation(result["rotation"])
if "svg" in result:
self.fromSvg(result["svg"])
def handleKeyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
:return: Boolean True the event has been captured
"""
key = event.key()
modifiers = event.modifiers()
if key in (QtCore.Qt.Key.Key_P, QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Equal) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Plus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
if self.rotation() == 0:
self.setRotation(359)
else:
self.setRotation(self.rotation() - 1)
return True
elif key in (QtCore.Qt.Key.Key_M, QtCore.Qt.Key.Key_Minus) and modifiers & QtCore.Qt.KeyboardModifier.AltModifier \
or key == QtCore.Qt.Key.Key_Minus and modifiers & QtCore.Qt.KeyboardModifier.AltModifier and modifiers & QtCore.Qt.KeyboardModifier.KeypadModifier:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
return True
elif modifiers & QtCore.Qt.KeyboardModifier.AltModifier:
self._allow_snap_to_grid = False
return True
return False
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
if not self.handleKeyPressEvent(event):
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
"""
Handles all key release events
:param event: QKeyEvent
"""
self._allow_snap_to_grid = True
def __json__(self):
data = {
"drawing_id": self._id,
"x": int(self.pos().x()),
"y": int(self.pos().y()),
"z": int(self.zValue()),
"locked": self._locked,
"rotation": int(self.rotation())
}
svg = self.toSvg()
hash_svg = binascii.crc32(svg.encode())
if hash_svg != self._hash_svg:
data["svg"] = svg
self._hash_svg = hash_svg
return data
def locked(self):
"""
Is the drawing locked
"""
return self._locked
def setLocked(self, locked):
"""
Sets the locked value.
:param value: Z value
"""
if locked is True:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
else:
self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
self._locked = locked
def deleting(self):
"""
Is the drawing being deleted
"""
return self._deleting
def setDeleting(self):
"""
Mark this drawing as being deleted
"""
self._deleting = True
def delete(self, skip_controller=False):
"""
Deletes this drawing.
:param skip_controller: Do not replicate change on the controller (useful when it's already deleted on controller)
"""
self.setDeleting()
self.scene().removeItem(self)
from ..topology import Topology
Topology.instance().removeDrawing(self)
if self._id and not skip_controller:
self._project.delete("/drawings/" + self._id, None)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
and self._allow_snap_to_grid:
grid_size = self._graphics_view.drawingGridSize()
mid_x = self.boundingRect().width() / 2
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
mid_y = self.boundingRect().height() / 2
value.setY((grid_size * round((value.y()+mid_y)/grid_size)) - mid_y)
if change == QtWidgets.QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
if not value:
self.updateDrawing()
return QtWidgets.QGraphicsItem.itemChange(self, change, value)
def updateNode(self):
self.updateDrawing()
def drawLayerInfo(self, painter):
"""
Draws the layer position.
:param painter: QPainter instance
"""
if self.show_layer is False:
return
brect = self.boundingRect()
# don't draw anything if the object is too small
if brect.width() < 20 or brect.height() < 20:
return
center = self.mapFromItem(self, brect.width() / 2.0, brect.height() / 2.0)
painter.setBrush(QtCore.Qt.GlobalColor.red)
painter.setPen(QtCore.Qt.GlobalColor.red)
painter.drawRect(QtCore.QRectF((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20))
painter.setPen(QtCore.Qt.GlobalColor.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)
def _styleSvg(self, element):
"""
Add style from the shape item to the SVG element that we will
export
"""
style = ""
pen = self.pen()
if hasattr(self, "brush"): # Line don't have a brush
element.set("fill", "#{}".format(hex(self.brush().color().rgba())[4:]))
element.set("fill-opacity", str(self.brush().color().alphaF()))
dasharray = self.QT_DASH_TO_SVG[pen.style()]
if dasharray is None: # No border to the element
return element
elif dasharray == "":
pass # Solid line
else:
element.set("stroke-dasharray", dasharray)
element.set("stroke-width", str(pen.width()))
element.set("stroke", "#" + hex(pen.color().rgba())[4:])
return element
def _penFromSVGElement(self, svg):
"""
Get a pen from a SVG element
:param svg:
"""
pen = QtGui.QPen()
if svg.get("stroke-width"):
pen.setWidth(int(svg.get("stroke-width")))
if svg.get("stroke"):
pen.setColor(colorFromSvg(svg.get("stroke")))
# Map SVG stroke style (border of the element to the Qt version)
if not svg.get("stroke"):
pen.setStyle(QtCore.Qt.PenStyle.NoPen)
else:
pen.setStyle(QtCore.Qt.PenStyle.SolidLine)
stroke = svg.get("stroke-dasharray")
if stroke:
for (qt_stroke, svg_stroke) in self.QT_DASH_TO_SVG.items():
if svg_stroke == stroke:
pen.setStyle(qt_stroke)
return pen