Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2.2.46 #3568

Merged
merged 13 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
Expand Down
9 changes: 9 additions & 0 deletions gns3/console_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Handles commands typed in the GNS3 console.
"""

import os
import sys
import cmd
import struct
Expand All @@ -34,6 +35,14 @@

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.
Expand Down
2 changes: 1 addition & 1 deletion gns3/crash_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
6 changes: 4 additions & 2 deletions gns3/graphics_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ def mousePressEvent(self, event):
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
Expand Down Expand Up @@ -595,7 +597,7 @@ def scaleView(self, scale_factor):
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):
"""
Expand Down Expand Up @@ -640,7 +642,7 @@ def mouseMoveEvent(self, event):
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():
Expand Down
16 changes: 15 additions & 1 deletion gns3/items/drawing_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -135,6 +136,9 @@ def handleKeyPressEvent(self, event):
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):
Expand All @@ -147,6 +151,15 @@ def keyPressEvent(self, event):
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,
Expand Down Expand Up @@ -213,7 +226,8 @@ def delete(self, skip_controller=False):

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)
Expand Down
25 changes: 24 additions & 1 deletion gns3/items/node_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(self, node):
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.
Expand Down Expand Up @@ -469,7 +470,8 @@ def itemChange(self, change, value):
: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)
Expand Down Expand Up @@ -531,6 +533,27 @@ def setZValue(self, value):
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
Expand Down
11 changes: 7 additions & 4 deletions gns3/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ def exceptionHook(exception, value, tb):
# 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))
Expand Down Expand Up @@ -221,8 +221,11 @@ def exceptionHook(exception, value, tb):
# 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:
Expand Down
26 changes: 24 additions & 2 deletions gns3/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def __init__(self, parent=None, open_file=None):
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()))

Expand Down Expand Up @@ -232,6 +233,7 @@ def _connections(self):
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
Expand Down Expand Up @@ -366,6 +368,18 @@ def _lockActionSlot(self):
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.
Expand Down Expand Up @@ -1113,6 +1127,10 @@ def keyPressEvent(self, event):
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)

Expand Down Expand Up @@ -1149,8 +1167,12 @@ def _finish_application_closing(self, close_windows=True):

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()
Expand Down
8 changes: 4 additions & 4 deletions gns3/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,11 @@ def createLoadProject(self, project_settings):
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):
Expand All @@ -225,7 +225,7 @@ def restoreSnapshot(self, project_id):
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):
"""
Expand All @@ -241,7 +241,7 @@ def loadProject(self, path):
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):
Expand Down
6 changes: 6 additions & 0 deletions gns3/ui/main_window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
18 changes: 9 additions & 9 deletions gns3/ui/main_window_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -450,11 +451,10 @@ def setupUi(self, MainWindow):
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()
Expand Down Expand Up @@ -495,6 +495,7 @@ def setupUi(self, MainWindow):
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)
Expand Down Expand Up @@ -554,7 +555,7 @@ def setupUi(self, MainWindow):
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)
Expand Down Expand Up @@ -721,9 +722,8 @@ def retranslateUi(self, MainWindow):
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
Expand Down
4 changes: 2 additions & 2 deletions gns3/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 4 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading