#!/usr/bin/env python # File: pyqso.py # Copyright (C) 2012 Christian 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 . from gi.repository import Gtk, GObject import logging import optparse # PyQSO modules from adif import * from logbook import * from menu import * # The PyQSO application class class PyQSO(Gtk.Window): def __init__(self): # Call the constructor of the super class (Gtk.Window) Gtk.Window.__init__(self, title="PyQSO (development version)") self.set_size_request(500, 300) self.set_position(Gtk.WindowPosition.CENTER) # Kills the application if the close button is clicked # on the main window itself. self.connect("delete-event", Gtk.main_quit) # Vertically aligned packing layout vbox = Gtk.VBox(spacing=6) self.add(vbox) # Set up menu bar and populate it menu = Menu(self, vbox) # SCROLLED WINDOW to host the list box defined below. sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) vbox.pack_start(sw, True, True, 0) # LIST BOX for the radio log entries self.logbook = Logbook() self.treeview = Gtk.TreeView(self.logbook) self.treeview.set_grid_lines(Gtk.TreeViewGridLines.BOTH) sw.add(self.treeview) # The first column of the logbook will always be the unique record index. # Let's append this separately to the field names. renderer = Gtk.CellRendererText() renderer.set_property("editable", False) column = Gtk.TreeViewColumn("INDEX", renderer, text=0) column.set_resizable(True) column.set_min_width(50) self.treeview.append_column(column) # Set up column names for each selected field field_names = self.logbook.SELECTED_FIELD_NAMES_TYPES.keys() data_types = self.logbook.SELECTED_FIELD_NAMES_TYPES.values() for i in range(0, len(self.logbook.SELECTED_FIELD_NAMES_TYPES)): if(data_types[i] == "S"): renderer = Gtk.CellRendererText() renderer.set_property("editable", True) renderer.connect("edited", self.textcell_edited_callback, self.treeview, i+1) column = Gtk.TreeViewColumn(field_names[i], renderer, text=i+1) elif(data_types[i] == "B"): choices = Gtk.ListStore(str) for item in ["", "Yes", "No"]: choices.append([item]) renderer = Gtk.CellRendererCombo() renderer.set_property("editable", True) renderer.set_property("model", choices) renderer.set_property("text-column", 0) renderer.connect("edited", self.booleancell_edited_callback, self.treeview, i+1) column = Gtk.TreeViewColumn(field_names[i], renderer, text=i+1) else: # Default to a text-based cell if the field name is unknown. renderer = Gtk.CellRendererText() renderer.set_property("editable", True) renderer.connect("edited", self.textcell_edited_callback, self.treeview, i+1) column = Gtk.TreeViewColumn(field_names[i], renderer, text=i+1) column.set_resizable(True) column.set_min_width(50) self.treeview.append_column(column) self.show_all() return def new_log(self, widget): self.logbook.records = [] self.logbook.populate() return def open_log(self, widget): dialog = Gtk.FileChooserDialog("Open File", None, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) filter = Gtk.FileFilter() filter.set_name("All ADIF files") filter.add_pattern("*.adi") filter.add_pattern("*.adx") dialog.add_filter(filter) response = dialog.run() if(response == Gtk.ResponseType.OK): path = dialog.get_filename() else: path = None dialog.destroy() if(path is None): logging.debug("No file path specified.") return adif = ADIF() self.logbook.records = adif.read(path) self.logbook.populate() return def save_log(self, widget): dialog = Gtk.FileChooserDialog("Save File", None, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) response = dialog.run() if(response == Gtk.ResponseType.OK): path = dialog.get_filename() else: path = None dialog.destroy() if(path is None): logging.debug("No file path specified.") return adif = ADIF() adif.write(self.logbook.records, path) return def add_record_callback(self, widget): self.logbook.add_record() return def delete_record_callback(self, widget): # Get the selected row in the logbook and # delete it. selection = self.treeview.get_selection() selection.set_mode(Gtk.SelectionMode.SINGLE) (model, path) = selection.get_selected_rows() try: iter = model.get_iter(path[0]) index = model.get_value(iter,0) except IndexError: logging.debug("Trying to delete a record, but there are no records in the logbook!") return dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Are you sure you want to delete record %d?" % index) response = dialog.run() if(response == Gtk.ResponseType.YES): self.logbook.delete_record(iter, index) dialog.destroy() return def textcell_edited_callback(self, widget, path, text, treeview, column_index): #TODO: Validate user input! # First update the Record object. In this case, path is the row number. column_name = treeview.get_column(column_index).get_title() self.logbook.records[int(path)].set_field(column_name, text) # And then the GTK TreeView (i.e. the logbook) self.logbook[path][column_index] = text return def booleancell_edited_callback(self, widget, path, text, treeview, column_index): # Boolean combo boxes require special treatment: # ADIF specification denotes True by the letter "Y" and False by the # letter "N"... if(text == "Yes"): text_for_record = "Y" elif(text == "No"): text_for_record = "N" else: text_for_record = "" column_name = treeview.get_column(column_index).get_title() self.logbook.records[int(path)].set_field(column_name, text_for_record) # Update the logbook using the actual text from the combo box. iter = self.logbook.get_iter(path) self.logbook[path][column_index] = text return def show_about(self, widget): about = Gtk.AboutDialog() about.set_program_name("PyQSO") about.set_version("Development version") about.set_authors(["Christian Jacobs"]) about.set_license('''This program 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. This program 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 this program. If not, see .''') about.set_comments("PyQSO: A Python-based contact logging tool for amateur radio operators") about.run() about.destroy() return if(__name__ == '__main__'): # Get any command line arguments parser = optparse.OptionParser() parser.add_option('-l', '--log', action="store_true", default=False, help='Enable logging. All log messages will be written to pyqso.log.') (options, args) = parser.parse_args() # Set up debugging output if(options.log): logging.basicConfig(level=logging.DEBUG, filename="pyqso.log", format="%(asctime)s %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S") application = PyQSO() # Populate the main window and show it Gtk.main() # Start up the event loop!