Added a new menu item for updating the ADIF modes from the ADIF website. Addresses issue #69.

modes-update
Christian Jacobs 2019-04-14 17:52:06 +01:00
rodzic 091da72806
commit 2487424839
9 zmienionych plików z 435 dodań i 134 usunięć

Wyświetl plik

@ -85,6 +85,7 @@ class PyQSO:
# Kills the application if the close button is clicked on the main window itself. # Kills the application if the close button is clicked on the main window itself.
self.window.connect("delete-event", Gtk.main_quit) self.window.connect("delete-event", Gtk.main_quit)
# Status bar.
self.statusbar = self.builder.get_object("statusbar") self.statusbar = self.builder.get_object("statusbar")
context_id = self.statusbar.get_context_id("Status") context_id = self.statusbar.get_context_id("Status")
self.statusbar.push(context_id, "No logbook is currently open.") self.statusbar.push(context_id, "No logbook is currently open.")

Wyświetl plik

@ -27,6 +27,8 @@ except ImportError:
import ConfigParser as configparser import ConfigParser as configparser
from os.path import expanduser from os.path import expanduser
from pyqso.modes import Modes
# ADIF field names and their associated data types available in PyQSO. # ADIF field names and their associated data types available in PyQSO.
AVAILABLE_FIELD_NAMES_TYPES = {"CALL": "S", AVAILABLE_FIELD_NAMES_TYPES = {"CALL": "S",
"QSO_DATE": "D", "QSO_DATE": "D",
@ -98,98 +100,6 @@ AVAILABLE_FIELD_NAMES_FRIENDLY = {"CALL": "Callsign",
# E: Enumerated # E: Enumerated
DATA_TYPES = ["A", "B", "N", "S", "I", "D", "T", "M", "G", "L", "E"] 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. # 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"] 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. # The lower and upper frequency bounds (in MHz) for each band in BANDS.
@ -207,6 +117,7 @@ class ADIF:
def __init__(self): def __init__(self):
""" Initialise class for I/O of files using the Amateur Data Interchange Format (ADIF). """ """ Initialise class for I/O of files using the Amateur Data Interchange Format (ADIF). """
self.modes = Modes()
return return
def read(self, path): def read(self, path):
@ -521,9 +432,9 @@ class ADIF:
elif(data_type == "E" or data_type == "A"): elif(data_type == "E" or data_type == "A"):
# Enumeration, AwardList. # Enumeration, AwardList.
if(field_name == "MODE"): if(field_name == "MODE"):
return (data in list(MODES.keys())) return (data in list(self.modes.all.keys()))
elif(field_name == "SUBMODE"): 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) return (data in submodes)
elif(field_name == "BAND"): elif(field_name == "BAND"):
return (data in BANDS) return (data in BANDS)

Wyświetl plik

@ -38,6 +38,7 @@ from pyqso.summary import Summary
from pyqso.blank import Blank from pyqso.blank import Blank
from pyqso.printer import Printer from pyqso.printer import Printer
from pyqso.compare import compare_date_and_time, compare_default from pyqso.compare import compare_date_and_time, compare_default
from pyqso.update_modes_dialog import UpdateModesDialog
class Logbook: class Logbook:
@ -907,7 +908,7 @@ class Logbook:
return return
log = self.logs[log_index] 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: try:
sort_iter = sort_model.get_iter(path[0]) sort_iter = sort_model.get_iter(path[0])
filter_iter = self.sorter[log_index].convert_iter_to_child_iter(sort_iter) filter_iter = self.sorter[log_index].convert_iter_to_child_iter(sort_iter)
@ -1113,6 +1114,16 @@ class Logbook:
return 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 @property
def log_count(self): def log_count(self):
""" Return the total number of logs in the logbook. """ Return the total number of logs in the logbook.

Wyświetl plik

@ -109,6 +109,10 @@ class Menu:
self.items["RECORD_COUNT"] = self.builder.get_object("mitem_record_count") self.items["RECORD_COUNT"] = self.builder.get_object("mitem_record_count")
self.items["RECORD_COUNT"].connect("activate", self.application.logbook.record_count_callback) 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 # View toolbox
self.items["TOOLBOX"] = self.builder.get_object("mitem_toolbox") self.items["TOOLBOX"] = self.builder.get_object("mitem_toolbox")
config = configparser.ConfigParser() config = configparser.ConfigParser()

Wyświetl plik

@ -17,24 +17,192 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PyQSO. If not, see <http://www.gnu.org/licenses/>. # along with PyQSO. If not, see <http://www.gnu.org/licenses/>.
import sqlite3
import os
from urllib.request import urlopen from urllib.request import urlopen
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import logging
page = urlopen('http://www.adif.org/307/ADIF_307.htm').read() MODES_FILE = os.path.expanduser("~/.config/pyqso/modes.db")
soup = BeautifulSoup(page, "html.parser")
# Remove the <span> tags but keep the tags' contents.
for match in soup.findAll('span'):
match.unwrap()
# Find the MODES table. class Modes:
rows = soup.find(id="Enumeration_Mode").find_all('tr')
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 <span> 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)

Wyświetl plik

@ -38,7 +38,8 @@ except ImportError:
logging.warning("Could not import the geocoder module!") logging.warning("Could not import the geocoder module!")
have_geocoder = False 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 from pyqso.auxiliary_dialogs import error
PREFERENCES_FILE = os.path.expanduser("~/.config/pyqso/preferences.ini") PREFERENCES_FILE = os.path.expanduser("~/.config/pyqso/preferences.ini")
@ -289,27 +290,28 @@ class RecordsPage:
# Default values # Default values
# Mode # Mode
self.modes = Modes()
self.sources["DEFAULT_MODE"] = self.builder.get_object("default_values_mode_combo") 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) self.sources["DEFAULT_MODE"].append_text(mode)
(section, option) = ("records", "default_mode") (section, option) = ("records", "default_mode")
if(have_config and config.has_option(section, option)): if(have_config and config.has_option(section, option)):
mode = config.get(section, option) mode = config.get(section, option)
else: else:
mode = "" 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) self.sources["DEFAULT_MODE"].connect("changed", self.on_mode_changed)
# Submode # Submode
self.sources["DEFAULT_SUBMODE"] = self.builder.get_object("default_values_submode_combo") 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) self.sources["DEFAULT_SUBMODE"].append_text(submode)
(section, option) = ("records", "default_submode") (section, option) = ("records", "default_submode")
if(have_config and config.has_option(section, option)): if(have_config and config.has_option(section, option)):
submode = config.get(section, option) submode = config.get(section, option)
else: else:
submode = "" submode = ""
self.sources["DEFAULT_SUBMODE"].set_active(MODES[mode].index(submode)) self.sources["DEFAULT_SUBMODE"].set_active(self.modes.all[mode].index(submode))
# Power # Power
self.sources["DEFAULT_POWER"] = self.builder.get_object("default_values_tx_power_entry") 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. """ """ 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() self.sources["DEFAULT_SUBMODE"].get_model().clear()
mode = combo.get_active_text() 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"].append_text(submode)
self.sources["DEFAULT_SUBMODE"].set_active(MODES[mode].index("")) self.sources["DEFAULT_SUBMODE"].set_active(self.modes.all[mode].index(""))
return return
@ -406,8 +408,8 @@ class ImportExportPage:
config = configparser.ConfigParser() config = configparser.ConfigParser()
have_config = (config.read(PREFERENCES_FILE) != []) have_config = (config.read(PREFERENCES_FILE) != [])
# Import # ADIF
self.sources["MERGE_COMMENT"] = self.builder.get_object("adif_import_merge_comment_checkbutton") self.sources["MERGE_COMMENT"] = self.builder.get_object("adif_merge_comment_checkbutton")
(section, option) = ("import_export", "merge_comment") (section, option) = ("import_export", "merge_comment")
if(have_config and config.has_option(section, option)): if(have_config and config.has_option(section, option)):
self.sources["MERGE_COMMENT"].set_active(config.getboolean(section, option)) self.sources["MERGE_COMMENT"].set_active(config.getboolean(section, option))

