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.adif import *
from pyqso.logbook import * from pyqso.logbook import *
from pyqso.menu import * from pyqso.menu import *
from pyqso.popup import *
from pyqso.toolbar import * from pyqso.toolbar import *
from pyqso.toolbox import * from pyqso.toolbox import *
from pyqso.preferences_dialog import * from pyqso.preferences_dialog import *
@ -93,8 +94,9 @@ class PyQSO:
self.logbook = Logbook(self) self.logbook = Logbook(self)
self.toolbox = Toolbox(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.menu = Menu(self)
self.popup = Popup(self)
self.toolbar = Toolbar(self) self.toolbar = Toolbar(self)
self.window.show_all() self.window.show_all()

Wyświetl plik

@ -38,6 +38,30 @@ except ImportError as e:
logging.warning(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.") 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 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: class GreyLine:
@ -53,22 +77,7 @@ class GreyLine:
self.application = application self.application = application
self.builder = self.application.builder self.builder = self.application.builder
self.points = []
# 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
if(have_necessary_modules): if(have_necessary_modules):
self.fig = matplotlib.figure.Figure() self.fig = matplotlib.figure.Figure()
@ -76,12 +85,63 @@ class GreyLine:
self.builder.get_object("greyline").pack_start(self.canvas, True, True, 0) 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). 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() self.builder.get_object("greyline").show_all()
logging.debug("Grey line ready!") logging.debug("Grey line ready!")
return 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): def draw(self):
""" Draw the world map and the grey line on top of it. """ 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. m.nightshade(datetime.utcnow()) # Add in the grey line using UTC time. Note that this requires NetCDF.
logging.debug("Grey line drawn.") logging.debug("Grey line drawn.")
# Pin-point QTH on the map. # Plot points on the map.
if(self.show_qth): if(self.points):
qth_x, qth_y = m(self.qth_longitude, self.qth_latitude) for p in self.points:
m.plot(qth_x, qth_y, "ro") x, y = m(p.longitude, p.latitude)
sub.text(qth_x+0.015*qth_x, qth_y+0.015*qth_y, self.qth_name, color="white", size="medium", weight="bold") 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 return True
else: else:

Wyświetl plik

@ -247,6 +247,14 @@ class Logbook:
self.application.menu.set_record_items_sensitive(True) self.application.menu.set_record_items_sensitive(True)
return 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): def new_log(self, widget=None):
""" Create a new log in the logbook. """ """ Create a new log in the logbook. """
@ -382,8 +390,10 @@ class Logbook:
self.treeview.append(Gtk.TreeView(model=self.sorter[index])) self.treeview.append(Gtk.TreeView(model=self.sorter[index]))
self.treeview[index].set_grid_lines(Gtk.TreeViewGridLines.BOTH) self.treeview[index].set_grid_lines(Gtk.TreeViewGridLines.BOTH)
self.treeview[index].connect("row-activated", self.edit_record_callback) 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.append(self.treeview[index].get_selection())
self.treeselection[index].set_mode(Gtk.SelectionMode.SINGLE) self.treeselection[index].set_mode(Gtk.SelectionMode.SINGLE)
# Allow the Log to be scrolled up/down. # Allow the Log to be scrolled up/down.
sw = Gtk.ScrolledWindow() sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
@ -643,12 +653,14 @@ class Logbook:
def export_log_adif(self, widget=None): def export_log_adif(self, widget=None):
""" Export the log (that is currently selected) to an ADIF file. """ """ 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. # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page... try:
logging.debug("No log currently selected!") 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 return
log_index = self.get_log_index()
log = self.logs[log_index] log = self.logs[log_index]
dialog = Gtk.FileChooserDialog("Export Log as ADIF", dialog = Gtk.FileChooserDialog("Export Log as ADIF",
@ -703,12 +715,14 @@ class Logbook:
def export_log_cabrillo(self, widget=None): def export_log_cabrillo(self, widget=None):
""" Export the log (that is currently selected) to a Cabrillo file. """ """ 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. # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page... try:
logging.debug("No log currently selected!") 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 return
log_index = self.get_log_index()
log = self.logs[log_index] log = self.logs[log_index]
dialog = Gtk.FileChooserDialog("Export Log as Cabrillo", dialog = Gtk.FileChooserDialog("Export Log as Cabrillo",
@ -775,11 +789,14 @@ class Logbook:
""" Print all the records in the log (that is currently selected). """ 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. """ 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. # Get the index of the selected tab in the logbook.
if(page_index == 0): # If we are on the Summary page... try:
logging.debug("No log currently selected!") 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 return
log_index = self.get_log_index()
log = self.logs[log_index] log = self.logs[log_index]
# Retrieve the records. # Retrieve the records.
@ -798,7 +815,7 @@ class Logbook:
def add_record_callback(self, widget): def add_record_callback(self, widget):
""" A callback function used to add a particular record/QSO. """ """ 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: try:
log_index = self.get_log_index() log_index = self.get_log_index()
if(log_index is None): if(log_index is None):
@ -1034,6 +1051,23 @@ class Logbook:
return 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 @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.
@ -1074,7 +1108,7 @@ class Logbook:
""" Given the name of a log, return its index in the list of Log objects. """ 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. :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 :rtype: int
""" """
if(name is None): if(name is None):
@ -1095,6 +1129,37 @@ class Logbook:
break break
return log_index 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): def get_logs(self):
""" Retrieve all the logs in the logbook file, and create Log objects that represent them. """ 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?") error(parent=self.parent, message="Unable to lookup QTH coordinates. Is the QTH name correct?")
logging.exception(e) logging.exception(e)
except Exception as 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) logging.exception(e)
return 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-widget response="-5">telnet_connection_ok_button</action-widget>
</action-widgets> </action-widgets>
</object> </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"> <object class="GtkBox" id="summary_page">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>