Source code for samsifter.gui.widgets

# -*- coding: utf-8 -*-
"""Widgets for the main component of the SamSifter GUI representing workflows.

.. moduleauthor:: Florian Aldehoff <samsifter@biohazardous.de>
"""

from os.path import expanduser

# Qt4 imports
from PyQt4.QtGui import (
    QWidget, QFileDialog, QGridLayout, QPushButton, QLineEdit, QIcon, QLabel,
    QVBoxLayout, QFormLayout, QCheckBox, QTabWidget
)
from PyQt4.QtCore import (QRect, QSize, pyqtSignal)

# custom libraries
from samsifter.gui.composites import (
    SliderSpinboxCombo, FileChooser, OptionSwitcher
)


[docs]class OutputWidget(QWidget): """Representation of file output action in GUI. Provides a way to specify an output file for the workflow and set options for post-processing, eg. the compilation of statistics files and creation of an RMA file. """ def __init__(self, parent=None): """Initialize new instance of OutputWidget. Parameters ---------- parent : QWidget, optional Parent Qt4 widget this widget belongs to, defaults to None. """ super(OutputWidget, self).__init__(parent) self.setGeometry(QRect(10, 1, 381, 51)) self.setObjectName("output") self.icon = QIcon.fromTheme('document-save-as') self.icon_label = QLabel() self.icon_label.setPixmap(self.icon.pixmap( self.icon.actualSize(QSize(32, 32)))) self.label = QLabel("Output") self.file_entry = QLineEdit() self.file_btn = QPushButton("Save as...") self.file_btn.pressed.connect(self.show_dialog) self.compile_box = QCheckBox("combine statistics") self.compile_box.setToolTip( "combine any temporary statistic files into one CSV" ) self.compile_box.setChecked(True) self.sam2rma_box = QCheckBox("create RMA file") self.sam2rma_box.setToolTip( "create RMA file for quick inspection in MEGAN (requires MEGAN " "5.8.3 or newer)" ) self.sam2rma_box.setChecked(False) self.sam2rma_btn = QPushButton( QIcon.fromTheme('preferences-other'), "Settings..." ) self.grid = QGridLayout(self) self.grid.addWidget(self.icon_label, 0, 0, 1, 1) self.grid.addWidget(self.label, 0, 1, 1, 1) self.grid.addWidget(self.file_entry, 0, 2, 1, 1) self.grid.addWidget(self.file_btn, 0, 3, 1, 1) self.grid.addWidget(self.sam2rma_box, 1, 1, 1, 2) self.grid.addWidget(self.sam2rma_btn, 1, 3, 1, 1) # self.grid.addWidget(self.compile_box, 2, 1, 1, 2) self.setLayout(self.grid)
[docs] def show_dialog(self): """Opens OS-specific file selection dialog to specify output file.""" dialog = QFileDialog() dialog.setDefaultSuffix('SAM files (*.sam)') fname = dialog.getSaveFileName( self, 'Save as', expanduser("~"), "SAM (*.sam);;zipped SAM (*.sam.gz);;BAM (*.bam)" ) self.file_entry.setText(fname)
[docs] def set_filename(self, filename): """Updates filename in text box. Retains current cursor position despite updates. Parameters ---------- filename : str Writable path to an existing directory for the new output file. """ cursor = self.file_entry.cursorPosition() self.file_entry.setText(filename) self.file_entry.setCursorPosition(cursor)
[docs] def highlight(self, boolean=True): """Indicates errors or pending actions by changing background color. Parameters ---------- boolean : bool Activate highlighting of errors, defaults to True. """ css = "QLineEdit {background-color: #ffe08c;}" if boolean: self.file_entry.setStyleSheet(css) else: self.file_entry.setStyleSheet(None)
[docs]class InputWidget(QWidget): """Representation of file input action in GUI. Provides a way to specify the input file for the workflow. """ def __init__(self, parent=None): super(InputWidget, self).__init__(parent) self.setGeometry(QRect(10, 1, 381, 51)) self.setObjectName("input") self.icon = QIcon.fromTheme('document-open') self.icon_label = QLabel() self.icon_label.setPixmap(self.icon.pixmap( self.icon.actualSize(QSize(32, 32)))) self.label = QLabel("Input") self.file_entry = QLineEdit() self.file_btn = QPushButton("Open...") self.file_btn.pressed.connect(self.show_dialog) self.grid = QGridLayout(self) self.grid.addWidget(self.icon_label, 0, 0, 1, 1) self.grid.addWidget(self.label, 0, 1, 1, 1) self.grid.addWidget(self.file_entry, 0, 2, 1, 1) self.grid.addWidget(self.file_btn, 0, 3, 1, 1) self.setLayout(self.grid)
[docs] def show_dialog(self): """Opens OS-specific file selection dialog to set the input file.""" dialog = QFileDialog() dialog.setDefaultSuffix( 'SAM/BAM (*.sam *.SAM *.sam.gz *.SAM.GZ *.bam *.BAM)' ) fname = dialog.getOpenFileName( self, 'Open file', expanduser("~"), "SAM/BAM (*.sam *.SAM *.sam.gz *.SAM.GZ *.bam *.BAM);;" "SAM (*.sam *.SAM);;zipped SAM (*.sam.gz *.SAM.GZ);;" "BAM (*.bam *.BAM);;All files (*)" ) self.file_entry.setText(fname)
[docs] def set_filename(self, filename): """Update filename in text box. Retains current cursor position despite updates. Parameters ---------- filename : str Readable path to an existing directory for the existing input file. """ cursor = self.file_entry.cursorPosition() self.file_entry.setText(filename) self.file_entry.setCursorPosition(cursor)
[docs] def highlight(self, boolean=True): """Indicates errors or pending actions by changing background color. Parameters ---------- boolean : bool Activate highlighting of errors, defaults to True. """ css = "QLineEdit {background-color: #ffe08c;}" if boolean: self.file_entry.setStyleSheet(css) else: self.file_entry.setStyleSheet(None)
[docs]class FilterWidget(QWidget): """Representation of a filter action in GUI. Shows name, description and input/output requirements for filter items. Further provides a simple way to edit settings for all parameters of a tool or filter. Parameters are divided between two tabs for a) required and b) optional parameters and the focus is set on the required parameters. """ value_changed = pyqtSignal(str, name='value_changed') def __init__(self, item, parent=None): """Initialize a new widget for the given filter item. Parameters ---------- item : FilterItem Filter item to be displayed. parent : QWidget, optional Parent Qt4 widget this widget belongs to, defaults to None. """ super().__init__(parent) self.item = item self.setGeometry(QRect(10, 1, 381, 51)) self.setObjectName("widget") # Filter label and description title = QLabel(self.item.text()) title.setWordWrap(True) description = QLabel(self.item.get_description()) description.setWordWrap(True) # compression labels in_comp = item.get_input_compression() in_comp_str = "" if in_comp == "gzip": in_comp_str = "zipped " out_comp = item.get_output_compression() out_comp_str = "" if out_comp == "as_input": out_comp_str = in_comp_str elif out_comp == "gzip": out_comp_str = "zipped " # file format labels in_format = self.item.get_input_format() in_format_str = "" if in_format in ("SAM", "BAM"): in_format_str = in_format elif in_format == "any": in_format_str = "SAM or BAM" out_format = self.item.get_output_format() out_format_str = "" if out_format == "as_input": out_format_str = in_format_str elif out_format in ("SAM", "BAM"): out_format_str = out_format elif out_format == "any": out_format_str = "SAM or BAM" # sort order labels in_sorting = self.item.get_input_sorting() in_sorting_str = "" if in_sorting in ("queryname", "coordinate"): in_sorting_str = " (sorted by %s)" % in_sorting elif in_sorting == "unsorted": in_sorting_str = " (%s)" % in_sorting out_sorting = self.item.get_output_sorting() out_sorting_str = "" if out_sorting == "as_input": out_sorting_str = in_sorting_str elif out_sorting in ("queryname", "coordinate"): out_sorting_str = " (sorted by %s)" % out_sorting elif in_sorting == "unsorted": out_sorting_str = " (%s)" % out_sorting io_format_str = ("Input:\t %s%s%s\n" "Output:\t %s%s%s" % (in_comp_str, in_format_str, in_sorting_str, out_comp_str, out_format_str, out_sorting_str)) io_format_lbl = QLabel(io_format_str) title_layout = QVBoxLayout() # title_layout.setMargin(0) title_layout.addWidget(title) title_layout.addWidget(description) title_layout.addWidget(io_format_lbl) title_frame = QWidget(self) title_frame.setLayout(title_layout) # Filter settings self.settings = QWidget(self) self.settings.setEnabled(False) self.settings_layout = QFormLayout(self.settings) self.options = QWidget(self) self.options.setEnabled(False) self.options_layout = QFormLayout(self.options) self.toolbox = QTabWidget(self) self.toolbox.setEnabled(False) self.toolbox.addTab(self.settings, "Required") self.toolbox.addTab(self.options, "Optional") self.parameters = [] for param in self.item.get_parameters(): self.add_parameter(param) icon = QIcon.fromTheme('view-filter') icon_label = QLabel() icon_label.setPixmap(icon.pixmap(icon.actualSize(QSize(32, 32)))) # Put it all together layout = QVBoxLayout(self) layout.setMargin(0) layout.addWidget(title_frame) layout.addWidget(self.toolbox) self.setLayout(layout) # Event Handlers
[docs] def on_value_change(self, value): """Handles changes to editor values by emitting another signal.""" self.value_changed.emit("value in editor changed to %s" % value) # Getters & Setters
[docs] def add_parameter(self, parameter): """Adds specific composite widget to the parameter form. Chooses the widget based on type of parameter and divides them between the required and the optional tab depending on parameter settings. Parameters ---------- parameter : FilterParameter Filter parameter to be displayed. """ # import here to avoid circular imports! from samsifter.models.parameter import ( FilterParameter, FilterThreshold, FilterSwitch, FilterFilepath ) self.parameters.append(parameter) if isinstance(parameter, FilterThreshold): widget = SliderSpinboxCombo(self, parameter.get_minimum(), parameter.get_maximum(), parameter.get_default(), parameter.get_precision()) widget.set_value(parameter.get_value()) widget.setToolTip(parameter.get_description()) widget.spinner.valueChanged.connect(parameter.set_value) widget.spinner.valueChanged.connect(self.on_value_change) elif isinstance(parameter, FilterSwitch): widget = OptionSwitcher(self, parameter.get_options()) widget.set_current_index(parameter.get_value()) widget.setToolTip(parameter.get_description()) widget.group.buttonClicked[int].connect(parameter.set_value) widget.group.buttonClicked[int].connect(self.on_value_change) elif isinstance(parameter, FilterFilepath): widget = FileChooser(self) widget.set_filename(parameter.get_value()) widget.setToolTip(parameter.get_description()) # ensure immediate update for revalidation widget.lineedit.textChanged.connect(widget.set_filename) widget.lineedit.textChanged.connect(parameter.set_value) widget.lineedit.textChanged.connect(self.on_value_change) elif isinstance(parameter, FilterParameter): widget = QCheckBox(self) widget.setChecked(parameter.get_value()) widget.setToolTip(parameter.get_description()) widget.toggled.connect(parameter.set_value) widget.toggled.connect(self.on_value_change) if parameter.is_required(): self.settings.setEnabled(True) self.toolbox.setEnabled(True) self.settings_layout.addRow(parameter.get_form_text(), widget) else: self.options.setEnabled(True) self.toolbox.setEnabled(True) self.options_layout.addRow(parameter.get_form_text(), widget) self.update()