#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2010, 2011, 2015 GiMaRIS <info@gimaris.com>
#
# This file is part of BioDen - A data normalizer and transponer for
# files containing taxon biomass/density data for ecotopes.
#
# BioDen 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.
#
# BioDen 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 <http://www.gnu.org/licenses/>.
import sys
import os
import threading
import webbrowser
import csv
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GObject
from bioden import __copyright__, __version__, resource_filename
import bioden.std
import bioden.processor
USER_MANUAL_URL = "http://bioden.sourceforge.net/user_manual.html"
[docs]class ProgressDialog(object):
"""Display a progress dialog."""
def __init__(self, parent=None):
self.worker = None
self.builder = Gtk.Builder()
self.builder.add_from_file( resource_filename('glade/progress_dialog.glade') )
self.dialog = self.builder.get_object('progress_dialog')
self.dialog.set_transient_for(parent)
self.progress_bar = self.builder.get_object('progress_bar')
self.label_action = self.builder.get_object('label_action')
self.textview = self.builder.get_object('textview_details')
self.textbuffer = self.builder.get_object('textbuffer_details')
self.builder.connect_signals(self)
self.dialog.show()
[docs] def on_progress_dialog_cancel(self, widget, data=None):
"""Stop the worker and close the progress dialog."""
dialog = Gtk.MessageDialog(parent=self.dialog, flags=0,
type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
message_format="Are you sure you want to cancel the current process?")
dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
response = dialog.run()
if response == Gtk.ResponseType.YES:
dialog.destroy()
else:
dialog.destroy()
return True
try:
self.worker.stop()
self.worker.join()
except:
pass
self.dialog.destroy()
def set_worker(self, worker):
self.worker = worker
def destroy(self):
self.dialog.destroy()
class MainWindow(object):
def __init__(self):
self.builder = Gtk.Builder()
self.builder.add_from_file( resource_filename('glade/main.glade') )
self.window = self.builder.get_object('main_window')
self.combobox_property = self.builder.get_object('combobox_property')
self.combobox_output_format = self.builder.get_object('combobox_output_format')
# Connect the window signals to the handlers.
self.builder.connect_signals(self)
# Reset the widgets.
self.reset_widgets()
# Set additional signal handlers.
self.handlers = {
'process-finished': bioden.std.sender.connect('process-finished',
self.on_process_finished),
'load-data-failed': bioden.std.sender.connect('load-data-failed',
self.on_load_data_failed)
}
def reset_widgets(self):
"""Reset the UI components."""
# Create a CSV filter for file choosers.
self.filefilter_csv = Gtk.FileFilter()
self.filefilter_csv.set_name("Comma Separated Values (.csv)")
self.filefilter_csv.add_mime_type("text/csv")
self.filefilter_csv.add_pattern("*.csv")
# Create a XML filter for file choosers.
self.filefilter_xsl = Gtk.FileFilter()
self.filefilter_xsl.set_name("Microsoft Excel 97/2000/XP (.xls)")
self.filefilter_xsl.add_mime_type("application/vnd.ms-excel")
self.filefilter_xsl.add_pattern("*.xls")
# Add filters to the file chooser.
self.chooser_input_file = self.builder.get_object('chooser_input_file')
self.chooser_input_file.add_filter(self.filefilter_xsl)
self.chooser_input_file.add_filter(self.filefilter_csv)
# Set the default folder for the output folder to the user's
# home folder rather than bioden's installation folder.
chooser_output_folder = self.builder.get_object('chooser_output_folder')
chooser_output_folder.set_current_folder(os.path.expanduser('~'))
# Set the default value for the 'round' spinbutton.
self.builder.get_object('adjustment_round').set_value(-1)
def on_combobox_output_format_changed(self, combobox, data=None):
"""Show/hide the Excel limitation message."""
active = combobox.get_active()
output_format = self.combobox_output_format.get_active_text()
if ".xls" in output_format:
self.builder.get_object('frame_warning').show()
else:
self.builder.get_object('frame_warning').hide()
def on_window_destroy(self, widget, data=None):
"""Close the application."""
Gtk.main_quit()
def on_button_start_clicked(self, widget, data=None):
"""Start processing the data."""
input_file = self.chooser_input_file.get_filename()
output_folder = self.builder.get_object('chooser_output_folder').get_filename()
if not input_file:
self.show_message(title="No data file selected",
message="Please select the input file.",
type=Gtk.MessageType.ERROR)
return
# Get all values from the GUI.
delimiter = self.builder.get_object('entry_delimiter').get_text()
quotechar = self.builder.get_object('entry_quotechar').get_text()
active = self.combobox_property.get_active()
property_ = self.combobox_property.get_active_text()
active = self.combobox_output_format.get_active()
output_format = self.combobox_output_format.get_active_text()
target_sample_surface = float(self.builder.get_object('entry_sample_surface').get_text())
decimals = int(self.builder.get_object('spinbutton_round').get_value())
# Normalize the output format name.
if '.csv' in output_format:
output_format = 'csv'
elif '.xls' in output_format:
output_format = 'xls'
# Get the name of the selected file type.
self.filter_name = self.builder.get_object('chooser_input_file').get_filter().get_name()
# Show the progress dialog.
self.progress_dialog = ProgressDialog(parent=self.window)
# Set up the data processor.
if ".csv" in self.filter_name:
self.worker = bioden.processor.CSVProcessor()
self.worker.set_csv_dialect(delimiter, quotechar)
self.worker.set_input_file(input_file, 'csv')
elif ".xls" in self.filter_name:
self.worker = bioden.processor.XLSProcessor()
self.worker.set_input_file(input_file, 'xls')
self.worker.set_property(property_)
self.worker.set_output_folder(output_folder)
self.worker.set_progress_dialog(self.progress_dialog)
self.worker.set_target_sample_surface(target_sample_surface)
self.worker.set_output_format(output_format)
if decimals >= 0:
self.worker.set_round(decimals)
# Pass the worker to the progress dialog.
self.progress_dialog.set_worker(self.worker)
# Start processing the data.
self.worker.start()
def on_process_finished(self, sender):
"""Show a message dialog showing that the process was finished."""
self.progress_dialog.destroy()
output_folder = self.builder.get_object('chooser_output_folder').get_filename()
message_finished = self.show_message("Finished!",
"The output files have been saved to\n%s." % output_folder)
def on_load_data_failed(self, sender, strerror, data=None):
"""Show a error dialog showing that loading the data has failed."""
self.progress_dialog.destroy()
builder = Gtk.Builder()
builder.add_from_file( resource_filename('glade/error_dialog.glade') )
dialog = builder.get_object('error_dialog')
dialog.set_transient_for(self.window)
if ".csv" in self.filter_name:
message = ("The data could not be loaded. This is probably caused "
"by an incorrect input file or the CSV file was in a different "
"format. If the CSV file was in a different format, change "
"the settings under \"CSV Input File Options\" accordingly "
"and make sure the format matches the format described in the "
"documentation.")
elif ".xls" in self.filter_name:
message = ("The data could not be loaded. This is probably caused "
"by an incorrect input file or the XLS file was in a different "
"format. Make sure the format matches the format described in "
"the documentation.")
dialog.format_secondary_text(message)
textbuffer = builder.get_object('textbuffer_details')
textbuffer.set_text(strerror)
dialog.run()
dialog.destroy()
def show_message(self, title, message, type=Gtk.MessageType.INFO):
"""Show a message dialog showing that input file was not set."""
dialog = Gtk.MessageDialog(parent=self.window, flags=0,
type=type,
buttons=Gtk.ButtonsType.OK,
message_format=title)
dialog.format_secondary_text(message)
dialog.run()
dialog.destroy()
def on_about(self, widget, data=None):
"""Display the about dialog."""
about = self.builder.get_object('about_dialog')
about.set_copyright(__copyright__)
about.set_version(__version__)
about.run()
about.hide()
def on_help(button, section):
"""Display the help contents in the web browser."""
webbrowser.open(USER_MANUAL_URL)