Pinpoint selected callsigns on the grey line map by looking up the latitude-longitude coordinates based on the value in the COUNTRY field. A new right-click popup menu has been created for this purpose.
pull/65/head
Christian T. Jacobs 2018-01-18 20:52:44 +00:00 zatwierdzone przez GitHub
rodzic d06e326cbd
commit 7a162b2a2d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
6 zmienionych plików z 219 dodań i 39 usunięć

Wyświetl plik

@ -44,6 +44,7 @@ sys.path.insert(0, pyqso_path)
from pyqso.adif import *
from pyqso.logbook import *
from pyqso.menu import *
from pyqso.popup import *
from pyqso.toolbar import *
from pyqso.toolbox import *
from pyqso.preferences_dialog import *
@ -93,8 +94,9 @@ class PyQSO:
self.logbook = Logbook(self)
self.toolbox = Toolbox(self)
# Set up menu and tool bars. These classes depend on the Logbook and Toolbox class.
# Set up menu and toolbar. These classes depend on the Logbook and Toolbox class.
self.menu = Menu(self)
self.popup = Popup(self)
self.toolbar = Toolbar(self)
self.window.show_all()

Wyświetl plik

@ -38,6 +38,30 @@ except ImportError as e:
logging.warning(e)
logging.warning("Could not import a non-standard Python module needed by the GreyLine class, or the version of the non-standard module is too old. Check that all the PyQSO dependencies are satisfied.")
have_necessary_modules = False
try:
import geocoder
have_geocoder = True
except ImportError:
logging.warning("Could not import the geocoder module!")
have_geocoder = False
class Point:
""" A point on the grey line map. """
def __init__(self, name, latitude, longitude, style="yo"):
""" Set up the point's attributes.
:arg str name: The name that identifies the point.
:arg float latitude: The latitude of the point on the map.
:arg float longitude: The longitude of the point on the map.
:arg str style: The style of the point when plotted. By default it is a filled yellow circle.
"""
self.name = name
self.latitude = latitude
self.longitude = longitude
self.style = style
return
class GreyLine:
@ -53,22 +77,7 @@ class GreyLine:
self.application = application
self.builder = self.application.builder
# Get the QTH coordinates, if available.
config = configparser.ConfigParser()
have_config = (config.read(expanduser('~/.config/pyqso/preferences.ini')) != [])
(section, option) = ("general", "show_qth")
self.show_qth = False
if(have_config and config.has_option(section, option)):
if(config.getboolean(section, option)):
self.show_qth = True
try:
self.qth_name = config.get("general", "qth_name")
self.qth_latitude = float(config.get("general", "qth_latitude"))
self.qth_longitude = float(config.get("general", "qth_longitude"))
except ValueError:
logging.warning("Unable to get the QTH name, latitude and/or longitude. The QTH will not be pinpointed on the grey line map. Check preferences?")
self.show_qth = False
self.points = []
if(have_necessary_modules):
self.fig = matplotlib.figure.Figure()
@ -76,12 +85,63 @@ class GreyLine:
self.builder.get_object("greyline").pack_start(self.canvas, True, True, 0)
self.refresh_event = GObject.timeout_add(1800000, self.draw) # Re-draw the grey line automatically after 30 minutes (if the grey line tool is visible).
# Plot the QTH coordinates, if available.
config = configparser.ConfigParser()
have_config = (config.read(expanduser('~/.config/pyqso/preferences.ini')) != [])
(section, option) = ("general", "show_qth")
if(have_config and config.has_option(section, option)):
if(config.getboolean(section, option)):
try:
qth_name = config.get("general", "qth_name")
qth_latitude = float(config.get("general", "qth_latitude"))
qth_longitude = float(config.get("general", "qth_longitude"))
self.add_point(qth_name, qth_latitude, qth_longitude, "ro")
except ValueError:
logging.warning("Unable to get the QTH name, latitude and/or longitude. The QTH will not be pinpointed on the grey line map. Check preferences?")
self.builder.get_object("greyline").show_all()
logging.debug("Grey line ready!")
return
def add_point(self, name, latitude, longitude, style="yo"):
""" Add a point and re-draw the map.
:arg str name: The name that identifies the point.
:arg float latitude: The latitude of the point on the map.
:arg float longitude: The longitude of the point on the map.
:arg str style: The style of the point when plotted. By default it is a filled yellow circle.
"""
p = Point(name, latitude, longitude, style)
self.points.append(p)
self.draw()
return
def pinpoint(self, r):
""" Pinpoint the location of a QSO on the grey line map based on the COUNTRY field.
:arg r: The QSO record containing the location to pinpoint.
"""
if(have_geocoder):
country = r["COUNTRY"]
callsign = r["CALL"]
# Get the latitude-longitude coordinates of the country.
if(country):
try:
g = geocoder.google(country)
latitude, longitude = g.latlng
logging.debug("QTH coordinates found: (%s, %s)", str(latitude), str(longitude))
self.add_point(callsign, latitude, longitude)
except ValueError:
logging.exception("Unable to lookup QTH coordinates.")
except Exception:
logging.exception("Unable to lookup QTH coordinates. Check connection to the internets? Lookup limit reached?")
return
def draw(self):
""" Draw the world map and the grey line on top of it.
@ -113,11 +173,12 @@ class GreyLine:
m.nightshade(datetime.utcnow()) # Add in the grey line using UTC time. Note that this requires NetCDF.
logging.debug("Grey line drawn.")
# Pin-point QTH on the map.
if(self.show_qth):
qth_x, qth_y = m(self.qth_longitude, self.qth_latitude)
m.plot(qth_x, qth_y, "ro")
sub.text(qth_x+0.015*qth_x, qth_y+0.015*qth_y, self.qth_name, color="white", size="medium", weight="bold")
# Plot points on the map.
if(self.points):
for p in self.points:
x, y = m(p.longitude, p.latitude)
m.plot(x, y, p.style)
sub.text(x+0.01*x, y+0.01*y, p.name, color="white", size="small", weight="bold")
return True
else:

Wyświetl plik

@ -247,6 +247,14 @@ class Logbook:
self.application.menu.set_record_items_sensitive(True)
return
def on_button_release_event(self, treeview, event):
""" Show a popup menu when the user right-clicks a record in the logbook. """
if(event.button == 3):
self.application.popup.menu.popup(None, None, None, None, event.button, event.time)
self.application.popup.menu.show_all()
return True
def new_log(self, widget=None):
""" Create a new log in the logbook. """
@ -382,8 +390,10 @@ class Logbook:
self.treeview.append(Gtk.TreeView(model=self.sorter[index]))
self.treeview[index].set_grid_lines(Gtk.TreeViewGridLines.BOTH)
self.treeview[index].connect("row-activated", self.edit_record_callback)
self.treeview[index].connect("button-release-event", self.on_button_release_event)
self.treeselection.append(self.treeview[index].get_selection())
self.treeselection[index].set_mode(Gtk.SelectionMode.SINGLE)
# Allow the Log to be scrolled up/down.
sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
@ -643,12 +653,14 @@ class Logbook:
def export_log_adif(self, widget=None):
""" Export the log (that is currently selected) to an ADIF file. """
page_index = self.notebook.get_current_page() # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page...
logging.debug("No log currently selected!")
# Get the index of the selected tab in the logbook.
try:
log_index = self.get_log_index()
if(log_index is None):
raise ValueError("The log index could not be determined. Perhaps the Summary page is selected?")
except ValueError as e:
error(parent=self.application.window, message=e)
return
log_index = self.get_log_index()
log = self.logs[log_index]
dialog = Gtk.FileChooserDialog("Export Log as ADIF",
@ -703,12 +715,14 @@ class Logbook:
def export_log_cabrillo(self, widget=None):
""" Export the log (that is currently selected) to a Cabrillo file. """
page_index = self.notebook.get_current_page() # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page...
logging.debug("No log currently selected!")
# Get the index of the selected tab in the logbook.
try:
log_index = self.get_log_index()
if(log_index is None):
raise ValueError("The log index could not be determined. Perhaps the Summary page is selected?")
except ValueError as e:
error(parent=self.application.window, message=e)
return
log_index = self.get_log_index()
log = self.logs[log_index]
dialog = Gtk.FileChooserDialog("Export Log as Cabrillo",
@ -775,11 +789,14 @@ class Logbook:
""" Print all the records in the log (that is currently selected).
Note that only a few important fields are printed because of the restricted width of the page. """
page_index = self.notebook.get_current_page() # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page...
logging.debug("No log currently selected!")
# Get the index of the selected tab in the logbook.
try:
log_index = self.get_log_index()
if(log_index is None):
raise ValueError("The log index could not be determined. Perhaps the Summary page is selected?")
except ValueError as e:
error(parent=self.application.window, message=e)
return
log_index = self.get_log_index()
log = self.logs[log_index]
# Retrieve the records.
@ -798,7 +815,7 @@ class Logbook:
def add_record_callback(self, widget):
""" A callback function used to add a particular record/QSO. """
# Get the log index.
# Get the index of the selected tab in the logbook.
try:
log_index = self.get_log_index()
if(log_index is None):
@ -1034,6 +1051,23 @@ class Logbook:
return
def pinpoint_callback(self, widget=None, path=None):
""" A callback function used to pinpoint the callsign on the grey line map. """
try:
log_index = self.get_log_index()
row_index = self.get_record_index()
if(log_index is None or row_index is None):
raise ValueError("Could not determine the log and/or record index.")
r = self.logs[log_index].get_record_by_index(row_index)
except ValueError as e:
logging.error(e)
return
self.application.toolbox.grey_line.pinpoint(r)
return
@property
def log_count(self):
""" Return the total number of logs in the logbook.
@ -1074,7 +1108,7 @@ class Logbook:
""" Given the name of a log, return its index in the list of Log objects.
:arg str name: The name of the log. If None, use the name of the currently-selected log.
:returns: The index of the named log in the list of Log objects. Returns None is the log cannot be found.
:returns: The index of the named log in the list of Log objects. Returns None if the log cannot be found.
:rtype: int
"""
if(name is None):
@ -1095,6 +1129,37 @@ class Logbook:
break
return log_index
def get_record_index(self):
""" Return the index of the currently selected record.
:returns: The index of the currently selected record in the currently selected log. Returns None if the record or log cannot be found.
:rtype: int
"""
# Get the index of the selected log.
try:
log_index = self.get_log_index()
if(log_index is None):
raise ValueError("The log index could not be determined. Perhaps the Summary page is selected?")
except ValueError as e:
logging.error(e)
return None
log = self.logs[log_index]
# Get the selected row in the log.
(sort_model, path) = self.treeselection[log_index].get_selected_rows()
try:
sort_iter = sort_model.get_iter(path[0])
filter_iter = self.sorter[log_index].convert_iter_to_child_iter(sort_iter)
# ...and the ListStore model (i.e. the log) is a child of the filter model.
child_iter = self.filter[log_index].convert_iter_to_child_iter(filter_iter)
row_index = log.get_value(child_iter, 0)
except IndexError:
logging.error("Could not find the selected row's index!")
return None
return row_index
def get_logs(self):
""" Retrieve all the logs in the logbook file, and create Log objects that represent them.

40
pyqso/popup.py 100644
Wyświetl plik

@ -0,0 +1,40 @@
#!/usr/bin/env python3
# Copyright (C) 2018 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/>.
class Popup:
""" The popup menu that appears when a QSO record is right-clicked. """
def __init__(self, application):
""" Set up popup menu items. """
self.application = application
self.builder = self.application.builder
self.menu = self.builder.get_object("popup")
# Collect Gtk menu items and connect signals.
self.items = {}
# Plot selected QSO on the grey line map.
self.items["PINPOINT"] = self.builder.get_object("mitem_pinpoint")
self.items["PINPOINT"].connect("activate", self.application.logbook.pinpoint_callback)
return

Wyświetl plik

@ -287,7 +287,7 @@ class GeneralPage:
error(parent=self.parent, message="Unable to lookup QTH coordinates. Is the QTH name correct?")
logging.exception(e)
except Exception as e:
error(parent=self.parent, message="Unable to lookup QTH coordinates. Check connection to the internets?")
error(parent=self.parent, message="Unable to lookup QTH coordinates. Check connection to the internets? Lookup limit reached?")
logging.exception(e)
return

Wyświetl plik

@ -4547,6 +4547,18 @@ Base64-encoded plain text in the configuration file.</property>
<action-widget response="-5">telnet_connection_ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkMenu" id="popup">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="mitem_pinpoint">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pinpoint</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
<object class="GtkBox" id="summary_page">
<property name="visible">True</property>
<property name="can_focus">False</property>