Wyświetl plik

@ -104,8 +104,9 @@ class RecordDialog:
self.sources["BAND"].set_active(0) # Set an empty string as the default option. self.sources["BAND"].set_active(0) # Set an empty string as the default option.
# MODE # MODE
self.modes = Modes().all
self.sources["MODE"] = self.builder.get_object("qso_mode_combo") 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"].append_text(mode)
self.sources["MODE"].set_active(0) # Set an empty string as the default option. self.sources["MODE"].set_active(0) # Set an empty string as the default option.
self.sources["MODE"].connect("changed", self.on_mode_changed) 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) converted = self.convert_frequency(data, from_unit="MHz", to_unit=self.frequency_unit)
self.sources[field_names[i]].set_text(str(converted)) self.sources[field_names[i]].set_text(str(converted))
elif(field_names[i] == "MODE"): 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. # Handle SUBMODE at the same time.
submode_data = record["submode"] submode_data = record["submode"]
if(submode_data is None): if(submode_data is None):
submode_data = "" 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"): elif(field_names[i] == "SUBMODE"):
# Skip, because this has been (or will be) handled when populating the MODE field. # Skip, because this has been (or will be) handled when populating the MODE field.
continue continue
@ -227,7 +228,7 @@ class RecordDialog:
mode = config.get(section, option) mode = config.get(section, option)
else: else:
mode = "" mode = ""
self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode))
# Submode # Submode
(section, option) = ("records", "default_submode") (section, option) = ("records", "default_submode")
@ -235,7 +236,7 @@ class RecordDialog:
submode = config.get(section, option) submode = config.get(section, option)
else: else:
submode = "" submode = ""
self.sources["SUBMODE"].set_active(MODES[mode].index(submode)) self.sources["SUBMODE"].set_active(self.modes[mode].index(submode))
# Power # Power
(section, option) = ("records", "default_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. """ """ 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() self.sources["SUBMODE"].get_model().clear()
mode = combo.get_active_text() 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"].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 return
def on_key_press(self, widget, event): def on_key_press(self, widget, event):
@ -375,10 +376,10 @@ class RecordDialog:
if(mode == "USB" or mode == "LSB"): if(mode == "USB" or mode == "LSB"):
submode = mode submode = mode
mode = "SSB" mode = "SSB"
self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode))
self.sources["SUBMODE"].set_active(MODES[mode].index(submode)) self.sources["SUBMODE"].set_active(self.modes[mode].index(submode))
else: else:
self.sources["MODE"].set_active(sorted(MODES.keys()).index(mode)) self.sources["MODE"].set_active(sorted(self.modes.keys()).index(mode))
except: except:
logging.error("Could not obtain the current mode (e.g. FM, AM, CW) via Hamlib!") logging.error("Could not obtain the current mode (e.g. FM, AM, CW) via Hamlib!")

Wyświetl plik

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 --> <!-- Generated with glade 3.22.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.10"/> <requires lib="gtk+" version="3.10"/>
<object class="GtkImage" id="image1"> <object class="GtkImage" id="image1">
@ -68,6 +68,12 @@
<property name="stock">gtk-open</property> <property name="stock">gtk-open</property>
<property name="icon_size">1</property> <property name="icon_size">1</property>
</object> </object>
<object class="GtkImage" id="image25">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-refresh</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="image29"> <object class="GtkImage" id="image29">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -128,6 +134,9 @@
<property name="default_width">800</property> <property name="default_width">800</property>
<property name="default_height">600</property> <property name="default_height">600</property>
<property name="icon">log_64x64.png</property> <property name="icon">log_64x64.png</property>
<child>
<placeholder/>
</child>
<child> <child>
<object class="GtkBox" id="vbox_outer"> <object class="GtkBox" id="vbox_outer">
<property name="visible">True</property> <property name="visible">True</property>
@ -364,6 +373,28 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkMenuItem" id="mitem_adif">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">ADIF</property>
<child type="submenu">
<object class="GtkMenu" id="subm_adif">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="mitem_update_modes">
<property name="label" translatable="yes">Update Modes</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image25</property>
<property name="use_stock">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child> <child>
<object class="GtkMenuItem" id="mitem_view"> <object class="GtkMenuItem" id="mitem_view">
<property name="visible">True</property> <property name="visible">True</property>
@ -927,6 +958,9 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="authors">Christian Thomas Jacobs, M0UOS</property> <property name="authors">Christian Thomas Jacobs, M0UOS</property>
<property name="logo">log_64x64.png</property> <property name="logo">log_64x64.png</property>
<property name="license_type">custom</property> <property name="license_type">custom</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="about_dialog_vbox"> <object class="GtkBox" id="about_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -956,6 +990,9 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property> <property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="cabrillo_export_dialog_vbox"> <object class="GtkBox" id="cabrillo_export_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -1098,6 +1135,9 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property> <property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="log_name_dialog_vbox"> <object class="GtkBox" id="log_name_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -1195,6 +1235,9 @@ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pro
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property> <property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="preferences_dialog_vbox"> <object class="GtkBox" id="preferences_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -2505,7 +2548,7 @@ Base64-encoded plain text in the configuration file.</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<child> <child>
<object class="GtkFrame" id="preferences_adif_import"> <object class="GtkFrame" id="preferences_adif">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label_xalign">0</property> <property name="label_xalign">0</property>
@ -2522,7 +2565,7 @@ Base64-encoded plain text in the configuration file.</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<child> <child>
<object class="GtkCheckButton" id="adif_import_merge_comment_checkbutton"> <object class="GtkCheckButton" id="adif_merge_comment_checkbutton">
<property name="label" translatable="yes">Merge any text in the COMMENT field with the NOTES field</property> <property name="label" translatable="yes">Merge any text in the COMMENT field with the NOTES field</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
@ -2541,10 +2584,10 @@ Base64-encoded plain text in the configuration file.</property>
</object> </object>
</child> </child>
<child type="label"> <child type="label">
<object class="GtkLabel" id="preferences_adif_import_label"> <object class="GtkLabel" id="preferences_adif_label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">ADIF Import</property> <property name="label" translatable="yes">ADIF</property>
</object> </object>
</child> </child>
</object> </object>
@ -3029,6 +3072,9 @@ Base64-encoded plain text in the configuration file.</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property> <property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="record_dialog_vbox"> <object class="GtkBox" id="record_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -4343,6 +4389,9 @@ Base64-encoded plain text in the configuration file.</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">record_dialog</property> <property name="transient_for">record_dialog</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="calendar_dialog_vbox"> <object class="GtkBox" id="calendar_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -4415,6 +4464,9 @@ Base64-encoded plain text in the configuration file.</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property> <property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="telnet_connection_dialog_vbox"> <object class="GtkBox" id="telnet_connection_dialog_vbox">
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -4658,6 +4710,106 @@ Base64-encoded plain text in the configuration file.</property>
<action-widget response="-5">telnet_connection_ok_button</action-widget> <action-widget response="-5">telnet_connection_ok_button</action-widget>
</action-widgets> </action-widgets>
</object> </object>
<object class="GtkDialog" id="update_modes_dialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Update ADIF Modes</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="update_modes_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="update_modes_ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="adif_url_hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="adif_url_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">URL of ADIF webpage</property>
<property name="width_chars">12</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="adif_url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">update_modes_cancel_button</action-widget>
<action-widget response="-5">update_modes_ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkMenu" id="popup"> <object class="GtkMenu" id="popup">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>

Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>.
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()