diff --git a/README.md b/README.md index 6307b3e7..385852fb 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 --- @@ -235,11 +236,9 @@ For more comprehensive installation instructions, please refer to [INSTALLATION. **Legacy UBX:** 1. Version panel shows current device hardware/firmware versions (*Double-left-click to refresh*). -1. Protocol Configuration panel (CFG-PRT) sets baud rate and inbound/outbound protocols across all available ports (*legacy protocols only*). -1. Solution Rate panel (CFG-RATE) sets navigation solution interval in ms (e.g. 1000 = 1/second) and measurement ratio (ratio between the number of measurements and the number of navigation solutions, e.g. 5 = five measurements per navigation solution) (*legacy protocols only*). -1. For each of the panels above, clicking anywhere in the panel background will refresh the displayed information with the current configuration. -1. Message Rate panel (CFG-MSG) sets message rates per port for UBX and NMEA messages (*legacy protocols only*). Message rate is relative to navigation solution frequency e.g. a message rate of '4' means 'every 4th navigation solution' (higher = less frequent). -1. UBX Legacy Command configuration panel providing structured updates for a range of legacy CFG-* configuration commands for *legacy protocols only*. Note: 'X' (byte) type attributes can be entered as integers or hexadecimal strings e.g. 522125312 or 0x1f1f0000. Once a command is selected, the configuration is polled and the current values displayed. The user can then amend these values as required and send the updated configuration. Some polls require input arguments (e.g. portID) - these are highlighted and will be set at default values initially (e.g. portID = 0), but can be amended by the user and re-polled using the ![refresh](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-refresh-lined-24.png?raw=true) button. +1. Protocol Configuration panel (CFG-PRT) sets baud rate and inbound/outbound protocols across all available ports. Clicking anywhere in the panel background will refresh the displayed information with the current configuration. +1. Message Rate panel (CFG-MSG) sets message rates per port for UBX and NMEA messages. Message rate is relative to navigation solution frequency e.g. a message rate of '4' means 'every 4th navigation solution' (higher = less frequent). +1. UBX Legacy Command configuration panel providing structured updates for a range of legacy CFG-* configuration commands. Note: 'X' (byte) type attributes can be entered as integers or hexadecimal strings e.g. 522125312 or 0x1f1f0000. Once a command is selected, the configuration is polled and the current values displayed. The user can then amend these values as required and send the updated configuration. Some polls require input arguments (e.g. portID) - these are highlighted and will be set at default values initially (e.g. portID = 0), but can be amended by the user and re-polled using the ![refresh](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-refresh-lined-24.png?raw=true) button. --- ## NMEA Configuration Facilities @@ -454,12 +453,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 +513,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..8fefd9a5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,14 @@ # 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. Streamline UBX Legacy Configuration panel for improved access on low res displays. +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..1fd6b817 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 ---------------------------------------- @@ -500,14 +508,6 @@ pygpsclient.ubx\_preset\_frame module :undoc-members: :show-inheritance: -pygpsclient.ubx\_solrate\_frame module --------------------------------------- - -.. automodule:: pygpsclient.ubx_solrate_frame - :members: - :undoc-members: - :show-inheritance: - pygpsclient.uni\_handler module ------------------------------- diff --git a/images/ubxconfiglegacy_widget.png b/images/ubxconfiglegacy_widget.png index c1296c3e..a09ef59f 100644 Binary files a/images/ubxconfiglegacy_widget.png and b/images/ubxconfiglegacy_widget.png differ 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..13430c3b 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 @@ -21,7 +32,6 @@ CLICK_CURSOR, ERRCOL, ICON_APP128, - ICON_GITHUB, ICON_SPONSOR, INFOCOL, LICENSE_URL, @@ -61,7 +71,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 +88,23 @@ 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_icon = Label( + self._frm_body, image=self._img_icon, borderwidth=0, anchor=CENTER + ) + 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 +113,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 +121,8 @@ def _body(self): Label( self._frm_body, text=f"{nam}: {ver}", - borderwidth=0, + anchor=CENTER, + border=0, highlightthickness=0, ) ) @@ -123,17 +141,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 +156,34 @@ 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_icon.grid(column=0, row=0, columnspan=2, padx=3, pady=0, sticky=EW) + 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 +261,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 +295,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("