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/>.
"""
Main window for the GUI .
"""
2014-11-03 17:35:23 -07:00
import sys
2014-02-27 21:45:56 -07:00
import os
2014-10-14 21:16:09 -06:00
import time
2014-06-20 14:30:52 -06:00
import logging
2014-05-01 15:11:43 -06:00
2015-02-08 12:48:45 -07:00
from . local_config import LocalConfig
2016-05-20 18:19:31 +02:00
from . local_server import LocalServer
2014-07-03 18:56:37 -06:00
from . modules import MODULES
2016-10-03 15:38:56 +02:00
from . qt import QtGui , QtCore , QtWidgets , qslot
2016-05-20 18:19:31 +02:00
from . controller import Controller
2014-03-02 15:26:24 -07:00
from . node import Node
2014-01-31 16:22:31 -07:00
from . ui . main_window_ui import Ui_MainWindow
2016-05-14 10:36:30 -06:00
from . style import Style
2014-06-20 14:30:52 -06:00
from . dialogs . about_dialog import AboutDialog
2016-06-15 18:49:26 +02:00
from . dialogs . project_dialog import ProjectDialog
2014-06-20 14:30:52 -06:00
from . dialogs . preferences_dialog import PreferencesDialog
2014-10-19 21:24:21 -06:00
from . dialogs . snapshots_dialog import SnapshotsDialog
2015-11-05 15:43:11 +01:00
from . dialogs . export_debug_dialog import ExportDebugDialog
2016-01-18 21:02:08 +01:00
from . dialogs . doctor_dialog import DoctorDialog
2016-08-16 11:12:38 +02:00
from . dialogs . edit_project_dialog import EditProjectDialog
2015-07-07 12:59:16 -06:00
from . dialogs . setup_wizard import SetupWizard
2015-07-08 17:40:59 +02:00
from . settings import GENERAL_SETTINGS
2014-02-27 21:45:56 -07:00
from . items . node_item import NodeItem
2019-04-13 18:40:54 +07:00
from . items . link_item import LinkItem , SvgIconItem
2014-08-30 18:41:06 -06:00
from . items . shape_item import ShapeItem
2019-03-02 16:26:41 +07:00
from . items . label_item import LabelItem
2014-12-15 17:05:53 -07:00
from . topology import Topology
2015-02-18 17:26:04 +01:00
from . http_client import HTTPClient
from . progress import Progress
2015-07-01 11:46:57 +02:00
from . update_manager import UpdateManager
2015-10-20 19:31:18 +02:00
from . dialogs . appliance_wizard import ApplianceWizard
2018-11-28 16:12:58 +07:00
from . dialogs . new_template_wizard import NewTemplateWizard
2017-01-20 18:53:45 +01:00
from . dialogs . notif_dialog import NotifDialog , NotifDialogHandler
2017-02-17 16:24:58 +01:00
from . status_bar import StatusBarHandler
2015-11-02 18:01:46 +01:00
from . registry . appliance import ApplianceError
2018-11-28 16:12:58 +07:00
from . template_manager import TemplateManager
2018-02-27 16:07:06 +01:00
from . appliance_manager import ApplianceManager
2014-03-19 18:24:07 -06:00
2014-02-27 21:45:56 -07:00
log = logging . getLogger ( __name__ )
2014-01-31 16:22:31 -07:00
2015-04-17 12:14:21 +02:00
class MainWindow ( QtWidgets . QMainWindow , Ui_MainWindow ) :
2015-01-28 11:13:10 +01:00
2014-01-31 16:22:31 -07:00
"""
Main window implementation .
: param parent : parent widget
"""
# signal to tell the view if the user is adding a link or not
2025-12-14 18:19:47 +08:00
adding_link_signal = QtCore . Signal ( bool )
2014-01-31 16:22:31 -07:00
2018-02-27 16:07:06 +01:00
# Signal of settings updates
settings_updated_signal = QtCore . Signal ( )
2016-07-06 15:23:56 +02:00
def __init__ ( self , parent = None , open_file = None ) :
"""
: param open_file : Open this file instead of asking for a new project
"""
2014-01-31 16:22:31 -07:00
2015-04-17 12:14:21 +02:00
super ( ) . __init__ ( parent )
2017-07-26 14:55:13 +02:00
self . _settings = { }
2014-01-31 16:22:31 -07:00
self . setupUi ( self )
2020-04-29 13:08:51 +09:30
self . setUnifiedTitleAndToolBarOnMac ( True )
2015-04-17 12:14:21 +02:00
2021-06-10 12:28:25 +09:30
# These widgets will be disabled when no project is loaded
self . disableWhenNoProjectWidgets = [
self . uiGraphicsView ,
self . uiAnnotateMenu ,
self . uiAnnotationToolBar ,
self . uiControlToolBar ,
self . uiControlMenu ,
self . uiSaveProjectAsAction ,
self . uiExportProjectAction ,
self . uiScreenshotAction ,
self . uiSnapshotAction ,
self . uiEditProjectAction ,
self . uiDeleteProjectAction ,
self . uiImportExportConfigsAction ,
self . uiLockAllAction
]
2017-02-28 17:42:23 +01:00
self . _notif_dialog = NotifDialog ( self )
2017-01-20 18:53:45 +01:00
# Setup logger
2017-02-28 17:42:23 +01:00
logging . getLogger ( ) . addHandler ( NotifDialogHandler ( self . _notif_dialog ) )
2017-02-17 16:24:58 +01:00
logging . getLogger ( ) . addHandler ( StatusBarHandler ( self . uiStatusBar ) )
2017-01-20 18:53:45 +01:00
2016-07-06 15:23:56 +02:00
self . _open_file_at_startup = open_file
2014-11-24 14:21:51 -07:00
MainWindow . _instance = self
2016-06-15 18:49:26 +02:00
topology = Topology . instance ( )
topology . setMainWindow ( self )
2016-08-15 12:59:13 +02:00
topology . project_changed_signal . connect ( self . _projectChangedSlot )
2016-08-23 19:34:24 +02:00
Controller . instance ( ) . setParent ( self )
2017-07-20 11:51:05 +02:00
LocalServer . instance ( ) . setParent ( self )
2016-06-15 18:49:26 +02:00
2016-03-22 17:15:09 +01:00
HTTPClient . setProgressCallback ( Progress . instance ( self ) )
2015-02-18 17:26:04 +01:00
2015-09-10 13:44:42 +02:00
self . _first_file_load = True
2016-05-14 12:15:20 -06:00
self . _open_project_path = None
2014-02-27 21:45:56 -07:00
self . _loadSettings ( )
self . _connections ( )
2017-04-10 17:59:17 +02:00
self . _maxrecent_files = 5
2015-09-10 13:44:42 +02:00
self . _project_dialog = None
2017-04-10 17:59:17 +02:00
self . recent_file_actions = [ ]
self . recent_project_actions = [ ]
2014-10-16 08:42:10 -06:00
self . _start_time = time . time ( )
2015-06-20 16:10:33 -06:00
local_config = LocalConfig . instance ( )
2018-08-16 21:47:52 +07:00
#local_config.config_changed_signal.connect(self._localConfigChangedSlot)
2015-08-10 18:33:59 -06:00
self . _local_config_timer = QtCore . QTimer ( self )
self . _local_config_timer . timeout . connect ( local_config . checkConfigChanged )
self . _local_config_timer . start ( 1000 ) # milliseconds
2018-11-28 16:12:58 +07:00
self . _template_manager = TemplateManager ( ) . instance ( )
self . _appliance_manager = ApplianceManager ( ) . instance ( )
2014-05-28 14:44:03 -06:00
2014-11-19 10:22:09 -07:00
# restore the geometry and state of the main window.
2024-02-12 16:16:07 +11:00
self . _save_gui_state_geometry = True
2015-07-27 11:26:32 +02:00
self . restoreGeometry ( QtCore . QByteArray ( ) . fromBase64 ( self . _settings [ " geometry " ] . encode ( ) ) )
self . restoreState ( QtCore . QByteArray ( ) . fromBase64 ( self . _settings [ " state " ] . encode ( ) ) )
2014-11-19 10:22:09 -07:00
2014-04-29 18:04:14 -06:00
# populate the view -> docks menu
self . uiDocksMenu . addAction ( self . uiTopologySummaryDockWidget . toggleViewAction ( ) )
2016-05-20 18:19:31 +02:00
self . uiDocksMenu . addAction ( self . uiComputeSummaryDockWidget . toggleViewAction ( ) )
2014-04-29 18:04:14 -06:00
self . uiDocksMenu . addAction ( self . uiConsoleDockWidget . toggleViewAction ( ) )
2017-04-19 09:35:31 +02:00
action = self . uiNodesDockWidget . toggleViewAction ( )
action . setIconText ( " All devices " )
self . uiDocksMenu . addAction ( action )
2016-05-14 12:15:20 -06:00
2017-07-26 14:59:13 +02:00
# Sometimes the parent seem invalid https://github.com/GNS3/gns3-gui/issues/2182
self . uiNodesDockWidget . setParent ( self )
2016-05-14 12:15:20 -06:00
# make sure the dock widget is not open
2016-04-26 10:55:07 +02:00
self . uiNodesDockWidget . setVisible ( False )
2014-04-29 18:04:14 -06:00
2015-06-09 17:43:50 -06:00
# default directories for QFileDialog
2025-12-14 18:19:47 +08:00
self . _import_configs_from_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DocumentsLocation )
self . _export_configs_to_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DocumentsLocation )
self . _screenshots_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . PicturesLocation )
self . _pictures_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . PicturesLocation )
self . _appliance_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DownloadLocation )
self . _portable_project_dir = QtCore . QStandardPaths . writableLocation ( QtCore . QStandardPaths . StandardLocation . DownloadLocation )
2018-04-08 16:42:07 +07:00
self . _project_dir = None
2015-06-09 17:43:50 -06:00
2014-06-14 09:36:14 -06:00
# add recent file actions to the File menu
2017-04-10 17:59:17 +02:00
for i in range ( 0 , self . _maxrecent_files ) :
2025-12-14 18:19:47 +08:00
action = QtGui . QAction ( self . uiFileMenu )
2014-06-14 09:36:14 -06:00
action . setVisible ( False )
2014-11-09 18:27:16 -07:00
action . triggered . connect ( self . openRecentFileSlot )
2017-04-10 17:59:17 +02:00
self . recent_file_actions . append ( action )
self . uiFileMenu . insertActions ( self . uiQuitAction , self . recent_file_actions )
self . recent_file_actions_separator = self . uiFileMenu . insertSeparator ( self . uiQuitAction )
self . recent_file_actions_separator . setVisible ( False )
2016-05-14 12:15:20 -06:00
self . updateRecentFileActions ( )
2014-06-14 09:36:14 -06:00
2016-12-19 17:40:20 +01:00
# add recent projects to the File menu
2017-04-10 17:59:17 +02:00
for i in range ( 0 , self . _maxrecent_files ) :
2025-12-14 18:19:47 +08:00
action = QtGui . QAction ( self . uiFileMenu )
2016-12-19 17:40:20 +01:00
action . setVisible ( False )
action . triggered . connect ( self . openRecentProjectSlot )
2017-04-10 17:59:17 +02:00
self . recent_project_actions . append ( action )
self . recent_project_actions_separator = self . uiFileMenu . addSeparator ( )
self . recent_project_actions_separator . setVisible ( False )
self . uiFileMenu . addActions ( self . recent_project_actions )
2016-08-16 11:45:58 +02:00
2014-08-17 18:08:08 -06:00
# set the window icon
self . setWindowIcon ( QtGui . QIcon ( " :/images/gns3.ico " ) )
2015-04-17 12:14:21 +02:00
# restore the style
self . _setStyle ( self . _settings . get ( " style " ) )
2018-11-28 16:12:58 +07:00
if self . _settings . get ( " hide_new_template_button " ) :
self . uiNewTemplatePushButton . hide ( )
2016-02-02 10:37:59 +01:00
2016-02-04 11:09:12 +01:00
self . setWindowTitle ( " [*] GNS3 " )
2016-02-02 10:37:59 +01:00
2014-02-27 21:45:56 -07:00
# load initial stuff once the event loop isn't busy
2015-01-29 15:01:50 +01:00
self . run_later ( 0 , self . startupLoading )
2014-02-27 21:45:56 -07:00
def _connections ( self ) :
"""
Connect widgets to slots
"""
# file menu connections
self . uiNewProjectAction . triggered . connect ( self . _newProjectActionSlot )
2014-11-09 18:27:16 -07:00
self . uiOpenProjectAction . triggered . connect ( self . openProjectActionSlot )
2015-09-11 14:33:59 +02:00
self . uiOpenApplianceAction . triggered . connect ( self . openApplianceActionSlot )
2018-12-21 16:07:06 +08:00
self . uiNewTemplateAction . triggered . connect ( self . _newTemplateActionSlot )
2014-02-27 21:45:56 -07:00
self . uiSaveProjectAsAction . triggered . connect ( self . _saveProjectAsActionSlot )
2016-03-30 15:43:49 +02:00
self . uiExportProjectAction . triggered . connect ( self . _exportProjectActionSlot )
2016-03-31 18:30:42 +02:00
self . uiImportProjectAction . triggered . connect ( self . _importProjectActionSlot )
2014-07-03 18:56:37 -06:00
self . uiImportExportConfigsAction . triggered . connect ( self . _importExportConfigsActionSlot )
2014-02-27 21:45:56 -07:00
self . uiScreenshotAction . triggered . connect ( self . _screenshotActionSlot )
self . uiSnapshotAction . triggered . connect ( self . _snapshotActionSlot )
2016-08-16 11:12:38 +02:00
self . uiEditProjectAction . triggered . connect ( self . _editProjectActionSlot )
2016-05-26 11:21:43 +02:00
self . uiDeleteProjectAction . triggered . connect ( self . _deleteProjectActionSlot )
2014-02-27 21:45:56 -07:00
# edit menu connections
self . uiSelectAllAction . triggered . connect ( self . _selectAllActionSlot )
self . uiSelectNoneAction . triggered . connect ( self . _selectNoneActionSlot )
2016-08-24 18:53:05 +02:00
self . uiPreferencesAction . triggered . connect ( self . preferencesActionSlot )
2014-02-27 21:45:56 -07:00
# view menu connections
2014-11-21 16:52:06 -07:00
self . uiActionFullscreen . triggered . connect ( self . _fullScreenActionSlot )
2014-02-27 21:45:56 -07:00
self . uiZoomInAction . triggered . connect ( self . _zoomInActionSlot )
self . uiZoomOutAction . triggered . connect ( self . _zoomOutActionSlot )
self . uiZoomResetAction . triggered . connect ( self . _zoomResetActionSlot )
self . uiFitInViewAction . triggered . connect ( self . _fitInViewActionSlot )
self . uiShowLayersAction . triggered . connect ( self . _showLayersActionSlot )
self . uiResetPortLabelsAction . triggered . connect ( self . _resetPortLabelsActionSlot )
2014-06-13 11:56:38 -06:00
self . uiShowPortNamesAction . triggered . connect ( self . _showPortNamesActionSlot )
2016-06-22 13:08:20 -06:00
self . uiShowGridAction . triggered . connect ( self . _showGridActionSlot )
2017-07-04 08:27:54 +02:00
self . uiSnapToGridAction . triggered . connect ( self . _snapToGridActionSlot )
2017-08-11 14:37:45 +08:00
self . uiLockAllAction . triggered . connect ( self . _lockActionSlot )
2024-02-12 16:16:07 +11:00
self . uiResetGUIStateAction . triggered . connect ( self . _resetGUIState )
2022-04-20 15:53:32 +07:00
self . uiResetDocksAction . triggered . connect ( self . _resetDocksSlot )
2014-02-27 21:45:56 -07:00
2017-01-16 17:42:14 +01:00
# tool menu connections
2018-06-26 12:04:02 +02:00
self . uiWebUIAction . triggered . connect ( self . _openWebInterfaceActionSlot )
2017-01-16 17:42:14 +01:00
2014-02-27 21:45:56 -07:00
# control menu connections
self . uiStartAllAction . triggered . connect ( self . _startAllActionSlot )
self . uiSuspendAllAction . triggered . connect ( self . _suspendAllActionSlot )
self . uiStopAllAction . triggered . connect ( self . _stopAllActionSlot )
self . uiReloadAllAction . triggered . connect ( self . _reloadAllActionSlot )
self . uiAuxConsoleAllAction . triggered . connect ( self . _auxConsoleAllActionSlot )
self . uiConsoleAllAction . triggered . connect ( self . _consoleAllActionSlot )
2020-07-26 18:27:18 +09:30
self . uiResetConsoleAllAction . triggered . connect ( self . _consoleResetAllActionSlot )
2014-02-27 21:45:56 -07:00
2014-06-13 13:12:36 -06:00
# device menu is contextual and is build on-the-fly
self . uiDeviceMenu . aboutToShow . connect ( self . _deviceMenuActionSlot )
2014-02-27 21:45:56 -07:00
# annotate menu connections
self . uiAddNoteAction . triggered . connect ( self . _addNoteActionSlot )
self . uiInsertImageAction . triggered . connect ( self . _insertImageActionSlot )
self . uiDrawRectangleAction . triggered . connect ( self . _drawRectangleActionSlot )
self . uiDrawEllipseAction . triggered . connect ( self . _drawEllipseActionSlot )
2017-01-25 17:36:43 +01:00
self . uiDrawLineAction . triggered . connect ( self . _drawLineActionSlot )
2019-10-18 16:46:48 +02:00
self . uiEditReadmeAction . triggered . connect ( self . _editReadmeActionSlot )
2014-02-27 21:45:56 -07:00
# help menu connections
self . uiOnlineHelpAction . triggered . connect ( self . _onlineHelpActionSlot )
self . uiCheckForUpdateAction . triggered . connect ( self . _checkForUpdateActionSlot )
2015-07-07 12:59:16 -06:00
self . uiSetupWizard . triggered . connect ( self . _setupWizardActionSlot )
2014-02-27 21:45:56 -07:00
self . uiAboutQtAction . triggered . connect ( self . _aboutQtActionSlot )
self . uiAboutAction . triggered . connect ( self . _aboutActionSlot )
2015-11-14 18:31:46 -07:00
self . uiExportDebugInformationAction . triggered . connect ( self . _exportDebugInformationSlot )
2016-01-18 21:02:08 +01:00
self . uiDoctorAction . triggered . connect ( self . _doctorSlot )
2016-04-26 11:42:48 -06:00
self . uiAcademyAction . triggered . connect ( self . _academyActionSlot )
2024-07-27 16:23:23 +02:00
self . uiShortcutsAction . triggered . connect ( self . _shortcutsActionSlot )
2014-02-27 21:45:56 -07:00
# browsers tool bar connections
self . uiBrowseRoutersAction . triggered . connect ( self . _browseRoutersActionSlot )
self . uiBrowseSwitchesAction . triggered . connect ( self . _browseSwitchesActionSlot )
self . uiBrowseEndDevicesAction . triggered . connect ( self . _browseEndDevicesActionSlot )
self . uiBrowseSecurityDevicesAction . triggered . connect ( self . _browseSecurityDevicesActionSlot )
self . uiBrowseAllDevicesAction . triggered . connect ( self . _browseAllDevicesActionSlot )
self . uiAddLinkAction . triggered . connect ( self . _addLinkActionSlot )
2014-01-31 16:22:31 -07:00
2018-11-28 16:12:58 +07:00
# new template button
self . uiNewTemplatePushButton . clicked . connect ( self . _newTemplateActionSlot )
2016-05-10 13:31:28 -06:00
2014-01-31 16:22:31 -07:00
# connect the signal to the view
self . adding_link_signal . connect ( self . uiGraphicsView . addingLinkSlot )
2018-03-09 13:31:55 +01:00
# connect to the signal when settings change
self . settings_updated_signal . connect ( self . settingsChangedSlot )
2016-05-14 12:15:20 -06:00
def _loadSettings ( self ) :
2015-02-02 16:20:17 +01:00
"""
2016-05-14 12:15:20 -06:00
Loads the settings from the persistent settings file .
2015-02-02 16:20:17 +01:00
"""
2016-05-14 12:15:20 -06:00
local_config = LocalConfig . instance ( )
self . _settings = local_config . loadSectionSettings ( self . __class__ . __name__ , GENERAL_SETTINGS )
2015-02-02 16:20:17 +01:00
2016-05-14 12:15:20 -06:00
def settings ( self ) :
"""
Returns the general settings .
: returns : settings dictionary
2015-02-03 22:28:20 +01:00
"""
2016-05-14 12:15:20 -06:00
return self . _settings
def setSettings ( self , new_settings ) :
2015-02-03 22:28:20 +01:00
"""
2016-05-14 12:15:20 -06:00
Set new general settings .
2015-02-03 22:28:20 +01:00
2016-05-14 12:15:20 -06:00
: param new_settings : settings dictionary
"""
# change the GUI style
style = new_settings . get ( " style " )
if style and new_settings [ " style " ] != self . _settings [ " style " ] :
self . _setStyle ( style )
self . _settings . update ( new_settings )
# save the settings
LocalConfig . instance ( ) . saveSectionSettings ( self . __class__ . __name__ , self . _settings )
2018-02-27 16:07:06 +01:00
self . settings_updated_signal . emit ( )
2016-05-14 12:15:20 -06:00
2018-06-26 12:04:02 +02:00
def _openWebInterfaceActionSlot ( self ) :
if Controller . instance ( ) . connected ( ) :
base_url = Controller . instance ( ) . httpClient ( ) . fullUrl ( )
2019-03-27 11:56:32 +01:00
webui_url = " {} /static/web-ui/bundled " . format ( base_url )
2018-06-26 12:04:02 +02:00
QtGui . QDesktopServices . openUrl ( QtCore . QUrl ( webui_url ) )
2016-06-20 15:19:01 +02:00
def _showGridActionSlot ( self ) :
"""
Called when we ask to display the grid
"""
2017-07-04 08:14:19 +02:00
self . showGrid ( self . uiShowGridAction . isChecked ( ) )
2016-06-22 13:08:20 -06:00
2017-07-04 08:14:19 +02:00
# save settings
project = Topology . instance ( ) . project ( )
if project is not None :
project . setShowGrid ( self . uiShowGridAction . isChecked ( ) )
project . update ( )
2016-06-20 15:19:01 +02:00
2017-07-04 08:27:54 +02:00
def _snapToGridActionSlot ( self ) :
"""
Called when user click on the snap to grid menu item
: return : None
"""
self . snapToGrid ( self . uiSnapToGridAction . isChecked ( ) )
# save settings
project = Topology . instance ( ) . project ( )
if project is not None :
project . setSnapToGrid ( self . uiSnapToGridAction . isChecked ( ) )
project . update ( )
2017-08-11 14:37:45 +08:00
def _lockActionSlot ( self ) :
"""
Called when user click on the lock menu item
: return : None
"""
2019-07-30 15:50:40 -02:30
if self . uiGraphicsView . isEnabled ( ) :
for item in self . uiGraphicsView . items ( ) :
if not isinstance ( item , LinkItem ) and not isinstance ( item , LabelItem ) and not isinstance ( item , SvgIconItem ) :
if self . uiLockAllAction . isChecked ( ) and not item . locked ( ) :
item . setLocked ( True )
elif not self . uiLockAllAction . isChecked ( ) and item . locked ( ) :
item . setLocked ( False )
if item . parentItem ( ) is None :
item . updateNode ( )
item . update ( )
2017-08-11 14:37:45 +08:00
2024-02-12 16:16:07 +11:00
def _resetGUIState ( self ) :
"""
Reset the GUI state .
"""
self . _save_gui_state_geometry = False
self . close ( )
if hasattr ( sys , " frozen " ) :
QtCore . QProcess . startDetached ( os . path . abspath ( sys . executable ) , sys . argv )
else :
QtWidgets . QMessageBox . information ( self , " GUI state " , " The GUI state has been reset, please restart the application " )
2022-04-20 15:53:32 +07:00
def _resetDocksSlot ( self ) :
"""
Reset the dock widgets .
"""
self . uiTopologySummaryDockWidget . setFloating ( False )
self . uiComputeSummaryDockWidget . setFloating ( False )
self . uiConsoleDockWidget . setFloating ( False )
self . uiNodesDockWidget . setFloating ( False )
2014-02-27 21:45:56 -07:00
def _newProjectActionSlot ( self ) :
"""
Slot called to create a new project .
"""
2017-07-06 11:47:58 +02:00
# prevents race condition
if self . _project_dialog is not None :
return
2016-06-15 18:49:26 +02:00
self . _project_dialog = ProjectDialog ( self )
self . _project_dialog . show ( )
2025-12-14 18:19:47 +08:00
create_new_project = self . _project_dialog . exec ( )
2014-07-20 16:42:32 +02:00
2016-06-15 18:49:26 +02:00
if create_new_project :
2018-08-19 16:51:48 +07:00
Topology . instance ( ) . createLoadProject ( self . _project_dialog . getProjectSettings ( ) )
2016-05-24 18:56:56 +02:00
2016-06-15 18:49:26 +02:00
self . _project_dialog = None
2014-07-21 12:32:58 +02:00
2018-11-28 16:12:58 +07:00
def _newTemplateActionSlot ( self ) :
2016-04-26 10:55:07 +02:00
"""
2018-11-28 16:12:58 +07:00
Called when user want to create a new template .
2016-04-26 10:55:07 +02:00
"""
2018-08-19 16:51:48 +07:00
2018-11-28 16:12:58 +07:00
dialog = NewTemplateWizard ( self )
2016-04-26 10:55:07 +02:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2016-04-26 10:55:07 +02:00
2016-10-03 15:38:56 +02:00
@qslot
def openApplianceActionSlot ( self , * args ) :
2015-09-11 14:33:59 +02:00
"""
Slot called to open an appliance .
"""
2018-04-08 16:42:07 +07:00
directory = self . _appliance_dir
if not os . path . exists ( self . _appliance_dir ) :
2016-08-15 12:59:13 +02:00
directory = Topology . instance ( ) . projectsDirPath ( )
2018-08-16 21:47:52 +07:00
path , _ = QtWidgets . QFileDialog . getOpenFileName ( self , " Import appliance " , directory ,
2025-01-07 11:32:17 +07:00
" All files (*);;GNS3 Appliance (*.gns3appliance *.gns3a) " ,
2016-05-04 16:22:58 -06:00
" GNS3 Appliance (*.gns3appliance *.gns3a) " )
2015-09-11 14:33:59 +02:00
if path :
self . loadPath ( path )
2018-04-08 16:42:07 +07:00
self . _appliance_dir = os . path . dirname ( path )
2015-09-11 14:33:59 +02:00
2014-11-09 18:27:16 -07:00
def openProjectActionSlot ( self ) :
2014-02-27 21:45:56 -07:00
"""
Slot called to open a project .
"""
2016-08-23 17:09:02 +02:00
if Controller . instance ( ) . isRemote ( ) :
# If the server is remote we use the new project windows with the project library
self . _newProjectActionSlot ( )
else :
2018-04-08 16:42:07 +07:00
directory = self . _project_dir
2019-07-11 16:50:59 +02:00
if self . _project_dir is None or not os . path . exists ( self . _project_dir ) :
2018-04-08 16:42:07 +07:00
directory = Topology . instance ( ) . projectsDirPath ( )
path , _ = QtWidgets . QFileDialog . getOpenFileName ( self , " Open project " , directory ,
2025-01-07 11:32:17 +07:00
" All files (*);;GNS3 Project (*.gns3);;GNS3 Portable Project (*.gns3project *.gns3p);;NET files (*.net) " ,
2016-08-23 17:09:02 +02:00
" GNS3 Project (*.gns3) " )
if path :
self . loadPath ( path )
2018-04-08 16:42:07 +07:00
self . _project_dir = os . path . dirname ( path )
2014-02-27 21:45:56 -07:00
2014-11-09 18:27:16 -07:00
def openRecentFileSlot ( self ) :
2014-06-14 09:36:14 -06:00
"""
Slot called to open recent file from the File menu .
"""
action = self . sender ( )
if action :
path = action . data ( )
if not os . path . isfile ( path ) :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Recent file " , " {} : no such file " . format ( path ) )
2014-06-14 09:36:14 -06:00
return
2015-09-10 13:44:42 +02:00
self . loadPath ( path )
2015-02-24 15:34:33 +01:00
2016-08-16 11:45:58 +02:00
def openRecentProjectSlot ( self ) :
"""
Slot called to open recent project from the project menu .
"""
action = self . sender ( )
2017-01-23 10:09:49 +01:00
if action and action . data ( ) :
2016-10-14 22:55:34 +02:00
if len ( action . data ( ) ) == 2 :
project_id , project_path = action . data ( )
Topology . instance ( ) . createLoadProject ( {
" project_path " : project_path ,
" project_id " : project_id } )
else :
( project_id , ) = action . data ( )
Topology . instance ( ) . createLoadProject ( { " project_id " : project_id } )
2016-08-16 11:45:58 +02:00
2015-09-10 13:44:42 +02:00
def loadPath ( self , path ) :
2015-02-24 15:34:33 +01:00
""" Open a file and close the previous project """
2015-02-25 16:49:53 -07:00
2016-07-06 15:23:56 +02:00
if not path :
return
if self . _first_file_load is True :
self . _first_file_load = False
time . sleep ( 0.5 ) # give some time to the server to initialize
if self . _project_dialog :
self . _project_dialog . reject ( )
self . _project_dialog = None
if path . endswith ( " .gns3project " ) or path . endswith ( " .gns3p " ) :
# Portable GNS3 project
2016-08-15 12:59:13 +02:00
Topology . instance ( ) . importProject ( path )
2016-11-03 15:15:03 +01:00
elif path . endswith ( " .net " ) :
2017-05-05 20:52:03 +07:00
QtWidgets . QMessageBox . critical ( self , " Open project " , " Importing legacy project is not supported in 2.0. \n You must open it using GNS3 1.x in order to convert it or manually run the gns3 converter. " )
2016-11-03 15:15:03 +01:00
return
2016-07-06 15:23:56 +02:00
elif path . endswith ( " .gns3appliance " ) or path . endswith ( " .gns3a " ) :
# GNS3 appliance
try :
2016-11-03 18:45:42 +01:00
self . _appliance_wizard = ApplianceWizard ( self , path )
2016-07-06 15:23:56 +02:00
except ApplianceError as e :
QtWidgets . QMessageBox . critical ( self , " Appliance " , " Error while importing appliance {} : {} " . format ( path , str ( e ) ) )
return
self . _appliance_wizard . show ( )
2025-12-14 18:19:47 +08:00
self . _appliance_wizard . exec ( )
2016-11-22 18:41:20 +01:00
elif path . endswith ( " .gns3 " ) :
2017-05-02 09:27:40 +02:00
if Controller . instance ( ) . isRemote ( ) :
2017-05-05 20:52:03 +07:00
QtWidgets . QMessageBox . critical ( self , " Open project " , " Cannot open a .gns3 file on a remote server, please use a portable project (.gns3p) instead " )
2017-05-02 09:27:40 +02:00
return
else :
Topology . instance ( ) . loadProject ( path )
2016-09-15 17:12:09 +02:00
else :
try :
extension = path . split ( ' . ' ) [ 1 ]
QtWidgets . QMessageBox . critical ( self , " File open " , " Unsupported file extension {} for {} " . format ( extension , path ) )
except IndexError :
QtWidgets . QMessageBox . critical ( self , " File open " , " Missing file extension for {} " . format ( path ) )
2015-02-24 15:34:33 +01:00
2017-05-05 18:28:47 +02:00
@qslot
def _projectChangedSlot ( self , * args ) :
2016-05-24 18:56:56 +02:00
"""
Called when a project finish to load
"""
2017-01-23 17:22:17 +01:00
project = Topology . instance ( ) . project ( )
if project is not None and self . _project_dialog :
self . _project_dialog . reject ( )
self . _project_dialog = None
2016-08-23 16:42:22 +02:00
self . _refreshVisibleWidgets ( )
2018-02-27 16:07:06 +01:00
@qslot
def settingsChangedSlot ( self , * args ) :
"""
Called when settings are updated
"""
# It covers case when project is not set
2018-11-28 16:12:58 +07:00
# and we need to refresh template manager
# and appliance manager
2018-02-27 16:07:06 +01:00
project = Topology . instance ( ) . project ( )
2018-11-13 15:40:18 +08:00
if project is None :
2018-11-28 16:12:58 +07:00
self . _template_manager . instance ( ) . refresh ( )
2018-11-13 15:40:18 +08:00
self . _appliance_manager . instance ( ) . refresh ( )
2018-08-16 21:47:52 +07:00
2016-08-23 16:42:22 +02:00
def _refreshVisibleWidgets ( self ) :
"""
Refresh widgets that should be visible or not
"""
2016-05-26 11:21:43 +02:00
# No projects
2016-08-15 12:59:13 +02:00
if Topology . instance ( ) . project ( ) is None :
2016-05-26 11:21:43 +02:00
for widget in self . disableWhenNoProjectWidgets :
widget . setEnabled ( False )
else :
for widget in self . disableWhenNoProjectWidgets :
widget . setEnabled ( True )
2016-05-24 18:56:56 +02:00
2014-02-27 21:45:56 -07:00
def _saveProjectAsActionSlot ( self ) :
"""
Slot called to save a project to another location / name .
"""
2016-08-15 12:59:13 +02:00
Topology . instance ( ) . saveProjectAs ( )
2014-02-27 21:45:56 -07:00
2014-07-03 18:56:37 -06:00
def _importExportConfigsActionSlot ( self ) :
2014-02-27 21:45:56 -07:00
"""
2014-07-03 18:56:37 -06:00
Slot called when importing and exporting configs
2018-11-27 18:30:16 +07:00
for the entire project .
2014-02-27 21:45:56 -07:00
"""
2016-11-03 18:45:42 +01:00
options = [ " Export configs to a directory " , " Import configs from a directory " ]
selection , ok = QtWidgets . QInputDialog . getItem ( self , " Import/Export configs " , " Please choose an option: " , options , 0 , False )
2014-07-03 18:56:37 -06:00
if ok :
if selection == options [ 0 ] :
self . _exportConfigs ( )
else :
self . _importConfigs ( )
def _exportConfigs ( self ) :
"""
Exports all configs to a directory .
"""
2025-12-14 18:19:47 +08:00
path = QtWidgets . QFileDialog . getExistingDirectory ( self , " Export directory " , self . _export_configs_to_dir , QtWidgets . QFileDialog . Option . ShowDirsOnly )
2014-07-03 18:56:37 -06:00
if path :
2016-11-03 18:45:42 +01:00
self . _export_configs_to_dir = os . path . dirname ( path )
2014-07-03 18:56:37 -06:00
for module in MODULES :
2016-11-03 18:45:42 +01:00
instance = module . instance ( )
2014-07-03 18:56:37 -06:00
if hasattr ( instance , " exportConfigs " ) :
instance . exportConfigs ( path )
def _importConfigs ( self ) :
"""
Imports all configs from a directory .
"""
2025-12-14 18:19:47 +08:00
path = QtWidgets . QFileDialog . getExistingDirectory ( self , " Import directory " , self . _import_configs_from_dir , QtWidgets . QFileDialog . Option . ShowDirsOnly )
2014-07-03 18:56:37 -06:00
if path :
2016-11-03 18:45:42 +01:00
self . _import_configs_from_dir = os . path . dirname ( path )
2014-07-03 18:56:37 -06:00
for module in MODULES :
2016-11-03 18:45:42 +01:00
instance = module . instance ( )
2014-07-03 18:56:37 -06:00
if hasattr ( instance , " importConfigs " ) :
instance . importConfigs ( path )
2014-02-27 21:45:56 -07:00
2016-05-14 12:15:20 -06:00
def createScreenshot ( self , path ) :
2014-02-27 21:45:56 -07:00
"""
Create a screenshot of the scene .
: returns : True if the image was successfully saved ; otherwise returns False
"""
2016-11-03 18:45:42 +01:00
scene = self . uiGraphicsView . scene ( )
2014-02-27 21:45:56 -07:00
scene . clearSelection ( )
2016-11-03 18:45:42 +01:00
source = scene . itemsBoundingRect ( ) . adjusted ( - 20.0 , - 20.0 , 20.0 , 20.0 )
2025-12-14 18:19:47 +08:00
image = QtGui . QImage ( source . size ( ) . toSize ( ) , QtGui . QImage . Format . Format_RGB32 )
image . fill ( QtCore . Qt . GlobalColor . white )
2016-11-03 18:45:42 +01:00
painter = QtGui . QPainter ( image )
2025-12-14 18:19:47 +08:00
painter . setRenderHint ( QtGui . QPainter . RenderHint . Antialiasing , True )
painter . setRenderHint ( QtGui . QPainter . RenderHint . TextAntialiasing , True )
painter . setRenderHint ( QtGui . QPainter . RenderHint . SmoothPixmapTransform , True )
2016-11-03 18:45:42 +01:00
scene . render ( painter , source = source )
2014-02-27 21:45:56 -07:00
painter . end ( )
2015-01-28 11:13:10 +01:00
# TODO: quality option
2014-02-27 21:45:56 -07:00
return image . save ( path )
2017-07-03 15:09:12 +02:00
def showLayers ( self , show_layers ) :
"""
Shows layers in GUI
: param show_layers : boolean
: return : None
"""
NodeItem . show_layer = show_layers
ShapeItem . show_layer = show_layers
for item in self . uiGraphicsView . items ( ) :
item . update ( )
2017-07-04 08:14:19 +02:00
def showGrid ( self , show_grid ) :
"""
Shows grid in GUI
: param show_grid : boolean
: return : None
"""
self . uiGraphicsView . viewport ( ) . update ( )
2017-07-04 08:27:54 +02:00
def snapToGrid ( self , snap_to_grid ) :
"""
Snap to grid in GUI
: param snap_to_grid : boolean
: return : None
"""
self . uiGraphicsView . viewport ( ) . update ( )
2017-07-04 08:36:37 +02:00
def showInterfaceLabels ( self , show_interface_labels ) :
"""
Show interface labels in GUI
: param show_interface_labels : boolean
: return : None
"""
LinkItem . showPortLabels ( show_interface_labels )
for item in self . uiGraphicsView . scene ( ) . items ( ) :
if isinstance ( item , LinkItem ) :
item . adjust ( )
2018-02-16 14:32:07 +01:00
2017-07-03 10:40:23 +02:00
def _updateZoomSettings ( self , zoom = None ) :
"""
Updates zoom settings
: param zoom integer optional , when not provided then calculated from current view
: return : None
"""
2017-07-04 09:33:25 +02:00
2017-07-03 10:40:23 +02:00
if zoom is None :
zoom = round ( self . uiGraphicsView . transform ( ) . m11 ( ) * 100 )
2017-07-04 09:33:25 +02:00
# save settings
project = Topology . instance ( ) . project ( )
if project is not None :
project . setZoom ( zoom )
project . update ( )
2017-07-03 10:40:23 +02:00
2014-02-27 21:45:56 -07:00
def _screenshotActionSlot ( self ) :
"""
Slot called to take a screenshot of the scene .
"""
# supported image file formats
2016-11-03 18:45:42 +01:00
file_formats = " PNG File (*.png);;JPG File (*.jpeg *.jpg);;BMP File (*.bmp);;XPM File (*.xpm *.xbm);;PPM File (*.ppm);;TIFF File (*.tiff) "
path , selected_filter = QtWidgets . QFileDialog . getSaveFileName ( self , " Screenshot " , self . _screenshots_dir , file_formats )
2014-02-27 21:45:56 -07:00
if not path :
return
2016-11-03 18:45:42 +01:00
self . _screenshots_dir = os . path . dirname ( path )
2014-02-27 21:45:56 -07:00
2017-10-11 04:58:59 +08:00
# add the extension if missing (Mac OS automatically adds an extension already)
if not sys . platform . startswith ( " darwin " ) :
file_format = " . " + selected_filter [ : 4 ] . lower ( ) . strip ( )
if not path . endswith ( file_format ) :
path + = file_format
2014-02-27 21:45:56 -07:00
2016-05-14 12:15:20 -06:00
if not self . createScreenshot ( path ) :
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . critical ( self , " Screenshot " , " Could not create screenshot file {} " . format ( path ) )
2014-02-27 21:45:56 -07:00
def _snapshotActionSlot ( self ) :
"""
Slot called to open the snapshot dialog .
"""
2016-11-03 18:45:42 +01:00
project = Topology . instance ( ) . project ( )
2015-02-25 16:49:53 -07:00
2016-11-03 18:45:42 +01:00
dialog = SnapshotsDialog ( self , project )
2014-10-19 21:24:21 -06:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2014-02-27 21:45:56 -07:00
def _selectAllActionSlot ( self ) :
"""
Slot called to select all the items on the scene .
"""
2016-11-03 18:45:42 +01:00
scene = self . uiGraphicsView . scene ( )
2014-02-27 21:45:56 -07:00
for item in scene . items ( ) :
item . setSelected ( True )
def _selectNoneActionSlot ( self ) :
"""
Slot called to none of the items on the scene .
"""
2016-11-03 18:45:42 +01:00
scene = self . uiGraphicsView . scene ( )
2014-02-27 21:45:56 -07:00
for item in scene . items ( ) :
item . setSelected ( False )
2014-11-21 16:52:06 -07:00
def _fullScreenActionSlot ( self ) :
"""
Slot to switch to full screen .
"""
2025-12-14 18:19:47 +08:00
if not self . windowState ( ) & QtCore . Qt . WindowState . WindowFullScreen :
2014-11-21 16:52:06 -07:00
# switch to full screen
2025-12-14 18:19:47 +08:00
self . setWindowState ( self . windowState ( ) | QtCore . Qt . WindowState . WindowFullScreen )
2014-11-21 16:52:06 -07:00
else :
# switch back to normal
2025-12-14 18:19:47 +08:00
self . setWindowState ( self . windowState ( ) & ~ QtCore . Qt . WindowState . WindowFullScreen )
2014-11-21 16:52:06 -07:00
2014-02-27 21:45:56 -07:00
def _zoomInActionSlot ( self ) :
"""
Slot called to scale in the view .
"""
2018-03-25 13:05:50 +07:00
factor_in = pow ( 2.0 , 60 / 240.0 )
2014-02-27 21:45:56 -07:00
self . uiGraphicsView . scaleView ( factor_in )
2017-07-03 10:40:23 +02:00
self . _updateZoomSettings ( )
2014-02-27 21:45:56 -07:00
def _zoomOutActionSlot ( self ) :
"""
Slot called to scale out the view .
"""
2018-03-25 13:05:50 +07:00
factor_out = pow ( 2.0 , - 60 / 240.0 )
2014-02-27 21:45:56 -07:00
self . uiGraphicsView . scaleView ( factor_out )
2017-07-03 10:40:23 +02:00
self . _updateZoomSettings ( )
2014-02-27 21:45:56 -07:00
def _zoomResetActionSlot ( self ) :
"""
Slot called to reset the zoom .
"""
2015-06-22 12:24:43 +02:00
self . uiGraphicsView . resetTransform ( )
2017-07-03 10:40:23 +02:00
self . _updateZoomSettings ( )
2014-02-27 21:45:56 -07:00
def _fitInViewActionSlot ( self ) :
"""
Slot called to fit the topology in the view .
"""
2016-11-03 18:45:42 +01:00
view = self . uiGraphicsView
bounding_rect = view . scene ( ) . itemsBoundingRect ( ) . adjusted ( - 20.0 , - 20.0 , 20.0 , 20.0 )
2014-02-27 21:45:56 -07:00
view . ensureVisible ( bounding_rect )
2025-12-14 18:19:47 +08:00
view . fitInView ( bounding_rect , QtCore . Qt . AspectRatioMode . KeepAspectRatio )
2014-02-27 21:45:56 -07:00
def _showLayersActionSlot ( self ) :
"""
Slot called to show the layer positions on the scene .
"""
2017-07-03 15:09:12 +02:00
self . showLayers ( self . uiShowLayersAction . isChecked ( ) )
# save settings
project = Topology . instance ( ) . project ( )
if project is not None :
project . setShowLayers ( self . uiShowLayersAction . isChecked ( ) )
project . update ( )
2014-02-27 21:45:56 -07:00
def _resetPortLabelsActionSlot ( self ) :
"""
Slot called to reset the port labels on the scene .
"""
2016-01-25 20:41:38 -07:00
for item in self . uiGraphicsView . scene ( ) . items ( ) :
if isinstance ( item , LinkItem ) :
item . resetPortLabels ( )
item . adjust ( )
2014-02-27 21:45:56 -07:00
2014-06-13 11:56:38 -06:00
def _showPortNamesActionSlot ( self ) :
"""
Slot called to show the port names on the scene .
"""
2017-07-04 08:36:37 +02:00
self . showInterfaceLabels ( self . uiShowPortNamesAction . isChecked ( ) )
# save settings
project = Topology . instance ( ) . project ( )
if project is not None :
project . setShowInterfaceLabels ( self . uiShowPortNamesAction . isChecked ( ) )
project . update ( )
2014-02-27 21:45:56 -07:00
def _startAllActionSlot ( self ) :
"""
Slot called when starting all the nodes .
"""
2020-11-20 21:11:15 -05:00
2020-11-20 21:26:08 -05:00
reply = QtWidgets . QMessageBox . question ( self , " Confirm Start All " , " Are you sure you want to start all devices? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
2020-11-20 21:33:44 -05:00
2025-12-14 18:19:47 +08:00
if reply == QtWidgets . QMessageBox . StandardButton . No :
2020-11-20 21:11:15 -05:00
return
2016-11-03 18:45:42 +01:00
project = Topology . instance ( ) . project ( )
2016-05-13 20:44:41 -06:00
if project is not None :
project . start_all_nodes ( )
2014-02-27 21:45:56 -07:00
def _suspendAllActionSlot ( self ) :
"""
Slot called when suspending all the nodes .
"""
2020-11-20 21:32:26 -05:00
2020-11-20 21:26:08 -05:00
reply = QtWidgets . QMessageBox . question ( self , " Confirm Suspend All " , " Are you sure you want to suspend all devices? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
2014-02-27 21:45:56 -07:00
2025-12-14 18:19:47 +08:00
if reply == QtWidgets . QMessageBox . StandardButton . No :
2020-11-20 21:11:15 -05:00
return
2020-11-20 21:32:26 -05:00
2016-11-03 18:45:42 +01:00
project = Topology . instance ( ) . project ( )
2016-05-13 20:44:41 -06:00
if project is not None :
project . suspend_all_nodes ( )
2014-02-27 21:45:56 -07:00
def _stopAllActionSlot ( self ) :
"""
Slot called when stopping all the nodes .
"""
2020-11-20 21:32:26 -05:00
2020-11-20 18:28:30 -05:00
reply = QtWidgets . QMessageBox . question ( self , " Confirm Stop All " , " Are you sure you want to stop all devices? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
2020-11-20 21:32:26 -05:00
2025-12-14 18:19:47 +08:00
if reply == QtWidgets . QMessageBox . StandardButton . No :
2020-11-20 20:27:43 -05:00
return
2020-11-20 21:11:15 -05:00
2016-11-03 18:45:42 +01:00
project = Topology . instance ( ) . project ( )
2016-05-13 20:44:41 -06:00
if project is not None :
project . stop_all_nodes ( )
2014-02-27 21:45:56 -07:00
def _reloadAllActionSlot ( self ) :
"""
Slot called when reloading all the nodes .
"""
2020-11-20 21:32:26 -05:00
2020-11-20 21:26:08 -05:00
reply = QtWidgets . QMessageBox . question ( self , " Confirm Reload All " , " Are you sure you want to reload all devices? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
2020-11-20 21:33:44 -05:00
2025-12-14 18:19:47 +08:00
if reply == QtWidgets . QMessageBox . StandardButton . No :
2020-11-20 21:11:15 -05:00
return
2014-02-27 21:45:56 -07:00
2016-11-03 18:45:42 +01:00
project = Topology . instance ( ) . project ( )
2016-05-13 20:44:41 -06:00
if project is not None :
project . reload_all_nodes ( )
2014-02-27 21:45:56 -07:00
2020-07-26 18:27:18 +09:30
def _consoleResetAllActionSlot ( self ) :
"""
Slot called when reset all console connections .
"""
project = Topology . instance ( ) . project ( )
if project is not None :
project . reset_console_all_nodes ( )
2014-06-13 13:12:36 -06:00
def _deviceMenuActionSlot ( self ) :
"""
Slot to contextually show the device menu .
"""
self . uiDeviceMenu . clear ( )
self . uiGraphicsView . populateDeviceContextualMenu ( self . uiDeviceMenu )
2014-02-27 21:45:56 -07:00
def _auxConsoleAllActionSlot ( self ) :
"""
Slot called when connecting to all the nodes using the AUX console .
"""
2015-03-22 14:52:58 -06:00
self . uiGraphicsView . auxConsoleFromItems ( self . uiGraphicsView . scene ( ) . items ( ) )
2014-02-27 21:45:56 -07:00
def _consoleAllActionSlot ( self ) :
"""
Slot called when connecting to all the nodes using the console .
"""
2017-11-12 17:02:00 +08:00
self . uiGraphicsView . consoleFromAllItems ( )
2014-02-27 21:45:56 -07:00
def _addNoteActionSlot ( self ) :
"""
Slot called when adding a new note on the scene .
"""
2014-06-11 10:04:40 -06:00
self . uiGraphicsView . addNote ( self . uiAddNoteAction . isChecked ( ) )
2014-02-27 21:45:56 -07:00
def _insertImageActionSlot ( self ) :
"""
Slot called when inserting an image on the scene .
"""
2014-07-03 09:32:17 -06:00
# supported image file formats
2025-01-07 11:32:17 +07:00
file_formats = " Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*) "
2014-07-03 09:32:17 -06:00
2016-11-03 18:45:42 +01:00
path , _ = QtWidgets . QFileDialog . getOpenFileName ( self , " Image " , self . _pictures_dir , file_formats )
2014-07-03 09:32:17 -06:00
if not path :
return
2016-11-03 18:45:42 +01:00
self . _pictures_dir = os . path . dirname ( path )
2014-07-03 09:32:17 -06:00
2017-02-01 14:32:05 +01:00
QtGui . QPixmap ( path )
2016-06-22 17:47:17 +02:00
self . uiGraphicsView . addImage ( path )
2014-02-27 21:45:56 -07:00
def _drawRectangleActionSlot ( self ) :
"""
Slot called when adding a rectangle on the scene .
"""
2014-06-11 13:42:40 -06:00
self . uiGraphicsView . addRectangle ( self . uiDrawRectangleAction . isChecked ( ) )
2014-02-27 21:45:56 -07:00
def _drawEllipseActionSlot ( self ) :
"""
Slot called when adding a ellipse on the scene .
"""
2014-06-11 13:42:40 -06:00
self . uiGraphicsView . addEllipse ( self . uiDrawEllipseAction . isChecked ( ) )
2014-02-27 21:45:56 -07:00
2017-01-25 17:36:43 +01:00
def _drawLineActionSlot ( self ) :
"""
Slot called when adding a line on the scene .
"""
self . uiGraphicsView . addLine ( self . uiDrawLineAction . isChecked ( ) )
2014-02-27 21:45:56 -07:00
def _onlineHelpActionSlot ( self ) :
"""
Slot to launch a browser pointing to the documentation page .
"""
2020-05-08 12:42:06 +09:30
QtGui . QDesktopServices . openUrl ( QtCore . QUrl ( " https://docs.gns3.com/ " ) )
2014-02-27 21:45:56 -07:00
2016-11-03 18:45:42 +01:00
def _checkForUpdateActionSlot ( self , silent = False ) :
2014-02-27 21:45:56 -07:00
"""
Slot to check if a newer version is available .
2014-10-14 21:16:09 -06:00
: param silent : do not display any message
2014-02-27 21:45:56 -07:00
"""
2016-11-03 18:45:42 +01:00
self . _update_manager = UpdateManager ( )
2015-07-01 11:46:57 +02:00
self . _update_manager . checkForUpdate ( self , silent )
2014-02-27 21:45:56 -07:00
2015-07-07 12:59:16 -06:00
def _setupWizardActionSlot ( self ) :
"""
Slot to open the setup wizard .
"""
2015-08-01 11:19:12 -06:00
2016-11-03 18:45:42 +01:00
with Progress . instance ( ) . context ( min_duration = 0 ) :
setup_wizard = SetupWizard ( self )
2015-07-07 12:59:16 -06:00
setup_wizard . show ( )
2025-12-14 18:19:47 +08:00
res = setup_wizard . exec ( )
2016-12-01 12:33:00 +01:00
# start and connect to the local server if needed
2019-01-28 15:13:37 +08:00
LocalServer . instance ( ) . localServerAutoStartIfRequired ( )
2015-07-07 12:59:16 -06:00
2024-07-27 16:23:23 +02:00
def _shortcutsActionSlot ( self ) :
shortcuts_text = " "
2025-12-14 18:19:47 +08:00
for action in self . findChildren ( QtGui . QAction ) :
2024-07-27 16:23:23 +02:00
shortcut = action . shortcut ( ) . toString ( )
if shortcut :
shortcuts_text + = f " { action . toolTip ( ) } : { shortcut } \n "
QtWidgets . QMessageBox . information ( self , " Shortcuts " , shortcuts_text )
2014-02-27 21:45:56 -07:00
def _aboutQtActionSlot ( self ) :
"""
Slot to display the Qt About dialog .
"""
2015-04-17 12:14:21 +02:00
QtWidgets . QMessageBox . aboutQt ( self )
2014-02-27 21:45:56 -07:00
def _aboutActionSlot ( self ) :
"""
Slot to display the GNS3 About dialog .
"""
2016-11-03 18:45:42 +01:00
dialog = AboutDialog ( self )
2014-03-19 18:24:07 -06:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2014-02-27 21:45:56 -07:00
2015-11-14 18:31:46 -07:00
def _exportDebugInformationSlot ( self ) :
2015-11-05 15:43:11 +01:00
"""
2015-11-14 18:31:46 -07:00
Slot to display a window for exporting debug information
2015-11-05 15:43:11 +01:00
"""
2016-11-03 18:45:42 +01:00
dialog = ExportDebugDialog ( self , Topology . instance ( ) . project ( ) )
2015-11-05 15:43:11 +01:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2015-11-05 15:43:11 +01:00
2016-01-18 21:02:08 +01:00
def _doctorSlot ( self ) :
"""
Slot to display a window for exporting debug information
"""
2016-11-03 18:45:42 +01:00
dialog = DoctorDialog ( self )
2016-01-18 21:02:08 +01:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2016-01-18 21:02:08 +01:00
2016-04-26 11:42:48 -06:00
def _academyActionSlot ( self ) :
"""
Slot to launch a browser pointing to the courses page .
"""
QtGui . QDesktopServices . openUrl ( QtCore . QUrl ( " http://academy.gns3.com/ " ) )
2015-08-28 20:55:59 +02:00
def _showNodesDockWidget ( self , title , category ) :
2014-03-24 21:06:56 -06:00
"""
Makes the NodesDockWidget appear with the appropriate title and the devices
from the specified category listed .
Makes the dock disappear if the same category is selected .
: param title : NodesDockWidget title
: param category : category of device to list
"""
if self . uiNodesDockWidget . windowTitle ( ) == title :
self . uiNodesDockWidget . setVisible ( False )
self . uiNodesDockWidget . setWindowTitle ( " " )
else :
self . uiNodesDockWidget . setWindowTitle ( title )
self . uiNodesDockWidget . setVisible ( True )
2017-01-24 19:29:19 +01:00
self . uiNodesDockWidget . populateNodesView ( category )
2014-02-27 21:45:56 -07:00
2015-05-28 16:54:39 +02:00
def _localConfigChangedSlot ( self ) :
"""
Called when the local config change
"""
2015-06-20 16:10:33 -06:00
2015-05-28 16:54:39 +02:00
self . uiNodesView . refresh ( )
2014-02-27 21:45:56 -07:00
def _browseRoutersActionSlot ( self ) :
"""
Slot to browse all the routers .
"""
2014-03-24 21:06:56 -06:00
self . _showNodesDockWidget ( " Routers " , Node . routers )
2014-02-27 21:45:56 -07:00
def _browseSwitchesActionSlot ( self ) :
"""
Slot to browse all the switches .
"""
2014-03-24 21:06:56 -06:00
self . _showNodesDockWidget ( " Switches " , Node . switches )
2014-02-27 21:45:56 -07:00
def _browseEndDevicesActionSlot ( self ) :
"""
Slot to browse all the end devices .
"""
2014-03-24 21:06:56 -06:00
self . _showNodesDockWidget ( " End devices " , Node . end_devices )
2014-02-27 21:45:56 -07:00
def _browseSecurityDevicesActionSlot ( self ) :
"""
Slot to browse all the security devices .
"""
2014-03-24 21:06:56 -06:00
self . _showNodesDockWidget ( " Security devices " , Node . security_devices )
2014-02-27 21:45:56 -07:00
def _browseAllDevicesActionSlot ( self ) :
"""
Slot to browse all the devices .
"""
2015-08-28 20:55:59 +02:00
self . _showNodesDockWidget ( " All devices " , None )
2014-01-31 16:22:31 -07:00
def _addLinkActionSlot ( self ) :
"""
Slot to receive events from the add a link action .
"""
if not self . uiAddLinkAction . isChecked ( ) :
self . uiAddLinkAction . setText ( " Add a link " )
self . adding_link_signal . emit ( False )
else :
self . uiAddLinkAction . setText ( " Cancel " )
self . adding_link_signal . emit ( True )
2016-08-24 18:53:05 +02:00
def preferencesActionSlot ( self ) :
2014-01-31 16:22:31 -07:00
"""
Slot to show the preferences dialog .
"""
2016-11-03 18:45:42 +01:00
with Progress . instance ( ) . context ( min_duration = 0 ) :
dialog = PreferencesDialog ( self )
2020-04-27 11:55:01 +09:30
#dialog.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["preferences_dialog_geometry"].encode()))
2015-06-19 12:15:21 +02:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2020-04-27 11:55:01 +09:30
#self._settings["preferences_dialog_geometry"] = bytes(dialog.saveGeometry().toBase64()).decode()
#self.setSettings(self._settings)
2014-01-31 16:22:31 -07:00
2019-10-18 16:46:48 +02:00
def _editReadmeActionSlot ( self ) :
"""
Slot to edit the README file
"""
Topology . instance ( ) . editReadme ( )
2017-02-28 17:42:23 +01:00
def resizeEvent ( self , event ) :
self . _notif_dialog . resize ( )
super ( ) . resizeEvent ( event )
2014-01-31 16:22:31 -07:00
def keyPressEvent ( self , event ) :
"""
Handles all key press events for the main window .
: param event : QKeyEvent
"""
2016-11-03 18:45:42 +01:00
key = event . key ( )
2014-01-31 16:22:31 -07:00
# if the user is adding a link and press Escape, then cancel the link addition.
2025-12-14 18:19:47 +08:00
if self . uiAddLinkAction . isChecked ( ) and key == QtCore . Qt . Key . Key_Escape :
2014-01-31 16:22:31 -07:00
self . uiAddLinkAction . setChecked ( False )
self . _addLinkActionSlot ( )
2025-12-15 19:27:55 +08:00
elif key == QtCore . Qt . Key . Key_C and ( event . modifiers ( ) & QtCore . Qt . KeyboardModifier . ControlModifier ) :
2024-02-14 17:33:15 +08:00
status_bar_message = self . uiStatusBar . currentMessage ( )
if status_bar_message :
QtWidgets . QApplication . clipboard ( ) . setText ( status_bar_message )
2014-01-31 16:22:31 -07:00
else :
2015-04-17 12:14:21 +02:00
super ( ) . keyPressEvent ( event )
2014-01-31 16:22:31 -07:00
def closeEvent ( self , event ) :
"""
Handles the event when the main window is closed .
: param event : QCloseEvent
"""
2018-01-08 18:00:59 +07:00
if Topology . instance ( ) . project ( ) :
reply = QtWidgets . QMessageBox . question ( self , " Confirm Exit " , " Are you sure you want to exit GNS3? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
if reply == QtWidgets . QMessageBox . StandardButton . No :
2018-01-08 18:00:59 +07:00
event . ignore ( )
return
2016-11-03 18:45:42 +01:00
progress = Progress . instance ( )
2015-06-23 21:58:08 +02:00
progress . setAllowCancelQuery ( True )
progress . setCancelButtonText ( " Force quit " )
2016-05-14 12:15:20 -06:00
log . debug ( " Close the Main Window " )
2017-05-02 09:07:14 +02:00
self . _finish_application_closing ( close_windows = False )
event . accept ( )
self . uiConsoleTextEdit . closeIO ( )
2014-02-27 21:45:56 -07:00
2016-11-03 18:45:42 +01:00
def _finish_application_closing ( self , close_windows = True ) :
2015-01-30 21:47:06 +01:00
"""
Handles the event when the main window is closed .
And project closed .
2015-12-22 16:11:50 +01:00
: params closing : True the windows is currently closing do not try to reclose it
2015-01-30 21:47:06 +01:00
"""
2014-02-27 21:45:56 -07:00
2015-02-19 16:04:15 -07:00
log . debug ( " _finish_application_closing " )
2014-10-16 08:42:10 -06:00
2024-02-12 16:16:07 +11:00
if self . _save_gui_state_geometry :
self . _settings [ " geometry " ] = bytes ( self . saveGeometry ( ) . toBase64 ( ) ) . decode ( )
self . _settings [ " state " ] = bytes ( self . saveState ( ) . toBase64 ( ) ) . decode ( )
else :
self . _settings [ " geometry " ] = " "
self . _settings [ " state " ] = " "
2015-06-20 16:10:33 -06:00
self . setSettings ( self . _settings )
2014-11-24 12:47:56 +01:00
2019-03-30 17:20:15 +07:00
Controller . instance ( ) . stopListenNotifications ( )
2016-11-03 18:45:42 +01:00
server = LocalServer . instance ( )
server . stopLocalServer ( wait = True )
2015-01-30 21:47:06 +01:00
2016-11-03 18:45:42 +01:00
time_spent = " {:.0f} " . format ( time . time ( ) - self . _start_time )
2015-05-08 18:35:18 -06:00
log . debug ( " Time spend in the software is {} " . format ( time_spent ) )
2015-12-22 16:11:50 +01:00
if close_windows :
self . close ( )
2014-01-31 16:22:31 -07:00
2016-03-23 15:40:51 +01:00
def _nodeRunning ( self ) :
2014-01-31 16:22:31 -07:00
"""
2016-03-23 15:40:51 +01:00
Display a warning to user
2014-01-31 16:22:31 -07:00
2016-03-23 15:40:51 +01:00
: returns : False is a device is still running
2014-01-31 16:22:31 -07:00
"""
2016-03-14 09:42:51 +01:00
# check if any node is running
2016-11-03 18:45:42 +01:00
topology = Topology . instance ( )
topology . project = Topology . instance ( ) . project ( )
2016-03-14 09:42:51 +01:00
for node in topology . nodes ( ) :
2016-05-15 21:02:50 -06:00
if not node . isAlwaysOn ( ) and node . status ( ) == Node . started :
2016-03-23 15:40:51 +01:00
return True
return False
2014-01-31 16:22:31 -07:00
def startupLoading ( self ) :
"""
Called by QTimer . singleShot to load everything needed at startup .
"""
2016-03-19 22:23:08 -06:00
2016-03-14 09:50:43 +01:00
if not LocalConfig . instance ( ) . isMainGui ( ) :
2016-11-03 18:45:42 +01:00
reply = QtWidgets . QMessageBox . warning ( self , " GNS3 " , " Another GNS3 GUI is already running. Continue? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
if reply == QtWidgets . QMessageBox . StandardButton . No :
2017-05-23 18:00:02 +02:00
sys . exit ( 1 )
2016-03-14 09:50:43 +01:00
return
2017-07-12 14:07:49 +02:00
run_as_root_path = LocalConfig . instance ( ) . runAsRootPath ( )
2016-03-19 22:23:08 -06:00
if not sys . platform . startswith ( " win " ) and os . geteuid ( ) == 0 :
2017-07-12 14:07:49 +02:00
# touches file to know that user has run GNS3 as root and to prevent
# from running as user
if not os . path . exists ( run_as_root_path ) :
2017-07-14 13:11:41 +02:00
try :
open ( run_as_root_path , ' a ' ) . close ( )
except OSError as e :
log . warning ( " Cannot write `run_as_root` file due to: {} " . format ( str ( e ) ) )
2017-07-12 14:07:49 +02:00
2016-03-19 22:23:08 -06:00
QtWidgets . QMessageBox . warning ( self , " Root " , " Running GNS3 as root is not recommended and could be dangerous " )
2015-06-14 14:24:18 -06:00
2017-07-20 12:15:00 +07:00
if not sys . platform . startswith ( " win " ) and os . geteuid ( ) != 0 and os . path . exists ( run_as_root_path ) :
2017-07-12 14:07:49 +02:00
QtWidgets . QMessageBox . critical (
2017-07-14 13:11:41 +02:00
self , " Run as user " ,
" GNS3 has been previously run as root. It is not possible "
2017-07-16 12:26:45 +07:00
" to change to another user and GNS3 will be shutdown. Please delete the ' {} ' file "
" and start the program again. " . format ( run_as_root_path ) )
2017-07-14 13:11:41 +02:00
2017-07-12 14:07:49 +02:00
sys . exit ( 1 )
2015-07-01 16:16:08 -06:00
# restore debug level
if self . _settings [ " debug_level " ] :
2020-07-07 21:06:56 +09:30
print ( " Activating debugging (use command ' debug 0 ' to deactivate) " )
2016-11-03 18:45:42 +01:00
root = logging . getLogger ( )
2020-07-07 21:06:56 +09:30
root . setLevel ( logging . DEBUG )
2015-07-01 16:16:08 -06:00
2015-04-17 12:14:21 +02:00
# restore the style
self . _setStyle ( self . _settings . get ( " style " ) )
2014-03-24 21:06:56 -06:00
2016-08-23 16:42:22 +02:00
Controller . instance ( ) . connected_signal . connect ( self . _controllerConnectedSlot )
2016-12-19 18:52:31 +01:00
Controller . instance ( ) . project_list_updated_signal . connect ( self . updateRecentProjectActions )
2016-08-23 16:42:22 +02:00
2016-09-01 10:36:01 +02:00
self . uiGraphicsView . setEnabled ( False )
2016-10-04 15:58:33 +02:00
# show the setup wizard
if not self . _settings [ " hide_setup_wizard " ] :
self . _setupWizardActionSlot ( )
2016-07-06 15:23:56 +02:00
else :
2016-12-01 12:33:00 +01:00
# start and connect to the local server if needed
2019-01-28 15:13:37 +08:00
LocalServer . instance ( ) . localServerAutoStartIfRequired ( )
2016-10-04 15:58:33 +02:00
if self . _open_file_at_startup :
self . loadPath ( self . _open_file_at_startup )
2016-11-03 18:45:42 +01:00
self . _open_file_at_startup = None
2017-01-23 17:22:17 +01:00
elif Topology . instance ( ) . project ( ) is None :
2016-10-04 15:58:33 +02:00
self . _newProjectActionSlot ( )
2014-11-09 18:27:16 -07:00
2014-10-14 21:16:09 -06:00
if self . _settings [ " check_for_update " ] :
# automatic check for update every week (604800 seconds)
2016-11-03 18:45:42 +01:00
current_epoch = int ( time . mktime ( time . localtime ( ) ) )
2014-10-14 21:16:09 -06:00
if current_epoch - self . _settings [ " last_check_for_update " ] > = 604800 :
# let's check for an update
2016-11-03 18:45:42 +01:00
self . _checkForUpdateActionSlot ( silent = True )
self . _settings [ " last_check_for_update " ] = current_epoch
2014-10-14 21:16:09 -06:00
self . setSettings ( self . _settings )
2016-10-14 22:55:34 +02:00
def updateRecentProjectsSettings ( self , project_id , project_name , project_path ) :
2016-08-16 11:45:58 +02:00
"""
Updates the recent project settings .
: param project_id : The ID of the project
: param project_name : The name of the project
2016-10-14 22:55:34 +02:00
: param project_path : The project path
2016-08-16 11:45:58 +02:00
"""
# Projects are stored as a list of project_id:project_name
2016-11-03 18:45:42 +01:00
key = " {} : {} : {} " . format ( project_id , project_name , project_path )
2016-08-16 11:45:58 +02:00
2016-11-03 18:45:42 +01:00
recent_projects = [ ]
2016-08-16 11:45:58 +02:00
for project in self . _settings [ " recent_projects " ] :
recent_projects . append ( project )
2016-10-18 11:28:40 +02:00
# Because the name can change we compare only the project id and path
2016-08-16 11:45:58 +02:00
for project_key in list ( recent_projects ) :
if project_key . split ( " : " ) [ 0 ] == project_id :
recent_projects . remove ( project_key )
2016-10-18 11:28:40 +02:00
for project_key in list ( recent_projects ) :
try :
if project_key . split ( " : " ) [ 2 ] == project_path :
recent_projects . remove ( project_key )
# 2.0.0 alpha1 compatible
except IndexError :
pass
2016-08-16 11:45:58 +02:00
recent_projects . insert ( 0 , key )
2017-04-10 17:59:17 +02:00
if len ( recent_projects ) > self . _maxrecent_files :
2016-08-16 11:45:58 +02:00
recent_projects . pop ( )
# write the recent file list
2016-11-03 18:45:42 +01:00
self . _settings [ " recent_projects " ] = recent_projects
2016-08-16 11:45:58 +02:00
self . setSettings ( self . _settings )
def updateRecentProjectActions ( self ) :
"""
Updates recent project actions .
"""
2016-11-03 18:45:42 +01:00
index = 0
size = len ( self . _settings [ " recent_projects " ] )
2016-08-16 11:45:58 +02:00
for project in self . _settings [ " recent_projects " ] :
# Projects are stored as a list of project_id:project_name
2016-10-14 22:55:34 +02:00
try :
2016-11-03 18:45:42 +01:00
project_id , project_name , project_path = project . split ( " : " , maxsplit = 2 )
2016-10-14 22:55:34 +02:00
except ValueError : # Compatible with 2.0.0a1
2016-11-03 18:45:42 +01:00
project_path = None
project_id , project_name = project . split ( " : " , maxsplit = 1 )
2016-12-19 18:52:31 +01:00
if project_id not in [ p [ " project_id " ] for p in Controller . instance ( ) . projects ( ) ] :
2017-04-10 17:59:17 +02:00
size - = 1
2016-12-19 18:52:31 +01:00
continue
2017-04-10 17:59:17 +02:00
action = self . recent_project_actions [ index ]
2016-10-14 22:55:34 +02:00
if project_path and os . path . exists ( project_path ) :
action . setText ( " {} . {} [ {} ] " . format ( index + 1 , project_name , project_path ) )
action . setData ( ( project_id , project_path , ) )
else :
action . setText ( " {} . {} " . format ( index + 1 , project_name ) )
action . setData ( ( project_id , ) )
2016-08-16 11:45:58 +02:00
index + = 1
2016-12-16 11:41:18 +01:00
if Controller . instance ( ) . isRemote ( ) :
for index in range ( 0 , size ) :
2017-04-10 17:59:17 +02:00
self . recent_project_actions [ index ] . setVisible ( True )
for index in range ( size + 1 , self . _maxrecent_files ) :
self . recent_project_actions [ index ] . setVisible ( False )
2016-08-16 11:45:58 +02:00
2016-12-16 11:41:18 +01:00
if size :
2017-04-10 17:59:17 +02:00
self . recent_project_actions_separator . setVisible ( True )
2016-12-16 11:41:18 +01:00
else :
2017-04-10 17:59:17 +02:00
for action in self . recent_project_actions :
2016-12-16 11:41:18 +01:00
action . setVisible ( False )
2017-04-10 17:59:17 +02:00
self . recent_project_actions_separator . setVisible ( False )
2016-08-16 11:45:58 +02:00
2016-05-14 12:15:20 -06:00
def updateRecentFileSettings ( self , path ) :
2014-06-14 09:36:14 -06:00
"""
Updates the recent file settings .
: param path : path to the new file
"""
2016-11-03 18:45:42 +01:00
recent_files = [ ]
2015-06-20 16:10:33 -06:00
for file_path in self . _settings [ " recent_files " ] :
if file_path :
2016-11-03 18:45:42 +01:00
file_path = os . path . normpath ( file_path )
2015-06-20 16:10:33 -06:00
if file_path not in recent_files and os . path . exists ( file_path ) :
recent_files . append ( file_path )
2014-06-14 09:36:14 -06:00
# update the recent file list
if path in recent_files :
recent_files . remove ( path )
recent_files . insert ( 0 , path )
2017-04-10 17:59:17 +02:00
if len ( recent_files ) > self . _maxrecent_files :
2014-06-14 09:36:14 -06:00
recent_files . pop ( )
# write the recent file list
2016-11-03 18:45:42 +01:00
self . _settings [ " recent_files " ] = recent_files
2015-06-20 16:10:33 -06:00
self . setSettings ( self . _settings )
2014-06-14 09:36:14 -06:00
2016-05-14 12:15:20 -06:00
def updateRecentFileActions ( self ) :
2014-06-14 09:36:14 -06:00
"""
Updates recent file actions .
"""
2016-11-03 18:45:42 +01:00
index = 0
size = len ( self . _settings [ " recent_files " ] )
2015-06-20 16:10:33 -06:00
for file_path in self . _settings [ " recent_files " ] :
2015-10-07 10:35:12 +02:00
try :
if file_path and os . path . exists ( file_path ) :
2017-04-10 17:59:17 +02:00
action = self . recent_file_actions [ index ]
2018-03-30 23:18:07 +07:00
duplicate = False
for file_path_2 in self . _settings [ " recent_files " ] :
if file_path != file_path_2 and os . path . basename ( file_path ) == os . path . basename ( file_path_2 ) :
duplicate = True
break
if duplicate :
action . setText ( " {} . {} [ {} ] " . format ( index + 1 , os . path . basename ( file_path ) , file_path ) )
else :
action . setText ( " {} . {} " . format ( index + 1 , os . path . basename ( file_path ) ) )
2015-10-07 10:35:12 +02:00
action . setData ( file_path )
action . setVisible ( True )
index + = 1
# We can have this error if user save a file with unicode char
# and change his system locale.
except UnicodeEncodeError :
pass
2014-06-14 09:36:14 -06:00
2016-08-23 16:42:22 +02:00
if not Controller . instance ( ) . isRemote ( ) :
2017-04-10 17:59:17 +02:00
for index in range ( size + 1 , self . _maxrecent_files ) :
self . recent_file_actions [ index ] . setVisible ( False )
2014-06-14 09:36:14 -06:00
2016-08-23 16:42:22 +02:00
if size :
2017-04-10 17:59:17 +02:00
self . recent_file_actions_separator . setVisible ( True )
2016-08-23 16:42:22 +02:00
else :
2017-04-10 17:59:17 +02:00
for index in range ( 0 , self . _maxrecent_files ) :
self . recent_file_actions [ index ] . setVisible ( False )
self . recent_file_actions_separator . setVisible ( False )
2016-08-23 16:42:22 +02:00
def _controllerConnectedSlot ( self ) :
self . updateRecentFileActions ( )
self . _refreshVisibleWidgets ( )
2014-06-14 09:36:14 -06:00
2015-01-29 17:22:42 +01:00
def run_later ( self , counter , callback ) :
2015-01-29 15:01:50 +01:00
"""
Run a function after X milliseconds
: params counter : Time to wait before fire the callback ( in milliseconds )
: params callback : Function to run
"""
2015-03-22 14:52:58 -06:00
2015-01-29 15:01:50 +01:00
QtCore . QTimer . singleShot ( counter , callback )
2016-03-30 15:43:49 +02:00
def _exportProjectActionSlot ( self ) :
2016-05-06 15:18:12 -06:00
"""
Slot called to export a portable project
"""
2016-08-15 12:59:13 +02:00
Topology . instance ( ) . exportProject ( )
2015-05-18 12:26:45 +02:00
2016-03-31 18:30:42 +02:00
def _importProjectActionSlot ( self ) :
"""
2016-05-06 15:18:12 -06:00
Slot called to import a portable project
2016-03-31 18:30:42 +02:00
"""
2018-04-08 16:42:07 +07:00
directory = self . _portable_project_dir
if not os . path . exists ( directory ) :
2016-11-03 18:45:42 +01:00
directory = Topology . instance ( ) . projectsDirPath ( )
2018-04-08 16:42:07 +07:00
path , _ = QtWidgets . QFileDialog . getOpenFileName ( self , " Open portable project " , directory ,
2025-01-07 11:32:17 +07:00
" All files (*);;GNS3 Portable Project (*.gns3project *.gns3p) " ,
2016-07-21 16:21:38 +02:00
" GNS3 Portable Project (*.gns3project *.gns3p) " )
if path :
2016-08-15 12:59:13 +02:00
Topology . instance ( ) . importProject ( path )
2018-04-08 16:42:07 +07:00
self . _portable_project_dir = os . path . dirname ( path )
2016-03-31 18:30:42 +02:00
2016-08-16 11:12:38 +02:00
def _editProjectActionSlot ( self ) :
2017-03-15 14:30:00 +01:00
if Topology . instance ( ) . project ( ) is None :
return
2016-11-03 18:45:42 +01:00
dialog = EditProjectDialog ( self )
2016-08-16 11:12:38 +02:00
dialog . show ( )
2025-12-14 18:19:47 +08:00
dialog . exec ( )
2016-08-16 11:12:38 +02:00
2016-05-26 11:21:43 +02:00
def _deleteProjectActionSlot ( self ) :
2017-03-15 14:30:00 +01:00
if Topology . instance ( ) . project ( ) is None :
return
2016-11-03 18:45:42 +01:00
reply = QtWidgets . QMessageBox . warning (
2016-05-26 11:21:43 +02:00
self ,
" GNS3 " ,
" The project will be deleted from disk. All files will be removed including the project subdirectories. Continue? " ,
2025-12-14 18:19:47 +08:00
QtWidgets . QMessageBox . StandardButton . Yes | QtWidgets . QMessageBox . StandardButton . No )
if reply == QtWidgets . QMessageBox . StandardButton . Yes :
2016-08-15 12:59:13 +02:00
Topology . instance ( ) . deleteProject ( )
2016-05-26 11:21:43 +02:00
2016-05-14 10:36:30 -06:00
def _setStyle ( self , style_name ) :
"""
Applies a style .
2014-11-17 19:29:22 -07:00
2016-05-14 10:36:30 -06:00
: param style_name : Style name
"""
2014-11-17 19:29:22 -07:00
2016-11-03 18:45:42 +01:00
style = Style ( self )
2016-05-14 10:36:30 -06:00
if style_name . startswith ( " Charcoal " ) :
style . setCharcoalStyle ( )
elif style_name == " Classic " :
style . setClassicStyle ( )
else :
style . setLegacyStyle ( )
2016-05-14 12:15:20 -06:00
@staticmethod
def instance ( ) :
"""
Singleton to return only one instance of MainWindow .
: returns : instance of MainWindow
"""
if not hasattr ( MainWindow , " _instance " ) :
2016-11-03 18:45:42 +01:00
MainWindow . _instance = MainWindow ( )
2016-05-14 12:15:20 -06:00
return MainWindow . _instance