2014-01-31 16:22:31 -07:00
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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 view on the scene where items are drawn .
"""
2014-10-27 18:11:39 -06:00
import logging
2014-06-13 13:30:19 -06:00
import os
2019-02-16 14:38:44 +08:00
from . qt import sip
2017-05-16 11:14:53 +02:00
import sys
2014-06-11 10:04:40 -06:00
2017-01-24 19:29:19 +01:00
from . qt import QtCore , QtGui , QtNetwork , QtWidgets , qpartial , qslot
2014-01-31 16:22:31 -07:00
from . items . node_item import NodeItem
2015-06-01 16:46:53 -06:00
from . dialogs . node_properties_dialog import NodePropertiesDialog
2014-01-31 16:22:31 -07:00
from . link import Link
2014-02-27 21:45:56 -07:00
from . node import Node
2014-03-11 16:44:37 -06:00
from . modules import MODULES
2014-02-28 13:52:01 -07:00
from . modules . module_error import ModuleError
2017-11-12 17:02:00 +08:00
from . modules . builtin import Builtin
2015-06-10 15:11:19 -06:00
from . settings import GRAPHICS_VIEW_SETTINGS
2014-02-27 21:45:56 -07:00
from . topology import Topology
2018-11-28 16:12:58 +07:00
from . template_manager import TemplateManager
2014-06-20 14:30:52 -06:00
from . dialogs . style_editor_dialog import StyleEditorDialog
from . dialogs . text_editor_dialog import TextEditorDialog
2014-07-09 14:49:24 -06:00
from . dialogs . symbol_selection_dialog import SymbolSelectionDialog
2014-10-22 18:06:37 -06:00
from . dialogs . idlepc_dialog import IdlePCDialog
2016-02-05 19:00:53 +01:00
from . dialogs . console_command_dialog import ConsoleCommandDialog
2016-07-27 18:49:49 +02:00
from . dialogs . file_editor_dialog import FileEditorDialog
2018-12-30 19:35:25 +07:00
from . dialogs . node_info_dialog import NodeInfoDialog
2015-02-08 12:48:45 -07:00
from . local_config import LocalConfig
2015-06-19 12:15:21 +02:00
from . progress import Progress
2015-09-15 22:36:25 +02:00
from . utils . server_select import server_select
2016-05-20 18:19:31 +02:00
from . compute_manager import ComputeManager
2019-01-17 17:34:30 +07:00
from . utils . get_icon import get_icon
2014-06-11 10:04:40 -06:00
2014-01-31 16:22:31 -07:00
# link items
2019-04-13 18:40:54 +07:00
from . items . link_item import LinkItem , SvgIconItem
2014-01-31 16:22:31 -07:00
from . items . ethernet_link_item import EthernetLinkItem
from . items . serial_link_item import SerialLinkItem
2014-06-11 10:04:40 -06:00
# other items
2018-06-09 19:13:36 +07:00
from . items . label_item import LabelItem
2016-06-23 18:21:20 +02:00
from . items . text_item import TextItem
2014-06-14 13:26:22 -06:00
from . items . shape_item import ShapeItem
2016-06-23 12:26:54 +02:00
from . items . drawing_item import DrawingItem
2014-06-11 13:42:40 -06:00
from . items . rectangle_item import RectangleItem
2017-01-25 17:36:43 +01:00
from . items . line_item import LineItem
2014-06-11 13:42:40 -06:00
from . items . ellipse_item import EllipseItem
2014-07-03 09:32:17 -06:00
from . items . image_item import ImageItem
2018-05-08 16:22:01 +02:00
from . items . logo_item import LogoItem
2014-06-11 13:42:40 -06:00
2014-10-27 18:11:39 -06:00
log = logging . getLogger ( __name__ )
2014-01-31 16:22:31 -07:00
2015-04-17 12:14:21 +02:00
class GraphicsView ( QtWidgets . QGraphicsView ) :
2014-01-31 16:22:31 -07:00
"""
Graphics view that displays the scene .
: param parent : parent widget
"""
2024-12-02 00:13:01 -09:00
# Class-level constants for default colors
DEFAULT_DRAWING_GRID_COLOR = QtGui . QColor ( 208 , 208 , 208 ) # #D0D0D0
DEFAULT_NODE_GRID_COLOR = QtGui . QColor ( 190 , 190 , 190 ) # #BEBEBE
2014-01-31 16:22:31 -07:00
def __init__ ( self , parent ) :
2015-06-20 16:10:33 -06:00
# Our parent is the central widget which parent is the main window.
2014-03-17 11:51:01 -06:00
self . _main_window = parent . parent ( )
2015-04-17 12:14:21 +02:00
super ( ) . __init__ ( parent )
2014-02-27 21:45:56 -07:00
self . _settings = { }
self . _loadSettings ( )
2014-01-31 16:22:31 -07:00
self . _adding_link = False
2014-06-11 10:04:40 -06:00
self . _adding_note = False
2014-06-11 13:42:40 -06:00
self . _adding_rectangle = False
self . _adding_ellipse = False
2017-01-25 17:36:43 +01:00
self . _adding_line = False
2014-01-31 16:22:31 -07:00
self . _newlink = None
self . _dragging = False
2018-04-13 16:56:37 +08:00
self . _grid_size = 75
2018-11-22 09:35:18 +00:00
self . _drawing_grid_size = 25
2024-12-02 00:13:01 -09:00
self . _drawing_grid_color = self . DEFAULT_DRAWING_GRID_COLOR
self . _node_grid_color = self . DEFAULT_NODE_GRID_COLOR
2014-01-31 16:22:31 -07:00
self . _last_mouse_position = None
2014-02-27 21:45:56 -07:00
self . _topology = Topology . instance ( )
2015-04-28 11:08:36 +02:00
self . _background_warning_msgbox = QtWidgets . QErrorMessage ( self )
2015-04-27 19:11:37 -06:00
self . _background_warning_msgbox . setWindowTitle ( " Layer position " )
2014-01-31 16:22:31 -07:00
# set the scene
2015-04-17 12:14:21 +02:00
scene = QtWidgets . QGraphicsScene ( parent = self )
2014-02-27 21:45:56 -07:00
width = self . _settings [ " scene_width " ]
height = self . _settings [ " scene_height " ]
2014-01-31 16:22:31 -07:00
self . setScene ( scene )
2016-09-21 09:43:18 +02:00
self . setSceneSize ( width , height )
2014-01-31 16:22:31 -07:00
# set the custom flags for this view
2025-12-14 18:19:47 +08:00
self . setDragMode ( QtWidgets . QGraphicsView . DragMode . RubberBandDrag )
self . setRenderHint ( QtGui . QPainter . RenderHint . Antialiasing )
self . setTransformationAnchor ( QtWidgets . QGraphicsView . ViewportAnchor . AnchorUnderMouse )
self . setResizeAnchor ( QtWidgets . QGraphicsView . ViewportAnchor . AnchorViewCenter )
2014-01-31 16:22:31 -07:00
2015-06-09 17:43:50 -06:00
# default directories for QFileDialog
2025-12-14 18:19:47 +08:00
self . _import_config_directory = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DocumentsLocation )
self . _export_config_directory = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DocumentsLocation )
2014-03-29 21:39:15 -06:00
self . _local_addresses = [ ' 0.0.0.0 ' , ' 127.0.0.1 ' , ' localhost ' , ' ::1 ' , ' 0:0:0:0:0:0:0:1 ' , ' :: ' , QtNetwork . QHostInfo . localHostName ( ) ]
2016-09-21 09:43:18 +02:00
def setSceneSize ( self , width , height ) :
self . scene ( ) . setSceneRect ( - ( width / 2 ) , - ( height / 2 ) , width , height )
2017-07-04 09:33:25 +02:00
def setZoom ( self , zoom ) :
"""
Sets zoom of the Graphics View
"""
2017-07-06 09:58:13 +02:00
if zoom :
factor = zoom / 100.
self . scale ( factor , factor )
2017-07-04 09:33:25 +02:00
2018-11-27 16:13:22 +07:00
def setNodeGridSize ( self , grid_size ) :
2018-04-13 16:56:37 +08:00
"""
2018-11-27 16:13:22 +07:00
Sets the grid size for nodes .
2018-04-13 16:56:37 +08:00
"""
self . _grid_size = grid_size
2018-11-27 16:13:22 +07:00
def nodeGridSize ( self ) :
2018-04-13 16:56:37 +08:00
"""
2018-11-27 16:13:22 +07:00
Returns the grid size for nodes .
2018-11-22 09:35:18 +00:00
: return : integer
"""
return self . _grid_size
2018-04-13 16:56:37 +08:00
2018-11-22 09:35:18 +00:00
def setDrawingGridSize ( self , grid_size ) :
2018-04-13 16:56:37 +08:00
"""
2018-11-22 09:35:18 +00:00
Sets the grid size for drawings
"""
self . _drawing_grid_size = grid_size
2018-04-13 16:56:37 +08:00
2018-11-22 09:35:18 +00:00
def drawingGridSize ( self ) :
"""
Returns the grid size for drawings
: return : integer
"""
return self . _drawing_grid_size
2018-04-13 16:56:37 +08:00
2016-05-25 10:04:32 -06:00
def setEnabled ( self , enabled ) :
if enabled is False :
2016-07-20 11:25:45 +02:00
self . reset ( )
2016-05-25 10:04:32 -06:00
item = QtWidgets . QGraphicsTextItem ( " Please create a project " )
item . setPos ( 0 , 0 )
self . scene ( ) . addItem ( item )
super ( ) . setEnabled ( enabled )
2017-08-22 13:01:50 +02:00
self . toggleUiDeviceMenu ( )
2014-02-27 21:45:56 -07:00
def reset ( self ) :
"""
Remove all the items from the scene and
reset the instances count .
"""
2014-03-11 16:44:37 -06:00
# reset the modules
for module in MODULES :
instance = module . instance ( )
instance . reset ( )
2014-02-27 21:45:56 -07:00
2014-05-06 12:22:13 -06:00
# reset instance IDs for
# nodes, links and ports
2014-02-27 21:45:56 -07:00
Node . reset ( )
Link . reset ( )
2014-05-06 12:22:13 -06:00
# reset the topology
2014-02-27 21:45:56 -07:00
self . _topology . reset ( )
2014-05-06 12:22:13 -06:00
# clear the topology summary
self . _main_window . uiTopologySummaryTreeWidget . clear ( )
2017-08-11 14:37:45 +08:00
# reset the lock button
self . _main_window . uiLockAllAction . setChecked ( False )
2014-05-06 12:22:13 -06:00
# clear all objects on the scene
self . scene ( ) . clear ( )
2022-12-29 08:49:10 +08:00
# reset zoom / scale
self . resetTransform ( )
2018-06-13 17:31:27 +08:00
2014-02-27 21:45:56 -07:00
def _loadSettings ( self ) :
"""
Loads the settings from the persistent settings file .
"""
2015-05-27 19:54:45 -06:00
self . _settings = LocalConfig . instance ( ) . loadSectionSettings ( self . __class__ . __name__ , GRAPHICS_VIEW_SETTINGS )
2015-02-08 12:48:45 -07:00
2014-02-27 21:45:56 -07:00
def settings ( self ) :
"""
Returns the graphics view settings .
: returns : settings dictionary
"""
return self . _settings
def setSettings ( self , new_settings ) :
"""
Set new graphics view settings .
: param new_settings : settings dictionary
"""
# save the settings
self . _settings . update ( new_settings )
2015-02-08 12:48:45 -07:00
LocalConfig . instance ( ) . saveSectionSettings ( self . __class__ . __name__ , self . _settings )
2014-02-27 21:45:56 -07:00
2014-01-31 16:22:31 -07:00
def addingLinkSlot ( self , enabled ) :
"""
Slot to receive events from MainWindow
when a user has clicked on " Add a link " button .
: param enable : either the user is adding a link or not ( boolean )
"""
if enabled :
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . CrossCursor )
2014-01-31 16:22:31 -07:00
else :
2015-04-25 16:40:33 -06:00
if self . _newlink and self . _newlink in self . scene ( ) . items ( ) :
2014-01-31 16:22:31 -07:00
self . scene ( ) . removeItem ( self . _newlink )
2015-04-25 16:40:33 -06:00
self . _newlink = None
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-01-31 16:22:31 -07:00
self . _adding_link = enabled
2014-06-11 10:04:40 -06:00
def addNote ( self , state ) :
"""
Adds a note .
2014-06-11 13:42:40 -06:00
: param state : boolean
2014-06-11 10:04:40 -06:00
"""
if state :
self . _adding_note = True
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . IBeamCursor )
2014-06-11 10:04:40 -06:00
else :
self . _adding_note = False
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 10:04:40 -06:00
2014-06-11 13:42:40 -06:00
def addRectangle ( self , state ) :
"""
Adds a rectangle .
: param state : boolean
"""
if state :
self . _adding_rectangle = True
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . PointingHandCursor )
2014-06-11 13:42:40 -06:00
else :
self . _adding_rectangle = False
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 13:42:40 -06:00
def addEllipse ( self , state ) :
"""
Adds an ellipse .
: param state : boolean
"""
if state :
self . _adding_ellipse = True
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . PointingHandCursor )
2014-06-11 13:42:40 -06:00
else :
self . _adding_ellipse = False
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 13:42:40 -06:00
2017-01-25 17:36:43 +01:00
def addLine ( self , state ) :
"""
Adds a line .
: param state : boolean
"""
if state :
self . _adding_line = True
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . PointingHandCursor )
2017-01-25 17:36:43 +01:00
else :
self . _adding_line = False
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2017-01-25 17:36:43 +01:00
2016-09-06 18:17:01 +02:00
def addImage ( self , image_path ) :
2014-07-03 09:32:17 -06:00
"""
Adds an image .
: param image_path : path to the image
"""
2016-08-15 12:59:13 +02:00
image_item = ImageItem ( image_path = image_path , project = self . _topology . project ( ) )
2016-09-02 12:02:44 +02:00
image_item . create ( )
2014-07-03 09:32:17 -06:00
self . scene ( ) . addItem ( image_item )
2016-06-23 13:29:30 +02:00
self . _topology . addDrawing ( image_item )
2014-07-03 09:32:17 -06:00
2018-05-08 16:22:01 +02:00
def addLogo ( self , logo_path , logo_url ) :
logo_item = LogoItem ( logo_path , logo_url , self . _topology . project ( ) )
self . scene ( ) . addItem ( logo_item )
2016-09-02 15:56:47 +02:00
def addLink ( self , source_node , source_port , destination_node , destination_port , * * link_data ) :
2014-01-31 16:22:31 -07:00
"""
2014-02-27 21:45:56 -07:00
Creates a Link instance representing a connection between 2 devices .
2014-01-31 16:22:31 -07:00
2014-02-27 21:45:56 -07:00
: param source_node : source Node instance
: param source_port : source Port instance
: param destination_node : destination Node instance
: param destination_port : destination Port instance
2016-09-02 15:56:47 +02:00
: param link_data : information about link from the API
2015-05-26 16:56:19 +02:00
: returns : Link
2014-01-31 16:22:31 -07:00
"""
2016-09-02 15:56:47 +02:00
link = Link ( source_node , source_port , destination_node , destination_port , * * link_data )
2014-01-31 16:22:31 -07:00
# connect the signals that let the graphics view knows about events such as
# a new link creation or deletion.
2015-05-05 22:27:34 +02:00
if self . _topology . addLink ( link ) :
2021-11-08 21:55:29 +10:30
source_node . addLink ( link )
destination_node . addLink ( link )
2015-05-05 22:27:34 +02:00
link . add_link_signal . connect ( self . addLinkSlot )
link . delete_link_signal . connect ( self . deleteLinkSlot )
2016-06-15 18:49:26 +02:00
2016-09-02 15:56:47 +02:00
if link . initialized ( ) :
2016-07-01 11:37:43 +02:00
self . addLinkSlot ( link . id ( ) )
2015-05-26 16:56:19 +02:00
return link
2014-01-31 16:22:31 -07:00
def addLinkSlot ( self , link_id ) :
"""
Slot to receive events from Link instances
when a link has been created .
: param link_id : link identifier
"""
2014-02-27 21:45:56 -07:00
link = self . _topology . getLink ( link_id )
2017-02-15 19:21:30 +01:00
if not link :
return
2014-01-31 16:22:31 -07:00
source_item = None
destination_item = None
2014-06-13 11:56:38 -06:00
source_port = link . sourcePort ( )
destination_port = link . destinationPort ( )
2014-01-31 16:22:31 -07:00
# find the correct source and destination node items
for item in self . scene ( ) . items ( ) :
if isinstance ( item , NodeItem ) :
2014-06-13 11:56:38 -06:00
if item . node ( ) . id ( ) == link . sourceNode ( ) . id ( ) :
2014-01-31 16:22:31 -07:00
source_item = item
2014-06-13 11:56:38 -06:00
if item . node ( ) . id ( ) == link . destinationNode ( ) . id ( ) :
2014-01-31 16:22:31 -07:00
destination_item = item
if source_item and destination_item :
break
if not source_item or not destination_item :
2016-04-12 18:54:34 +02:00
log . error ( " Could not find a source or destination item for the link! " )
2014-01-31 16:22:31 -07:00
self . deleteLinkSlot ( link_id )
return
2016-03-14 17:05:16 +01:00
if link . sourcePort ( ) . linkType ( ) == " Serial " :
2017-01-06 11:42:26 +01:00
link_item = SerialLinkItem ( source_item , source_port , destination_item , destination_port , link )
2014-01-31 16:22:31 -07:00
else :
2017-01-06 11:42:26 +01:00
link_item = EthernetLinkItem ( source_item , source_port , destination_item , destination_port , link )
2014-01-31 16:22:31 -07:00
self . scene ( ) . addItem ( link_item )
def deleteLinkSlot ( self , link_id ) :
"""
Slot to receive events from Link instances
when a link has been deleted .
: param link_id : link identifier
"""
2014-02-27 21:45:56 -07:00
link = self . _topology . getLink ( link_id )
self . _topology . removeLink ( link )
def _userNodeLinking ( self , event , item ) :
"""
Handles node linking by the user .
: param event : QMouseEvent instance
: param item : NodeItem instance
"""
# link addition code
if not self . _newlink :
source_item = item
2025-12-16 13:19:17 +08:00
source_port = source_item . connectToPort ( event . globalPosition ( ) . toPoint ( ) )
2014-02-27 21:45:56 -07:00
if not source_port :
return
2017-01-06 10:30:24 +01:00
if source_port . link ( ) is not None :
QtWidgets . QMessageBox . warning ( self , " Create link " , " Can ' t create the link the port is not free " )
return
2014-02-27 21:45:56 -07:00
if source_port . linkType ( ) == " Serial " :
2025-12-14 19:52:17 +08:00
self . _newlink = SerialLinkItem ( source_item , source_port , self . mapToScene ( event . position ( ) . toPoint ( ) ) , None , adding_flag = True )
2014-02-27 21:45:56 -07:00
else :
2025-12-14 19:52:17 +08:00
self . _newlink = EthernetLinkItem ( source_item , source_port , self . mapToScene ( event . position ( ) . toPoint ( ) ) , None , adding_flag = True )
2014-02-27 21:45:56 -07:00
self . scene ( ) . addItem ( self . _newlink )
else :
source_item = self . _newlink . sourceItem ( )
source_port = self . _newlink . sourcePort ( )
destination_item = item
2025-12-16 13:19:17 +08:00
destination_port = destination_item . connectToPort ( event . globalPosition ( ) . toPoint ( ) )
2014-02-27 21:45:56 -07:00
if not destination_port :
return
2014-05-16 13:30:04 -06:00
2017-01-06 10:30:24 +01:00
if destination_port . link ( ) is not None :
QtWidgets . QMessageBox . warning ( self , " Create link " , " Can ' t create the link the destination port is not free " )
return
2015-04-25 16:40:33 -06:00
if self . _newlink in self . scene ( ) . items ( ) :
self . scene ( ) . removeItem ( self . _newlink )
2014-02-27 21:45:56 -07:00
self . _newlink = None
2015-04-25 16:40:33 -06:00
self . addLink ( source_item . node ( ) , source_port , destination_item . node ( ) , destination_port )
2014-01-31 16:22:31 -07:00
def mousePressEvent ( self , event ) :
"""
Handles all mouse press events .
2014-02-27 21:45:56 -07:00
: param event : QMouseEvent instance
2014-01-31 16:22:31 -07:00
"""
2014-02-27 21:45:56 -07:00
is_not_link = True
2018-05-24 13:21:13 +02:00
is_not_logo = True
2025-12-14 19:52:17 +08:00
item = self . itemAt ( event . position ( ) . toPoint ( ) )
2016-07-04 17:30:09 +02:00
if item and sip . isdeleted ( item ) :
return
2024-02-12 16:32:17 +11:00
elif not item :
self . _main_window . uiStatusBar . clearMessage ( ) # reset the status bar message when clicking on the scene
2016-07-04 17:30:09 +02:00
2016-03-29 13:48:23 -06:00
if item and ( isinstance ( item , LinkItem ) or isinstance ( item . parentItem ( ) , LinkItem ) ) :
2014-02-27 21:45:56 -07:00
is_not_link = False
2018-05-24 13:21:13 +02:00
if item and ( isinstance ( item , LogoItem ) or isinstance ( item . parentItem ( ) , LogoItem ) ) :
is_not_logo = False
2014-07-04 19:14:50 -06:00
else :
for it in self . scene ( ) . items ( ) :
if isinstance ( it , LinkItem ) :
it . setHovered ( False )
2014-01-31 16:22:31 -07:00
2025-12-14 18:19:47 +08:00
if ( event . buttons ( ) == QtCore . Qt . MouseButton . LeftButton and event . modifiers ( ) == QtCore . Qt . KeyboardModifier . ShiftModifier ) or event . buttons ( ) == QtCore . Qt . MouseButton . MiddleButton :
2014-02-27 21:45:56 -07:00
# checks to see if either the middle mouse is pressed
# or a combination of left mouse button and SHIT key are pressed to start dragging the view
2025-12-14 19:52:17 +08:00
self . _last_mouse_position = self . mapFromGlobal ( event . globalPosition ( ) )
2014-01-31 16:22:31 -07:00
self . _dragging = True
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ClosedHandCursor )
2014-01-31 16:22:31 -07:00
return
2025-12-14 18:19:47 +08:00
if is_not_link and item and event . modifiers ( ) == QtCore . Qt . KeyboardModifier . ControlModifier and event . button ( ) == QtCore . Qt . MouseButton . LeftButton and item and not self . _adding_link :
2014-02-27 21:45:56 -07:00
# manual selection using CTRL
2014-01-31 16:22:31 -07:00
if item . isSelected ( ) :
item . setSelected ( False )
else :
item . setSelected ( True )
2025-12-14 18:19:47 +08:00
elif is_not_link and is_not_logo and event . button ( ) == QtCore . Qt . MouseButton . RightButton and not self . _adding_link :
2020-03-30 21:37:52 -07:00
pass #TODO: remove this without creating a bug...
2025-12-14 18:19:47 +08:00
elif is_not_link and self . _adding_link and event . button ( ) == QtCore . Qt . MouseButton . RightButton :
2015-05-03 14:03:20 -06:00
# send a escape key to the main window to cancel the link addition
2025-12-14 18:19:47 +08:00
key = QtGui . QKeyEvent ( QtCore . QEvent . Type . KeyPress , QtCore . Qt . Key . Key_Escape , QtCore . Qt . KeyboardModifier . NoModifier )
2015-05-13 14:03:39 +02:00
QtWidgets . QApplication . sendEvent ( self . _main_window , key )
2025-12-14 18:19:47 +08:00
elif item and isinstance ( item , NodeItem ) and self . _adding_link and event . button ( ) == QtCore . Qt . MouseButton . LeftButton :
2014-02-27 21:45:56 -07:00
self . _userNodeLinking ( event , item )
2025-12-14 18:19:47 +08:00
#context_event = QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Reason.Mouse, event.pos())
2020-03-30 21:37:52 -07:00
#QtWidgets.QApplication.sendEvent(self, context_event)
2025-12-14 18:19:47 +08:00
elif event . button ( ) == QtCore . Qt . MouseButton . LeftButton and self . _adding_note :
2025-12-14 19:52:17 +08:00
pos = self . mapToScene ( event . position ( ) . toPoint ( ) )
2021-12-24 13:38:26 +10:30
note = self . createDrawingItem ( " text " , int ( pos . x ( ) ) , int ( pos . y ( ) ) , 2 )
2014-06-11 10:04:40 -06:00
pos_x = note . pos ( ) . x ( )
pos_y = note . pos ( ) . y ( ) - ( note . boundingRect ( ) . height ( ) / 2 )
note . setPos ( pos_x , pos_y )
note . editText ( )
self . _main_window . uiAddNoteAction . setChecked ( False )
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 10:04:40 -06:00
self . _adding_note = False
2025-12-14 18:19:47 +08:00
elif event . button ( ) == QtCore . Qt . MouseButton . LeftButton and self . _adding_rectangle :
2025-12-14 19:52:17 +08:00
pos = self . mapToScene ( event . position ( ) . toPoint ( ) )
2021-12-24 13:38:26 +10:30
self . createDrawingItem ( " rect " , int ( pos . x ( ) ) , int ( pos . y ( ) ) , 1 )
2014-06-11 13:42:40 -06:00
self . _main_window . uiDrawRectangleAction . setChecked ( False )
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 13:42:40 -06:00
self . _adding_rectangle = False
2025-12-14 18:19:47 +08:00
elif event . button ( ) == QtCore . Qt . MouseButton . LeftButton and self . _adding_ellipse :
2025-12-14 19:52:17 +08:00
pos = self . mapToScene ( event . position ( ) . toPoint ( ) )
2021-12-24 13:38:26 +10:30
self . createDrawingItem ( " ellipse " , int ( pos . x ( ) ) , int ( pos . y ( ) ) , 1 )
2014-06-11 13:42:40 -06:00
self . _main_window . uiDrawEllipseAction . setChecked ( False )
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-06-11 13:42:40 -06:00
self . _adding_ellipse = False
2025-12-14 18:19:47 +08:00
elif event . button ( ) == QtCore . Qt . MouseButton . LeftButton and self . _adding_line :
2025-12-14 19:52:17 +08:00
pos = self . mapToScene ( event . position ( ) . toPoint ( ) )
2021-12-24 13:38:26 +10:30
self . createDrawingItem ( " line " , int ( pos . x ( ) ) , int ( pos . y ( ) ) , 1 )
2017-01-25 17:36:43 +01:00
self . _main_window . uiDrawLineAction . setChecked ( False )
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2017-01-25 17:36:43 +01:00
self . _adding_line = False
2014-01-31 16:22:31 -07:00
else :
2015-04-17 12:14:21 +02:00
super ( ) . mousePressEvent ( event )
2014-01-31 16:22:31 -07:00
2017-08-22 13:01:50 +02:00
self . toggleUiDeviceMenu ( )
2020-03-30 21:37:52 -07:00
def contextMenuEvent ( self , event ) :
"""
Handles all context menu events .
: param event : QContextMenuEvent instance
"""
is_not_link = True
is_not_logo = True
item = self . itemAt ( event . pos ( ) )
if item and sip . isdeleted ( item ) :
return
if item and ( isinstance ( item , LinkItem ) or isinstance ( item . parentItem ( ) , LinkItem ) ) :
is_not_link = False
if item and ( isinstance ( item , LogoItem ) or isinstance ( item . parentItem ( ) , LogoItem ) ) :
is_not_logo = False
else :
for it in self . scene ( ) . items ( ) :
if isinstance ( it , LinkItem ) :
it . setHovered ( False )
if is_not_link and is_not_logo and not self . _adding_link :
if item and not sip . isdeleted ( item ) :
2020-06-09 13:19:20 +09:30
# Prevent right clicking on a selected item from de-selecting all other items
if not item . isSelected ( ) :
2025-12-15 19:27:55 +08:00
if not ( event . modifiers ( ) & QtCore . Qt . KeyboardModifier . ControlModifier ) :
2020-06-09 13:19:20 +09:30
for it in self . scene ( ) . items ( ) :
2020-03-30 21:37:52 -07:00
it . setSelected ( False )
2020-06-09 13:19:20 +09:30
item . setSelected ( True )
2020-06-09 13:50:31 +09:30
self . _showDeviceContextualMenu ( event . globalPos ( ) )
2020-03-30 21:37:52 -07:00
# when more than one item is selected display the contextual menu even if mouse is not above an item
elif len ( self . scene ( ) . selectedItems ( ) ) > 1 :
self . _showDeviceContextualMenu ( event . globalPos ( ) )
#elif item and isinstance(item, NodeItem) and self._adding_link:
# self._userNodeLinking(event, item)
else :
super ( ) . contextMenuEvent ( event )
2014-01-31 16:22:31 -07:00
def mouseReleaseEvent ( self , event ) :
"""
Handles all mouse release events .
2014-02-27 21:45:56 -07:00
: param : QMouseEvent instance
2014-01-31 16:22:31 -07:00
"""
2016-09-14 15:52:26 +02:00
for item in self . scene ( ) . selectedItems ( ) :
if isinstance ( item , NodeItem ) :
item . mouseRelease ( )
2014-02-27 21:45:56 -07:00
# If the left mouse button is not still pressed TOGETHER with the SHIFT key and neither is the middle button
2014-01-31 16:22:31 -07:00
# this means the user is no longer trying to drag the view
2025-12-15 19:27:55 +08:00
if self . _dragging and not ( event . buttons ( ) == QtCore . Qt . MouseButton . LeftButton and event . modifiers ( ) == QtCore . Qt . KeyboardModifier . ShiftModifier ) and not ( event . buttons ( ) & QtCore . Qt . MouseButton . MiddleButton ) :
2014-01-31 16:22:31 -07:00
self . _dragging = False
2025-12-14 18:19:47 +08:00
self . setCursor ( QtCore . Qt . CursorShape . ArrowCursor )
2014-01-31 16:22:31 -07:00
else :
2025-12-14 19:52:17 +08:00
item = self . itemAt ( event . position ( ) . toPoint ( ) )
2025-12-15 19:27:55 +08:00
if item is not None and not ( event . modifiers ( ) & QtCore . Qt . KeyboardModifier . ControlModifier ) :
2014-01-31 16:22:31 -07:00
item . setSelected ( True )
2015-04-17 12:14:21 +02:00
super ( ) . mouseReleaseEvent ( event )
2014-01-31 16:22:31 -07:00
2017-08-22 13:01:50 +02:00
self . toggleUiDeviceMenu ( )
2014-08-19 12:15:52 -06:00
def wheelEvent ( self , event ) :
"""
Handles zoom in or out using the mouse wheel .
: param : QWheelEvent instance
"""
2025-12-14 18:19:47 +08:00
if event . modifiers ( ) == QtCore . Qt . KeyboardModifier . ControlModifier :
2016-04-29 16:40:32 +02:00
delta = event . angleDelta ( )
2015-05-18 12:29:58 +02:00
if delta is not None and delta . x ( ) == 0 :
2015-05-06 18:21:44 +03:00
# CTRL is pressed then use the mouse wheel to zoom in or out.
2021-09-08 15:26:56 +09:30
self . scaleView ( pow ( 2.0 , ( delta . y ( ) / 2 ) / 240.0 ) )
2017-07-04 09:33:25 +02:00
self . _topology . project ( ) . setZoom ( round ( self . transform ( ) . m11 ( ) * 100 ) )
self . _topology . project ( ) . update ( )
2014-08-19 12:15:52 -06:00
else :
2015-04-17 12:14:21 +02:00
super ( ) . wheelEvent ( event )
2014-02-27 21:45:56 -07:00
2014-01-31 16:22:31 -07:00
def scaleView ( self , scale_factor ) :
"""
Scales the view ( zoom in and out ) .
"""
2015-04-17 12:14:21 +02:00
factor = self . transform ( ) . scale ( scale_factor , scale_factor ) . mapRect ( QtCore . QRectF ( 0 , 0 , 1 , 1 ) ) . width ( )
2015-09-05 14:38:44 -06:00
if factor < 0.10 or factor > 10 :
2014-01-31 16:22:31 -07:00
return
self . scale ( scale_factor , scale_factor )
2024-02-12 16:32:17 +11:00
self . _main_window . uiStatusBar . showMessage ( " Zoom: {} % " . format ( round ( self . transform ( ) . m11 ( ) * 100 ) ) , 5000 )
2014-01-31 16:22:31 -07:00
def keyPressEvent ( self , event ) :
"""
Handles all key press events for this view .
: param event : QKeyEvent
"""
2025-12-14 18:19:47 +08:00
if event . key ( ) == QtCore . Qt . Key . Key_Delete :
2018-06-09 19:13:36 +07:00
# check if we are editing an LabelItem instance, then send the delete key event to it
2014-06-16 12:11:45 -06:00
for item in self . scene ( ) . selectedItems ( ) :
2018-06-09 19:13:36 +07:00
if ( isinstance ( item , LabelItem ) or isinstance ( item , TextItem ) ) and item . hasFocus ( ) :
2015-04-17 12:14:21 +02:00
super ( ) . keyPressEvent ( event )
2014-06-16 12:11:45 -06:00
return
2014-04-15 15:57:18 -06:00
self . deleteActionSlot ( )
2015-05-06 13:23:42 +02:00
super ( ) . keyPressEvent ( event )
2014-01-31 16:22:31 -07:00
def mouseMoveEvent ( self , event ) :
"""
Handles all mouse move events ( mouse tracking has been enabled ) .
2014-02-27 21:45:56 -07:00
: param event : QMouseEvent instance
2014-01-31 16:22:31 -07:00
"""
if self . _dragging :
# This if statement event checks to see if the user is dragging the scene
# if so it sets the value of the scene scroll bars based on the change between
# the previous and current mouse position
2025-12-14 19:52:17 +08:00
mapped_global_pos = self . mapFromGlobal ( event . globalPosition ( ) )
2014-01-31 16:22:31 -07:00
hBar = self . horizontalScrollBar ( )
vBar = self . verticalScrollBar ( )
delta = mapped_global_pos - self . _last_mouse_position
2026-02-18 18:50:47 +08:00
hBar . setValue ( int ( hBar . value ( ) + ( delta . x ( ) if QtWidgets . QApplication . isRightToLeft ( ) else - delta . x ( ) ) ) )
vBar . setValue ( int ( vBar . value ( ) - delta . y ( ) ) )
2014-01-31 16:22:31 -07:00
self . _last_mouse_position = mapped_global_pos
2015-04-25 16:40:33 -06:00
if self . _adding_link and self . _newlink and self . _newlink in self . scene ( ) . items ( ) :
2014-01-31 16:22:31 -07:00
# update the mouse position when the user is adding a link.
2025-12-14 19:52:17 +08:00
self . _newlink . setMousePoint ( self . mapToScene ( event . position ( ) . toPoint ( ) ) )
2014-01-31 16:22:31 -07:00
event . ignore ( )
else :
2025-12-14 19:52:17 +08:00
item = self . itemAt ( event . position ( ) . toPoint ( ) )
2014-06-13 11:56:38 -06:00
if item :
2014-05-15 17:35:24 -06:00
# show item coords in the status bar
coords = " X: {} Y: {} Z: {} " . format ( item . x ( ) , item . y ( ) , item . zValue ( ) )
2024-02-12 16:32:17 +11:00
self . _main_window . uiStatusBar . showMessage ( coords )
2015-06-23 10:52:50 -06:00
# force the children to redraw because of a problem with QGraphicsEffect
for item in self . scene ( ) . selectedItems ( ) :
for child in item . childItems ( ) :
child . update ( )
2015-04-17 12:14:21 +02:00
super ( ) . mouseMoveEvent ( event )
2014-01-31 16:22:31 -07:00
def mouseDoubleClickEvent ( self , event ) :
"""
Handles all mouse double click events .
2014-02-27 21:45:56 -07:00
: param event : QMouseEvent instance
2014-01-31 16:22:31 -07:00
"""
2025-12-14 19:52:17 +08:00
item = self . itemAt ( event . position ( ) . toPoint ( ) )
2015-06-01 15:54:12 +02:00
if not self . _adding_link :
if isinstance ( item , NodeItem ) and item . node ( ) . initialized ( ) :
item . setSelected ( True )
2022-04-20 17:55:16 +07:00
if item . node ( ) . status ( ) == Node . stopped or item . node ( ) . consoleType ( ) == " none " or item . node ( ) . consoleType ( ) is None :
2015-06-01 15:54:12 +02:00
self . configureSlot ( )
return
else :
2024-11-10 18:23:01 +10:00
if item . node ( ) . bringToFront ( ) :
2017-07-11 20:02:43 +07:00
return
2015-06-01 15:54:12 +02:00
self . consoleFromItems ( self . scene ( ) . selectedItems ( ) )
return
2018-06-09 19:13:36 +07:00
elif isinstance ( item , LabelItem ) and isinstance ( item . parentItem ( ) , NodeItem ) :
2015-06-01 15:54:12 +02:00
if item . parentItem ( ) . node ( ) . initialized ( ) :
item . parentItem ( ) . setSelected ( True )
self . changeHostnameActionSlot ( )
return
2015-08-28 15:44:10 +02:00
super ( ) . mouseDoubleClickEvent ( event )
2014-01-31 16:22:31 -07:00
def configureSlot ( self , items = None ) :
"""
2015-06-01 16:46:53 -06:00
Opens the node properties dialog .
2014-01-31 16:22:31 -07:00
"""
if not items :
2015-08-05 18:53:56 -06:00
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2016-08-19 20:01:31 +02:00
if isinstance ( item , NodeItem ) and item . node ( ) . initialized ( ) and hasattr ( item . node ( ) , " configPage " ) :
2015-08-05 18:53:56 -06:00
items . append ( item )
2015-06-19 12:15:21 +02:00
with Progress . instance ( ) . context ( min_duration = 0 ) :
node_properties = NodePropertiesDialog ( items , self . _main_window )
node_properties . setModal ( True )
node_properties . show ( )
2025-12-14 18:19:47 +08:00
node_properties . exec ( )
2014-01-31 16:22:31 -07:00
def dragMoveEvent ( self , event ) :
"""
Handles all drag move events .
2014-02-27 21:45:56 -07:00
: param event : QDragMoveEvent instance
2014-01-31 16:22:31 -07:00
"""
# check if what is dragged is handled by this view
2017-01-24 19:29:19 +01:00
if event . mimeData ( ) . hasFormat ( " text/uri-list " ) \
2018-11-28 16:12:58 +07:00
or event . mimeData ( ) . hasFormat ( " application/x-gns3-template " ) :
2014-01-31 16:22:31 -07:00
event . acceptProposedAction ( )
event . accept ( )
else :
event . ignore ( )
def dropEvent ( self , event ) :
"""
Handles all drop events .
2014-02-27 21:45:56 -07:00
: param event : QDropEvent instance
2014-01-31 16:22:31 -07:00
"""
2023-06-20 14:49:25 +09:30
log . debug ( " Drop event received with mime data: {} " . format ( event . mimeData ( ) . formats ( ) ) )
2014-01-31 16:22:31 -07:00
# check if what has been dropped is handled by this view
2018-11-28 16:12:58 +07:00
if event . mimeData ( ) . hasFormat ( " application/x-gns3-template " ) :
template_id = event . mimeData ( ) . data ( " application/x-gns3-template " ) . data ( ) . decode ( )
2025-12-14 18:19:47 +08:00
event . setDropAction ( QtCore . Qt . DropAction . CopyAction )
2014-01-31 16:22:31 -07:00
event . accept ( )
2025-12-14 19:52:17 +08:00
if event . modifiers ( ) == QtCore . Qt . KeyboardModifier . ShiftModifier :
2014-08-31 19:25:42 -06:00
max_nodes_per_line = 10 # max number of nodes on a single line
offset = 100 # spacing between elements
2015-06-11 11:20:15 +02:00
integer , ok = QtWidgets . QInputDialog . getInt ( self , " Nodes " , " Number of nodes: " , 2 , 1 , 100 , 1 )
2014-08-31 19:25:42 -06:00
if ok :
for node_number in range ( integer ) :
2025-12-14 19:52:17 +08:00
x = event . position ( ) . x ( ) - ( 150 / / 2 ) + ( node_number % max_nodes_per_line ) * offset
y = event . position ( ) . y ( ) - ( 70 / / 2 ) + ( node_number / / max_nodes_per_line ) * offset
2026-03-25 08:52:32 +08:00
if self . createNodeFromTemplateId ( template_id , QtCore . QPointF ( x , y ) ) is False :
2017-07-17 12:25:06 +07:00
event . ignore ( )
break
2014-08-31 19:25:42 -06:00
else :
2025-12-14 19:52:17 +08:00
if self . createNodeFromTemplateId ( template_id , event . position ( ) ) is False :
2017-07-17 12:25:06 +07:00
event . ignore ( )
2014-06-13 13:30:19 -06:00
elif event . mimeData ( ) . hasFormat ( " text/uri-list " ) and event . mimeData ( ) . hasUrls ( ) :
2015-05-06 10:52:29 +02:00
# This should not arrive but we received bug report with it...
2015-05-05 19:58:20 +02:00
if len ( event . mimeData ( ) . urls ( ) ) == 0 :
return
2014-06-13 13:30:19 -06:00
if len ( event . mimeData ( ) . urls ( ) ) > 1 :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Project files " , " Please drop only one file " )
2014-06-13 13:30:19 -06:00
return
path = event . mimeData ( ) . urls ( ) [ 0 ] . toLocalFile ( )
2017-01-23 10:08:03 +01:00
if os . path . isfile ( path ) :
2015-09-11 12:13:02 +02:00
self . _main_window . loadPath ( path )
2014-06-13 13:30:19 -06:00
event . acceptProposedAction ( )
2014-01-31 16:22:31 -07:00
else :
event . ignore ( )
2014-06-13 13:12:36 -06:00
def _showDeviceContextualMenu ( self , pos ) :
2014-01-31 16:22:31 -07:00
"""
2014-06-13 13:12:36 -06:00
Create and display the device contextual menu on the view .
2014-01-31 16:22:31 -07:00
2014-02-27 21:45:56 -07:00
: param pos : position where to display the menu
2014-01-31 16:22:31 -07:00
"""
2026-02-18 17:35:52 +08:00
menu = QtWidgets . QMenu ( parent = self )
2014-06-13 13:12:36 -06:00
self . populateDeviceContextualMenu ( menu )
2025-12-14 18:19:47 +08:00
menu . exec ( pos )
2014-02-27 21:45:56 -07:00
menu . clear ( )
2020-06-09 13:50:31 +09:30
# Make sure to deselect all items.
# This is to prevent a bug on Windows
# see https://github.com/GNS3/gns3-gui/issues/2986
for it in self . scene ( ) . items ( ) :
it . setSelected ( False )
2014-01-31 16:22:31 -07:00
2014-06-13 13:12:36 -06:00
def populateDeviceContextualMenu ( self , menu ) :
2014-01-31 16:22:31 -07:00
"""
2014-06-13 13:12:36 -06:00
Adds device actions to the device contextual menu .
2014-01-31 16:22:31 -07:00
2014-02-27 21:45:56 -07:00
: param menu : QMenu instance
2014-01-31 16:22:31 -07:00
"""
items = self . scene ( ) . selectedItems ( )
if not items :
return
2016-08-19 20:01:31 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " configPage " ) , items ) ) :
2018-12-30 19:35:25 +07:00
# Action: Configure node
2025-12-14 18:19:47 +08:00
configure_action = QtGui . QAction ( " Configure " , menu )
2019-01-17 17:34:30 +07:00
configure_action . setIcon ( get_icon ( " configuration.svg " ) )
2014-06-11 10:04:40 -06:00
configure_action . triggered . connect ( self . configureActionSlot )
menu . addAction ( configure_action )
2014-02-27 21:45:56 -07:00
2019-01-24 14:57:01 +08:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and item . node ( ) . console ( ) is not None , items ) ) :
2025-12-14 18:19:47 +08:00
console_action = QtGui . QAction ( " Console " , menu )
2019-01-24 14:57:01 +08:00
console_action . setIcon ( get_icon ( " console.svg " ) )
console_action . triggered . connect ( self . consoleActionSlot )
menu . addAction ( console_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " auxConsole " ) , items ) ) :
2025-12-14 18:19:47 +08:00
aux_console_action = QtGui . QAction ( " Auxiliary console " , menu )
2019-01-24 14:57:01 +08:00
aux_console_action . setIcon ( get_icon ( " aux-console.svg " ) )
aux_console_action . triggered . connect ( self . auxConsoleActionSlot )
menu . addAction ( aux_console_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and not item . node ( ) . isAlwaysOn ( ) , items ) ) :
2025-12-14 18:19:47 +08:00
start_action = QtGui . QAction ( " Start " , menu )
2019-01-24 14:57:01 +08:00
start_action . setIcon ( get_icon ( " start.svg " ) )
start_action . triggered . connect ( self . startActionSlot )
menu . addAction ( start_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and not item . node ( ) . isAlwaysOn ( ) , items ) ) :
2025-12-14 18:19:47 +08:00
suspend_action = QtGui . QAction ( " Suspend " , menu )
2019-01-24 14:57:01 +08:00
suspend_action . setIcon ( get_icon ( " pause.svg " ) )
suspend_action . triggered . connect ( self . suspendActionSlot )
menu . addAction ( suspend_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and not item . node ( ) . isAlwaysOn ( ) , items ) ) :
2025-12-14 18:19:47 +08:00
stop_action = QtGui . QAction ( " Stop " , menu )
2019-01-24 14:57:01 +08:00
stop_action . setIcon ( get_icon ( " stop.svg " ) )
stop_action . triggered . connect ( self . stopActionSlot )
menu . addAction ( stop_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and not item . node ( ) . isAlwaysOn ( ) , items ) ) :
2025-12-14 18:19:47 +08:00
reload_action = QtGui . QAction ( " Reload " , menu )
2019-01-24 14:57:01 +08:00
reload_action . setIcon ( get_icon ( " reload.svg " ) )
reload_action . triggered . connect ( self . reloadActionSlot )
menu . addAction ( reload_action )
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and item . node ( ) . console ( ) is not None , items ) ) :
2025-12-14 18:19:47 +08:00
console_edit_action = QtGui . QAction ( " Custom console " , menu )
2019-01-24 14:57:01 +08:00
console_edit_action . setIcon ( get_icon ( " console_edit.svg " ) )
console_edit_action . triggered . connect ( self . customConsoleActionSlot )
menu . addAction ( console_edit_action )
2016-08-19 20:01:31 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) , items ) ) :
2015-01-11 18:25:36 -07:00
# Action: Change hostname
2025-12-14 18:19:47 +08:00
change_hostname_action = QtGui . QAction ( " Change hostname " , menu )
2019-01-17 17:34:30 +07:00
change_hostname_action . setIcon ( get_icon ( " show-hostname.svg " ) )
2015-04-30 18:24:01 -06:00
change_hostname_action . triggered . connect ( self . changeHostnameActionSlot )
2015-01-11 18:25:36 -07:00
menu . addAction ( change_hostname_action )
2016-08-19 20:01:31 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) , items ) ) :
2014-07-09 14:49:24 -06:00
# Action: Change symbol
2025-12-14 18:19:47 +08:00
change_symbol_action = QtGui . QAction ( " Change symbol " , menu )
2019-01-17 17:34:30 +07:00
change_symbol_action . setIcon ( get_icon ( " node_conception.svg " ) )
2015-04-30 18:24:01 -06:00
change_symbol_action . triggered . connect ( self . changeSymbolActionSlot )
2014-07-09 14:49:24 -06:00
menu . addAction ( change_symbol_action )
2017-07-24 15:38:54 +07:00
if True in list ( map ( lambda item : isinstance ( item , DrawingItem ) or isinstance ( item , NodeItem ) , items ) ) :
2025-12-14 18:19:47 +08:00
duplicate_action = QtGui . QAction ( " Duplicate " , menu )
2019-01-17 17:34:30 +07:00
duplicate_action . setIcon ( get_icon ( " duplicate.svg " ) )
2017-07-24 15:38:54 +07:00
duplicate_action . triggered . connect ( self . duplicateActionSlot )
menu . addAction ( duplicate_action )
2019-01-24 14:57:01 +08:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " info " ) , items ) ) :
# Action: Show node information
2025-12-14 18:19:47 +08:00
show_node_info_action = QtGui . QAction ( " Show node information " , menu )
2019-01-24 14:57:01 +08:00
show_node_info_action . setIcon ( get_icon ( " help.svg " ) )
show_node_info_action . triggered . connect ( self . showNodeInfoSlot )
menu . addAction ( show_node_info_action )
2016-05-11 16:54:55 -06:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " nodeDir " ) , items ) ) :
2015-06-07 11:53:53 -06:00
# Action: Show in file manager
2025-12-14 18:19:47 +08:00
show_in_file_manager_action = QtGui . QAction ( " Show in file manager " , menu )
2019-01-17 17:34:30 +07:00
show_in_file_manager_action . setIcon ( get_icon ( " open.svg " ) )
2015-06-07 11:53:53 -06:00
show_in_file_manager_action . triggered . connect ( self . showInFileManagerSlot )
menu . addAction ( show_in_file_manager_action )
2024-11-10 18:23:01 +10:00
if not sys . platform . startswith ( " darwin " ) and True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " bringToFront " ) , items ) ) :
# Action: bring console or window to front (Windows and Linux only)
2025-12-14 18:19:47 +08:00
bring_to_front_action = QtGui . QAction ( " Bring to front " , menu )
2019-01-17 17:34:30 +07:00
bring_to_front_action . setIcon ( get_icon ( " front.svg " ) )
2017-07-11 21:22:22 +07:00
bring_to_front_action . triggered . connect ( self . bringToFrontSlot )
menu . addAction ( bring_to_front_action )
2020-08-17 13:09:59 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and bool ( item . node ( ) . configFiles ( ) ) , items ) ) :
2025-12-14 18:19:47 +08:00
import_config_action = QtGui . QAction ( " Import config " , menu )
2019-01-17 17:34:30 +07:00
import_config_action . setIcon ( get_icon ( " import.svg " ) )
2014-12-26 20:33:35 -07:00
import_config_action . triggered . connect ( self . importConfigActionSlot )
menu . addAction ( import_config_action )
2020-08-17 13:09:59 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and bool ( item . node ( ) . configFiles ( ) ) , items ) ) :
2025-12-14 18:19:47 +08:00
export_config_action = QtGui . QAction ( " Export config " , menu )
2019-01-17 17:34:30 +07:00
export_config_action . setIcon ( get_icon ( " export.svg " ) )
2014-12-26 20:33:35 -07:00
export_config_action . triggered . connect ( self . exportConfigActionSlot )
menu . addAction ( export_config_action )
2020-08-18 02:27:31 +02:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and bool ( item . node ( ) . configTextFiles ( ) ) , items ) ) :
2025-12-14 18:19:47 +08:00
export_config_action = QtGui . QAction ( " Edit config " , menu )
2019-01-17 17:34:30 +07:00
export_config_action . setIcon ( get_icon ( " edit.svg " ) )
2016-07-27 18:49:49 +02:00
export_config_action . triggered . connect ( self . editConfigActionSlot )
menu . addAction ( export_config_action )
2015-03-21 14:52:17 -06:00
2015-02-20 16:53:51 -07:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " idlepc " ) , items ) ) :
2025-12-14 18:19:47 +08:00
idlepc_action = QtGui . QAction ( " Idle-PC " , menu )
2019-01-17 17:34:30 +07:00
idlepc_action . setIcon ( get_icon ( " calculate.svg " ) )
2014-03-17 11:51:01 -06:00
idlepc_action . triggered . connect ( self . idlepcActionSlot )
menu . addAction ( idlepc_action )
2015-05-13 17:58:24 -06:00
if True in list ( map ( lambda item : isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " idlepc " ) , items ) ) :
2025-12-14 18:19:47 +08:00
auto_idlepc_action = QtGui . QAction ( " Auto Idle-PC " , menu )
2019-01-17 17:34:30 +07:00
auto_idlepc_action . setIcon ( get_icon ( " calculate.svg " ) )
2015-05-13 17:58:24 -06:00
auto_idlepc_action . triggered . connect ( self . autoIdlepcActionSlot )
menu . addAction ( auto_idlepc_action )
2018-06-09 19:13:36 +07:00
if True in list ( map ( lambda item : isinstance ( item , LabelItem ) , items ) ) :
2025-12-14 18:19:47 +08:00
text_edit_action = QtGui . QAction ( " Text edit " , menu )
2019-01-17 17:34:30 +07:00
text_edit_action . setIcon ( get_icon ( " show-hostname.svg " ) )
2016-06-23 18:21:20 +02:00
text_edit_action . triggered . connect ( self . textEditActionSlot )
menu . addAction ( text_edit_action )
if True in list ( map ( lambda item : isinstance ( item , TextItem ) , items ) ) :
2025-12-14 18:19:47 +08:00
text_edit_action = QtGui . QAction ( " Text edit " , menu )
2019-01-17 17:34:30 +07:00
text_edit_action . setIcon ( get_icon ( " edit.svg " ) )
2014-06-20 09:52:21 -06:00
text_edit_action . triggered . connect ( self . textEditActionSlot )
menu . addAction ( text_edit_action )
2017-01-25 17:36:43 +01:00
if True in list ( map ( lambda item : isinstance ( item , ShapeItem ) or isinstance ( item , LineItem ) , items ) ) :
2025-12-14 18:19:47 +08:00
style_action = QtGui . QAction ( " Style " , menu )
2019-01-17 17:34:30 +07:00
style_action . setIcon ( get_icon ( " node_conception.svg " ) )
2014-06-20 09:52:21 -06:00
style_action . triggered . connect ( self . styleActionSlot )
menu . addAction ( style_action )
2018-06-09 19:13:36 +07:00
if True in list ( map ( lambda item : isinstance ( item , LabelItem ) , items ) ) and False in list ( map ( lambda item : item . parentItem ( ) is None , items ) ) :
2016-04-25 17:47:45 -06:00
# action only for port labels
2025-12-14 18:19:47 +08:00
reset_label_position_action = QtGui . QAction ( " Reset position " , menu )
2019-01-17 17:34:30 +07:00
reset_label_position_action . setIcon ( get_icon ( " reset.svg " ) )
2016-04-25 17:47:45 -06:00
reset_label_position_action . triggered . connect ( self . resetLabelPositionActionSlot )
menu . addAction ( reset_label_position_action )
2014-08-30 18:41:06 -06:00
# item must have no parent
2014-08-14 17:30:39 -06:00
if True in list ( map ( lambda item : item . parentItem ( ) is None , items ) ) :
2014-08-30 18:41:06 -06:00
2015-03-16 13:13:40 -06:00
if len ( items ) > 1 :
2025-12-14 18:19:47 +08:00
horizontal_align_action = QtGui . QAction ( " Align horizontally " , menu )
2019-01-17 17:34:30 +07:00
horizontal_align_action . setIcon ( get_icon ( " horizontally.svg " ) )
2015-03-16 13:13:40 -06:00
horizontal_align_action . triggered . connect ( self . horizontalAlignmentSlot )
menu . addAction ( horizontal_align_action )
2025-12-14 18:19:47 +08:00
vertical_align_action = QtGui . QAction ( " Align vertically " , menu )
2019-01-17 17:34:30 +07:00
vertical_align_action . setIcon ( get_icon ( " vertically.svg " ) )
2015-03-16 13:13:40 -06:00
vertical_align_action . triggered . connect ( self . verticalAlignmentSlot )
menu . addAction ( vertical_align_action )
2025-12-14 18:19:47 +08:00
raise_layer_action = QtGui . QAction ( " Raise one layer " , menu )
2019-01-17 17:34:30 +07:00
raise_layer_action . setIcon ( get_icon ( " raise_z_value.svg " ) )
2014-08-30 18:41:06 -06:00
raise_layer_action . triggered . connect ( self . raiseLayerActionSlot )
menu . addAction ( raise_layer_action )
2025-12-14 18:19:47 +08:00
lower_layer_action = QtGui . QAction ( " Lower one layer " , menu )
2019-01-17 17:34:30 +07:00
lower_layer_action . setIcon ( get_icon ( " lower_z_value.svg " ) )
2014-08-30 18:41:06 -06:00
lower_layer_action . triggered . connect ( self . lowerLayerActionSlot )
menu . addAction ( lower_layer_action )
2017-08-11 17:35:24 +08:00
if len ( items ) > 1 :
2025-12-14 18:19:47 +08:00
lock_action = QtGui . QAction ( " Lock or unlock items " , menu )
2019-01-17 17:34:30 +07:00
lock_action . setIcon ( get_icon ( " lock.svg " ) )
2017-08-11 17:35:24 +08:00
else :
item = items [ 0 ]
2025-12-14 18:19:47 +08:00
if item . flags ( ) & QtWidgets . QGraphicsItem . GraphicsItemFlag . ItemIsMovable :
lock_action = QtGui . QAction ( " Lock item " , menu )
2019-01-17 17:34:30 +07:00
lock_action . setIcon ( get_icon ( " lock.svg " ) )
2017-08-11 17:35:24 +08:00
else :
2025-12-14 18:19:47 +08:00
lock_action = QtGui . QAction ( " Unlock item " , menu )
2019-01-17 17:34:30 +07:00
lock_action . setIcon ( get_icon ( " unlock.svg " ) )
2017-08-11 17:35:24 +08:00
lock_action . triggered . connect ( self . lockActionSlot )
menu . addAction ( lock_action )
2025-12-14 18:19:47 +08:00
delete_action = QtGui . QAction ( " Delete " , menu )
2019-01-17 17:34:30 +07:00
delete_action . setIcon ( get_icon ( " delete.svg " ) )
2014-08-14 17:30:39 -06:00
delete_action . triggered . connect ( self . deleteActionSlot )
menu . addAction ( delete_action )
2014-01-31 16:22:31 -07:00
def startActionSlot ( self ) :
"""
Slot to receive events from the start action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2014-06-26 03:06:59 -06:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " start " ) and item . node ( ) . initialized ( ) :
2014-01-31 16:22:31 -07:00
item . node ( ) . start ( )
def stopActionSlot ( self ) :
"""
Slot to receive events from the stop action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2014-06-26 03:06:59 -06:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " stop " ) and item . node ( ) . initialized ( ) :
2014-01-31 16:22:31 -07:00
item . node ( ) . stop ( )
def suspendActionSlot ( self ) :
"""
Slot to receive events from the suspend action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2014-06-26 03:06:59 -06:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " suspend " ) and item . node ( ) . initialized ( ) :
2014-01-31 16:22:31 -07:00
item . node ( ) . suspend ( )
2014-02-27 21:45:56 -07:00
def reloadActionSlot ( self ) :
"""
Slot to receive events from the reload action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2014-06-26 03:06:59 -06:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " reload " ) and item . node ( ) . initialized ( ) :
2014-02-27 21:45:56 -07:00
item . node ( ) . reload ( )
2014-01-31 16:22:31 -07:00
def configureActionSlot ( self ) :
"""
Slot to receive events from the configure action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2016-12-01 10:10:19 +01:00
if isinstance ( item , NodeItem ) and item . node ( ) . initialized ( ) and hasattr ( item . node ( ) , " configPage " ) :
2014-01-31 16:22:31 -07:00
items . append ( item )
if items :
self . configureSlot ( items )
2015-01-11 18:25:36 -07:00
def changeHostnameActionSlot ( self ) :
"""
Slot to receive events from the change hostname action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
if isinstance ( item , NodeItem ) and item . node ( ) . initialized ( ) :
2025-12-14 18:19:47 +08:00
new_hostname , ok = QtWidgets . QInputDialog . getText ( self , " Change hostname " , " Hostname: " , QtWidgets . QLineEdit . EchoMode . Normal , item . node ( ) . name ( ) )
2015-01-11 18:25:36 -07:00
if ok :
2020-01-25 18:21:02 +08:00
if not new_hostname . strip ( ) :
QtWidgets . QMessageBox . critical ( self , " Change hostname " , " Hostname cannot be blank " )
continue
2024-07-08 14:19:42 +02:00
if hasattr ( item . node ( ) , " validateHostname " ) and not LocalConfig . instance ( ) . experimental ( ) :
2015-01-11 18:25:36 -07:00
if not item . node ( ) . validateHostname ( new_hostname ) :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Change hostname " , " Invalid name detected for this node: {} " . format ( new_hostname ) )
2015-01-11 18:25:36 -07:00
continue
item . node ( ) . update ( { " name " : new_hostname } )
2014-07-09 14:49:24 -06:00
def changeSymbolActionSlot ( self ) :
"""
Slot to receive events from the change symbol action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
if isinstance ( item , NodeItem ) and item . node ( ) . initialized ( ) :
items . append ( item )
if items :
dialog = SymbolSelectionDialog ( self , items )
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2014-07-09 14:49:24 -06:00
2015-06-07 11:53:53 -06:00
def showInFileManagerSlot ( self ) :
"""
Slot to receive events from the show in file manager action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2016-05-11 16:54:55 -06:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " nodeDir " ) and item . node ( ) . initialized ( ) :
2015-06-07 11:53:53 -06:00
node = item . node ( )
2016-05-11 16:54:55 -06:00
node_dir = node . nodeDir ( )
if node_dir is None :
QtWidgets . QMessageBox . critical ( self , " Show in file manager " , " This node has no working directory " )
2015-06-25 10:02:45 +02:00
break
2016-05-11 16:54:55 -06:00
if os . path . exists ( node_dir ) :
2017-05-12 16:49:26 +08:00
log . debug ( " Open %s in file manager " )
2016-05-11 16:54:55 -06:00
if QtGui . QDesktopServices . openUrl ( QtCore . QUrl . fromLocalFile ( node_dir ) ) is False :
QtWidgets . QMessageBox . critical ( self , " Show in file manager " , " Failed to open {} " . format ( node_dir ) )
2015-06-07 11:53:53 -06:00
break
else :
2025-12-14 18:19:47 +08:00
reply = QtWidgets . QMessageBox . information ( self , " Show in file manager " , " The device directory is located in {} on {} \n \n Copy path to clipboard? " . format ( node_dir , node . compute ( ) . name ( ) ) , QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
if reply == QtWidgets . QMessageBox . StandardButton . Yes :
2017-05-12 16:49:26 +08:00
QtWidgets . QApplication . clipboard ( ) . setText ( node_dir )
2015-06-25 10:02:45 +02:00
break
2015-06-07 11:53:53 -06:00
2014-12-19 15:43:15 -07:00
def consoleToNode ( self , node , aux = False ) :
2014-10-26 19:28:08 -06:00
"""
Start a console application to connect to a node .
: param node : Node instance
2014-12-19 15:43:15 -07:00
: param aux : auxiliary console mode
2014-10-26 19:28:08 -06:00
: returns : False if the console application could not be started
"""
if not hasattr ( node , " console " ) or not node . initialized ( ) or node . status ( ) != Node . started :
# returns True to ignore this node.
return True
2014-12-19 15:43:15 -07:00
if aux and not hasattr ( node , " auxConsole " ) :
# returns True to ignore this node.
return True
2017-06-30 10:19:07 +02:00
# TightVNC has lack support of IPv6 host at this moment
if " vncviewer " in node . consoleCommand ( ) and " : " in node . consoleHost ( ) :
2019-10-30 17:59:19 +08:00
QtWidgets . QMessageBox . warning ( self , " TightVNC " , " TightVNC (vncviewer) may not start because of lack of IPv6 support. " )
2017-06-30 10:19:07 +02:00
2016-02-05 19:00:53 +01:00
try :
2016-02-10 17:39:44 +01:00
node . openConsole ( aux = aux )
2016-02-05 19:00:53 +01:00
except ( OSError , ValueError ) as e :
QtWidgets . QMessageBox . critical ( self , " Console " , " Cannot start console application: {} " . format ( e ) )
return False
2014-10-26 19:28:08 -06:00
return True
2015-03-22 14:52:58 -06:00
def consoleFromItems ( self , items ) :
"""
Console from scene items .
: param items : Item instances
"""
nodes = { }
2016-11-04 13:34:43 +01:00
node_initialized = False
2015-03-22 14:52:58 -06:00
for item in items :
2018-04-08 16:44:12 +07:00
if isinstance ( item , NodeItem ) and item . node ( ) . console ( ) is not None and item . node ( ) . initialized ( ) :
2016-11-04 13:34:43 +01:00
node_initialized = True
if item . node ( ) . status ( ) == Node . started :
node = item . node ( )
nodes [ node . name ( ) ] = node
2015-03-22 14:52:58 -06:00
2016-11-04 13:34:43 +01:00
if not nodes and node_initialized :
2016-07-11 10:08:45 -06:00
if len ( items ) > 1 :
QtWidgets . QMessageBox . warning ( self , " Console " , " At least one node must be started before a console can be opened " )
else :
QtWidgets . QMessageBox . warning ( self , " Console " , " This node must be started before a console can be opened " )
2015-03-22 14:52:58 -06:00
delay = self . _main_window . settings ( ) [ " delay_console_all " ]
counter = 0
2025-06-21 11:49:51 +02:00
for name in sorted ( nodes . keys ( ) , key = str . casefold ) :
2015-03-22 14:52:58 -06:00
node = nodes [ name ]
2015-10-29 15:51:12 +01:00
callback = qpartial ( self . consoleToNode , node )
2015-03-22 14:52:58 -06:00
self . _main_window . run_later ( counter , callback )
counter + = delay
2017-11-12 17:02:00 +08:00
def consoleFromAllItems ( self ) :
"""
2020-01-08 00:35:57 +08:00
Console from all scene items with console type different than " none "
2017-11-12 17:02:00 +08:00
"""
items = [ item for item in self . scene ( ) . items ( )
2020-01-08 00:35:57 +08:00
if isinstance ( item , NodeItem ) and item . node ( ) . consoleType ( ) != " none " ]
2020-04-28 15:04:39 +09:30
nb_items = len ( items )
if nb_items > 10 :
proceed = QtWidgets . QMessageBox . question ( self ,
" Console to all nodes " ,
" You are about to open console windows to {} nodes. Are you sure? " . format ( nb_items ) ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes ,
QtWidgets . QMessageBox . StandardButton . No )
2020-04-28 15:04:39 +09:30
2025-12-14 18:19:47 +08:00
if proceed == QtWidgets . QMessageBox . StandardButton . No :
2020-04-28 15:04:39 +09:30
return
2017-11-12 17:02:00 +08:00
self . consoleFromItems ( items )
2014-02-27 21:45:56 -07:00
def consoleActionSlot ( self ) :
"""
Slot to receive events from the console action in the
contextual menu .
"""
2015-03-22 14:52:58 -06:00
self . consoleFromItems ( self . scene ( ) . selectedItems ( ) )
2016-02-10 17:39:44 +01:00
def customConsoleActionSlot ( self ) :
2016-02-05 19:00:53 +01:00
"""
Allow user to use a custom console for this VM
"""
for item in self . scene ( ) . selectedItems ( ) :
2019-11-03 15:19:18 +08:00
if isinstance ( item , NodeItem ) and item . node ( ) . console ( ) is not None and item . node ( ) . initialized ( ) :
2018-03-25 14:35:25 +07:00
if item . node ( ) . consoleType ( ) not in ( " telnet " , " serial " , " vnc " , " spice " , " spice+agent " ) :
2016-05-04 17:01:54 +02:00
continue
2016-02-05 19:00:53 +01:00
current_cmd = item . node ( ) . consoleCommand ( )
console_type = item . node ( ) . consoleType ( )
2019-11-03 15:19:18 +08:00
( ok , cmd ) = ConsoleCommandDialog . getCommand ( self , console_type = console_type , current = current_cmd )
if ok :
2016-02-22 11:26:35 +01:00
try :
2019-11-03 15:19:18 +08:00
if item . node ( ) . status ( ) != Node . started :
QtWidgets . QMessageBox . warning ( self , " Console " , " This node must be started before a console can be opened " )
continue
item . node ( ) . openConsole ( command = cmd )
2016-02-22 11:26:35 +01:00
except ( OSError , ValueError ) as e :
QtWidgets . QMessageBox . critical ( self , " Console " , " Cannot start console application: {} " . format ( e ) )
2016-02-05 19:00:53 +01:00
2015-03-22 14:52:58 -06:00
def auxConsoleFromItems ( self , items ) :
"""
Aux console from scene items .
: param items : Item instances
"""
nodes = { }
2016-11-04 13:34:43 +01:00
node_initialized = False
2015-03-22 14:52:58 -06:00
for item in items :
2016-11-04 13:34:43 +01:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " auxConsole " ) and item . node ( ) . initialized ( ) :
node_initialized = True
if item . node ( ) . status ( ) == Node . started :
node = item . node ( )
nodes [ node . name ( ) ] = node
2015-03-22 14:52:58 -06:00
2016-11-04 13:34:43 +01:00
if not nodes and node_initialized :
2016-07-11 10:08:45 -06:00
if len ( items ) > 1 :
QtWidgets . QMessageBox . warning ( self , " Console " , " At least one node must be started before a console can be opened " )
else :
QtWidgets . QMessageBox . warning ( self , " Console " , " This node must be started before a console can be opened " )
2015-03-22 14:52:58 -06:00
delay = self . _main_window . settings ( ) [ " delay_console_all " ]
counter = 0
2025-06-21 11:49:51 +02:00
for name in sorted ( nodes . keys ( ) , key = str . casefold ) :
2015-03-22 14:52:58 -06:00
node = nodes [ name ]
2015-10-29 15:51:12 +01:00
callback = qpartial ( self . consoleToNode , node , aux = True )
2015-03-22 14:52:58 -06:00
self . _main_window . run_later ( counter , callback )
counter + = delay
2014-12-19 15:43:15 -07:00
def auxConsoleActionSlot ( self ) :
"""
Slot to receive events from the auxiliary console action in the
contextual menu .
"""
2015-03-22 14:52:58 -06:00
self . auxConsoleFromItems ( self . scene ( ) . selectedItems ( ) )
2014-02-27 21:45:56 -07:00
2014-12-26 20:33:35 -07:00
def importConfigActionSlot ( self ) :
"""
Slot to receive events from the import config action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2020-08-17 13:09:59 +02:00
if isinstance ( item , NodeItem ) and item . node ( ) . configFiles ( ) and item . node ( ) . initialized ( ) :
2014-12-26 20:33:35 -07:00
items . append ( item )
if not items :
return
2016-07-27 21:35:05 +02:00
for item in items :
if len ( item . node ( ) . configFiles ( ) ) == 1 :
config_file = item . node ( ) . configFiles ( ) [ 0 ]
else :
config_file , ok = QtWidgets . QInputDialog . getItem ( self , " Import file " , " File to import? " , item . node ( ) . configFiles ( ) , 0 , False )
if not ok :
continue
path , _ = QtWidgets . QFileDialog . getOpenFileName ( self ,
" Import {} " . format ( os . path . basename ( config_file ) ) ,
2019-04-13 18:45:16 +07:00
self . _import_config_directory ,
2025-01-07 11:32:17 +07:00
" All files (*);;Config files (*.cfg) " ,
2016-07-27 21:35:05 +02:00
" Config files (*.cfg) " )
2017-09-15 15:52:36 +07:00
if not path :
continue
2019-04-13 18:45:16 +07:00
self . _import_config_directory = os . path . dirname ( path )
2016-07-27 21:35:05 +02:00
item . node ( ) . importFile ( config_file , path )
2014-12-26 20:33:35 -07:00
2016-07-27 18:49:49 +02:00
def editConfigActionSlot ( self ) :
"""
Slot to receive event to edit the configuration file
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2020-08-18 02:27:31 +02:00
if isinstance ( item , NodeItem ) and item . node ( ) . configTextFiles ( ) and item . node ( ) . initialized ( ) :
2016-07-27 18:49:49 +02:00
items . append ( item )
if not items :
return
for item in items :
2020-08-18 02:27:31 +02:00
if len ( item . node ( ) . configTextFiles ( ) ) == 1 :
config_file = item . node ( ) . configTextFiles ( ) [ 0 ]
2016-07-27 18:49:49 +02:00
else :
2020-08-18 02:27:31 +02:00
config_file , ok = QtWidgets . QInputDialog . getItem ( self , " Edit file " , " File to edit? " , item . node ( ) . configTextFiles ( ) , 0 , False )
2016-07-27 18:49:49 +02:00
if not ok :
continue
dialog = FileEditorDialog ( item . node ( ) , config_file , parent = self )
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2016-07-27 18:49:49 +02:00
2014-12-26 20:33:35 -07:00
def exportConfigActionSlot ( self ) :
"""
Slot to receive events from the export config action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2020-08-17 13:09:59 +02:00
if isinstance ( item , NodeItem ) and item . node ( ) . configFiles ( ) and item . node ( ) . initialized ( ) :
2014-12-26 20:33:35 -07:00
items . append ( item )
if not items :
return
2016-07-27 21:45:13 +02:00
for item in items :
for config_file in item . node ( ) . configFiles ( ) :
2025-01-07 11:32:17 +07:00
path , ok = QtWidgets . QFileDialog . getSaveFileName ( self , " Export file " , os . path . join ( self . _export_config_directory , item . node ( ) . name ( ) + " _ " + os . path . basename ( config_file ) ) , " All files (*);;Config files (*.cfg) " )
2016-09-06 18:17:01 +02:00
if not path :
continue
2018-04-08 16:44:12 +07:00
self . _export_config_directory = os . path . dirname ( path )
2016-09-06 18:17:01 +02:00
item . node ( ) . exportFile ( config_file , path )
2015-03-21 14:52:17 -06:00
2018-12-30 19:35:25 +07:00
def showNodeInfoSlot ( self ) :
2016-02-02 17:10:59 +01:00
"""
2018-12-30 19:35:25 +07:00
Slot to receive events from the show node info action in the
2016-02-02 17:10:59 +01:00
contextual menu .
"""
items = self . scene ( ) . selectedItems ( )
if len ( items ) != 1 :
2018-12-30 19:35:25 +07:00
QtWidgets . QMessageBox . critical ( self , " Show node information " , " Please select only one node " )
2016-02-02 17:10:59 +01:00
return
item = items [ 0 ]
2018-12-30 19:35:25 +07:00
if isinstance ( item , NodeItem ) :
dialog = NodeInfoDialog ( item . node ( ) , parent = self )
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2016-02-02 17:10:59 +01:00
2017-05-16 11:14:53 +02:00
def bringToFrontSlot ( self ) :
"""
Slot to receive events from the bring to front action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " bringToFront " ) :
item . node ( ) . bringToFront ( )
2014-02-27 21:45:56 -07:00
def idlepcActionSlot ( self ) :
"""
Slot to receive events from the idlepc action in the
contextual menu .
"""
items = self . scene ( ) . selectedItems ( )
if len ( items ) != 1 :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Idle-PC " , " Please select only one router " )
2014-02-27 21:45:56 -07:00
return
item = items [ 0 ]
2015-02-20 16:53:51 -07:00
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " idlepc " ) and item . node ( ) . initialized ( ) :
2014-02-27 21:45:56 -07:00
router = item . node ( )
2015-03-11 19:32:40 -06:00
router . computeIdlepcs ( self . _idlepcCallback )
2014-02-27 21:45:56 -07:00
2015-02-20 16:53:51 -07:00
def _idlepcCallback ( self , result , error = False , context = { } , * * kwargs ) :
2014-02-27 21:45:56 -07:00
"""
2015-02-20 16:53:51 -07:00
Slot to allow the user to select an idle - pc value .
2014-02-27 21:45:56 -07:00
"""
2015-02-20 16:53:51 -07:00
if error :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Idle-PC " , " Error: {} " . format ( result [ " message " ] ) )
2015-02-20 16:53:51 -07:00
else :
router = context [ " router " ]
2017-04-27 15:46:39 +02:00
log . debug ( " {} has received Idle-PC proposals " . format ( router . name ( ) ) )
2015-02-20 16:53:51 -07:00
idlepcs = result
if idlepcs and idlepcs [ 0 ] != " 0x0 " :
dialog = IdlePCDialog ( router , idlepcs , parent = self )
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2015-02-20 16:53:51 -07:00
else :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Idle-PC " , " Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state " )
2014-02-27 21:45:56 -07:00
2015-05-13 17:58:24 -06:00
def autoIdlepcActionSlot ( self ) :
"""
Slot to receive events from the auto idlepc action in the
contextual menu .
"""
items = self . scene ( ) . selectedItems ( )
if len ( items ) != 1 :
2015-05-18 12:26:45 +02:00
QtWidgets . QMessageBox . critical ( self , " Auto Idle-PC " , " Please select only one router " )
2015-05-13 17:58:24 -06:00
return
item = items [ 0 ]
if isinstance ( item , NodeItem ) and hasattr ( item . node ( ) , " idlepc " ) and item . node ( ) . initialized ( ) :
router = item . node ( )
router . computeAutoIdlepc ( self . _autoIdlepcCallback )
2015-03-22 14:52:58 -06:00
def _autoIdlepcCallback ( self , result , error = False , context = { } , * * kwargs ) :
"""
Slot to allow the user to select an idlepc value .
"""
if error :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Auto Idle-PC " , " Error: {} " . format ( result [ " message " ] ) )
2015-03-22 14:52:58 -06:00
else :
router = context [ " router " ]
idlepc = result [ " idlepc " ]
2017-04-27 15:46:39 +02:00
log . debug ( " {} has received the auto idle-pc value: {} " . format ( router . name ( ) , idlepc ) )
2015-03-22 14:52:58 -06:00
router . setIdlepc ( idlepc )
2015-06-08 11:55:41 -06:00
# apply Idle-PC to all routers with the same IOS image
2015-05-13 17:58:24 -06:00
ios_image = os . path . basename ( router . settings ( ) [ " image " ] )
2015-06-08 11:55:41 -06:00
for node in Topology . instance ( ) . nodes ( ) :
if hasattr ( node , " idlepc " ) and node . settings ( ) [ " image " ] == ios_image :
node . setIdlepc ( idlepc )
# apply the idle-pc to templates with the same IOS image
2015-05-13 17:58:24 -06:00
router . module ( ) . updateImageIdlepc ( ios_image , idlepc )
2019-02-22 22:22:31 +07:00
QtWidgets . QMessageBox . information ( self , " Auto Idle-PC " , " Idle-PC value {} has been applied on {} and all templates with IOS image {} " . format ( idlepc ,
router . name ( ) ,
ios_image ) )
2014-02-27 21:45:56 -07:00
2014-06-14 13:26:22 -06:00
def duplicateActionSlot ( self ) :
"""
Slot to receive events from the duplicate action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2016-06-23 18:21:20 +02:00
if isinstance ( item , DrawingItem ) :
if isinstance ( item , EllipseItem ) :
type = " ellipse "
elif isinstance ( item , TextItem ) :
type = " text "
elif isinstance ( item , RectangleItem ) :
type = " rect "
else :
type = " image "
2022-01-15 18:55:38 +10:30
self . createDrawingItem (
type ,
2022-01-15 18:57:15 +10:30
int ( item . pos ( ) . x ( ) ) + 20 ,
2022-01-15 18:55:38 +10:30
int ( item . pos ( ) . y ( ) ) + 20 ,
item . zValue ( ) ,
rotation = item . rotation ( ) ,
svg = item . toSvg ( )
)
2017-07-20 17:17:31 +02:00
elif isinstance ( item , NodeItem ) :
item . node ( ) . duplicate ( item . pos ( ) . x ( ) + 20 , item . pos ( ) . y ( ) + 20 , item . zValue ( ) )
2014-06-14 13:26:22 -06:00
2014-06-20 09:52:21 -06:00
def styleActionSlot ( self ) :
"""
Slot to receive events from the style action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2017-01-25 17:36:43 +01:00
if isinstance ( item , ShapeItem ) or isinstance ( item , LineItem ) :
2014-06-20 09:52:21 -06:00
items . append ( item )
if items :
style_dialog = StyleEditorDialog ( self . _main_window , items )
style_dialog . show ( )
2025-12-14 18:19:47 +08:00
style_dialog . exec ( )
2014-06-20 09:52:21 -06:00
def textEditActionSlot ( self ) :
"""
Slot to receive events from the text edit action in the
contextual menu .
"""
items = [ ]
for item in self . scene ( ) . selectedItems ( ) :
2018-06-09 19:13:36 +07:00
if isinstance ( item , LabelItem ) or isinstance ( item , TextItem ) :
2014-06-20 09:52:21 -06:00
items . append ( item )
if items :
text_edit_dialog = TextEditorDialog ( self . _main_window , items )
text_edit_dialog . show ( )
2025-12-14 18:19:47 +08:00
text_edit_dialog . exec ( )
2014-06-20 09:52:21 -06:00
2016-04-25 17:47:45 -06:00
def resetLabelPositionActionSlot ( self ) :
"""
Slot to receive events from the reset label position action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2018-06-09 19:13:36 +07:00
if isinstance ( item , LabelItem ) and item . parentItem ( ) :
2016-04-25 17:47:45 -06:00
links = item . parentItem ( ) . links ( )
for port in item . parentItem ( ) . node ( ) . ports ( ) :
# find the correct port associated with the label
if port . label ( ) == item :
port . deleteLabel ( )
break
# adjust all node links to force to re-display the label
for link in links :
link . adjust ( )
2015-03-06 15:39:32 -07:00
def horizontalAlignmentSlot ( self ) :
"""
Slot to receive events from the horizontal align action in the
contextual menu .
"""
2015-03-16 13:13:40 -06:00
horizontal_pos = None
2015-03-06 15:39:32 -07:00
for item in self . scene ( ) . selectedItems ( ) :
2015-03-16 13:13:40 -06:00
if item . parentItem ( ) is None :
if horizontal_pos is None :
2015-09-14 15:41:37 -06:00
horizontal_pos = item . y ( ) + item . boundingRect ( ) . height ( ) / 2
2016-12-12 12:50:02 +01:00
item . setX ( item . x ( ) )
item . setY ( horizontal_pos - item . boundingRect ( ) . height ( ) / 2 )
item . updateNode ( )
2015-03-06 15:39:32 -07:00
def verticalAlignmentSlot ( self ) :
"""
Slot to receive events from the vertical align action in the
contextual menu .
"""
2015-03-16 13:13:40 -06:00
vertical_position = None
2015-03-06 15:39:32 -07:00
for item in self . scene ( ) . selectedItems ( ) :
2015-03-16 13:13:40 -06:00
if item . parentItem ( ) is None :
if vertical_position is None :
2015-09-14 15:41:37 -06:00
vertical_position = item . x ( ) + item . boundingRect ( ) . width ( ) / 2
2016-12-12 12:50:02 +01:00
item . setX ( vertical_position - item . boundingRect ( ) . width ( ) / 2 )
item . setY ( item . y ( ) )
item . updateNode ( )
2015-03-06 15:39:32 -07:00
2014-08-30 18:41:06 -06:00
def raiseLayerActionSlot ( self ) :
"""
Slot to receive events from the raise one layer action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
if item . parentItem ( ) is None :
2025-12-14 18:19:47 +08:00
if not ( item . flags ( ) & QtWidgets . QGraphicsItem . GraphicsItemFlag . ItemIsMovable ) :
2019-03-02 18:49:19 +07:00
log . error ( " Cannot move object to a upper layer because it is locked " )
continue
2019-03-02 16:26:41 +07:00
item . setZValue ( item . zValue ( ) + 1 )
2016-12-12 12:50:02 +01:00
item . updateNode ( )
2014-08-30 18:41:06 -06:00
item . update ( )
def lowerLayerActionSlot ( self ) :
"""
Slot to receive events from the lower one layer action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
if item . parentItem ( ) is None :
2025-12-14 18:19:47 +08:00
if not ( item . flags ( ) & QtWidgets . QGraphicsItem . GraphicsItemFlag . ItemIsMovable ) :
2019-03-02 18:49:19 +07:00
log . error ( " Cannot move object to a lower layer because it is locked " )
continue
2019-03-02 16:26:41 +07:00
item . setZValue ( item . zValue ( ) - 1 )
2016-12-12 12:50:02 +01:00
item . updateNode ( )
2014-08-30 18:41:06 -06:00
item . update ( )
2015-04-28 11:01:16 +02:00
2017-08-11 17:35:24 +08:00
def lockActionSlot ( self ) :
"""
Slot to receive events from the lock action in the
contextual menu .
"""
for item in self . scene ( ) . selectedItems ( ) :
2019-04-13 18:40:54 +07:00
if not isinstance ( item , LinkItem ) and not isinstance ( item , LabelItem ) and not isinstance ( item , SvgIconItem ) :
2019-03-02 16:26:41 +07:00
if item . locked ( ) is True :
item . setLocked ( False )
else :
item . setLocked ( True )
2018-06-13 17:31:27 +08:00
if item . parentItem ( ) is None :
item . updateNode ( )
2018-06-09 19:13:36 +07:00
item . update ( )
2017-08-11 17:35:24 +08:00
2014-01-31 16:22:31 -07:00
def deleteActionSlot ( self ) :
"""
Slot to receive events from the delete action in the
contextual menu .
"""
2014-06-11 10:04:40 -06:00
selected_nodes = [ ]
for item in self . scene ( ) . selectedItems ( ) :
if isinstance ( item , NodeItem ) :
2019-04-10 15:43:52 +07:00
node = item . node ( )
if node . locked ( ) :
QtWidgets . QMessageBox . critical ( self , " Delete " , " Cannot delete node ' {} ' because it is locked " . format ( node . name ( ) ) )
return
selected_nodes . append ( node )
2020-03-16 16:30:09 +10:30
if isinstance ( item , DrawingItem ) and item . locked ( ) :
QtWidgets . QMessageBox . critical ( self , " Delete " , " Cannot delete drawing because it is locked " )
return
2014-06-11 10:04:40 -06:00
if selected_nodes :
if len ( selected_nodes ) > 1 :
question = " Do you want to permanently delete these {} nodes? " . format ( len ( selected_nodes ) )
2014-06-10 09:00:20 -06:00
else :
2014-06-11 10:04:40 -06:00
question = " Do you want to permanently delete {} ? " . format ( selected_nodes [ 0 ] . name ( ) )
2015-04-17 12:14:21 +02:00
reply = QtWidgets . QMessageBox . question ( self , " Delete " , question ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes , QtWidgets . QMessageBox . StandardButton . No )
if reply == QtWidgets . QMessageBox . StandardButton . No :
2014-06-10 09:00:20 -06:00
return
2014-06-11 10:04:40 -06:00
for item in self . scene ( ) . selectedItems ( ) :
2014-01-31 16:22:31 -07:00
if isinstance ( item , NodeItem ) :
item . node ( ) . delete ( )
2014-02-27 21:45:56 -07:00
self . _topology . removeNode ( item . node ( ) )
2014-08-30 18:41:06 -06:00
elif item . parentItem ( ) is None :
2014-06-11 10:04:40 -06:00
item . delete ( )
2014-01-31 16:22:31 -07:00
2017-08-22 13:01:50 +02:00
self . scene ( ) . clearSelection ( )
self . toggleUiDeviceMenu ( )
2016-05-20 18:19:31 +02:00
def allocateCompute ( self , node_data , module_instance ) :
2015-09-13 11:11:14 -06:00
"""
Allocates a server .
2016-05-20 18:19:31 +02:00
: returns : allocated compute node
2015-09-13 11:11:14 -06:00
"""
2015-09-15 22:36:25 +02:00
from . main_window import MainWindow
mainwindow = MainWindow . instance ( )
2016-02-04 17:49:46 +01:00
2018-11-11 20:13:58 +08:00
if " compute_id " in node_data :
2017-03-08 14:44:30 +01:00
try :
2018-11-11 20:13:58 +08:00
return ComputeManager . instance ( ) . getCompute ( node_data [ " compute_id " ] )
2017-03-08 14:44:30 +01:00
except KeyError :
2018-11-11 20:13:58 +08:00
raise ModuleError ( " Compute {} doesn ' t exists " . format ( node_data [ " compute_id " ] ) )
2016-05-20 18:19:31 +02:00
2016-12-06 19:36:24 +01:00
server = server_select ( mainwindow , node_data . get ( " node_type " ) )
2015-09-15 22:36:25 +02:00
if server is None :
raise ModuleError ( " Please select a server " )
return server
2015-09-13 11:11:14 -06:00
2018-11-28 16:12:58 +07:00
def createNodeFromTemplateId ( self , template_id , pos ) :
2014-01-31 16:22:31 -07:00
"""
2018-11-28 16:12:58 +07:00
Ask the server to create a node using this template
2014-01-31 16:22:31 -07:00
"""
2025-12-14 19:52:17 +08:00
pos = self . mapToScene ( pos . toPoint ( ) )
2018-11-28 16:12:58 +07:00
return TemplateManager ( ) . instance ( ) . createNodeFromTemplateId ( self . _topology . project ( ) , template_id , pos . x ( ) , pos . y ( ) )
2016-06-15 18:49:26 +02:00
2019-03-02 16:26:41 +07:00
def createNodeItem ( self , node , symbol , x , y ) :
2016-07-05 18:02:47 +02:00
node . setSymbol ( symbol )
2019-03-02 16:26:41 +07:00
node . setPos ( x , y )
2016-07-05 18:02:47 +02:00
node_item = NodeItem ( node )
2017-07-03 15:09:12 +02:00
2014-01-31 16:22:31 -07:00
self . scene ( ) . addItem ( node_item )
2014-02-27 21:45:56 -07:00
self . _topology . addNode ( node )
2016-09-01 15:38:11 +02:00
node . error_signal . connect ( self . _displayNodeErrorSlot )
node . server_error_signal . connect ( self . _displayNodeErrorSlot )
2014-08-31 19:25:42 -06:00
return node_item
2016-06-21 19:03:58 +02:00
2017-01-30 10:51:31 +01:00
@qslot
def _displayNodeErrorSlot ( self , node_id , message , * args ) :
2016-09-01 15:38:11 +02:00
"""
Show error send by a node to the user
"""
node = Topology . instance ( ) . getNode ( node_id )
name = " Node "
2017-09-15 17:23:15 +07:00
if node and node . name ( ) :
name = node . name ( )
2017-01-30 10:51:31 +01:00
if self . _main_window and not sip . isdeleted ( self . _main_window ) :
QtWidgets . QMessageBox . critical ( self . _main_window , name , message . strip ( ) )
2016-09-01 15:38:11 +02:00
2019-03-02 16:26:41 +07:00
def createDrawingItem ( self , type , x , y , z , locked = False , rotation = 0 , svg = None , drawing_id = None ) :
2016-09-02 12:02:44 +02:00
2016-06-21 19:03:58 +02:00
if type == " ellipse " :
2025-12-15 19:27:55 +08:00
item = EllipseItem ( pos = QtCore . QPointF ( x , y ) , z = z , locked = locked , rotation = rotation , project = self . _topology . project ( ) , drawing_id = drawing_id , svg = svg )
2016-06-21 19:03:58 +02:00
elif type == " rect " :
2025-12-15 19:27:55 +08:00
item = RectangleItem ( pos = QtCore . QPointF ( x , y ) , z = z , locked = locked , rotation = rotation , project = self . _topology . project ( ) , drawing_id = drawing_id , svg = svg )
2017-01-25 17:36:43 +01:00
elif type == " line " :
2025-12-15 19:27:55 +08:00
item = LineItem ( pos = QtCore . QPointF ( x , y ) , dst = QtCore . QPoint ( 200 , 0 ) , z = z , locked = locked , rotation = rotation , project = self . _topology . project ( ) , drawing_id = drawing_id , svg = svg )
2016-06-22 17:47:17 +02:00
elif type == " image " :
2025-12-15 19:27:55 +08:00
item = ImageItem ( pos = QtCore . QPointF ( x , y ) , z = z , rotation = rotation , locked = locked , project = self . _topology . project ( ) , drawing_id = drawing_id , svg = svg )
2016-06-23 18:21:20 +02:00
elif type == " text " :
2025-12-15 19:27:55 +08:00
item = TextItem ( pos = QtCore . QPointF ( x , y ) , z = z , rotation = rotation , locked = locked , project = self . _topology . project ( ) , drawing_id = drawing_id , svg = svg )
2016-09-02 12:02:44 +02:00
if drawing_id is None :
item . create ( )
2016-06-21 19:03:58 +02:00
self . scene ( ) . addItem ( item )
2016-06-23 13:07:45 +02:00
self . _topology . addDrawing ( item )
2016-06-23 18:21:20 +02:00
return item
2016-06-22 14:12:38 +02:00
2024-12-02 00:13:01 -09:00
@QtCore.Property ( QtGui . QColor )
def drawingGridColor ( self ) :
""" Returns the drawing grid color """
return self . _drawing_grid_color
@drawingGridColor.setter
def drawingGridColor ( self , color ) :
""" Sets the drawing grid color """
self . _drawing_grid_color = color
self . viewport ( ) . update ( )
@QtCore.Property ( QtGui . QColor )
def nodeGridColor ( self ) :
""" Returns the node grid color """
return self . _node_grid_color
@nodeGridColor.setter
def nodeGridColor ( self , color ) :
""" Sets the node grid color """
self . _node_grid_color = color
self . viewport ( ) . update ( )
def resetGridColors ( self ) :
""" Reset grid colors to defaults """
self . _drawing_grid_color = self . DEFAULT_DRAWING_GRID_COLOR
self . _node_grid_color = self . DEFAULT_NODE_GRID_COLOR
self . viewport ( ) . update ( )
2016-06-20 15:19:01 +02:00
def drawBackground ( self , painter , rect ) :
super ( ) . drawBackground ( painter , rect )
2016-06-22 13:08:20 -06:00
if self . _main_window . uiShowGridAction . isChecked ( ) :
2024-12-02 00:13:01 -09:00
grids = [ ( self . drawingGridSize ( ) , self . _drawing_grid_color ) ,
( self . nodeGridSize ( ) , self . _node_grid_color ) ]
2016-06-22 11:02:36 +02:00
painter . save ( )
2019-05-23 14:51:53 +07:00
for ( grid , colour ) in grids :
if not grid :
continue
2018-11-22 09:35:18 +00:00
painter . setPen ( QtGui . QPen ( colour ) )
left = int ( rect . left ( ) ) - ( int ( rect . left ( ) ) % grid )
top = int ( rect . top ( ) ) - ( int ( rect . top ( ) ) % grid )
x = left
while x < rect . right ( ) :
2021-12-13 23:27:28 +01:00
painter . drawLine ( x , int ( rect . top ( ) ) , x , int ( rect . bottom ( ) ) )
2018-11-22 09:35:18 +00:00
x + = grid
y = top
while y < rect . bottom ( ) :
2021-12-13 23:27:28 +01:00
painter . drawLine ( int ( rect . left ( ) ) , y , int ( rect . right ( ) ) , y )
2018-11-22 09:35:18 +00:00
y + = grid
2016-06-22 11:02:36 +02:00
painter . restore ( )
2017-08-22 13:01:50 +02:00
def toggleUiDeviceMenu ( self ) :
""" Hook which enables/disables uiDeviceMenu based on the current items selection """
items = self . scene ( ) . selectedItems ( )
if len ( items ) > 0 :
self . _main_window . uiDeviceMenu . setEnabled ( True )
else :
self . _main_window . uiDeviceMenu . setEnabled ( False )