From 2487424839756b1fe419bb90b483ff278edcebc1 Mon Sep 17 00:00:00 2001 From: Christian Jacobs Date: Sun, 14 Apr 2019 17:52:06 +0100 Subject: [PATCH] Added a new menu item for updating the ADIF modes from the ADIF website. Addresses issue #69. --- bin/pyqso | 1 + pyqso/adif.py | 99 +----------------- pyqso/logbook.py | 13 ++- pyqso/menu.py | 4 + pyqso/modes.py | 198 ++++++++++++++++++++++++++++++++--- pyqso/preferences_dialog.py | 20 ++-- pyqso/record_dialog.py | 21 ++-- pyqso/res/pyqso.glade | 162 +++++++++++++++++++++++++++- pyqso/update_modes_dialog.py | 51 +++++++++ 9 files changed, 435 insertions(+), 134 deletions(-) create mode 100644 pyqso/update_modes_dialog.py diff --git a/bin/pyqso b/bin/pyqso index 71b68d1..49c6b8c 100755 --- a/bin/pyqso +++ b/bin/pyqso @@ -85,6 +85,7 @@ class PyQSO: # Kills the application if the close button is clicked on the main window itself. self.window.connect("delete-event", Gtk.main_quit) + # Status bar. self.statusbar = self.builder.get_object("statusbar") context_id = self.statusbar.get_context_id("Status") self.statusbar.push(context_id, "No logbook is currently open.") diff --git a/pyqso/adif.py b/pyqso/adif.py index 2869f2a..4655a28 100644 --- a/pyqso/adif.py +++ b/pyqso/adif.py @@ -27,6 +27,8 @@ except ImportError: import ConfigParser as configparser from os.path import expanduser +from pyqso.modes import Modes + # ADIF field names and their associated data types available in PyQSO. AVAILABLE_FIELD_NAMES_TYPES = {"CALL": "S", "QSO_DATE": "D", @@ -98,98 +100,6 @@ AVAILABLE_FIELD_NAMES_FRIENDLY = {"CALL": "Callsign", # E: Enumerated DATA_TYPES = ["A", "B", "N", "S", "I", "D", "T", "M", "G", "L", "E"] -# All the valid modes listed in the ADIF specification. This is a dictionary with the key-value pairs holding the MODE and SUBMODE(s) respectively. -MODES = {"": ("",), - "AM": ("",), - "ATV": ("",), - "CHIP": ("", "CHIP64", "CHIP128"), - "CLO": ("",), - "CONTESTI": ("",), - "CW": ("", "PCW"), - "DIGITALVOICE": ("",), - "DOMINO": ("", "DOMINOEX", "DOMINOF"), - "DSTAR": ("",), - "FAX": ("",), - "FM": ("",), - "FSK441": ("",), - "FT8": ("",), - "HELL": ("", "FMHELL", "FSKHELL", "HELL80", "HFSK", "PSKHELL"), - "ISCAT": ("", "ISCAT-A", "ISCAT-B"), - "JT4": ("", "JT4A", "JT4B", "JT4C", "JT4D", "JT4E", "JT4F", "JT4G"), - "JT6M": ("",), - "JT9": ("",), - "JT44": ("",), - "JT65": ("", "JT65A", "JT65B", "JT65B2", "JT65C", "JT65C2"), - "MFSK": ("", "MFSK4", "MFSK8", "MFSK11", "MFSK16", "MFSK22", "MFSK31", "MFSK32", "MFSK64", "MFSK128"), - "MT63": ("",), - "OLIVIA": ("", "OLIVIA 4/125", "OLIVIA 4/250", "OLIVIA 8/250", "OLIVIA 8/500", "OLIVIA 16/500", "OLIVIA 16/1000", "OLIVIA 32/1000"), - "OPERA": ("", "OPERA-BEACON", "OPERA-QSO"), - "PAC": ("", "PAC2", "PAC3", "PAC4"), - "PAX": ("", "PAX2"), - "PKT": ("",), - "PSK": ("", "FSK31", "PSK10", "PSK31", "PSK63", "PSK63F", "PSK125", "PSK250", "PSK500", "PSK1000", "PSKAM10", "PSKAM31", "PSKAM50", "PSKFEC31", "QPSK31", "QPSK63", "QPSK125", "QPSK250", "QPSK500"), - "PSK2K": ("",), - "Q15": ("",), - "ROS": ("", "ROS-EME", "ROS-HF", "ROS-MF"), - "RTTY": ("", "ASCI"), - "RTTYM": ("",), - "SSB": ("", "LSB", "USB"), - "SSTV": ("",), - "THOR": ("",), - "THRB": ("", "THRBX"), - "TOR": ("", "AMTORFEC", "GTOR"), - "V4": ("",), - "VOI": ("",), - "WINMOR": ("",), - "WSPR": ("",) - } - -# A dictionary of all the deprecated MODE values. -MODES_DEPRECATED = {"AMTORFEC": ("",), - "ASCI": ("",), - "CHIP64": ("",), - "CHIP128": ("",), - "DOMINOF": ("",), - "FMHELL": ("",), - "FSK31": ("",), - "GTOR": ("",), - "HELL80": ("",), - "HFSK": ("",), - "JT4A": ("",), - "JT4B": ("",), - "JT4C": ("",), - "JT4D": ("",), - "JT4E": ("",), - "JT4F": ("",), - "JT4G": ("",), - "JT65A": ("",), - "JT65B": ("",), - "JT65C": ("",), - "MFSK8": ("",), - "MFSK16": ("",), - "PAC2": ("",), - "PAC3": ("",), - "PAX2": ("",), - "PCW": ("",), - "PSK10": ("",), - "PSK31": ("",), - "PSK63": ("",), - "PSK63F": ("",), - "PSK125": ("",), - "PSKAM10": ("",), - "PSKAM31": ("",), - "PSKAM50": ("",), - "PSKFEC31": ("",), - "PSKHELL": ("",), - "QPSK31": ("",), - "QPSK63": ("",), - "QPSK125": ("",), - "THRBX": ("",) - } - -# Include all deprecated modes. -MODES.update(MODES_DEPRECATED) - # All the bands listed in the ADIF specification. BANDS = ["", "2190m", "630m", "560m", "160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m", "4m", "2m", "1.25m", "70cm", "33cm", "23cm", "13cm", "9cm", "6cm", "3cm", "1.25cm", "6mm", "4mm", "2.5mm", "2mm", "1mm"] # The lower and upper frequency bounds (in MHz) for each band in BANDS. @@ -207,6 +117,7 @@ class ADIF: def __init__(self): """ Initialise class for I/O of files using the Amateur Data Interchange Format (ADIF). """ + self.modes = Modes() return def read(self, path): @@ -521,9 +432,9 @@ class ADIF: elif(data_type == "E" or data_type == "A"): # Enumeration, AwardList. if(field_name == "MODE"): - return (data in list(MODES.keys())) + return (data in list(self.modes.all.keys())) elif(field_name == "SUBMODE"): - submodes = [submode for mode in list(MODES.keys()) for submode in MODES[mode]] + submodes = [submode for mode in list(self.modes.all.keys()) for submode in self.modes.all[mode]] return (data in submodes) elif(field_name == "BAND"): return (data in BANDS) diff --git a/pyqso/logbook.py b/pyqso/logbook.py index 972e784..a84bc49 100644 --- a/pyqso/logbook.py +++ b/pyqso/logbook.py @@ -38,6 +38,7 @@ from pyqso.summary import Summary from pyqso.blank import Blank from pyqso.printer import Printer from pyqso.compare import compare_date_and_time, compare_default +from pyqso.update_modes_dialog import UpdateModesDialog class Logbook: @@ -907,7 +908,7 @@ class Logbook: return log = self.logs[log_index] - (sort_model, path) = self.treeselection[log_index].get_selected_rows() # Get the selected row in the log + (sort_model, path) = self.treeselection[log_index].get_selected_rows() # Get the selected row in the log. try: sort_iter = sort_model.get_iter(path[0]) filter_iter = self.sorter[log_index].convert_iter_to_child_iter(sort_iter) @@ -1113,6 +1114,16 @@ class Logbook: return + def update_modes_callback(self, widget=None, path=None): + umd = UpdateModesDialog(self.application) + response = umd.dialog.run() + if(response == Gtk.ResponseType.OK): + modes = Modes() + modes.update(url=umd.url) + umd.dialog.destroy() + return + + @property def log_count(self): """ Return the total number of logs in the logbook. diff --git a/pyqso/menu.py b/pyqso/menu.py index fceeb83..1ad858f 100644 --- a/pyqso/menu.py +++ b/pyqso/menu.py @@ -109,6 +109,10 @@ class Menu: self.items["RECORD_COUNT"] = self.builder.get_object("mitem_record_count") self.items["RECORD_COUNT"].connect("activate", self.application.logbook.record_count_callback) + # Record count + self.items["UPDATE_MODES"] = self.builder.get_object("mitem_update_modes") + self.items["UPDATE_MODES"].connect("activate", self.application.logbook.update_modes_callback) + # View toolbox self.items["TOOLBOX"] = self.builder.get_object("mitem_toolbox") config = configparser.ConfigParser() diff --git a/pyqso/modes.py b/pyqso/modes.py index ac0a07f..ca57325 100644 --- a/pyqso/modes.py +++ b/pyqso/modes.py @@ -17,24 +17,192 @@ # You should have received a copy of the GNU General Public License # along with PyQSO. If not, see . +import sqlite3 +import os from urllib.request import urlopen from bs4 import BeautifulSoup +import logging -page = urlopen('http://www.adif.org/307/ADIF_307.htm').read() -soup = BeautifulSoup(page, "html.parser") +MODES_FILE = os.path.expanduser("~/.config/pyqso/modes.db") -# Remove the tags but keep the tags' contents. -for match in soup.findAll('span'): - match.unwrap() -# Find the MODES table. -rows = soup.find(id="Enumeration_Mode").find_all('tr') +class Modes: + + def __init__(self): + + try: + connection = sqlite3.connect(MODES_FILE) + c = connection.cursor() + c.execute("""CREATE TABLE IF NOT EXISTS modes ( + mode TEXT NOT NULL, + submode TEXT NOT NULL, + UNIQUE(mode, submode) + ); """) + + # Fill the new table with the basic list of modes and submodes. + for mode in self.basic: + for submode in self.basic[mode]: + c.execute("""REPLACE INTO modes(mode, submode) VALUES(?, ?)""", (mode, submode)) + connection.commit() + connection.close() + except sqlite3.Error as e: + logging.exception(e) + + #self.update("http://www.adif.org/309/ADIF_309.htm") + return + + @property + def basic(self): + """ A basic list of valid modes listed in the ADIF specification. + This is a dictionary with the key-value pairs holding the MODE and SUBMODE(s) respectively. """ + + modes = {"": ("",), + "AM": ("",), + "ATV": ("",), + "CHIP": ("", "CHIP64", "CHIP128"), + "CLO": ("",), + "CONTESTI": ("",), + "CW": ("", "PCW"), + "DIGITALVOICE": ("",), + "DOMINO": ("", "DOMINOEX", "DOMINOF"), + "DSTAR": ("",), + "FAX": ("",), + "FM": ("",), + "FSK441": ("",), + "FT8": ("",), + "HELL": ("", "FMHELL", "FSKHELL", "HELL80", "HFSK", "PSKHELL"), + "ISCAT": ("", "ISCAT-A", "ISCAT-B"), + "JT4": ("", "JT4A", "JT4B", "JT4C", "JT4D", "JT4E", "JT4F", "JT4G"), + "JT6M": ("",), + "JT9": ("",), + "JT44": ("",), + "JT65": ("", "JT65A", "JT65B", "JT65B2", "JT65C", "JT65C2"), + "MFSK": ("", "MFSK4", "MFSK8", "MFSK11", "MFSK16", "MFSK22", "MFSK31", "MFSK32", "MFSK64", "MFSK128"), + "MT63": ("",), + "OLIVIA": ("", "OLIVIA 4/125", "OLIVIA 4/250", "OLIVIA 8/250", "OLIVIA 8/500", "OLIVIA 16/500", "OLIVIA 16/1000", "OLIVIA 32/1000"), + "OPERA": ("", "OPERA-BEACON", "OPERA-QSO"), + "PAC": ("", "PAC2", "PAC3", "PAC4"), + "PAX": ("", "PAX2"), + "PKT": ("",), + "PSK": ("", "FSK31", "PSK10", "PSK31", "PSK63", "PSK63F", "PSK125", "PSK250", "PSK500", "PSK1000", "PSKAM10", "PSKAM31", "PSKAM50", "PSKFEC31", "QPSK31", "QPSK63", "QPSK125", "QPSK250", "QPSK500"), + "PSK2K": ("",), + "Q15": ("",), + "ROS": ("", "ROS-EME", "ROS-HF", "ROS-MF"), + "RTTY": ("", "ASCI"), + "RTTYM": ("",), + "SSB": ("", "LSB", "USB"), + "SSTV": ("",), + "THOR": ("",), + "THRB": ("", "THRBX"), + "TOR": ("", "AMTORFEC", "GTOR"), + "V4": ("",), + "VOI": ("",), + "WINMOR": ("",), + "WSPR": ("",) + } + + # A dictionary of all the deprecated MODE values. + deprecated = {"AMTORFEC": ("",), + "ASCI": ("",), + "CHIP64": ("",), + "CHIP128": ("",), + "DOMINOF": ("",), + "FMHELL": ("",), + "FSK31": ("",), + "GTOR": ("",), + "HELL80": ("",), + "HFSK": ("",), + "JT4A": ("",), + "JT4B": ("",), + "JT4C": ("",), + "JT4D": ("",), + "JT4E": ("",), + "JT4F": ("",), + "JT4G": ("",), + "JT65A": ("",), + "JT65B": ("",), + "JT65C": ("",), + "MFSK8": ("",), + "MFSK16": ("",), + "PAC2": ("",), + "PAC3": ("",), + "PAX2": ("",), + "PCW": ("",), + "PSK10": ("",), + "PSK31": ("",), + "PSK63": ("",), + "PSK63F": ("",), + "PSK125": ("",), + "PSKAM10": ("",), + "PSKAM31": ("",), + "PSKAM50": ("",), + "PSKFEC31": ("",), + "PSKHELL": ("",), + "QPSK31": ("",), + "QPSK63": ("",), + "QPSK125": ("",), + "THRBX": ("",) + } + + # Include all deprecated modes. + modes.update(deprecated) + return modes + + @property + def all(self): + try: + connection = sqlite3.connect(MODES_FILE) + c = connection.cursor() + result = c.execute("""SELECT * FROM modes""") + rows = result.fetchall() + + modes = {} + for row in rows: + mode = row[0] + submode = row[1] + if(mode in modes.keys()): + modes[mode].append(submode) + else: + modes[mode] = [submode] + connection.close() + except sqlite3.Error as e: + logging.exception(e) + return modes + + def update(self, url): + modes = self.parse(url) + try: + connection = sqlite3.connect(MODES_FILE) + c = connection.cursor() + for mode in modes: + for submode in modes[mode]: + c.execute("REPLACE INTO modes(mode, submode) VALUES(?,?)", (mode, submode)) + connection.commit() + connection.close() + except sqlite3.Error as e: + logging.exception(e) + + return + + def parse(self, url): + page = urlopen(url).read() + soup = BeautifulSoup(page, "html.parser") + + # Remove the tags but keep the tags' contents. + for match in soup.findAll("span"): + match.unwrap() + + # Find the MODES table. + rows = soup.find(id="Enumeration_Mode").find_all("tr") + + # Extract modes and submodes. + modes = {} + for row in rows[1:]: # Ignores the header row. + mode, submode = row.find_all("td")[0:2] + mode = mode.text.split(" (import-only)")[0].strip() + submode = tuple(submode.text.strip().split(", ")) + if(mode not in modes): + modes[mode] = submode + + return modes -# Extract modes and submodes. -modes = {} -for row in rows[1:]: - mode, submode, description = row.find_all('td') - mode = mode.text.split(" (import-only)")[0].strip() - submode = tuple(submode.text.strip().split(", ")) - modes[mode] = submode -print(modes) diff --git a/pyqso/preferences_dialog.py b/pyqso/preferences_dialog.py index 2fc8bf2..29b485d 100644 --- a/pyqso/preferences_dialog.py +++ b/pyqso/preferences_dialog.py @@ -38,7 +38,8 @@ except ImportError: logging.warning("Could not import the geocoder module!") have_geocoder = False -from pyqso.adif import AVAILABLE_FIELD_NAMES_ORDERED, MODES +from pyqso.adif import AVAILABLE_FIELD_NAMES_ORDERED +from pyqso.modes import Modes from pyqso.auxiliary_dialogs import error PREFERENCES_FILE = os.path.expanduser("~/.config/pyqso/preferences.ini") @@ -289,27 +290,28 @@ class RecordsPage: # Default values # Mode + self.modes = Modes() self.sources["DEFAULT_MODE"] = self.builder.get_object("default_values_mode_combo") - for mode in sorted(MODES.keys()): + for mode in sorted(self.modes.all.keys()): self.sources["DEFAULT_MODE"].append_text(mode) (section, option) = ("records", "default_mode") if(have_config and config.has_option(section, option)): mode = config.get(section, option) else: mode = "" - self.sources["DEFAULT_MODE"].set_active(sorted(MODES.keys()).index(mode)) + self.sources["DEFAULT_MODE"].set_active(sorted(self.modes.all.keys()).index(mode)) self.sources["DEFAULT_MODE"].connect("changed", self.on_mode_changed) # Submode self.sources["DEFAULT_SUBMODE"] = self.builder.get_object("default_values_submode_combo") - for submode in MODES[mode]: + for submode in self.modes.all[mode]: self.sources["DEFAULT_SUBMODE"].append_text(submode) (section, option) = ("records", "default_submode") if(have_config and config.has_option(section, option)): submode = config.get(section, option) else: submode = "" - self.sources["DEFAULT_SUBMODE"].set_active(MODES[mode].index(submode)) + self.sources["DEFAULT_SUBMODE"].set_active(self.modes.all[mode].index(submode)) # Power self.sources["DEFAULT_POWER"] = self.builder.get_object("default_values_tx_power_entry") @@ -384,9 +386,9 @@ class RecordsPage: """ If the MODE field has changed its value, then fill the SUBMODE field with all the available SUBMODE options for that new MODE. """ self.sources["DEFAULT_SUBMODE"].get_model().clear() mode = combo.get_active_text() - for submode in MODES[mode]: + for submode in self.modes.all[mode]: self.sources["DEFAULT_SUBMODE"].append_text(submode) - self.sources["DEFAULT_SUBMODE"].set_active(MODES[mode].index("")) + self.sources["DEFAULT_SUBMODE"].set_active(self.modes.all[mode].index("")) return @@ -406,8 +408,8 @@ class ImportExportPage: config = configparser.ConfigParser() have_config = (config.read(PREFERENCES_FILE) != []) - # Import - self.sources["MERGE_COMMENT"] = self.builder.get_object("adif_import_merge_comment_checkbutton") + # ADIF + self.sources["MERGE_COMMENT"] = self.builder.get_object("adif_merge_comment_checkbutton") (section, option) = ("import_export", "merge_comment") if(have_config and config.has_option(section, option)): self.sources["MERGE_COMMENT"].set_active(config.getboolean(section, option)) diff --git a/pyqso/record_dialog.py b/pyqso/record_dialog.py index c6f7fc1..40c3f52 100644 --- a/pyqso/record_dialog.py +++ b/pyqso/record_dialog.py @@ -104,8 +104,9 @@ class RecordDialog: self.sources["BAND"].set_active(0) # Set an empty string as the default option. # MODE + self.modes = Modes().all self.sources["MODE"] = self.builder.get_object("qso_mode_combo") - for mode in sorted(MODES.keys()): + for mode in sorted(self.modes.keys()): self.sources["MODE"].append_text(mode) self.sources["MODE"].set_active(0) # Set an empty string as the default option. self.sources["MODE"].connect("changed", self.on_mode_changed) @@ -199,12 +200,12 @@ class RecordDialog: converted = self.convert_frequency(data, from_unit="MHz", to_unit=self.frequency_unit) self.sources[field_names[i]].set_text(str(converted)) elif(field_names[i] == "MODE"): - self.sources[field_names[i]].set_active(sorted(MODES.keys()).index(data)) + self.sources[field_names[i]].set_active(sorted(self.modes.keys()).index(data)) # Handle SUBMODE at the same time. submode_data = record["submode"] if(submode_data is None): submode_data = "" - self.sources["SUBMODE"].set_active(MODES[data].index(submode_data)) + self.sources["SUBMODE"].set_active(self.modes[data].index(submode_data)) elif(field_names[i] == "SUBMODE"): # Skip, because this has been (or will be) handled when populating the MODE field. continue @@ -227,7 +228,7 @@ class RecordDialog: mode = config.get(section, option) else: mode = "" - self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) + self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode)) # Submode (section, option) = ("records", "default_submode") @@ -235,7 +236,7 @@ class RecordDialog: submode = config.get(section, option) else: submode = "" - self.sources["SUBMODE"].set_active(MODES[mode].index(submode)) + self.sources["SUBMODE"].set_active(self.modes[mode].index(submode)) # Power (section, option) = ("records", "default_power") @@ -303,9 +304,9 @@ class RecordDialog: """ If the MODE field has changed its value, then fill the SUBMODE field with all the available SUBMODE options for that new MODE. """ self.sources["SUBMODE"].get_model().clear() mode = combo.get_active_text() - for submode in MODES[mode]: + for submode in self.modes[mode]: self.sources["SUBMODE"].append_text(submode) - self.sources["SUBMODE"].set_active(MODES[mode].index("")) # Set the submode to an empty string. + self.sources["SUBMODE"].set_active(self.modes[mode].index("")) # Set the submode to an empty string. return def on_key_press(self, widget, event): @@ -375,10 +376,10 @@ class RecordDialog: if(mode == "USB" or mode == "LSB"): submode = mode mode = "SSB" - self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) - self.sources["SUBMODE"].set_active(MODES[mode].index(submode)) + self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode)) + self.sources["SUBMODE"].set_active(self.modes[mode].index(submode)) else: - self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) + self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode)) except: logging.error("Could not obtain the current mode (e.g. FM, AM, CW) via Hamlib!") diff --git a/pyqso/res/pyqso.glade b/pyqso/res/pyqso.glade index 47c8730..dd6ec56 100644 --- a/pyqso/res/pyqso.glade +++ b/pyqso/res/pyqso.glade @@ -1,5 +1,5 @@ - + @@ -68,6 +68,12 @@ gtk-open 1 + + True + False + gtk-refresh + 1 + True False @@ -128,6 +134,9 @@ 800 600 log_64x64.png + + + True @@ -364,6 +373,28 @@ + + + True + False + ADIF + + + True + False + + + Update Modes + True + False + image25 + False + + + + + + True @@ -927,6 +958,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.Christian Thomas Jacobs, M0UOS log_64x64.png custom + + + False @@ -956,6 +990,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.True dialog pyqso + + + False @@ -1098,6 +1135,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.True dialog pyqso + + + False @@ -1195,6 +1235,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.True dialog pyqso + + + False @@ -2505,7 +2548,7 @@ Base64-encoded plain text in the configuration file. vertical 2 - + True False 0 @@ -2522,7 +2565,7 @@ Base64-encoded plain text in the configuration file. vertical 2 - + Merge any text in the COMMENT field with the NOTES field True True @@ -2541,10 +2584,10 @@ Base64-encoded plain text in the configuration file. - + True False - ADIF Import + ADIF @@ -3029,6 +3072,9 @@ Base64-encoded plain text in the configuration file. True dialog pyqso + + + False @@ -4343,6 +4389,9 @@ Base64-encoded plain text in the configuration file. True dialog record_dialog + + + False @@ -4415,6 +4464,9 @@ Base64-encoded plain text in the configuration file. True dialog pyqso + + + False @@ -4658,6 +4710,106 @@ Base64-encoded plain text in the configuration file. telnet_connection_ok_button + + False + Update ADIF Modes + True + dialog + pyqso + + + + + + False + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + True + + + False + True + 0 + + + + + gtk-ok + True + True + True + True + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + + + True + False + URL of ADIF webpage + 12 + 0 + + + False + True + 2 + 0 + + + + + True + True + + + True + True + 1 + + + + + False + True + 1 + + + + + + update_modes_cancel_button + update_modes_ok_button + + True False diff --git a/pyqso/update_modes_dialog.py b/pyqso/update_modes_dialog.py new file mode 100644 index 0000000..112e57d --- /dev/null +++ b/pyqso/update_modes_dialog.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2019 Christian Thomas Jacobs. + +# This file is part of PyQSO. + +# PyQSO is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyQSO is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PyQSO. If not, see . + +import os + + +class UpdateModesDialog: + + """ A handler for the Gtk.Dialog through which a user can specify a URL to an ADIF specification. """ + + def __init__(self, application): + """ Create and show the dialog to the user. + + :arg application: The PyQSO application containing the main Gtk window, etc. + """ + + self.builder = application.builder + glade_file_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "res", "pyqso.glade") + self.builder.add_objects_from_file(glade_file_path, ("update_modes_dialog",)) + self.dialog = self.builder.get_object("update_modes_dialog") + self.sources = {"URL": self.builder.get_object("adif_url_entry")} + + self.dialog.show_all() + + return + + @property + def url(self): + """ Return the URL of the ADIF specification's webpage. + + :returns: The URL of the ADIF specification's webpage. + :rtype: str + """ + return self.sources["URL"].get_text() +