From 0529daa345695e0190dbee5b33638b26f8f684d3 Mon Sep 17 00:00:00 2001 From: SEMU Admin <28569967+semuadmin@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:03:48 +0100 Subject: [PATCH 1/4] update readme add gui behaviour options dialog update about text update about text streamline About dialog update string --- README.md | 9 +- RELEASE_NOTES.md | 8 + docs/pygpsclient.rst | 8 + pyproject.toml | 2 +- src/pygpsclient/_version.py | 2 +- src/pygpsclient/about_dialog.py | 93 +++--- src/pygpsclient/app.py | 18 +- src/pygpsclient/banner_frame.py | 26 +- src/pygpsclient/configuration.py | 2 +- src/pygpsclient/dialog_state.py | 7 + src/pygpsclient/dynamic_config_frame.py | 18 +- src/pygpsclient/globals.py | 5 +- src/pygpsclient/gui_config_dialog.py | 278 ++++++++++++++++++ src/pygpsclient/hardware_info_frame.py | 4 +- src/pygpsclient/helpers.py | 17 +- src/pygpsclient/importmap_dialog.py | 2 +- src/pygpsclient/menu_bar.py | 2 + src/pygpsclient/nmea_preset_frame.py | 16 +- src/pygpsclient/ntrip_client_dialog.py | 1 + .../resources/iconmonstr-antenna-3-24.png | Bin 714 -> 0 bytes .../resources/iconmonstr-gear-2-24.png | Bin 0 -> 974 bytes .../resources/iconmonstr-save-disabled-24.png | Bin 0 -> 201 bytes src/pygpsclient/rinex_dialog.py | 2 +- src/pygpsclient/serialconfig_frame.py | 8 +- src/pygpsclient/settings_child_frame.py | 10 +- src/pygpsclient/strings.py | 15 +- src/pygpsclient/ubx_cfgval_frame.py | 48 +-- src/pygpsclient/ubx_config_dialog.py | 15 +- src/pygpsclient/ubx_legacy_config_dialog.py | 21 +- src/pygpsclient/ubx_msgrate_frame.py | 8 +- src/pygpsclient/ubx_port_frame.py | 8 +- src/pygpsclient/ubx_preset_frame.py | 14 +- src/pygpsclient/ubx_solrate_frame.py | 8 +- 33 files changed, 525 insertions(+), 150 deletions(-) create mode 100644 src/pygpsclient/gui_config_dialog.py delete mode 100644 src/pygpsclient/resources/iconmonstr-antenna-3-24.png create mode 100644 src/pygpsclient/resources/iconmonstr-gear-2-24.png create mode 100644 src/pygpsclient/resources/iconmonstr-save-disabled-24.png diff --git a/README.md b/README.md index 6307b3e7..fbddc886 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION. #### GUI refresh rate setting -30. PyGPSClient processes all incoming GNSS data in 'real time' but, by default, the GUI is only refreshed every 0.5 seconds. The refresh rate can be manually configured via the `guiupdateinterval_f` setting in the json configuration file. **NB:** PyGPSClient may become unresponsive on slower platforms (e.g. Raspberry Pi) at high message rates if the GUI update interval is less than 0.2 seconds. +30. PyGPSClient processes all incoming GNSS data in 'real time' but, by default, the GUI is only refreshed every 0.5 seconds. The refresh rate can be manually configured via the `guiupdateinterval_f` setting in the json configuration file or Menu..Options..GUI Configuration dialog (*save configuration and restart after any changes*). **NB:** PyGPSClient may become unresponsive on slower platforms (e.g. Raspberry Pi) at high message rates if the GUI update interval is less than 0.2 seconds. #### Toplevel ('pop-up') dialog setting @@ -185,6 +185,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION. - A boolean configuration setting `transient_dialog_b` governs whether Toplevel dialogs are 'transient' (i.e. always on top of main application dialog) or not. Changing this setting to `0` allows Toplevel dialogs to be minimised independently of the main application window, but be mindful that some dialogs may end up hidden behind others e.g. "Open file/folder" dialogs. **If a file open button appears unresponsive, check that the "Open file/folder" panel isn't already open but obscured**. - If you're accessing the desktop via a VNC session (e.g. to a headless Raspberry Pi) it is recommended to keep the setting at the default `1`, as VNC may not recognise keystrokes on overlaid non-transient windows. - A boolean configuration setting `resizeable_dialog_b` governs whether *all* Toplevel dialogs are resizeable, irrespective of the default setting in `DialogState`. Setting this to '1' provides a workaround for issues with some scaled Linux Wayland displays. + - Settings can be updated via the Menu..Options..GUI Configuration dialog (*save configuration and restart after any changes*). #### User-selectable widgets --- @@ -454,12 +455,12 @@ facility is *not* intended to be used for real time navigational purposes. Once you have received the API key (a 32-character alphanumeric string), you can (in order of precedence): -1. Copy it to the `"mqapikey_s":` value in your json configuration file (see example provided). +1. Copy it to the `"mqapikey_s":` value in your json configuration file (see example provided), or via the Menu..Options..GUI Configuration dialog. 2. Create an environment variable named `MQAPIKEY` (all upper case) and set this to the API key value. It is recommended that this is a User variable rather than a System/Global variable. 3. Pass it via command line argument `--mqapikey`. -*The web map refresh rate can be amended if required by changing the `mapupdateinterval_n:` value in your json configuration file. +\*The web map refresh rate can be amended if required by changing the `mapupdateinterval_n:` value in your json configuration file, or via the Menu..Options..GUI Configuration Options dialog (*save configuration and restart after any changes*). --- ## User Defined Presets @@ -514,7 +515,7 @@ For further details, refer to the `pygnssutils` homepage at [https://github.com/ 1. There is a known issue with PyGPSClient GUI refreshes becoming progressively slower on certain platforms if the app is left unattended (_i.e. no user interaction_) for an extended period - typically 30 minutes or more. The issue is more pronounced on low-end SBC platforms like the Raspberry Pi. **Underlying processing (including message parsing and datalogging) is unaffected**, and the GUI can generally be 'woken up' within a few seconds via a simple user interaction e.g. resizing the main panel. The root cause of this issue is under investigation, but as a workaround, users can try a) increasing the `guiupdateinterval_f` setting in the json configuration file, or b) hiding some or all user-selectable widgets until needed. -2. **NB:** The latest version of Python for MacOS (>=3.14.5) comes with a new version of tkinter (9.0). There appear to be fairly serious performance issues with this version on MacOS Tahoe which render the PyGPSClient GUI somewhat sluggish. For the time being, it is recommended that users use >=3.14.4. This issue does *not* affect other operating systems or Python apps not using tkinter. +2. **NB:** The latest version of Python for MacOS (>=3.14.5) comes with a new version of tkinter (9.0). Early iterations of this version (3.14.5, 3.14.6) displayed fairly serious performance issues on MacOS Sonoma & Tahoe. The issue appears to have been resolved in version >=3.14.7 (tkinter 9.0.4), but if you experience any compatibility or performance issues with tkinter 9.0, consider reverting to Python <=3.14.4. This issue does *not* affect other operating systems or Python apps not using tkinter. 3. If you encounter persistent `WARNING>>Error parsing data stream Serial stream terminated unexpectedly` messages in the console, this may be indicative of insufficient serial port bandwidth (baudrate or timeout) for the current output message cohort (*particularly if this includes raw Ephemerides or Observation data*). Try increasing the baudrate in the first instance. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e4a02d4c..2f4c08c2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,13 @@ # PyGPSClient Release Notes +### RELEASE 1.7.4 + +ENHANCEMENTS: + +1. Add GUI Configuration Options panel to set internal GUI configuration parameters (*previously only available via manual edits of json file*). **NB**: User must save configuration and restart app after any changes. +1. Add "Toggle Settings" button to Banner panel for quick access to Settings (docked or undocked). +1. Enhanced validation messaging in UBX and NMEA configuration panels. + ### RELEASE 1.7.3 FIXES: diff --git a/docs/pygpsclient.rst b/docs/pygpsclient.rst index 650f0e38..8ae9b755 100644 --- a/docs/pygpsclient.rst +++ b/docs/pygpsclient.rst @@ -132,6 +132,14 @@ pygpsclient.gpx\_dialog module :undoc-members: :show-inheritance: +pygpsclient.gui\_config\_dialog module +-------------------------------------- + +.. automodule:: pygpsclient.gui_config_dialog + :members: + :undoc-members: + :show-inheritance: + pygpsclient.hardware\_info\_frame module ---------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 0c6fa897..0fdd2087 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dependencies = [ "Pillow>=12.0.0", "pygnssutils>=1.2.7", "pyunigps>=1.0.0", - "pynmeagps>=1.1.5", + "pynmeagps>=1.1.7", "pyubx2>=1.3.5", ] diff --git a/src/pygpsclient/_version.py b/src/pygpsclient/_version.py index d1a925ac..0fd72f84 100644 --- a/src/pygpsclient/_version.py +++ b/src/pygpsclient/_version.py @@ -8,4 +8,4 @@ :license: BSD 3-Clause """ -__version__ = "1.7.3" +__version__ = "1.7.4" diff --git a/src/pygpsclient/about_dialog.py b/src/pygpsclient/about_dialog.py index 4e74ee2f..f2f4500a 100644 --- a/src/pygpsclient/about_dialog.py +++ b/src/pygpsclient/about_dialog.py @@ -12,7 +12,18 @@ import logging from platform import machine, python_version -from tkinter import Button, Checkbutton, Frame, IntVar, Label, Tcl +from tkinter import ( + CENTER, + EW, + NSEW, + Button, + Checkbutton, + Frame, + IntVar, + Label, + Tcl, + ttk, +) from webbrowser import open_new_tab from PIL import Image, ImageTk @@ -20,8 +31,6 @@ from pygpsclient.globals import ( CLICK_CURSOR, ERRCOL, - ICON_APP128, - ICON_GITHUB, ICON_SPONSOR, INFOCOL, LICENSE_URL, @@ -60,8 +69,6 @@ def __init__(self, app, *args, **kwargs): # pylint: disable=unused-argument self.__app = app # Reference to main application class self.logger = logging.getLogger(__name__) - self._img_icon = ImageTk.PhotoImage(Image.open(ICON_APP128).resize((64, 64))) - self._img_github = ImageTk.PhotoImage(Image.open(ICON_GITHUB).resize((32, 32))) self._img_sponsor = ImageTk.PhotoImage(Image.open(ICON_SPONSOR)) self._checkonstartup = IntVar() self._checkonstartup.set(self.__app.configuration.get("checkforupdate_b")) @@ -79,16 +86,20 @@ def _body(self): """ self._frm_body = Frame(self.container) - self._lbl_icon = Label(self._frm_body, image=self._img_icon, borderwidth=0) - self._lbl_descs = [] - for txt in ABOUTTXT: - self._lbl_descs.append( - Label( - self._frm_body, - text=txt, - borderwidth=0, - ) - ) + self._lbl_desc = Label( + self._frm_body, + text=ABOUTTXT, + wraplength=400, + justify=CENTER, + anchor=CENTER, + ) + self._lbl_github = Label( + self._frm_body, + text=GITHUB_URL, + foreground=INFOCOL, + cursor=CLICK_CURSOR, + anchor=CENTER, + ) tkv = Tcl().call("info", "patchlevel") self._lbl_python_version = Label( self._frm_body, @@ -97,6 +108,7 @@ def _body(self): f"Python: {python_version()} Tk: {tkv} " f"Spatial: {SQLSTATUS[self.__app.db_enabled]}" ), + anchor=CENTER, ) self._lbl_lib_versions = [] for nam, ver in LIBVERSIONS.items(): @@ -104,7 +116,8 @@ def _body(self): Label( self._frm_body, text=f"{nam}: {ver}", - borderwidth=0, + anchor=CENTER, + border=0, highlightthickness=0, ) ) @@ -123,17 +136,14 @@ def _body(self): self._frm_body, image=self._img_sponsor, cursor=CLICK_CURSOR, - ) - self._lbl_github = Label( - self._frm_body, - text=GITHUB_URL, - fg=INFOCOL, - cursor=CLICK_CURSOR, + anchor=CENTER, ) self._lbl_copyright = Label( self._frm_body, text=COPYRIGHT, + foreground=INFOCOL, cursor=CLICK_CURSOR, + anchor=CENTER, ) def _do_layout(self): @@ -141,26 +151,33 @@ def _do_layout(self): Arrange widgets in dialog. """ - i = 0 - self._frm_body.grid(column=0, row=0, padx=5, pady=5, ipadx=5, ipady=5) - self._lbl_icon.grid(column=0, row=1, columnspan=2, padx=3, pady=0) - for i, lbl in enumerate(self._lbl_descs): - lbl.grid(column=0, row=2 + i, columnspan=2, padx=3, pady=0) - self._lbl_python_version.grid(column=0, row=3 + i, columnspan=2, padx=3, pady=1) - n = 4 + i + self._frm_body.grid(column=0, row=0, ipadx=2, ipady=2, sticky=NSEW) + self._lbl_desc.grid(column=0, row=1, columnspan=2, padx=3, pady=0, sticky=EW) + self._lbl_github.grid(column=0, row=2, columnspan=2, padx=3, pady=0, sticky=EW) + ttk.Separator(self._frm_body).grid( + column=0, row=3, columnspan=2, padx=3, pady=3, sticky=EW + ) + self._lbl_python_version.grid( + column=0, row=4, columnspan=2, padx=3, pady=1, sticky=EW + ) for i, lbl in enumerate(self._lbl_lib_versions): - lbl.grid(column=0, row=n + i, columnspan=2, padx=2) + lbl.grid(column=0, row=5 + i, columnspan=2, padx=2, pady=0, sticky=EW) + lv = len(self._lbl_lib_versions) self._btn_checkupdate.grid( - column=0, row=1 + n + i, ipadx=3, ipady=3, padx=3, pady=3 + column=0, row=6 + lv, ipadx=3, ipady=3, padx=3, pady=3 ) self._chk_checkupdate.grid( - column=1, row=1 + n + i, ipadx=3, ipady=3, padx=3, pady=3 + column=1, row=6 + lv, ipadx=3, ipady=3, padx=3, pady=3 + ) + ttk.Separator(self._frm_body).grid( + column=0, row=7 + lv, columnspan=2, padx=3, pady=3, sticky=EW ) self._lbl_sponsoricon.grid( - column=0, row=2 + n + i, columnspan=2, padx=3, pady=3 + column=0, row=8 + lv, columnspan=2, padx=3, pady=3, sticky=EW + ) + self._lbl_copyright.grid( + column=0, row=9 + lv, columnspan=2, padx=3, pady=3, sticky=EW ) - self._lbl_github.grid(column=0, row=3 + n + i, columnspan=2, padx=3, pady=0) - self._lbl_copyright.grid(column=0, row=4 + n + i, columnspan=2, padx=3, pady=0) def _attach_events(self): """ @@ -238,7 +255,7 @@ def _check_for_update(self, *args, **kwargs): # pylint: disable=unused-argument txt += f" - Latest version is {latest}" col = ERRCOL self._lbl_lib_versions[i]["text"] = txt - self._lbl_lib_versions[i]["fg"] = col + self._lbl_lib_versions[i]["foreground"] = col updates = [nam for (nam, current, latest) in versions if latest != current] if len(updates) > 0: self.set_status_label("Updates available", OKCOL) @@ -272,10 +289,10 @@ def _set_update_btn_mode(self, update: bool): if update: self._btn_checkupdate["text"] = "UPDATE" - self._btn_checkupdate["fg"] = OKCOL + self._btn_checkupdate["foreground"] = OKCOL self._btn_checkupdate.bind("