class Ui_WaveformDialog(object):
    def setupUi(self, WaveformDialog):
    def retranslateUi(self, WaveformDialog): class WaveformsHandler(SignalingObject):
    """
    Manage the waveforms retrieval.

    The contents of self.waveforms is determined by the user selections
    in the main GUI events and SNCL tables. There will be a separate
    entry in self.waveforms for each event-SNCL combination regardless
    of any filtering that is applied in the WaveformsDialog to reduce
    the size of the visible table.
    """

    progress = QtCore.pyqtSignal(object)

    def __init__(self, logger, preferences, client):
        """
        Initialization.
        """
        super(WaveformsHandler, self).__init__()

        # Keep a reference to globally shared components
        self.preferences = preferences
        self.client = client

        # Important preferences
        self.downloadDir = self.preferences.Waveforms.downloadDir

        # Loader component
        self.waveforms_loader = None
        # Thread to run the loader in
        self.thread = None
        # Asynchronous requests, each is working to download a single waveform
        self.requests = None

        # A TimeWindow object giving offsets and phase arrivals
        self.time_window = TimeWindow()

        # Current list of waveform entries
        self.waveforms = None
        # Waveforms indexed by id
        self.waveforms_by_id = None

    def create_waveforms(self, pyweed):
        """
        Create a list of waveform entries based on the current event/station selections
        """
        self.waveforms = [
            WaveformEntry(
                event, network, station, channel
            )
            for (event, network, station, channel) in pyweed.iter_selected_events_stations()
        ]
        self.waveforms_by_id = dict(
            (waveform.waveform_id, waveform)
            for waveform in self.waveforms
        )

    def cancel_download(self):
        """
        Cancel the loader if it's running
        """
        LOGGER.debug('Cancelling existing downloads')
        if self.waveforms_loader:
            self.waveforms_loader.cancel()
        # Mark all the waveforms that are still marked as loading
        for waveform in self.waveforms:
            if waveform.loading:
                waveform.loading = False
                waveform.error = "Cancelled download"
                LOGGER.debug("Manually marking %s as cancelled", waveform.waveform_id)
                self.progress.emit(WaveformResult(waveform.waveform_id, None))
        self.done.emit(CancelledException())

    def download_waveforms(self, priority_ids, other_ids, time_window):
        """
        Initiate a download of all the given waveforms
        """
        LOGGER.info('Downloading waveforms')
        LOGGER.debug("Priority IDs: %s" % (priority_ids,))
        LOGGER.debug("Other IDs: %s" % (other_ids,))

        # Prepare the waveform entries
        self.time_window = time_window
        for waveform in self.waveforms:
            waveform.update_handler_values(self)
            # Clear error flag and set loading flag
            waveform.error = None
            waveform.loading = True

        # Get the waveforms ordered by priority
        waveform_ids = list(priority_ids) + list(other_ids)
        waveforms = [self.get_waveform(waveform_id) for waveform_id in waveform_ids]

        # Create a worker to load the data in a separate thread
        self.waveforms_loader = WaveformsLoader(self.client, waveforms)
        self.waveforms_loader.progress.connect(self.on_downloaded)
        self.waveforms_loader.done.connect(self.on_all_downloaded)
        self.waveforms_loader.start()

    def on_downloaded(self, result):
        """
        Called for each downloaded waveform.

        :param result: a `WaveformResult`
        """
        # LOGGER.debug("Downloaded waveform %s (%s)", result.waveform_id, QtCore.QThread.currentThreadId())
        self.progress.emit(result)

    def on_all_downloaded(self, result):
        LOGGER.debug("All waveforms downloaded  (%s)", QtCore.QThread.currentThreadId())
        if self.waveforms_loader:
            self.waveforms_loader.quit()
            self.waveforms_loader.wait()
            LOGGER.debug("Download thread exited")
        self.done.emit(result)

    def get_waveform(self, waveform_id):
        """
        Retrieve the Series for the given waveform
        """
        return self.waveforms_by_id.get(waveform_id)

    def save_waveforms_iter(self, base_output_path, output_format, waveforms):
        """
        Save waveforms, returning an iterator of WaveformResult objects whose values are
        True (saved), False (already saved), or Exception (error)
        This is so the GUI layer can handle save progress and errors in its own fashion
        """
        if not os.path.exists(base_output_path):
            try:
                os.makedirs(base_output_path, 0o700)
            except Exception as e:
                raise Exception("Could not create the output path: %s" % str(e))

        # Get the file extension to use
        extension = OUTPUT_FORMAT_EXTENSIONS[output_format]

        for waveform in waveforms:
            waveform_id = waveform.waveform_id
            try:
                output_file = os.path.extsep.join((waveform.base_filename, extension))
                output_path = os.path.join(base_output_path, output_file)
                # Don't repeat any work that has already been done
                if not os.path.exists(output_path):
                    LOGGER.debug('reading %s', waveform.mseed_path)
                    st = obspy.read(waveform.mseed_path)
                    self.save_waveform(st, output_path, output_format, waveform)
                    yield WaveformResult(waveform_id, True)
                else:
                    yield WaveformResult(waveform_id, False)
            except Exception as e:
                yield WaveformResult(waveform_id, e)

    def save_waveform(self, st, output_path, output_format, waveform):
        """
        Save a single waveform. This should handle any special output features (eg. SAC metadata)
        """
        LOGGER.debug('writing %s', output_path)
        if output_format in ('SAC', 'SACXY',):
            # For SAC output, we need to pull header data from the waveform record
            st = self.to_sac_stream(st, waveform)
        st.write(output_path, format=output_format)

    def to_sac_stream(self, st, waveform):
        """
        Return a SACTrace based on the given stream, containing metadata headers from the waveform
        """
        tr = SACTrace.from_obspy_trace(st[0])
        tr.kevnm = waveform.event_description[:16]
        event = waveform.event_ref()
        if not event:
            LOGGER.warn("Lost reference to event %s", waveform.event_description)
        else:
            origin = get_preferred_origin(waveform.event_ref())
            if origin:
                tr.evla = origin.latitude
                tr.evlo = origin.longitude
                tr.evdp = origin.depth / 1000
                tr.o = origin.time - waveform.start_time
                # Use event time as the reftime?
                if self.preferences.Waveforms.useEventTime:
                    tr.reftime = origin.time  # ObsPy does a lot of work here!
                    tr.iztype = 'io'
            magnitude = get_preferred_magnitude(waveform.event_ref())
            if magnitude:
                tr.mag = magnitude.mag
        channel = waveform.channel_ref()
        if not channel:
            LOGGER.warn("Lost reference to channel %s", waveform.sncl)
        else:
            tr.stla = channel.latitude
            tr.stlo = channel.longitude
            tr.stdp = channel.depth
            tr.stel = channel.elevation
            tr.cmpaz = channel.azimuth
            tr.cmpinc = channel.dip + 90
            tr.kinst = channel.sensor.description[:8]
        return Stream([tr.to_obspy_trace()]) NO_DATA_ERROR = "No data available" class TableItems(object):
    """
    Base helper class for adding a bunch of records to a QTableWidget.

    Subclasses just need to define the columns and how to turn a list of
    records into table cells.

    :example:

    >>> class ExampleTableItems(TableItems):
    >>>     columns = [
    >>>         Column('Id'),
    >>>         Column('Name', width=200),
    >>>         Column('Email'),
    >>>         Column('Age'),
    >>>     ]
    >>>     def rows(self, data):
    >>>         for person in data:
    >>>             yield [
    >>>                 self.stringWidget(person.id),
    >>>                 self.stringWidget(person.name),
    >>>                 self.stringWidget(person.email),
    >>>                 self.numericWidget(person.age),
    >>>             ]

    >>> items = ExampleTableItems(q_table_widget)
    >>> items.fill(list_of_people)

    """

    #: Subclass should define the columns
    columns = None
    #: Table that the items are tied to
    table = None
    #: Subclass can set this to enforce a fixed row height (otherwise it will be sized to fit when it gets filled)
    rowHeight = None

    def __init__(self, table, *args):
        self.table = table

    def rows(self, data):
        """
        Turn the data into rows (an iterable of lists) of QTableWidgetItems
        Subclasses should implement this
        """
        pass

    def applyProps(self, widget, **props):
        """
        Apply props to a newly created widget.

        This is used by the various widget methods to handle arbitrary keyword arguments by
        translating them into Qt setter calls.

        For example, passing `textAlignment=QtCore.Qt.AlignCenter` as a keyword argument will result in
        `widget.setTextAlignment(QtCore.Qt.AlignCenter)`
        """
        # Look for Qt setter for any given prop name
        for prop, value in props.items():
            setter = 'set%s%s' % (prop[:1].capitalize(), prop[1:])
            if hasattr(widget, setter):
                try:
                    getattr(widget, setter)(value)
                except Exception as e:
                    LOGGER.error("Tried and failed to set %s: %s", prop, e)
        return widget

    def stringWidget(self, s, **props):
        """ Create a new item displaying the given string """
        return self.applyProps(QtWidgets.QTableWidgetItem(s), **props)

    def numericWidget(self, i, text=None, **props):
        """
        Create a new item displaying the given numeric value. Pass a string or a string format as `text` to customize
        how the number is actually displayed. The numeric value will still be used for sorting.
        """
        if text is None:
            text = "%s"
        if '%' in text:
            text = text % i
        return self.applyProps(MyNumericTableWidgetItem(i, text), **props)

    def checkboxWidget(self, b, **props):
        """ Create a new checkbox widget showing the given boolean state """
        checkboxItem = self.applyProps(QtWidgets.QTableWidgetItem(), **props)
        checkboxItem.setFlags(QtCore.Qt.ItemIsEnabled)
        if b:
            checkboxItem.setCheckState(QtCore.Qt.Checked)
        else:
            checkboxItem.setCheckState(QtCore.Qt.Unchecked)
        return checkboxItem

    def initColumns(self):
        # Only run this if the table columns aren't already set up
        if self.table.columnCount() != len(self.columns):
            self.table.setColumnCount(len(self.columns))

            columnLabels = [c.label for c in self.columns]
            self.table.setHorizontalHeaderLabels(columnLabels)

            # Use the first column for identification
            self.table.setColumnHidden(0, True)

            # Set the tooltips
            for i, column in enumerate(self.columns):
                if column.description:
                    self.table.horizontalHeaderItem(i).setToolTip(column.description)

    def fill(self, data):
        """
        Fill the table
        """
        # Clear existing contents
        self.table.setRowCount(0)

        # Initialize the table columns if needed
        self.initColumns()

        # Need to turn off sorting before inserting items
        self.table.setSortingEnabled(False)

        # Add new contents
        for rowidx, row in enumerate(self.rows(data)):
            self.table.insertRow(rowidx)
            if len(row) != len(self.columns):
                LOGGER.error("Row length doesn't match column count: %s / %s", str(row), str([c.label for c in self.columns]))
            for cellidx, cell in enumerate(row):
                self.table.setItem(rowidx, cellidx, cell)

        # Turn sorting back on
        self.table.setSortingEnabled(True)

        # Adjust the row heights
        if self.rowHeight:
            for i in range(self.table.rowCount()):
                self.table.setRowHeight(i, self.rowHeight)
        else:
            self.table.resizeRowsToContents()

        # Adjust the column widths
        for i, column in enumerate(self.columns):
            if column.width:
                self.table.setColumnWidth(i, column.width)
            else:
                self.table.resizeColumnToContents(i)

    def filter(self, filterFn):
        for row in range(self.table.rowCount()):
            if not filterFn or filterFn(row):
                self.table.showRow(row)
            else:
                self.table.hideRow(row) class Column(object):
    """ Defines a column for `TableItems` """
    def __init__(self, label, description=None, width=None):
        """
        :param label: The label to display in the column header
        :param description: If set, text will appear when the user hovers over the header
        :param width: Hardcode the column width, this is useful for fields that may have long values that
            would otherwise blow out the table width
        """
        self.label = label
        self.description = description
        self.width = width def safe_int(s, default=0):
    try:
        return int(s)
    except:
        return default def safe_bool(s, default=False):
    try:
        # 'yes' or 'true'
        return s[0].lower() in 'yt'
    except:
        return default class ComboBoxAdapter(QtCore.QObject):
    """
    Adapter for handling the fact that QComboBox can only display strings, but usually the "real" value
    is something else.
    """

    #: Signal emitted when the QComboBox value changes, passing the actual value
    changed = QtCore.pyqtSignal(object)

    def __init__(self, comboBox, options, default=None):
        """
        :param comboBox: A QComboBox
        :param options: A list of options in the form of (value, label)
        :param default: If nothing is selected, what should be returned?
        """
        super(ComboBoxAdapter, self).__init__()
        self.comboBox = comboBox
        self.values = [option[0] for option in options]
        if default is None:
            default = self.values[0]
        self.default = default
        labels = [option[1] for option in options]
        self.comboBox.addItems(labels)
        self.comboBox.currentIndexChanged.connect(self.onChanged)

    def setValue(self, value):
        self.comboBox.setCurrentIndex(self.values.index(value))

    def getValue(self, index=None):
        if index is None:
            index = self.comboBox.currentIndex()
        if index < 0:
            return self.default
        else:
            return self.values[index]

    @pyqtSlot(int)
    def onChanged(self, index):
        self.changed.emit(self.getValue(index)) class BaseDialog(QtWidgets.QDialog):
    def __init__(self, parent=None, *args, **kwargs):
        # On non-Mac platforms, a dialog with a parent will always float above the parent. We want these
        # windows to be independent, so in that case remove the parent.
        # (We need the parent on Mac because it allows all windows to share a menu.)
        if not IS_DARWIN:
            parent = None
        super(BaseDialog, self).__init__(parent=parent, *args, **kwargs)
        # On Linux, dialogs don't have window controls, this can be fixed by turning off that window flag
        if IS_LINUX:
            self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.Dialog) class CustomTableWidgetItemMixin(object):
    def __init__(self, *args, **kwargs):
        global _next_type
        _next_type += 1
        return super(CustomTableWidgetItemMixin, self).__init__(*args, **kwargs, type=_next_type)
from PyQt5 import QtWidgets, QtGui, QtCore
from pyweed.gui.uic import WaveformDialog
from pyweed.waveforms_handler import WaveformsHandler, NO_DATA_ERROR
from logging import getLogger
from pyweed.gui.TableItems import TableItems, Column
from pyweed.pyweed_utils import get_event_name, TimeWindow, OUTPUT_FORMATS, PHASES
from pyweed.preferences import safe_int, safe_bool
from pyweed.gui.Adapters import ComboBoxAdapter
from pyweed.gui.BaseDialog import BaseDialog
from pyweed.gui.SpinnerWidget import SpinnerWidget
from pyweed.gui.TableWidget import CustomTableWidgetItemMixin

        LOGGER.debug("Clicked on table row")

        # Toggle the Keep state
        waveformID = str(self.selectionTable.item(row, WAVEFORM_ID_COLUMN).text())
        waveform = self.waveforms_handler.get_waveform(waveformID)
        waveform.keep = not waveform.keep
        keepItem = self.selectionTable.item(row, WAVEFORM_KEEP_COLUMN)
        keepItem.setKeep(waveform.keep)

    @QtCore.pyqtSlot()
    def loadWaveformChoices(self):
        """
        Fill the selectionTable with all SNCL-Event combinations selected in the MainWindow.
        This function is triggered whenever the "Get Waveforms" button in the MainWindow is clicked.
        """

        LOGGER.debug('Loading waveform choices...')

        self.resetDownload()

        self.waveforms_handler.create_waveforms(self.pyweed)

        # Add events to the eventComboBox -------------------------------

        self.eventComboBox.clear()

        self.eventComboBox.addItem('All events')
        for event in self.pyweed.iter_selected_events():
            self.eventComboBox.addItem(get_event_name(event))

        # Add networks/stations to the networkComboBox and stationsComboBox ---------------------------

        self.networkComboBox.clear()
        self.networkComboBox.addItem('All networks')

        self.stationComboBox.clear()
        self.stationComboBox.addItem('All stations')

        foundNetworks = set()
        foundStations = set()
        for (network, station, _channel) in self.pyweed.iter_selected_stations():
            if network.code not in foundNetworks:
                foundNetworks.add(network.code)
                self.networkComboBox.addItem(network.code)
            netstaCode = '.'.join((network.code, station.code))
            if netstaCode not in foundStations:
                foundStations.add(netstaCode)
                self.stationComboBox.addItem(netstaCode)

        self.loadSelectionTable()

        # Start downloading data
        self.downloadWaveformData()

        LOGGER.debug('Finished loading waveform choices')

    @QtCore.pyqtSlot()
    def loadSelectionTable(self):
        """
        Add event-SNCL combinations to the selection table
        """

        LOGGER.debug('Loading waveform selection table...')

        # Use WaveformTableItems to put the data into the table
        if not self.tableItems:
            self.tableItems = WaveformTableItems(
                self.selectionTable
            )
        self.tableItems.fill(self.iterWaveforms())

        self.filterSelectionTable()

        LOGGER.debug('Finished loading waveform selection table')

    @QtCore.pyqtSlot(int)
    def onFilterChanged(self):
        self.filters = {}
        self.filterSelectionTable()

    def filterSelectionTable(self):
        """
        Filter the selection table based on the currently defined filters
        """
        if self.tableItems:

            if not self.filters:
                self.filters = {
                    'event': self.eventComboBox.currentText(),
                    'network': self.networkComboBox.currentText(),
                    'station': self.stationComboBox.currentText(),
                }

            filterResults = dict(
                (waveform.waveform_id, self.applyFilter(waveform))
                for waveform in self.iterWaveforms()
            )

            def filterFn(row):
                waveformID = str(self.selectionTable.item(row, WAVEFORM_ID_COLUMN).text())
                return filterResults.get(waveformID)

            self.tableItems.filter(filterFn)

    def iterWaveforms(self, saveable_only=False):
        """
        Iterate through the waveforms, optionally yielding only the saveable ones
        """
        for waveform in self.waveforms_handler.waveforms:
            if saveable_only and not (waveform.keep and waveform.mseed_exists):
                continue
            yield waveform

    def applyFilter(self, waveform):
        """
        Apply self.filters to the given waveform
        @return True iff the waveform should be included
        """
        # Possibly hide rows with no data

        if waveform.error == NO_DATA_ERROR and self.hideNoDataCheckBox.isChecked():