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.
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.")

Wyświetl plik

@ -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)

Wyświetl plik

@ -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.

Wyświetl plik

@ -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()

Wyświetl plik

@ -17,24 +17,192 @@
# You should have received a copy of the GNU General Public License
# along with PyQSO. If not, see <http://www.gnu.org/licenses/>.
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 <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')
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 <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!")
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))

Wyświetl plik

@ -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!")

Wyświetl plik

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkImage" id="image1">
@ -68,6 +68,12 @@
<property name="stock">gtk-open</property>
<property name="icon_size">1</property>
</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">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -128,6 +134,9 @@
<property name="default_width">800</property>
<property name="default_height">600</property>
<property name="icon">log_64x64.png</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="vbox_outer">
<property name="visible">True</property>
@ -364,6 +373,28 @@
</child>
</object>
</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>
<object class="GtkMenuItem" id="mitem_view">
<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="logo">log_64x64.png</property>
<property name="license_type">custom</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="about_dialog_vbox">
<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="type_hint">dialog</property>
<property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="cabrillo_export_dialog_vbox">
<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="type_hint">dialog</property>
<property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="log_name_dialog_vbox">
<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="type_hint">dialog</property>
<property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="preferences_dialog_vbox">
<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="spacing">2</property>
<child>
<object class="GtkFrame" id="preferences_adif_import">
<object class="GtkFrame" id="preferences_adif">
<property name="visible">True</property>
<property name="can_focus">False</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="spacing">2</property>
<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="visible">True</property>
<property name="can_focus">True</property>
@ -2541,10 +2584,10 @@ Base64-encoded plain text in the configuration file.</property>
</object>
</child>
<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="can_focus">False</property>
<property name="label" translatable="yes">ADIF Import</property>
<property name="label" translatable="yes">ADIF</property>
</object>
</child>
</object>
@ -3029,6 +3072,9 @@ Base64-encoded plain text in the configuration file.</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" id="record_dialog_vbox">
<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="type_hint">dialog</property>
<property name="transient_for">record_dialog</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="calendar_dialog_vbox">
<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="type_hint">dialog</property>
<property name="transient_for">pyqso</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="telnet_connection_dialog_vbox">
<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-widgets>
</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">
<property name="visible">True</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()