Send image to the API as SVG element

This commit is contained in:
Julien Duponchelle
2016-06-22 17:47:17 +02:00
parent 8c349e4669
commit 9cb4eb775b
10 changed files with 364 additions and 337 deletions

View File

@@ -53,11 +53,11 @@ from .items.serial_link_item import SerialLinkItem
# other items
from .items.note_item import NoteItem
from .items.shape_item import ShapeItem
from .items.visual_item import VisualItem
from .items.rectangle_item import RectangleItem
from .items.ellipse_item import EllipseItem
from .items.image_item import ImageItem
from .items.pixmap_image_item import PixmapImageItem
from .items.svg_image_item import SvgImageItem
log = logging.getLogger(__name__)
@@ -231,25 +231,17 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._adding_ellipse = False
self.setCursor(QtCore.Qt.ArrowCursor)
def addImage(self, image, image_path):
def addImage(self,image_path):
"""
Adds an image.
:param image: QPixmap or QSvgRenderer instance
:param image_path: path to the image
"""
if isinstance(image, QtSvg.QSvgRenderer):
# use a SVG image item if this is a valid SVG file
image_item = SvgImageItem(image, image_path)
else:
image_item = PixmapImageItem(image, image_path)
# center the image on the scene
x = image_item.pos().x() - (image_item.boundingRect().width() / 2)
y = image_item.pos().y() - (image_item.boundingRect().height() / 2)
image_item.setPos(x, y)
image_item = ImageItem(image_path=image_path, project=self._main_window.projectManager().project())
self.scene().addItem(image_item)
self._topology.addImage(image_item)
self._topology.addShape(image_item)
def addLink(self, source_node, source_port, destination_node, destination_port, link_id=None):
"""
@@ -484,13 +476,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._adding_note = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_rectangle:
pos = self.mapToScene(event.pos())
self.createShapeItem("rect", pos.x(), pos.y(), 0)
self.createVisualItem("rect", pos.x(), pos.y(), 0)
self._main_window.uiDrawRectangleAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_rectangle = False
elif event.button() == QtCore.Qt.LeftButton and self._adding_ellipse:
pos = self.mapToScene(event.pos())
self.createShapeItem("ellipse", pos.x(), pos.y(), 0)
self.createVisualItem("ellipse", pos.x(), pos.y(), 0)
self._main_window.uiDrawEllipseAction.setChecked(False)
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_ellipse = False
@@ -809,7 +801,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
reload_action.triggered.connect(self.reloadActionSlot)
menu.addAction(reload_action)
if True in list(map(lambda item: isinstance(item, NoteItem) or isinstance(item, ShapeItem) or isinstance(item, ImageItem), items)):
if True in list(map(lambda item: isinstance(item, NoteItem) or isinstance(item, VisualItem), items)):
duplicate_action = QtWidgets.QAction("Duplicate", menu)
duplicate_action.setIcon(QtGui.QIcon(':/icons/new.svg'))
duplicate_action.triggered.connect(self.duplicateActionSlot)
@@ -1519,11 +1511,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._topology.addNode(node)
return node_item
def createShapeItem(self, type, x, y, z, rotation=0, svg=None, shape_id=None):
def createVisualItem(self, type, x, y, z, rotation=0, svg=None, shape_id=None):
if type == "ellipse":
item = EllipseItem(pos=QtCore.QPoint(x, y), rotation=rotation, project=self._main_window.projectManager().project(), shape_id=shape_id, svg=svg)
elif type == "rect":
item = RectangleItem(pos=QtCore.QPoint(x, y), rotation=rotation, project=self._main_window.projectManager().project(), shape_id=shape_id, svg=svg)
elif type == "image":
item = ImageItem(pos=QtCore.QPoint(x, y), rotation=rotation, project=self._main_window.projectManager().project(), shape_id=shape_id, svg=svg)
self.scene().addItem(item)
self._topology.addShape(item)

View File

@@ -19,82 +19,58 @@
Graphical representation of an image on the QGraphicsScene.
"""
from ..qt import QtWidgets, QtCore
import xml.etree.ElementTree as ET
from ..qt import QtWidgets, QtCore, QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .visual_item import VisualItem
class ImageItem():
class ImageItem(QtSvg.QGraphicsSvgItem, VisualItem):
"""
Class to insert an image on the scene.
"""
show_layer = False
def __init__(self, image_path, pos=None):
def __init__(self, image_path=None, pos=None, svg=None, **kws):
self.setFlags(self.ItemIsMovable | self.ItemIsSelectable)
self._image_path = image_path
if pos:
self.setPos(pos)
super().__init__(pos=pos, **kws)
def filePath(self):
"""
Return image file
"""
return self._image_path
if self._image_path:
renderer = QImageSvgRenderer(image_path)
self.setSharedRenderer(renderer)
def delete(self):
# By default center the image
if pos is None:
x = self.pos().x() - (self.boundingRect().width() / 2)
y = self.pos().y() - (self.boundingRect().height() / 2)
self.setPos(x, y)
if svg:
svg = self.fromSvg(svg)
if self._id is None:
self.create()
def fromSvg(self, svg):
renderer = QImageSvgRenderer(svg)
self.setSharedRenderer(renderer)
def toSvg(self):
"""
Deletes this image item.
Return an SVG version of the shape
"""
return self.renderer().svg()
def duplicate(self):
"""
Duplicates this image item.
:return: SvgImageItem instance
"""
self.scene().removeItem(self)
from ..topology import Topology
try:
Topology.instance().removeImage(self)
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Image", "Cannot delete the image: {}".format(str(e)))
def paint(self, painter, option, widget=None):
"""
Paints the contents of an item in local coordinates.
:param painter: QPainter instance
:param option: QStyleOptionGraphicsItem instance
:param widget: QWidget instance
"""
super().paint(painter, option, widget)
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.red)
painter.setPen(QtCore.Qt.red)
painter.drawRect((brect.width() / 2.0) - 10, (brect.height() / 2.0) - 10, 20, 20)
painter.setPen(QtCore.Qt.black)
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)
def setZValue(self, value):
"""
Sets a new Z value.
:param value: Z value
"""
super().setZValue(value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)
else:
self.setFlag(self.ItemIsSelectable, True)
self.setFlag(self.ItemIsMovable, True)
image_item = ImageItem(self.renderer(), self._image_path, pos=QtCore.QPointF(self.x() + 20, self.y() + 20))
image_item.setZValue(self.zValue())
return image_item

