>V%w93!$NBA-#+y`N6|$?wBQm`%9*RJw`#)3(ezVdn=v(1lSSP50` tuple:
+ """
+ Get current frame size.
+
+ :return: (width, height)
+ :rtype: tuple
+
+ """
+
+ self.update_idletasks() # Make sure we know about any resizing
+ return (self.winfo_width(), self.winfo_height())
diff --git a/src/pygpsclient/settings_dialog.py b/src/pygpsclient/settings_dialog.py
new file mode 100644
index 00000000..dd38ca57
--- /dev/null
+++ b/src/pygpsclient/settings_dialog.py
@@ -0,0 +1,75 @@
+"""
+settings_dialog.py
+
+Settings Toplevel dialog container class for PyGPSClient settings_child_frame.
+
+Used when Settings are "undocked".
+
+Created on 14 Jan 2026
+
+:author: semuadmin (Steve Smith)
+:copyright: 2020 semuadmin
+:license: BSD 3-Clause
+"""
+
+from tkinter import NSEW
+
+from pygpsclient.settings_child_frame import SettingsChildFrame
+from pygpsclient.strings import DLGTSETTINGS
+from pygpsclient.toplevel_dialog import ToplevelDialog
+
+
+class SettingsDialog(ToplevelDialog):
+ """
+ Settings frame class.
+ """
+
+ def __init__(self, app, *args, **kwargs):
+ """
+ Constructor.
+
+ :param Frame app: reference to main tkinter application
+ :param args: optional args to pass to Frame parent class
+ :param kwargs: optional kwargs to pass to Frame parent class
+ """
+
+ self.__app = app # Reference to main application class
+ self.__master = self.__app.appmaster # Reference to root class (Tk)
+
+ super().__init__(app, DLGTSETTINGS)
+
+ self._body()
+ self._do_layout()
+ self._finalise()
+ self.focus_force()
+
+ def on_expand(self):
+ """
+ Automatically expand container canvas when sub-frames are resized.
+ """
+
+ self._can_container.event_generate("")
+
+ def _body(self):
+ """
+ Set up frame and widgets.
+ """
+
+ self.frm_settings = SettingsChildFrame(self.__app, self.container)
+ self.frm_serial = self.frm_settings.frm_serial
+ self.frm_socketclient = self.frm_settings.frm_socketclient
+
+ def _do_layout(self):
+ """
+ Position widgets in frame.
+ """
+
+ self.frm_settings.grid(column=0, row=0, sticky=NSEW)
+
+ def on_exit(self, *args, **kwargs): # pylint: disable=unused-argument
+ """
+ Overridden method - closing this dialog 'docks' the Settings panel
+ back onto the main application window.
+ """
+
+ self.__app.settings_dock()
diff --git a/src/pygpsclient/settings_frame.py b/src/pygpsclient/settings_frame.py
index 9d8748fe..e022620f 100644
--- a/src/pygpsclient/settings_frame.py
+++ b/src/pygpsclient/settings_frame.py
@@ -1,15 +1,9 @@
"""
settings_frame.py
-Settings frame class for PyGPSClient application.
+Settings frame class for PyGPSClient settings_child_frame.
-- Reads and updates configuration held in self.__app.configuration.
-- Starts or stops data logging.
-- Sets initial (saved) configuration of the following frames:
-- frm_settings (SettingsFrame class) for general application settings.
-- frm_serial (SerialConfigFrame class) for serial port settings.
-- frm_socketclient (SocketConfigFrame class) for socket client settings.
-- frm_socketserver (ServerConfigFrame class) for socket server settings.
+Used when Settings are "docked".
Created on 12 Sep 2020
@@ -18,95 +12,10 @@
:license: BSD 3-Clause
"""
-# pylint: disable=unnecessary-lambda, unused-argument
-
-from tkinter import (
- DISABLED,
- EW,
- NORMAL,
- Button,
- Checkbutton,
- E,
- Frame,
- IntVar,
- Label,
- Spinbox,
- StringVar,
- TclError,
- W,
- ttk,
-)
-
-from PIL import Image, ImageTk
+from tkinter import Frame
from pygpsclient.canvas_subclasses import CanvasContainer
-from pygpsclient.globals import (
- BPSRATES,
- CONNECTED,
- CONNECTED_FILE,
- CONNECTED_SOCKET,
- DDD,
- DISCONNECTED,
- DMM,
- DMS,
- ECEF,
- ERRCOL,
- FORMATS,
- GNSS_EOF_EVENT,
- GNSS_ERR_EVENT,
- GNSS_EVENT,
- GNSS_TIMEOUT_EVENT,
- HOME,
- ICON_CONN,
- ICON_DISCONN,
- ICON_EXIT,
- ICON_LOGREAD,
- ICON_NMEACONFIG,
- ICON_NTRIPCONFIG,
- ICON_SERIAL,
- ICON_SOCKET,
- ICON_TRANSMIT,
- ICON_TTYCONFIG,
- ICON_UBXCONFIG,
- INFOCOL,
- KNOWNGPS,
- MSGMODES,
- NOPORTS,
- OKCOL,
- READONLY,
- TIMEOUTS,
- TRACEMODE_WRITE,
- UI,
- UIK,
- UMK,
- UMM,
-)
-from pygpsclient.serialconfig_frame import SerialConfigFrame
-from pygpsclient.socketconfig_frame import SocketConfigFrame
-from pygpsclient.sqlite_handler import SQLOK
-from pygpsclient.strings import (
- DLGTNMEA,
- DLGTNTRIP,
- DLGTSERVER,
- DLGTTTY,
- DLGTUBX,
- LBLAUTOSCROLL,
- LBLDATABASERECORD,
- LBLDATADISP,
- LBLDATALOG,
- LBLDEGFORMAT,
- LBLFILEDELAY,
- LBLNMEACONFIG,
- LBLNTRIPCONFIG,
- LBLPROTDISP,
- LBLSERVERCONFIG,
- LBLTRACKRECORD,
- LBLTTYCONFIG,
- LBLUBXCONFIG,
-)
-
-MAXLINES = ("200", "500", "1000", "2000", "100")
-FILEDELAYS = (2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000)
+from pygpsclient.settings_child_frame import SettingsChildFrame
class SettingsFrame(Frame):
@@ -126,49 +35,11 @@ def __init__(self, app, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- Frame.__init__(self, self.__master, *args, **kwargs)
-
- self.infilepath = None
- self.logpath = HOME
- self.trackpath = HOME
- self.databasepath = HOME
- self._prot_nmea = IntVar()
- self._prot_ubx = IntVar()
- self._prot_sbf = IntVar()
- self._prot_qgc = IntVar()
- self._prot_rtcm = IntVar()
- self._prot_spartn = IntVar()
- self._prot_tty = IntVar()
- self._autoscroll = IntVar()
- self._maxlines = IntVar()
- self._filedelay = IntVar()
- self._units = StringVar()
- self._degrees_format = StringVar()
- self._console_format = StringVar()
- self._datalog = IntVar()
- self._logformat = StringVar()
- self._record_track = IntVar()
- self._record_database = IntVar()
- self._colortag = IntVar()
- self.defaultports = self.__app.configuration.get("defaultport_s")
- self._validsettings = True
- self._img_conn = ImageTk.PhotoImage(Image.open(ICON_CONN))
- self._img_serial = ImageTk.PhotoImage(Image.open(ICON_SERIAL))
- self._img_socket = ImageTk.PhotoImage(Image.open(ICON_SOCKET))
- self._img_disconn = ImageTk.PhotoImage(Image.open(ICON_DISCONN))
- self._img_exit = ImageTk.PhotoImage(Image.open(ICON_EXIT))
- self._img_ubxconfig = ImageTk.PhotoImage(Image.open(ICON_UBXCONFIG))
- self._img_nmeaconfig = ImageTk.PhotoImage(Image.open(ICON_NMEACONFIG))
- self._img_ttyconfig = ImageTk.PhotoImage(Image.open(ICON_TTYCONFIG))
- self._img_ntripconfig = ImageTk.PhotoImage(Image.open(ICON_NTRIPCONFIG))
- self._img_serverconfig = ImageTk.PhotoImage(Image.open(ICON_TRANSMIT))
- self._img_dataread = ImageTk.PhotoImage(Image.open(ICON_LOGREAD))
+ super().__init__(self.__master, *args, **kwargs)
self._container() # create scrollable container
self._body()
self._do_layout()
- self.reset()
- # self._attach_events() # done in reset
self.focus_force()
def _container(self):
@@ -195,317 +66,16 @@ def _body(self):
"""
self._frm_container.option_add("*Font", self.__app.font_sm)
-
- # serial port configuration panel
- self.frm_serial = SerialConfigFrame(
- self.__app,
- self._frm_container,
- recognised=KNOWNGPS,
- timeouts=TIMEOUTS,
- bpsrates=BPSRATES,
- msgmodes=list(MSGMODES.keys()),
- )
-
- # socket client configuration panel
- self.frm_socketclient = SocketConfigFrame(self.__app, self._frm_container)
-
- # connection buttons
- self._frm_buttons = Frame(self._frm_container)
- self._btn_connect = Button(
- self._frm_buttons,
- width=45,
- height=35,
- image=self._img_serial,
- command=lambda: self._on_connect(CONNECTED),
- state=NORMAL,
- )
- self._lbl_connect = Label(self._frm_buttons, text="USB/UART")
- self._btn_connect_socket = Button(
- self._frm_buttons,
- width=45,
- height=35,
- image=self._img_socket,
- command=lambda: self._on_connect(CONNECTED_SOCKET),
- state=NORMAL,
- )
- self._lbl_connect_socket = Label(self._frm_buttons, text="TCP/UDP")
- self._btn_connect_file = Button(
- self._frm_buttons,
- width=45,
- height=35,
- image=self._img_dataread,
- command=lambda: self._on_connect(CONNECTED_FILE),
- state=NORMAL,
- )
- self._lbl_connect_file = Label(self._frm_buttons, text="FILE")
- self._btn_disconnect = Button(
- self._frm_buttons,
- width=45,
- height=35,
- image=self._img_disconn,
- command=lambda: self._on_connect(DISCONNECTED),
- state=DISABLED,
- )
- self._lbl_disconnect = Label(self._frm_buttons, text="STOP")
- self._btn_exit = Button(
- self._frm_buttons,
- width=45,
- height=35,
- image=self._img_exit,
- command=lambda: self.__app.on_exit(),
- state=NORMAL,
- )
-
- self._lbl_status_preset = Label(
- self._frm_buttons, font=self.__app.font_md2, text=""
- )
-
- # Other configuration options
- self._frm_options = Frame(self._frm_container)
- self._frm_options_btns = Frame(self._frm_options)
- self._lbl_protocol = Label(self._frm_options, text=LBLPROTDISP)
- self._chk_nmea = Checkbutton(
- self._frm_options,
- text="NMEA",
- variable=self._prot_nmea,
- )
- self._chk_ubx = Checkbutton(
- self._frm_options,
- text="UBX",
- variable=self._prot_ubx,
- )
- self._chk_rtcm = Checkbutton(
- self._frm_options,
- text="RTCM",
- variable=self._prot_rtcm,
- )
- self._chk_spartn = Checkbutton(
- self._frm_options,
- text="SPARTN",
- variable=self._prot_spartn,
- )
- self._chk_sbf = Checkbutton(
- self._frm_options,
- text="SBF",
- variable=self._prot_sbf,
- )
- self._chk_qgc = Checkbutton(
- self._frm_options,
- text="QGC",
- variable=self._prot_qgc,
- )
- self._chk_tty = Checkbutton(
- self._frm_options,
- text="TTY",
- variable=self._prot_tty,
- )
- self._lbl_consoledisplay = Label(self._frm_options, text=LBLDATADISP)
- self._spn_conformat = Spinbox(
- self._frm_options,
- values=FORMATS,
- width=10,
- state=READONLY,
- wrap=True,
- textvariable=self._console_format,
- )
- self._chk_tags = Checkbutton(
- self._frm_options,
- text="Tags",
- variable=self._colortag,
- )
- self._lbl_format = Label(self._frm_options, text=LBLDEGFORMAT)
- self._spn_format = Spinbox(
- self._frm_options,
- values=(DDD, DMS, DMM, ECEF),
- width=6,
- state=READONLY,
- wrap=True,
- textvariable=self._degrees_format,
- )
- self._spn_units = Spinbox(
- self._frm_options,
- values=(UMM, UIK, UI, UMK),
- width=13,
- state=READONLY,
- wrap=True,
- textvariable=self._units,
- )
- self._chk_scroll = Checkbutton(
- self._frm_options, text=LBLAUTOSCROLL, variable=self._autoscroll
- )
- self._spn_maxlines = Spinbox(
- self._frm_options,
- values=MAXLINES,
- width=6,
- wrap=True,
- textvariable=self._maxlines,
- state=READONLY,
- )
- self._lbl_filedelay = Label(
- self._frm_options,
- text=LBLFILEDELAY,
- )
- self._spn_filedelay = Spinbox(
- self._frm_options,
- value=FILEDELAYS,
- width=4,
- wrap=True,
- textvariable=self._filedelay,
- state=READONLY,
- repeatdelay=1000,
- repeatinterval=1000,
- )
- self._chk_datalog = Checkbutton(
- self._frm_options,
- text=LBLDATALOG,
- variable=self._datalog,
- )
- self._spn_datalog = Spinbox(
- self._frm_options,
- values=(FORMATS),
- width=20,
- wrap=True,
- textvariable=self._logformat,
- state=READONLY,
- )
- self._chk_recordtrack = Checkbutton(
- self._frm_options,
- text=LBLTRACKRECORD,
- variable=self._record_track,
- )
- self._chk_recorddatabase = Checkbutton(
- self._frm_options,
- text=LBLDATABASERECORD,
- variable=self._record_database,
- )
- # configuration panel buttons
- self._lbl_ubxconfig = Label(
- self._frm_options_btns,
- text=LBLUBXCONFIG,
- )
- self._btn_ubxconfig = Button(
- self._frm_options_btns,
- width=45,
- image=self._img_ubxconfig,
- command=lambda: self.__app.start_dialog(DLGTUBX),
- )
- self._lbl_nmeaconfig = Label(
- self._frm_options_btns,
- text=LBLNMEACONFIG,
- )
- self._btn_nmeaconfig = Button(
- self._frm_options_btns,
- width=45,
- image=self._img_nmeaconfig,
- command=lambda: self.__app.start_dialog(DLGTNMEA),
- state=NORMAL,
- )
- self._lbl_ttyconfig = Label(
- self._frm_options_btns,
- text=LBLTTYCONFIG,
- )
- self._btn_ttyconfig = Button(
- self._frm_options_btns,
- width=45,
- image=self._img_ttyconfig,
- command=lambda: self.__app.start_dialog(DLGTTTY),
- state=NORMAL,
- )
- self._lbl_ntripconfig = Label(
- self._frm_options_btns,
- text=LBLNTRIPCONFIG,
- )
- self._btn_ntripconfig = Button(
- self._frm_options_btns,
- width=45,
- image=self._img_ntripconfig,
- command=lambda: self.__app.start_dialog(DLGTNTRIP),
- state=NORMAL,
- )
- self._lbl_serverconfig = Label(
- self._frm_options_btns,
- text=LBLSERVERCONFIG,
- )
- self._btn_serverconfig = Button(
- self._frm_options_btns,
- width=45,
- image=self._img_serverconfig,
- command=lambda: self.__app.start_dialog(DLGTSERVER),
- state=NORMAL,
- )
+ self.frm_settings = SettingsChildFrame(self.__app, self._frm_container)
+ self.frm_serial = self.frm_settings.frm_serial
+ self.frm_socketclient = self.frm_settings.frm_socketclient
def _do_layout(self):
"""
Position widgets in frame.
"""
- self.frm_serial.grid(column=0, row=1, columnspan=4, padx=2, pady=2, sticky=EW)
- ttk.Separator(self._frm_container).grid(
- column=0, row=2, columnspan=4, padx=2, pady=2, sticky=EW
- )
-
- self.frm_socketclient.grid(
- column=0, row=3, columnspan=4, padx=2, pady=2, sticky=EW
- )
- ttk.Separator(self._frm_container).grid(
- column=0, row=4, columnspan=4, padx=2, pady=2, sticky=EW
- )
-
- self._frm_buttons.grid(column=0, row=5, columnspan=4, sticky=EW)
- self._btn_connect.grid(column=0, row=0, padx=2, pady=1)
- self._btn_connect_socket.grid(column=1, row=0, padx=2, pady=1)
- self._btn_connect_file.grid(column=2, row=0, padx=2, pady=1)
- self._btn_disconnect.grid(column=3, row=0, padx=2, pady=1)
- self._btn_exit.grid(column=4, row=0, padx=2, pady=1)
- self._lbl_connect.grid(column=0, row=1, padx=1, pady=1, sticky=EW)
- self._lbl_connect_socket.grid(column=1, row=1, padx=1, pady=1, sticky=EW)
- self._lbl_connect_file.grid(column=2, row=1, padx=1, pady=1, sticky=EW)
- self._lbl_disconnect.grid(column=3, row=1, padx=1, pady=1, sticky=EW)
-
- ttk.Separator(self._frm_container).grid(
- column=0, row=7, columnspan=4, padx=2, pady=2, sticky=EW
- )
-
- self._frm_options.grid(column=0, row=8, columnspan=4, sticky=EW)
- self._lbl_protocol.grid(column=0, row=0, padx=2, pady=2, sticky=W)
- self._chk_nmea.grid(column=1, row=0, padx=0, pady=0, sticky=W)
- self._chk_ubx.grid(column=2, row=0, padx=0, pady=0, sticky=W)
- self._chk_rtcm.grid(column=3, row=0, padx=0, pady=0, sticky=W)
- self._chk_sbf.grid(column=1, row=1, padx=0, pady=0, sticky=W)
- self._chk_qgc.grid(column=2, row=1, padx=0, pady=0, sticky=W)
- self._chk_spartn.grid(column=3, row=1, padx=0, pady=0, sticky=W)
- self._chk_tty.grid(column=1, row=2, padx=0, pady=0, sticky=W)
- self._lbl_consoledisplay.grid(column=0, row=3, padx=2, pady=2, sticky=W)
- self._spn_conformat.grid(
- column=1, row=3, columnspan=2, padx=1, pady=2, sticky=W
- )
- self._chk_tags.grid(column=3, row=3, padx=1, pady=2, sticky=W)
- self._lbl_format.grid(column=0, row=4, padx=2, pady=2, sticky=W)
- self._spn_format.grid(column=1, row=4, padx=2, pady=2, sticky=W)
- self._spn_units.grid(column=2, row=4, columnspan=2, padx=2, pady=2, sticky=W)
- self._chk_scroll.grid(column=0, row=6, padx=2, pady=2, sticky=W)
- self._spn_maxlines.grid(column=1, row=6, padx=2, pady=2, sticky=W)
- self._lbl_filedelay.grid(column=2, row=6, padx=2, pady=2, sticky=E)
- self._spn_filedelay.grid(column=3, row=6, padx=2, pady=2, sticky=W)
- self._chk_datalog.grid(column=0, row=8, padx=2, pady=2, sticky=W)
- self._spn_datalog.grid(column=1, row=8, columnspan=3, padx=2, pady=2, sticky=W)
- self._chk_recordtrack.grid(
- column=0, row=9, columnspan=2, padx=2, pady=2, sticky=W
- )
- self._chk_recorddatabase.grid(
- column=2, row=9, columnspan=2, padx=2, pady=2, sticky=W
- )
- self._frm_options_btns.grid(column=0, row=10, columnspan=4, sticky=EW)
- self._btn_ubxconfig.grid(column=0, row=0, padx=2, pady=1)
- self._lbl_ubxconfig.grid(column=0, row=1)
- self._btn_nmeaconfig.grid(column=1, row=0, padx=2, pady=1)
- self._lbl_nmeaconfig.grid(column=1, row=1)
- self._btn_ttyconfig.grid(column=2, row=0, padx=2, pady=1)
- self._lbl_ttyconfig.grid(column=2, row=1)
- self._btn_ntripconfig.grid(column=3, row=0, padx=2, pady=1)
- self._lbl_ntripconfig.grid(column=3, row=1)
- self._btn_serverconfig.grid(column=4, row=0, padx=2, pady=1)
- self._lbl_serverconfig.grid(column=4, row=1)
+ self.frm_settings.grid(column=0, row=0)
# resize container canvas to accommodate frame
self._frm_container.update()
@@ -515,387 +85,6 @@ def _do_layout(self):
)
self._can_container.update()
- def _attach_events(self, add: bool = True):
- """
- Bind events to widgets.
-
- (trace_update() is a class extension method defined in globals.py)
-
- :param bool add: add or remove trace
- """
-
- # pylint: disable=no-member
-
- tracemode = TRACEMODE_WRITE
- self._prot_ubx.trace_update(tracemode, self._on_update_ubxprot, add)
- self._prot_sbf.trace_update(tracemode, self._on_update_sbfprot, add)
- self._prot_qgc.trace_update(tracemode, self._on_update_qgcprot, add)
- self._prot_nmea.trace_update(tracemode, self._on_update_nmeaprot, add)
- self._prot_rtcm.trace_update(tracemode, self._on_update_rtcmprot, add)
- self._prot_spartn.trace_update(tracemode, self._on_update_spartnprot, add)
- self._prot_tty.trace_update(tracemode, self._on_update_ttyprot, add)
- self._autoscroll.trace_update(tracemode, self._on_update_autoscroll, add)
- self._maxlines.trace_update(tracemode, self._on_update_maxlines, add)
- self._filedelay.trace_update(tracemode, self._on_update_filedelay, add)
- self._units.trace_update(tracemode, self._on_update_units, add)
- self._degrees_format.trace_update(tracemode, self._on_update_degreesformat, add)
- self._console_format.trace_update(tracemode, self._on_update_consoleformat, add)
- self._colortag.trace_update(tracemode, self._on_update_colortag, add)
- self._logformat.trace_update(tracemode, self._on_update_logformat, add)
- self._datalog.trace_update(tracemode, self._on_data_log, add)
- self._record_track.trace_update(tracemode, self._on_record_track, add)
- self._record_database.trace_update(tracemode, self._on_record_database, add)
-
- def reset(self):
- """
- Reset settings to saved configuration.
- """
-
- self._attach_events(False)
- cfg = self.__app.configuration
- self._prot_nmea.set(cfg.get("nmeaprot_b"))
- self._prot_ubx.set(cfg.get("ubxprot_b"))
- self._prot_sbf.set(cfg.get("sbfprot_b"))
- self._prot_qgc.set(cfg.get("qgcprot_b"))
- self._prot_rtcm.set(cfg.get("rtcmprot_b"))
- self._prot_spartn.set(cfg.get("spartnprot_b"))
- self._prot_tty.set(cfg.get("ttyprot_b"))
- self._degrees_format.set(cfg.get("degreesformat_s"))
- self._colortag.set(cfg.get("colortag_b"))
- self._units.set(cfg.get("units_s"))
- self._autoscroll.set(cfg.get("autoscroll_b"))
- self._maxlines.set(cfg.get("maxlines_n"))
- self._filedelay.set(cfg.get("filedelay_n"))
- self._console_format.set(cfg.get("consoleformat_s"))
- self._logformat.set(cfg.get("logformat_s"))
- self._datalog.set(cfg.get("datalog_b"))
- self.logpath = cfg.get("logpath_s")
- self._record_track.set(cfg.get("recordtrack_b"))
- self.trackpath = cfg.get("trackpath_s")
- self.databasepath = cfg.get("databasepath_s")
- if self.__app.db_enabled == SQLOK:
- self._record_database.set(cfg.get("database_b"))
- else:
- self._record_database.set(0)
- self._chk_recorddatabase.config(state=DISABLED)
-
- self.clients = 0
- self._attach_events(True)
-
- def _on_update_ubxprot(self, var, index, mode):
- """
- Action on updating ubxprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("ubxprot_b", self._prot_ubx.get())
-
- def _on_update_sbfprot(self, var, index, mode):
- """
- Action on updating sbfprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("sbfprot_b", self._prot_sbf.get())
-
- def _on_update_qgcprot(self, var, index, mode):
- """
- Action on updating qgcprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("qgcprot_b", self._prot_qgc.get())
-
- def _on_update_nmeaprot(self, var, index, mode):
- """
- Action on updating nmeaprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("nmeaprot_b", self._prot_nmea.get())
-
- def _on_update_rtcmprot(self, var, index, mode):
- """
- Action on updating rtcmprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("rtcmprot_b", self._prot_rtcm.get())
-
- def _on_update_spartnprot(self, var, index, mode):
- """
- Action on updating spartnprot.
- """
-
- if not self._prot_tty.get():
- self.__app.configuration.set("spartnprot_b", self._prot_spartn.get())
-
- def _on_update_ttyprot(self, var, index, mode):
- """
- TTY mode has been updated.
- """
-
- try:
- cfg = self.__app.configuration
- tty = self._prot_tty.get()
- self.update()
- if tty:
-
- for wdg in (
- self._prot_nmea,
- self._prot_ubx,
- self._prot_sbf,
- self._prot_qgc,
- self._prot_rtcm,
- self._prot_spartn,
- ):
- wdg.set(0)
- else:
- self._prot_nmea.set(cfg.get("nmeaprot_b"))
- self._prot_ubx.set(cfg.get("ubxprot_b"))
- self._prot_sbf.set(cfg.get("sbfprot_b"))
- self._prot_qgc.set(cfg.get("qgcprot_b"))
- self._prot_rtcm.set(cfg.get("rtcmprot_b"))
- self._prot_spartn.set(cfg.get("spartnprot_b"))
- cfg.set("ttyprot_b", tty)
- except (ValueError, TclError):
- pass
-
- def _on_update_consoleformat(self, var, index, mode):
- """
- Action on updating console format.
- """
-
- self.__app.configuration.set("consoleformat_s", self._console_format.get())
-
- def _on_update_maxlines(self, var, index, mode):
- """
- Action on updating console maxlines.
- """
-
- self.__app.configuration.set("maxlines_n", self._maxlines.get())
-
- def _on_update_filedelay(self, var, index, mode):
- """
- Action on updating filedelay.
- """
-
- self.__app.configuration.set("filedelay_n", self._filedelay.get())
-
- def _on_update_degreesformat(self, var, index, mode):
- """
- Action on updating degrees format.
- """
-
- self.__app.configuration.set("degreesformat_s", self._degrees_format.get())
-
- def _on_update_units(self, var, index, mode):
- """
- Action on updating units.
- """
-
- self.__app.configuration.set("units_s", self._units.get())
-
- def _on_update_colortag(self, var, index, mode):
- """
- Action on updating color tagging.
- """
-
- self.__app.configuration.set("colortag_b", self._colortag.get())
-
- def _on_update_autoscroll(self, var, index, mode):
- """
- Action on updating autoscroll.
- """
-
- self.__app.configuration.set("autoscroll_b", self._autoscroll.get())
-
- def _on_update_logformat(self, var, index, mode):
- """
- Action on updating log format.
- """
-
- self.__app.configuration.set("logformat_s", self._logformat.get())
-
- def _on_data_log(self, var, index, mode):
- """
- Start or stop data logger.
- """
-
- if self._datalog.get() == 1:
- if self.logpath in ("", None):
- self.logpath = self.__app.file_handler.set_logfile_path()
- if self.logpath is not None:
- self.__app.configuration.set("datalog_b", 1)
- self.__app.configuration.set("logpath_s", self.logpath)
- self.__app.status_label = (
- f"Data logging enabled: {self.logpath}",
- INFOCOL,
- )
- if not self.__app.file_handler.open_logfile():
- self.logpath = ""
- self._datalog.set(0)
- else:
- self.logpath = ""
- self._datalog.set(0)
- self._spn_datalog.config(state=DISABLED)
- else:
- self.__app.configuration.set("datalog_b", 0)
- self._datalog.set(0)
- self.__app.file_handler.close_logfile()
- self.__app.status_label = ("Data logging disabled", INFOCOL)
- self._spn_datalog.config(state=READONLY)
-
- def _on_record_track(self, var, index, mode):
- """
- Start or stop track recorder.
- """
-
- if self._record_track.get() == 1:
- if self.trackpath in ("", None):
- self.trackpath = self.__app.file_handler.set_trackfile_path()
- if self.trackpath is not None:
- self.__app.configuration.set("recordtrack_b", 1)
- self.__app.configuration.set("trackpath_s", self.trackpath)
- self.__app.status_label = f"Track recording enabled: {self.trackpath}"
- if not self.__app.file_handler.open_trackfile():
- self.trackpath = ""
- self._record_track.set(0)
- else:
- self.trackpath = ""
- self._record_track.set(0)
- else:
- self._record_track.set(0)
- self.__app.configuration.set("recordtrack_b", 0)
- self.__app.file_handler.close_trackfile()
- self.__app.status_label = "Track recording disabled"
-
- def _on_record_database(self, var, index, mode):
- """
- Start or stop database recorder.
- """
-
- if self._record_database.get() == 1:
- if self.databasepath in ("", None):
- self.databasepath = self.__app.file_handler.set_database_path()
- if self.databasepath is not None:
- rc = self.__app.sqlite_handler.open(dbpath=self.databasepath)
- self.__app.configuration.set("database_b", rc == SQLOK)
- self.__app.configuration.set("databasepath_s", self.databasepath)
- else:
- self.databasepath = ""
- self._record_database.set(0)
- else:
- self.__app.configuration.set("database_b", 0)
- self.__app.status_label = "Database recording disabled"
-
- def _on_connect(self, conntype: int):
- """
- Start or stop connection (serial, socket or file).
-
- :param int conntype: connection type
- """
-
- connstr = ""
- conndict = {
- "protocol": self.__app.protocol_mask,
- "read_event": GNSS_EVENT,
- "eof_event": GNSS_EOF_EVENT,
- "timeout_event": GNSS_TIMEOUT_EVENT,
- "error_event": GNSS_ERR_EVENT,
- "inqueue": self.__app.gnss_inqueue,
- "outqueue": self.__app.gnss_outqueue,
- "socket_inqueue": self.__app.socket_inqueue,
- "conntype": conntype,
- "msgmode": self.frm_serial.msgmode,
- "inactivity_timeout": self.frm_serial.inactivity_timeout,
- "tlscrtpath": self.__app.configuration.get("tlscrtpath_s"),
- }
-
- # self.frm_socketserver.status_label = conntype
- if conntype == CONNECTED:
- frm = self.frm_serial
- if frm.status == NOPORTS:
- return
- connstr = f"{frm.port}:{frm.port_desc} @ {frm.bpsrate}"
- conndict = dict(conndict, **{"serial_settings": frm})
- # poll for device software version on connection
- self.__app.poll_version(conndict["protocol"])
- elif conntype == CONNECTED_SOCKET:
- frm = self.frm_socketclient
- if not frm.valid_settings():
- self.__app.status_label = ("ERROR - invalid settings", ERRCOL)
- return
- connstr = f"{frm.server.get()}:{frm.port.get()}"
- conndict = dict(conndict, **{"socket_settings": frm})
- # poll for device software version on connection
- self.__app.poll_version(conndict["protocol"])
- elif conntype == CONNECTED_FILE:
- self.infilepath = self.__app.file_handler.open_file(
- self,
- "datalog",
- (
- ("datalog files", "*.log"),
- ("u-center logs", "*.ubx"),
- ("all files", "*.*"),
- ),
- )
- if self.infilepath is None:
- return
- connstr = f"{self.infilepath}"
- conndict = dict(conndict, **{"in_filepath": self.infilepath})
- elif conntype == DISCONNECTED:
- if self.__app.conn_status != DISCONNECTED:
- self.__app.conn_status = DISCONNECTED
- self.__app.stream_handler.stop()
- return
- else:
- return
-
- self.__app.conn_status = conntype
- self.__app.conn_label = (connstr, OKCOL)
- self.__app.status_label = ("", INFOCOL)
- self.__app.reset_frames()
- self.__app.reset_gnssstatus()
- self.__app.stream_handler.start(self.__app, conndict)
-
- def enable_controls(self, status: int):
- """
- Public method to enable or disable controls depending on
- connection status.
-
- :param int status: connection status as integer
- (0=Disconnected, 1=Connected to serial,
- 2=Connected to file, 3=No serial ports available)
-
- """
-
- self.frm_serial.status_label = status
- self.frm_socketclient.status_label = status
-
- self._btn_connect.config(
- state=(
- DISABLED
- if status in (CONNECTED, CONNECTED_SOCKET, CONNECTED_FILE, NOPORTS)
- else NORMAL
- )
- )
- for ctl in (
- self._btn_connect_socket,
- self._btn_connect_file,
- self._chk_tty,
- ):
- ctl.config(
- state=(
- DISABLED
- if status in (CONNECTED, CONNECTED_SOCKET, CONNECTED_FILE)
- else NORMAL
- )
- )
- self._btn_disconnect.config(
- state=(DISABLED if status in (DISCONNECTED,) else NORMAL)
- )
-
def get_size(self) -> tuple:
"""
Get current frame size.
diff --git a/src/pygpsclient/signalsview_frame.py b/src/pygpsclient/signalsview_frame.py
index 2c323ca2..02800bae 100644
--- a/src/pygpsclient/signalsview_frame.py
+++ b/src/pygpsclient/signalsview_frame.py
@@ -69,11 +69,12 @@ class SignalsviewFrame(Frame):
Signalsview frame class.
"""
- def __init__(self, app, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
@@ -81,7 +82,7 @@ def __init__(self, app, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- Frame.__init__(self, self.__master, *args, **kwargs)
+ super().__init__(parent, *args, **kwargs)
def_w, def_h = WIDGETU3
self.width = kwargs.get("width", def_w)
diff --git a/src/pygpsclient/skyview_frame.py b/src/pygpsclient/skyview_frame.py
index e6f93deb..27f5a18d 100644
--- a/src/pygpsclient/skyview_frame.py
+++ b/src/pygpsclient/skyview_frame.py
@@ -40,11 +40,12 @@ class SkyviewFrame(Frame):
Skyview frame class.
"""
- def __init__(self, app, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
@@ -52,7 +53,7 @@ def __init__(self, app, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- Frame.__init__(self, self.__master, *args, **kwargs)
+ super().__init__(parent, *args, **kwargs)
def_w, def_h = WIDGETU2
self.width = kwargs.get("width", def_w)
diff --git a/src/pygpsclient/socketconfig_frame.py b/src/pygpsclient/socketconfig_frame.py
index 7af72f35..341c7499 100644
--- a/src/pygpsclient/socketconfig_frame.py
+++ b/src/pygpsclient/socketconfig_frame.py
@@ -61,21 +61,22 @@ class SocketConfigFrame(Frame):
Socket configuration frame class.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param tkinter.Frame container: reference to container frame
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs for value ranges, or to pass to Frame parent class
"""
+ self.__app = app
+ self._container = parent
self._server_callback = kwargs.pop("server_callback", None)
self._protocol_range = kwargs.pop("protocols", PROTOCOLS)
- Frame.__init__(self, container, *args, **kwargs)
+ super().__init__(parent, *args, **kwargs)
- self.__app = app
- self._container = container
self._show_advanced = False
self.status = DISCONNECTED
self.server = StringVar()
diff --git a/src/pygpsclient/socketconfig_ntrip_frame.py b/src/pygpsclient/socketconfig_ntrip_frame.py
index d71d31e1..fe69e444 100644
--- a/src/pygpsclient/socketconfig_ntrip_frame.py
+++ b/src/pygpsclient/socketconfig_ntrip_frame.py
@@ -13,7 +13,7 @@
# pylint: disable=unused-argument
-from tkinter import TclError
+from tkinter import Frame, TclError
from pygpsclient.globals import DEFAULT_TLS_PORTS, VALINT, VALURL
from pygpsclient.helpers import MAXPORT
@@ -25,17 +25,18 @@ class SocketConfigNtripFrame(SocketConfigFrame):
Socket configuration frame class.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param tkinter.Frame container: reference to container frame
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs for value ranges, or to pass to Frame parent class
"""
self.__app = app
- super().__init__(app, container, *args, **kwargs)
+ super().__init__(app, parent, *args, **kwargs)
def _on_update_server(self, var, index, mode):
"""
diff --git a/src/pygpsclient/spartn_gnss_frame.py b/src/pygpsclient/spartn_gnss_frame.py
index af5c8388..a996df04 100644
--- a/src/pygpsclient/spartn_gnss_frame.py
+++ b/src/pygpsclient/spartn_gnss_frame.py
@@ -102,9 +102,7 @@ class SPARTNGNSSDialog(Frame):
SPARTNConfigDialog class.
"""
- def __init__(
- self, app, container, *args, **kwargs
- ): # pylint: disable=unused-argument
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
@@ -116,9 +114,9 @@ def __init__(
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container # container frame
+ self.__container = parent # container frame
- Frame.__init__(self, self.__container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_blank = ImageTk.PhotoImage(Image.open(ICON_BLANK))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/spartn_lband_frame.py b/src/pygpsclient/spartn_lband_frame.py
index 134779cf..27f1d337 100644
--- a/src/pygpsclient/spartn_lband_frame.py
+++ b/src/pygpsclient/spartn_lband_frame.py
@@ -130,23 +130,21 @@ class SpartnLbandDialog(Frame):
SPARTNConfigDialog class.
"""
- def __init__(
- self, app, container, *args, **kwargs
- ): # pylint: disable=unused-argument
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to parent class (not currently used)
:param kwargs: optional kwargs to pass to parent class (not currently used)
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container # container frame
+ self.__container = parent # container frame
- Frame.__init__(self, self.__container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_blank = ImageTk.PhotoImage(Image.open(ICON_BLANK))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/spartn_mqtt_frame.py b/src/pygpsclient/spartn_mqtt_frame.py
index 2ebcc79c..4963507e 100644
--- a/src/pygpsclient/spartn_mqtt_frame.py
+++ b/src/pygpsclient/spartn_mqtt_frame.py
@@ -86,23 +86,21 @@ class SPARTNMQTTDialog(Frame):
SPARTNConfigDialog class.
"""
- def __init__(
- self, app, container, *args, **kwargs
- ): # pylint: disable=unused-argument
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to parent class (not currently used)
:param kwargs: optional kwargs to pass to parent class (not currently used)
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container # container frame
+ self.__container = parent # container frame
- Frame.__init__(self, self.__container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_blank = ImageTk.PhotoImage(Image.open(ICON_BLANK))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/spectrum_frame.py b/src/pygpsclient/spectrum_frame.py
index 641774d6..01c11281 100644
--- a/src/pygpsclient/spectrum_frame.py
+++ b/src/pygpsclient/spectrum_frame.py
@@ -95,11 +95,12 @@ class SpectrumviewFrame(Frame):
Spectrumview frame class.
"""
- def __init__(self, app, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
@@ -108,7 +109,7 @@ def __init__(self, app, *args, **kwargs):
self.__master = self.__app.appmaster # Reference to root class (Tk)
self.logger = logging.getLogger(__name__)
- Frame.__init__(self, self.__master, *args, **kwargs)
+ super().__init__(parent, *args, **kwargs)
def_w, def_h = WIDGETU2
self.width = kwargs.get("width", def_w)
diff --git a/src/pygpsclient/status_frame.py b/src/pygpsclient/status_frame.py
index 9830d10e..9015efc3 100644
--- a/src/pygpsclient/status_frame.py
+++ b/src/pygpsclient/status_frame.py
@@ -31,7 +31,8 @@ def __init__(self, app, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- Frame.__init__(self, self.__master, *args, **kwargs)
+
+ super().__init__(self.__master, *args, **kwargs)
self.width, self.height = self.get_size()
self._body()
diff --git a/src/pygpsclient/strings.py b/src/pygpsclient/strings.py
index 6d77fc56..43a9fb42 100644
--- a/src/pygpsclient/strings.py
+++ b/src/pygpsclient/strings.py
@@ -191,6 +191,7 @@
DLGTGPX = "GPX Track Viewer"
DLGTNTRIP = "NTRIP Configuration"
DLGTSERVER = "Server Configuration"
+DLGTSETTINGS = "Settings"
DLGTRECORD = "Configuration Command Recorder"
DLGTSPARTN = "SPARTN Configuration"
DLGTNMEA = "NMEA Configuration"
diff --git a/src/pygpsclient/sysmon_frame.py b/src/pygpsclient/sysmon_frame.py
index 6a725724..c351de75 100644
--- a/src/pygpsclient/sysmon_frame.py
+++ b/src/pygpsclient/sysmon_frame.py
@@ -53,11 +53,12 @@ class SysmonFrame(Frame):
SysmonFrame class.
"""
- def __init__(self, app, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
+ :param Frame parent: reference to parent frame
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
@@ -65,7 +66,7 @@ def __init__(self, app, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- Frame.__init__(self, self.__master, *args, **kwargs)
+ super().__init__(parent, *args, **kwargs)
def_w, def_h = WIDGETU2
self.width = kwargs.get("width", def_w)
diff --git a/src/pygpsclient/toplevel_dialog.py b/src/pygpsclient/toplevel_dialog.py
index 2af5c215..a370769b 100644
--- a/src/pygpsclient/toplevel_dialog.py
+++ b/src/pygpsclient/toplevel_dialog.py
@@ -60,7 +60,7 @@ class ToplevelDialog(Toplevel):
ToplevelDialog class.
"""
- def __init__(self, app, dlgname: str):
+ def __init__(self, app, dlgname: str, *args, **kwargs):
"""
Constructor.
@@ -73,13 +73,16 @@ def __init__(self, app, dlgname: str):
self._dlgname = dlgname
self.logger = logging.getLogger(f"{APPNAME}.{dlgname}")
self.width, self.height = 300, 300 # initial, updated in finalise()
- self._resizable = self.__app.dialog_state.state[self._dlgname].get(
- RESIZE, False
+ self._resizable = kwargs.pop(
+ "resizable", self.__app.dialog_state.state[self._dlgname].get(RESIZE, False)
+ )
+ transient = kwargs.pop(
+ "transient", self.__app.configuration.get("transient_dialog_b")
)
- super().__init__()
+ super().__init__(self.__master, *args, **kwargs)
- if self.__app.configuration.get("transient_dialog_b"):
+ if transient:
self.transient(self.__app)
self.title(dlgname) # pylint: disable=E1102
self.resizable(self._resizable, self._resizable)
@@ -99,13 +102,15 @@ def __init__(self, app, dlgname: str):
self._con_body(self._resizable)
- def _con_body(self, resizeable: bool):
+ def _con_body(self, resizable: bool):
"""
Set up scrollable frame and widgets.
+
+ :param bool resizable: resizable
"""
# create container frame for non-resizeable dialogs
- if resizeable:
+ if resizable:
self._frm_container = Frame(self)
self._frm_container.grid(column=0, row=0, sticky=NSEW)
else:
@@ -118,13 +123,13 @@ def _con_body(self, resizeable: bool):
self._btn_exit = Button(
self._frm_status,
image=self.img_exit,
- width=50,
+ width=45,
fg=ERRCOL,
command=self.on_exit,
)
self._frm_status.grid(column=0, row=2, sticky=EW)
self._lbl_status.grid(column=0, row=0, sticky=EW)
- self._btn_exit.grid(column=1, row=0, sticky=E)
+ self._btn_exit.grid(column=1, row=0, padx=4, sticky=E)
# set column and row weights
# NB!!! these govern the 'pack' behaviour of the frames on resize
@@ -139,24 +144,30 @@ def _con_body(self, resizeable: bool):
def _finalise(self):
"""
- Finalise Toplevel window after child frames have been created.
+ Finalise Toplevel dialog after child frames have been created.
+
+ If screen is smaller than dialog (`lowres=True`), display within
+ smaller, resizeable and scrollable canvas container.
+
+ Otherwise, make the container the same size as the dialog and hide
+ the scrollbars.
+
+ NB Some Linux platforms appear to require Toplevel dialog windows
+ to be non-transient for the window 'maximise' icon to work properly
"""
- # resize container canvas to accommodate frame
- # if frame is larger than screen, make container resizeable and
- # show scrollbars, otherwise hide scrollbars and make non-resizable
if hasattr(self, "_can_container"):
self._frm_container.update_idletasks()
fh = self._frm_container.winfo_height()
fw = self._frm_container.winfo_width()
- self._can_container.config(height=fh, width=fw)
- self._can_container.update_idletasks()
- lowres, _ = check_lowres(self.__master, (fh, fw))
+ lowres, (sh, sw) = check_lowres(self.__master, (fh, fw))
if lowres:
+ self._can_container.config(
+ height=min(int(sh * 0.75), fh), width=min(int(sw * 0.75), fw)
+ )
self.resizable(True, True)
- # NB Some Linux platforms appear to require Toplevel dialog windows
- # to be non-transient for the window 'maximise' icon to work properly
else:
+ self._can_container.config(height=fh, width=fw)
self._can_container.show_scroll(False)
self.resizable(self._resizable, self._resizable)
diff --git a/src/pygpsclient/ubx_cfgval_frame.py b/src/pygpsclient/ubx_cfgval_frame.py
index 1027c815..3e5c1ee4 100644
--- a/src/pygpsclient/ubx_cfgval_frame.py
+++ b/src/pygpsclient/ubx_cfgval_frame.py
@@ -74,21 +74,21 @@ class UBX_CFGVAL_Frame(Frame):
UBX CFG-VAL configuration command panel.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame (config-dialog)
+ :param Frame parent: reference to parent frame (config-dialog)
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container
+ self.__container = parent
- super().__init__(container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/ubx_handler.py b/src/pygpsclient/ubx_handler.py
index a06bbd05..1e29747d 100644
--- a/src/pygpsclient/ubx_handler.py
+++ b/src/pygpsclient/ubx_handler.py
@@ -376,7 +376,7 @@ def _process_NAV_SIG(self, data: UBXMessage):
cno = getattr(data, "cno" + idx)
corrsource = getattr(data, "corrSource" + idx)
quality = getattr(data, "qualityInd" + idx)
- sigflags = 0 # TODO collate sigFlags bits
+ sigflags = 0
self.__app.gnss_status.sig_data[(gnssId, svid, sigid)] = (
gnssId,
svid,
diff --git a/src/pygpsclient/ubx_msgrate_frame.py b/src/pygpsclient/ubx_msgrate_frame.py
index e9aec77b..d66d0cda 100644
--- a/src/pygpsclient/ubx_msgrate_frame.py
+++ b/src/pygpsclient/ubx_msgrate_frame.py
@@ -51,21 +51,21 @@ class UBX_MSGRATE_Frame(Frame):
UBX Message Rate configuration command panel.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame (config-dialog)
+ :param Frame parent: reference to parent frame (config-dialog)
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container
+ self.__container = parent
- super().__init__(container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/ubx_port_frame.py b/src/pygpsclient/ubx_port_frame.py
index 314c0037..be3781b9 100644
--- a/src/pygpsclient/ubx_port_frame.py
+++ b/src/pygpsclient/ubx_port_frame.py
@@ -47,21 +47,21 @@ class UBX_PORT_Frame(Frame):
UBX Port and Protocol configuration command panel.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame (config-dialog)
+ :param Frame parent: reference to parent frame (config-dialog)
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container
+ self.__container = parent
- super().__init__(container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/ubx_preset_frame.py b/src/pygpsclient/ubx_preset_frame.py
index 673cf710..2dadbfda 100644
--- a/src/pygpsclient/ubx_preset_frame.py
+++ b/src/pygpsclient/ubx_preset_frame.py
@@ -57,12 +57,12 @@ class UBX_PRESET_Frame(Frame):
UBX Preset and User-defined configuration command panel.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame (config-dialog)
+ :param Frame parent: reference to parent frame (config-dialog)
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
@@ -70,9 +70,9 @@ def __init__(self, app, container, *args, **kwargs):
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
self.logger = logging.getLogger(__name__)
- self.__container = container
+ self.__container = parent
- super().__init__(container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
diff --git a/src/pygpsclient/ubx_solrate_frame.py b/src/pygpsclient/ubx_solrate_frame.py
index 070fb4a8..cf6663cc 100644
--- a/src/pygpsclient/ubx_solrate_frame.py
+++ b/src/pygpsclient/ubx_solrate_frame.py
@@ -42,21 +42,21 @@ class UBX_RATE_Frame(Frame):
UBX Navigation Solution Rate configuration command panel.
"""
- def __init__(self, app, container, *args, **kwargs):
+ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):
"""
Constructor.
:param Frame app: reference to main tkinter application
- :param Frame container: reference to container frame (config-dialog)
+ :param Frame parent: reference to parent frame (config-dialog)
:param args: optional args to pass to Frame parent class
:param kwargs: optional kwargs to pass to Frame parent class
"""
self.__app = app # Reference to main application class
self.__master = self.__app.appmaster # Reference to root class (Tk)
- self.__container = container
+ self.__container = parent
- super().__init__(container.container, *args, **kwargs)
+ super().__init__(parent.container, *args, **kwargs)
self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
From 57c8922dd297bbbeec4ee561c2f084f33540e136 Mon Sep 17 00:00:00 2001
From: SEMU Admin <28569967+semuadmin@users.noreply.github.com>
Date: Sat, 17 Jan 2026 19:56:28 +0000
Subject: [PATCH 4/4] Fix Load Configuration error Fixes #232
---
RELEASE_NOTES.md | 6 ++++++
src/pygpsclient/app.py | 13 +++----------
src/pygpsclient/configuration.py | 2 ++
src/pygpsclient/file_handler.py | 1 +
src/pygpsclient/settings_child_frame.py | 1 -
src/pygpsclient/status_frame.py | 21 +++++++++++++++------
6 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index fa2d2f07..d596d9e9 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -2,6 +2,12 @@
### RELEASE 1.6.0
+FIXES:
+
+1. Fix Load Configuration error \#232 `AttributeError: 'tuple' object has no attribute 'upper'`.
+
+ENHANCEMENTS:
+
1. Add user-selectable Signals widget, displaying individual GNSS PRN / Signal ID levels and (where applicable) correction sources (receiver must support UBX NAV-SIG messages). Provides greater granularity than the existing Levels widget for UBX devices. Signal IDs are shown in RINEX format e.g. "L1_C/A", "E5_aQ", etc.
1. Add user-defined preset import facility to Configuration Load/Save/Record panel (accessed via Menu..Options..Configuration Command Recorder). This allows user to record a sequence of UBX, NMEA or TTY commands as they are sent to the receiver and to import this sequence as a user-defined preset in the PyGPSClient json configuration file. This obviates the need to edit the configuration file manually. Remember to re-save the configuration file to persist the changes.
1. Add Undock/Dock Settings panel facility, via Menu..View..Undock/Dock Settings. Settings panel can now be undocked from the main application window and displayed as a separate Toplevel dialog. If (*and only if*) non-transient (`transient_dialog_b: 0`), the settings panel can be minimized independently of the main window.
diff --git a/src/pygpsclient/app.py b/src/pygpsclient/app.py
index f0c4c658..6ec3f5e7 100644
--- a/src/pygpsclient/app.py
+++ b/src/pygpsclient/app.py
@@ -122,8 +122,6 @@
INACTIVE_TIMEOUT,
INTROTXTNOPORTS,
KILLSWITCH,
- LOADCONFIGBAD,
- LOADCONFIGOK,
NOTCONN,
NOWDGSWARN,
SAVECONFIGBAD,
@@ -499,7 +497,7 @@ def load_config(self):
if err == "": # load succeeded
self.update_widgets()
for frm in (
- self.frm_settings,
+ self.frm_settings.frm_settings,
self.frm_settings.frm_serial,
self.frm_settings.frm_socketclient,
):
@@ -507,12 +505,8 @@ def load_config(self):
self._do_layout()
if self._nowidgets:
self.status_label = (NOWDGSWARN.format(filename), ERRCOL)
- else:
- self.status_label = (LOADCONFIGOK.format(filename), OKCOL)
- elif err == "cancelled": # user cancelled
- return
- else: # config error
- self.status_label = (LOADCONFIGBAD.format(filename), ERRCOL)
+ elif err == "cancelled":
+ pass
def save_config(self):
"""
@@ -681,7 +675,6 @@ def update_clients(self, clients: int):
"""
self.server_status = clients
- # self.frm_settings.frm_socketserver.clients = clients TODO
def _shutdown(self):
"""
diff --git a/src/pygpsclient/configuration.py b/src/pygpsclient/configuration.py
index d28d35ac..eab42534 100644
--- a/src/pygpsclient/configuration.py
+++ b/src/pygpsclient/configuration.py
@@ -293,6 +293,8 @@ def loadfile(self, filename: str | NoneType = None) -> tuple:
resave = True
continue
else:
+ if err == "cancelled": # user cancelled
+ return filename, err
if "No such file or directory" in err:
err = LOADCONFIGNONE.format(fname)
else:
diff --git a/src/pygpsclient/file_handler.py b/src/pygpsclient/file_handler.py
index 3df6344b..8a1fe431 100644
--- a/src/pygpsclient/file_handler.py
+++ b/src/pygpsclient/file_handler.py
@@ -124,6 +124,7 @@ def load_config(self, filename: Path = CONFIGFILE) -> tuple:
try:
if filename is None:
filename = self.open_file(
+ None,
"config",
(
("config files", "*.json"),
diff --git a/src/pygpsclient/settings_child_frame.py b/src/pygpsclient/settings_child_frame.py
index 2e94643a..c8adfb9e 100644
--- a/src/pygpsclient/settings_child_frame.py
+++ b/src/pygpsclient/settings_child_frame.py
@@ -58,7 +58,6 @@
HOME,
ICON_CONN,
ICON_DISCONN,
- ICON_EXIT,
ICON_LOGREAD,
ICON_NMEACONFIG,
ICON_NTRIPCONFIG,
diff --git a/src/pygpsclient/status_frame.py b/src/pygpsclient/status_frame.py
index 9015efc3..a71d6cc2 100644
--- a/src/pygpsclient/status_frame.py
+++ b/src/pygpsclient/status_frame.py
@@ -36,24 +36,33 @@ def __init__(self, app, *args, **kwargs):
self.width, self.height = self.get_size()
self._body()
-
- self.bind("", self._on_resize)
+ self._do_layout()
+ self._attach_events()
def _body(self):
"""
Set up frame and widgets.
"""
- self.grid_rowconfigure(0, weight=1)
-
- self.option_add("*Font", self.__app.font_md)
-
self.lbl_connection = Label(self, anchor=W)
self.lbl_status = Label(self, anchor=W)
+
+ def _do_layout(self):
+ """
+ Position widgets in frame.
+ """
+
self.lbl_connection.grid(column=0, row=0, sticky=EW)
ttk.Separator(self, orient=VERTICAL).grid(column=1, row=0, sticky=NS)
self.lbl_status.grid(column=2, row=0, sticky=EW)
+ def _attach_events(self):
+ """
+ Bind events to frame.
+ """
+
+ self.bind("", self._on_resize)
+
def _on_resize(self, event): # pylint: disable=unused-argument
"""
Resize frame