mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
317 lines
11 KiB
Python
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
|