View File

@@ -21,12 +21,14 @@ Base class for shape items (Rectangle, ellipse etc.).
import xml.etree.ElementTree as ET
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from .visual_item import VisualItem
import logging
log = logging.getLogger(__name__)
class ShapeItem:
class ShapeItem(VisualItem):
# Map QT stroke to SVG style
QT_DASH_TO_SVG = {
@@ -42,29 +44,16 @@ class ShapeItem:
"""
Base class to draw shapes on the scene.
"""
show_layer = False
def __init__(self, project=None, pos=None, shape_id=None, svg=None, width=200, height=200, z=0, rotation=0, **kws):
self._id = shape_id
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable)
def __init__(self, width=200, height=200, svg=None, **kws):
super().__init__(svg=svg, **kws)
self.setAcceptHoverEvents(True)
self._border = 5
self._edge = None
from ..main_window import MainWindow
self._graphics_view = MainWindow.instance().uiGraphicsView
self._project = project
if pos:
self.setPos(pos)
if z:
self.setZValue(z)
if rotation:
self.setRotation(rotation)
if svg is None:
self.setRect(0, 0, width, height)
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
@@ -74,67 +63,7 @@ class ShapeItem:
else:
self.fromSvg(svg)
if self._id is None:
self._project.post("/shapes", self._createShapeCallback, body=self.__json__())
def shape_id(self):
return self._id
def _createShapeCallback(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 setting up shape: {}".format(result["message"]))
return False
self._id = result["shape_id"]
def updateShape(self):
if self._id:
self._project.put("/shapes/" + self._id, self.updateShapeCallback, body=self.__json__())
def updateShapeCallback(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:
log.error("Error while setting up shape: {}".format(result["message"]))
return False
self.setPos(QtCore.QPoint(result["x"], result["y"]))
self.setZValue(result["z"])
self.setRotation(result["rotation"])
self.fromSvg(result["svg"])
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
key = event.key()
modifiers = event.modifiers()
if key in (QtCore.Qt.Key_P, QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Plus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() == 0:
self.setRotation(359)
else:
self.setRotation(self.rotation() - 1)
elif key in (QtCore.Qt.Key_M, QtCore.Qt.Key_Minus) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Minus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
else:
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
self.create()
def mousePressEvent(self, event):
"""
@@ -254,19 +183,6 @@ class ShapeItem:
if self.zValue() >= 0:
self._graphics_view.setCursor(QtCore.Qt.ArrowCursor)
def delete(self, skip_controller=False):
"""
Deletes this shape.
:param skip_controller: Do not replicate change on the controller (usefull when it's already deleted on controller
"""
self.scene().removeItem(self)
from ..topology import Topology
Topology.instance().removeShape(self)
if self._id and not skip_controller:
self._project.delete("/shapes/" + self._id, None, body=self.__json__())
def drawLayerInfo(self, painter):
"""
Draws the layer position.
@@ -290,27 +206,6 @@ class ShapeItem:
zval = str(int(self.zValue()))
painter.drawText(QtCore.QPointF(center.x() - 4, center.y() + 4), zval)
def setZValue(self, value):
"""
Sets a new Z value.
:param value: Z value
"""
QtWidgets.QGraphicsItem.setZValue(self, value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)
else:
self.setFlag(self.ItemIsSelectable, True)
self.setFlag(self.ItemIsMovable, True)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if not value:
self.updateShape()
return QtWidgets.QGraphicsItem.itemChange(self, change, value)
def _styleSvg(self, element):
"""
Add style from the shape item to the SVG element that we will
@@ -333,22 +228,6 @@ class ShapeItem:
element.set("style", style)
return element
def __json__(self):
return {
"x": int(self.pos().x()),
"y": int(self.pos().y()),
"z": int(self.zValue()),
"rotation": int(self.rotation()),
"svg": self.toSvg()
}
def _colorFromSvg(self, value):
value = value.strip('#')
if len(value) == 6: # If alpha channel is missing
value = "ff" + value
value = int(value, base=16)
return QtGui.QColor.fromRgba(value)
def fromSvg(self, svg):
"""
Import element informations from an SVG

View File

@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 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/>.
"""
Graphical representation of a SVG image on the QGraphicsScene.
"""
from ..qt import QtCore, QtSvg
from .image_item import ImageItem
class SvgImageItem(ImageItem, QtSvg.QGraphicsSvgItem):
"""
Class to insert a SVG image on the scene.
"""
def __init__(self, renderer, image_path, pos=None):
QtSvg.QGraphicsSvgItem.__init__(self)
ImageItem.__init__(self, image_path, pos)
self.setSharedRenderer(renderer)
def duplicate(self):
"""
Duplicates this image item.
:return: SvgImageItem instance
"""
image_item = SvgImageItem(self.renderer(), self._image_path, QtCore.QPointF(self.x() + 20, self.y() + 20))
image_item.setZValue(self.zValue())
return image_item

171
gns3/items/visual_item.py Normal file
View File

@@ -0,0 +1,171 @@
#!/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, QtGui, QtWidgets, QtSvg
import logging
import binascii
log = logging.getLogger(__name__)
class VisualItem:
"""
Base class for non emulation item
"""
def __init__(self, project=None, pos=None, shape_id=None, svg=None, z=0, rotation=0, **kws):
self._id = shape_id
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable)
from ..main_window import MainWindow
self._graphics_view = MainWindow.instance().uiGraphicsView
self._project = project
# Store a hash of the SVG to avoid him
# to be send if he doesn't change
self._hash_svg = None
if pos:
self.setPos(pos)
if z:
self.setZValue(z)
if rotation:
self.setRotation(rotation)
def shape_id(self):
return self._id
def create(self):
self._project.post("/shapes", self._createShapeCallback, body=self.__json__())
def _createShapeCallback(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 setting up shape: {}".format(result["message"]))
return False
self._id = result["shape_id"]
def updateShape(self):
if self._id:
self._project.put("/shapes/" + self._id, self.updateShapeCallback, body=self.__json__())
def updateShapeCallback(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:
log.error("Error while setting up shape: {}".format(result["message"]))
return False
self.setPos(QtCore.QPoint(result["x"], result["y"]))
self.setZValue(result["z"])
self.setRotation(result["rotation"])
if "svg" in result:
self.fromSvg(result["svg"])
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
key = event.key()
modifiers = event.modifiers()
if key in (QtCore.Qt.Key_P, QtCore.Qt.Key_Plus, QtCore.Qt.Key_Equal) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Plus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() == 0:
self.setRotation(359)
else:
self.setRotation(self.rotation() - 1)
elif key in (QtCore.Qt.Key_M, QtCore.Qt.Key_Minus) and modifiers & QtCore.Qt.AltModifier \
or key == QtCore.Qt.Key_Minus and modifiers & QtCore.Qt.AltModifier and modifiers & QtCore.Qt.KeypadModifier:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
else:
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
def _colorFromSvg(self, value):
value = value.strip('#')
if len(value) == 6: # If alpha channel is missing
value = "ff" + value
value = int(value, base=16)
return QtGui.QColor.fromRgba(value)
def __json__(self):
data = {
"x": int(self.pos().x()),
"y": int(self.pos().y()),
"z": int(self.zValue()),
"rotation": int(self.rotation())
}
svg = self.toSvg()
hash_svg = binascii.crc32(svg.encode())
print(hash_svg)
if hash_svg != self._hash_svg:
data["svg"] = svg
self._hash_svg = hash_svg
return data
def setZValue(self, value):
"""
Sets a new Z value.
:param value: Z value
"""
QtWidgets.QGraphicsItem.setZValue(self, value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)
else:
self.setFlag(self.ItemIsSelectable, True)
self.setFlag(self.ItemIsMovable, True)
def delete(self, skip_controller=False):
"""
Deletes this shape.
:param skip_controller: Do not replicate change on the controller (usefull when it's already deleted on controller
"""
self.scene().removeItem(self)
from ..topology import Topology
Topology.instance().removeShape(self)
if self._id and not skip_controller:
self._project.delete("/shapes/" + self._id, None, body=self.__json__())
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if not value:
self.updateShape()
return QtWidgets.QGraphicsItem.itemChange(self, change, value)

View File

@@ -706,12 +706,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Slot called when inserting an image on the scene.
"""
files_dir = self._project_manager.project().filesDir()
if files_dir is None:
QtWidgets.QMessageBox.critical(self, "Image", "Please create a node first")
return
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
@@ -721,34 +715,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._pictures_dir = os.path.dirname(path)
image = QtGui.QPixmap(path)
if image.isNull():
QtWidgets.QMessageBox.critical(self, "Image", "Image file format not supported")
return
destination_dir = os.path.join(files_dir, "project-files", "images")
try:
os.makedirs(destination_dir, exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Image", "Could not create the image directory: {}".format(e))
return
image_filename = os.path.basename(path)
destination_image_path = os.path.join(destination_dir, image_filename)
if not os.path.isfile(destination_image_path):
# copy the image to the project files directory
try:
shutil.copyfile(path, destination_image_path)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Image", "Could not copy the image to the project image directory: {}".format(e))
return
renderer = QtSvg.QSvgRenderer(path)
if renderer.isValid():
# use a SVG image item if this is a valid SVG file
image = renderer
# path to the image is relative to the project-files dir
self.uiGraphicsView.addImage(image, os.path.join("images", image_filename))
self.uiGraphicsView.addImage(path)
def _drawRectangleActionSlot(self):
"""

View File

@@ -26,21 +26,33 @@ from . import QtGui
class QImageSvgRenderer(QtSvg.QSvgRenderer):
"""
Renderer pixmap and svg to SVG item
:param path_or_data: Svg element of path to a SVG
"""
def __init__(self, path):
def __init__(self, path_or_data):
super().__init__()
super().load(path)
if not os.path.exists(path_or_data):
self._svg = path_or_data
path_or_data = path_or_data.encode("utf-8")
super().load(path_or_data)
else:
super().load(path_or_data)
# If we can't render a SVG we load and base64 the image to create a SVG
if not self.isValid():
image = QtGui.QImage(path_or_data)
data = QtCore.QByteArray()
buf = QtCore.QBuffer(data)
image.save(buf, 'PNG')
self._svg = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}">
<image width="{width}" height="{height}" xlink:href="data:image/png;base64,{data}"/>
</svg>""".format(data=bytes(data.toBase64()).decode(),
width=image.rect().width(),
height=image.rect().height())
super().load(self._svg.encode())
# If we can't render a SVG we load and base64 the image to create a SVG
if not self.isValid() and os.path.exists(path):
image = QtGui.QImage(path)
data = QtCore.QByteArray()
buf = QtCore.QBuffer(data)
image.save(buf, 'PNG')
xml = """<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="{width}" height="{height}" xlink:href="data:image/png;base64,{data}"/>
</svg>""".format(data=bytes(data.toBase64()).decode(),
width=image.rect().width(),
height=image.rect().height())
super().load(xml.encode())
def svg(self):
"""
:returns: SVG source code
"""
return self._svg

View File

@@ -19,6 +19,8 @@
Contains this entire topology: nodes and links.
"""
import xml.etree.ElementTree as ET
from .qt import QtCore
from .modules import MODULES
from .modules.module_error import ModuleError
@@ -206,33 +208,6 @@ class Topology(QtCore.QObject):
return shape
return None
def addImage(self, image):
"""
Adds a new image to this topology.
:param image: ImageItem instance
"""
self._images.append(image)
def removeImage(self, image):
"""
Removes an image from this topology.
:param image: ImageItem instance
"""
if image in self._images:
self._images.remove(image)
if not self._project:
return
# We delete by security only images in the project files directory
if not os.path.isabs(image.filePath()):
if image.filePath() not in [ image.filePath() for image in self._images ]:
os.remove(os.path.join(self._project.filesDir(), "project-files", image.filePath()))
def nodes(self):
"""
Returns all the nodes in this topology.
@@ -328,10 +303,28 @@ class Topology(QtCore.QObject):
self._main_window.uiGraphicsView.addLink(source_node, source_port, destination_node, destination_port, link_id=link_data["link_id"])
def createShape(self, shape_data):
if "ellipse" in shape_data["svg"]:
self._main_window.uiGraphicsView.createShapeItem("ellipse", shape_data["x"], shape_data["y"], shape_data["z"], rotation=shape_data["rotation"], shape_id=shape_data["shape_id"], svg=shape_data["svg"])
elif "rect" in shape_data["svg"]:
self._main_window.uiGraphicsView.createShapeItem("rect", shape_data["x"], shape_data["y"], shape_data["z"], rotation=shape_data["rotation"], shape_id=shape_data["shape_id"], svg=shape_data["svg"])
"""
Take info from the API and create a shape
:param shape_data: Dict send by the API
"""
svg = ET.fromstring(shape_data["svg"])
try:
#If SVG is more complex we consider it as an image
if len(svg[0]) != 0:
type = "image"
else:
tag = svg[0].tag
if tag == "ellipse":
type = "ellipse"
elif tag == "rect":
type = "rect"
else:
type = "image"
except IndexError:
# If unknow we render it as a raw SVG image
type = "image"
self._main_window.uiGraphicsView.createVisualItem(type, shape_data["x"], shape_data["y"], shape_data["z"], rotation=shape_data["rotation"], shape_id=shape_data["shape_id"], svg=shape_data["svg"])
@staticmethod
def instance():

View File

@@ -0,0 +1,48 @@
#!/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/>.
import pytest
import xml.etree.ElementTree as ET
from gns3.items.image_item import ImageItem
from gns3.qt import QtGui, QtCore
@pytest.fixture
def image(project, controller):
path = "resources/images/gns3_icon_128x128.png"
return ImageItem(image_path=path, project=project)
def test_toSvg(image):
svg = ET.fromstring(image.toSvg())
assert float(svg.get("width")) == 128.0
assert float(svg.get("height")) == 128.0
def test_fromSvg(image, project):
image2 = ImageItem(svg=image.toSvg(), project=project)
assert image2.toSvg() == image.toSvg()
def test_json(image):
image._hash_svg = None
assert "svg" in image.__json__()
# If we call the function twice and the svg didn't change we don't send the modification
assert "svg" not in image.__json__()

View File

@@ -34,16 +34,50 @@ def test_topology_node(vpcs_device):
assert len(topology.nodes()) == 0
def test_createShape():
def test_createShape_ellipse():
topology = Topology()
shape_data = {
"x": 42,
"y": 12,
"z": 0,
"rotation": 0,
"shape_id": str(uuid.uuid4()),
"svg": "<svg height=\"105.0\" width=\"158.0\"><ellipse cx=\"79\" cy=\"52\" rx=\"79\" ry=\"53\" style=\"stroke-width:2;stroke:#000000;fill:#ffffff;\" /></svg>",
}
topology._main_window = MagicMock()
topology.createShape(shape_data)
topology._main_window.uiGraphicsView.createShapeItem.assert_called_with("ellipse", 42, 12, 0, svg=shape_data["svg"], shape_id=shape_data["shape_id"])
topology._main_window.uiGraphicsView.createVisualItem.assert_called_with("ellipse", 42, 12, 0, rotation=0, svg=shape_data["svg"], shape_id=shape_data["shape_id"])
def test_createShape_rect():
topology = Topology()
shape_data = {
"x": 42,
"y": 12,
"z": 0,
"rotation": 0,
"shape_id": str(uuid.uuid4()),
"svg": "<svg height=\"105.0\" width=\"158.0\"><rect/></svg>",
}
topology._main_window = MagicMock()
topology.createShape(shape_data)
topology._main_window.uiGraphicsView.createVisualItem.assert_called_with("rect", 42, 12, 0, rotation=0, svg=shape_data["svg"], shape_id=shape_data["shape_id"])
def test_createShape_svg():
"""
If SVG is more complex we consider it as an image
"""
topology = Topology()
shape_data = {
"x": 42,
"y": 12,
"z": 0,
"rotation": 0,
"shape_id": str(uuid.uuid4()),
"svg": "<svg height=\"105.0\" width=\"158.0\"><rect><line/></rect></svg>",
}
topology._main_window = MagicMock()
topology.createShape(shape_data)
topology._main_window.uiGraphicsView.createVisualItem.assert_called_with("image", 42, 12, 0, rotation=0, svg=shape_data["svg"], shape_id=shape_data["shape_id"])