mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-06 10:42:06 +03:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31711a9d4e | ||
|
|
fd2e236927 | ||
|
|
21409a899d | ||
|
|
25be9e7ec7 | ||
|
|
1260c2bc2d | ||
|
|
1441e38876 | ||
|
|
3c8cff20b7 | ||
|
|
f831d71c3f | ||
|
|
f71b6dcda1 | ||
|
|
62289e7be3 | ||
|
|
7447e9b7d4 | ||
|
|
c5961f400e | ||
|
|
6ca61905b2 | ||
|
|
130e91da76 | ||
|
|
1d4492c911 | ||
|
|
947733aada |
11
CHANGELOG
11
CHANGELOG
@@ -1,5 +1,16 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.46 26/02/2024
|
||||
|
||||
* Add GNS3 console command "env" to show what environment variables are used. Ref https://github.com/GNS3/gns3-server/issues/2306
|
||||
* Add CTRL+C shortcut to copy status bar message. Ref #3561
|
||||
* Key modifier (ALT) to ignore snap to grid. Fixes #3538
|
||||
* Increase timeout to 5s for status bar messages. The coordinates message has no timeout and can be reset when clicking on the scene. Ref #3561
|
||||
* Add reset GUI state feature. Ref #3549
|
||||
* Fix for hiding Windows terminal. Ref #3290
|
||||
* Drop support for Python 3.6
|
||||
* Upgrade sentry-sdk, psutil and distro dependencies
|
||||
|
||||
## 2.2.45 12/01/2024
|
||||
|
||||
* Add missing console_type values in appliance_v8.json. Ref https://github.com/GNS3/gns3-registry/issues/849
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
Handles commands typed in the GNS3 console.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import cmd
|
||||
import struct
|
||||
@@ -34,6 +35,14 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class ConsoleCmd(cmd.Cmd):
|
||||
|
||||
def do_env(self, args):
|
||||
"""
|
||||
Show the environment variables used by GNS3.
|
||||
"""
|
||||
|
||||
for key, val in os.environ.items():
|
||||
print("{}={}".format(key, val))
|
||||
|
||||
def do_version(self, args):
|
||||
"""
|
||||
Show the version of GNS3 and its dependencies.
|
||||
|
||||
@@ -50,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://85829ec3496883de83c445deb55eecc8@o19455.ingest.sentry.io/38506"
|
||||
DSN = "https://3dec04c8d64949b41e70d86b398871ee@o19455.ingest.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -438,6 +438,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
item = self.itemAt(event.pos())
|
||||
if item and sip.isdeleted(item):
|
||||
return
|
||||
elif not item:
|
||||
self._main_window.uiStatusBar.clearMessage() # reset the status bar message when clicking on the scene
|
||||
|
||||
if item and (isinstance(item, LinkItem) or isinstance(item.parentItem(), LinkItem)):
|
||||
is_not_link = False
|
||||
@@ -595,7 +597,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if factor < 0.10 or factor > 10:
|
||||
return
|
||||
self.scale(scale_factor, scale_factor)
|
||||
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Zoom: {}%".format(round(self.transform().m11() * 100)), 5000)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
@@ -640,7 +642,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if item:
|
||||
# show item coords in the status bar
|
||||
coords = "X: {} Y: {} Z: {}".format(item.x(), item.y(), item.zValue())
|
||||
self._main_window.uiStatusBar.showMessage(coords, 2000)
|
||||
self._main_window.uiStatusBar.showMessage(coords)
|
||||
|
||||
# force the children to redraw because of a problem with QGraphicsEffect
|
||||
for item in self.scene().selectedItems():
|
||||
|
||||
@@ -44,6 +44,7 @@ class DrawingItem:
|
||||
def __init__(self, project=None, pos=None, drawing_id=None, svg=None, z=0, locked=False, rotation=0, **kws):
|
||||
self._id = drawing_id
|
||||
self._deleting = False
|
||||
self._allow_snap_to_grid = True
|
||||
self._locked = locked
|
||||
if self._id is None:
|
||||
self._id = str(uuid.uuid4())
|
||||
@@ -135,6 +136,9 @@ class DrawingItem:
|
||||
if self.rotation() < 360.0:
|
||||
self.setRotation(self.rotation() + 1)
|
||||
return True
|
||||
elif modifiers & QtCore.Qt.AltModifier:
|
||||
self._allow_snap_to_grid = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
@@ -147,6 +151,15 @@ class DrawingItem:
|
||||
if not self.handleKeyPressEvent(event):
|
||||
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
"""
|
||||
Handles all key release events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
def __json__(self):
|
||||
data = {
|
||||
"drawing_id": self._id,
|
||||
@@ -213,7 +226,8 @@ class DrawingItem:
|
||||
|
||||
def itemChange(self, change, value):
|
||||
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked():
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
|
||||
and self._allow_snap_to_grid:
|
||||
grid_size = self._graphics_view.drawingGridSize()
|
||||
mid_x = self.boundingRect().width() / 2
|
||||
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
|
||||
|
||||
@@ -51,6 +51,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
self._links = []
|
||||
self._symbol = None
|
||||
self._locked = False
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
# says if the attached node has been initialized
|
||||
# by the server.
|
||||
@@ -469,7 +470,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
:param value: value of the change
|
||||
"""
|
||||
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked():
|
||||
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self._main_window.uiSnapToGridAction.isChecked() \
|
||||
and self._allow_snap_to_grid:
|
||||
grid_size = self._main_window.uiGraphicsView.nodeGridSize()
|
||||
mid_x = self.boundingRect().width() / 2
|
||||
value.setX((grid_size * round((value.x() + mid_x) / grid_size)) - mid_x)
|
||||
@@ -531,6 +533,27 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
|
||||
for link in self._links:
|
||||
link.adjust()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Handles all key press events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
if event.modifiers() & QtCore.Qt.AltModifier:
|
||||
self._allow_snap_to_grid = False
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
"""
|
||||
Handles all key release events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
self._allow_snap_to_grid = True
|
||||
|
||||
def locked(self):
|
||||
|
||||
return self._locked
|
||||
|
||||
11
gns3/main.py
11
gns3/main.py
@@ -184,9 +184,9 @@ def main():
|
||||
# catch exceptions to write them in a file
|
||||
sys.excepthook = exceptionHook
|
||||
|
||||
# we only support Python 3 version >= 3.4
|
||||
if sys.version_info < (3, 4):
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
# we only support Python 3 version >= 3.7
|
||||
if sys.version_info < (3, 7):
|
||||
raise SystemExit("Python 3.7 or higher is required")
|
||||
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
|
||||
raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
@@ -221,8 +221,11 @@ def main():
|
||||
# hide the console
|
||||
# win32console.AllocConsole()
|
||||
console_window = win32console.GetConsoleWindow()
|
||||
if console_window:
|
||||
parent_window = win32gui.GetParent(console_window)
|
||||
if not parent_window and console_window:
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
elif parent_window:
|
||||
win32gui.ShowWindow(parent_window, win32con.SW_HIDE)
|
||||
else:
|
||||
log.warning("Could not get the console window")
|
||||
except win32console.error as e:
|
||||
|
||||
@@ -136,6 +136,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._appliance_manager = ApplianceManager().instance()
|
||||
|
||||
# restore the geometry and state of the main window.
|
||||
self._save_gui_state_geometry = True
|
||||
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
|
||||
self.restoreState(QtCore.QByteArray().fromBase64(self._settings["state"].encode()))
|
||||
|
||||
@@ -232,6 +233,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
|
||||
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
|
||||
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
|
||||
self.uiResetGUIStateAction.triggered.connect(self._resetGUIState)
|
||||
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
|
||||
|
||||
# tool menu connections
|
||||
@@ -366,6 +368,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
item.updateNode()
|
||||
item.update()
|
||||
|
||||
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")
|
||||
|
||||
def _resetDocksSlot(self):
|
||||
"""
|
||||
Reset the dock widgets.
|
||||
@@ -1113,6 +1127,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if self.uiAddLinkAction.isChecked() and key == QtCore.Qt.Key_Escape:
|
||||
self.uiAddLinkAction.setChecked(False)
|
||||
self._addLinkActionSlot()
|
||||
elif key == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier:
|
||||
status_bar_message = self.uiStatusBar.currentMessage()
|
||||
if status_bar_message:
|
||||
QtWidgets.QApplication.clipboard().setText(status_bar_message)
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
@@ -1149,8 +1167,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
log.debug("_finish_application_closing")
|
||||
|
||||
self._settings["geometry"] = bytes(self.saveGeometry().toBase64()).decode()
|
||||
self._settings["state"] = bytes(self.saveState().toBase64()).decode()
|
||||
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"] = ""
|
||||
self.setSettings(self._settings)
|
||||
|
||||
Controller.instance().stopListenNotifications()
|
||||
|
||||
@@ -209,11 +209,11 @@ class Topology(QtCore.QObject):
|
||||
project.setId(project_settings["project_id"])
|
||||
self.setProject(project)
|
||||
project.load()
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded", 5000)
|
||||
else:
|
||||
self.setProject(project)
|
||||
project.create()
|
||||
self._main_window.uiStatusBar.showMessage("Project created", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project created", 5000)
|
||||
return project
|
||||
|
||||
def restoreSnapshot(self, project_id):
|
||||
@@ -225,7 +225,7 @@ class Topology(QtCore.QObject):
|
||||
project = self._project
|
||||
self.setProject(project, snapshot=True)
|
||||
project.load()
|
||||
self._main_window.uiStatusBar.showMessage("Snapshot restored", 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Snapshot restored", 5000)
|
||||
|
||||
def loadProject(self, path):
|
||||
"""
|
||||
@@ -241,7 +241,7 @@ class Topology(QtCore.QObject):
|
||||
from .project import Project
|
||||
self.setProject(Project())
|
||||
self._project.load(path)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 2000)
|
||||
self._main_window.uiStatusBar.showMessage("Project loaded {}".format(path), 5000)
|
||||
return True
|
||||
|
||||
def editReadme(self):
|
||||
|
||||
@@ -138,6 +138,7 @@ background-none;
|
||||
<addaction name="uiShowPortNamesAction"/>
|
||||
<addaction name="uiLockAllAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="uiResetGUIStateAction"/>
|
||||
<addaction name="uiResetDocksAction"/>
|
||||
<addaction name="uiDocksMenu"/>
|
||||
</widget>
|
||||
@@ -1300,6 +1301,11 @@ background-none;
|
||||
<string>Reset all console connections</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiResetGUIStateAction">
|
||||
<property name="text">
|
||||
<string>Reset GUI state</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -14,7 +15,7 @@ class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.setWindowModality(QtCore.Qt.NonModal)
|
||||
MainWindow.resize(986, 716)
|
||||
MainWindow.resize(986, 719)
|
||||
MainWindow.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
|
||||
MainWindow.setStyleSheet("#toolBar_Devices QToolButton {\n"
|
||||
"width: 50px;\n"
|
||||
@@ -450,11 +451,10 @@ class Ui_MainWindow(object):
|
||||
self.uiNewTemplateAction.setObjectName("uiNewTemplateAction")
|
||||
self.uiResetDocksAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetDocksAction.setObjectName("uiResetDocksAction")
|
||||
self.uiEditReadmeAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiEditReadmeAction.setIcon(icon31)
|
||||
self.uiEditReadmeAction.setObjectName("uiEditReadmeAction")
|
||||
self.uiResetConsoleAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetConsoleAllAction.setObjectName("uiResetConsoleAllAction")
|
||||
self.uiResetGUIStateAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiResetGUIStateAction.setObjectName("uiResetGUIStateAction")
|
||||
self.uiEditMenu.addAction(self.uiSelectAllAction)
|
||||
self.uiEditMenu.addAction(self.uiSelectNoneAction)
|
||||
self.uiEditMenu.addSeparator()
|
||||
@@ -495,6 +495,7 @@ class Ui_MainWindow(object):
|
||||
self.uiViewMenu.addAction(self.uiShowPortNamesAction)
|
||||
self.uiViewMenu.addAction(self.uiLockAllAction)
|
||||
self.uiViewMenu.addSeparator()
|
||||
self.uiViewMenu.addAction(self.uiResetGUIStateAction)
|
||||
self.uiViewMenu.addAction(self.uiResetDocksAction)
|
||||
self.uiViewMenu.addAction(self.uiDocksMenu.menuAction())
|
||||
self.uiControlMenu.addAction(self.uiStartAllAction)
|
||||
@@ -554,7 +555,7 @@ class Ui_MainWindow(object):
|
||||
self.uiAnnotationToolBar.addAction(self.uiScreenshotAction)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.uiQuitAction.triggered.connect(MainWindow.close)
|
||||
self.uiQuitAction.triggered.connect(MainWindow.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
MainWindow.setTabOrder(self.uiGraphicsView, self.uiNodesView)
|
||||
MainWindow.setTabOrder(self.uiNodesView, self.uiConsoleTextEdit)
|
||||
@@ -721,9 +722,8 @@ class Ui_MainWindow(object):
|
||||
self.uiWebUIAction.setText(_translate("MainWindow", "Web UI - beta"))
|
||||
self.uiNewTemplateAction.setText(_translate("MainWindow", "New template"))
|
||||
self.uiResetDocksAction.setText(_translate("MainWindow", "Reset docks"))
|
||||
self.uiEditReadmeAction.setText(_translate("MainWindow", "Edit readme"))
|
||||
self.uiEditReadmeAction.setToolTip(_translate("MainWindow", "Edit readme"))
|
||||
self.uiResetConsoleAllAction.setText(_translate("MainWindow", "Reset all console connections"))
|
||||
self.uiResetGUIStateAction.setText(_translate("MainWindow", "Reset GUI state"))
|
||||
from ..compute_summary_view import ComputeSummaryView
|
||||
from ..console_view import ConsoleView
|
||||
from ..graphics_view import GraphicsView
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "2.2.45"
|
||||
__version_info__ = (2, 2, 45, 0)
|
||||
__version__ = "2.2.46"
|
||||
__version_info__ = (2, 2, 46, 0)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
jsonschema>=4.17.3,<4.18; python_version >= '3.7' # v4.17.3 is the last version to support Python 3.7
|
||||
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
|
||||
sentry-sdk==1.36.0,<1.37
|
||||
psutil==5.9.6
|
||||
distro>=1.8.0
|
||||
sentry-sdk==1.39.2,<1.40
|
||||
psutil==5.9.8
|
||||
distro>=1.9.0
|
||||
truststore>=0.8.0; python_version >= '3.10'
|
||||
importlib-resources>=1.3; python_version < '3.9'
|
||||
setuptools>=60.8.1; python_version >= '3.7'
|
||||
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6
|
||||
setuptools>=60.8.1
|
||||
|
||||
11
setup.py
11
setup.py
@@ -19,9 +19,9 @@ import sys
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
# we only support Python 3 version >= 3.4
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 4):
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
# we only support Python 3 version >= 3.7
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 7):
|
||||
raise SystemExit("Python 3.7 or higher is required")
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
@@ -79,7 +79,7 @@ setup(
|
||||
include_package_data=True,
|
||||
package_data={"gns3": ["configs/*.txt", "schemas/*.json"]},
|
||||
platforms="any",
|
||||
python_requires=">=3.4",
|
||||
python_requires='>=3.7',
|
||||
setup_requires=["setuptools>=17.1"],
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -93,9 +93,6 @@ setup(
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
|
||||
Reference in New Issue
Block a user