# -*- coding: utf-8 -*- """ Base classes for EventOptionsWidet and StationOptionsWidget :copyright: Mazama Science, IRIS :license: GNU Lesser General Public License, Version 3 (http://www.gnu.org/copyleft/lesser.html) """ from PyQt5 import QtWidgets, QtCore import logging from distutils.util import strtobool LOGGER = logging.getLogger(__name__) class BaseOptionsWidget(QtWidgets.QDialog): """ Base functionality for the EventOptionsWidget and StationOptionsWidget. """ # We want to watch for changes in the widget inputs, but every type of input has a different one so # we need to try a bunch of options INPUT_CHANGE_SIGNALS = ('valueChanged', 'textChanged', 'dateTimeChanged', 'clicked',) # Signal to indicate that the options have changed changed = QtCore.pyqtSignal(object) # Signal indicating that the user changed a location parameter changedCoords = QtCore.pyqtSignal(object) # Map of the individual inputs by name inputs = None def __init__(self, parent, options, otherOptions, dockWidget, toggleButton): super(BaseOptionsWidget, self).__init__(parent=parent) # This function just saves variables and do some low-level widget operations # then calls `initialize()` for the higher-level behavior. self.setupUi(self) self.inputs = self.mapInputs() self.options = options self.otherOptions = otherOptions # Add to dock dockWidget.setWidget(self) # Connect the toggle switch for hiding the dock toggleButton.toggled.connect(dockWidget.setVisible) dockWidget.visibilityChanged.connect(toggleButton.setChecked) # Call a separate function for the higher-level behavior, so subclasses can extend self.initialize() def initialize(self): """ Second part of the initialization, subclass initialization should extend this. """ # Push values to the inputs before connecting them self.setOptions() self.connectInputs() # Hook up the shortcut buttons self.time30DaysPushButton.clicked.connect(self.setTime30Days) self.time1YearPushButton.clicked.connect(self.setTime1Year) # Hook up the copy buttons self.get_timeFromOtherButton().clicked.connect(self.copyTimeOptions) self.get_locationFromOtherButton().clicked.connect(self.copyLocationOptions) def mapInputs(self): """ Subclasses override this to provide a map of keys to input widgets ex: return { 'network': self.networkLineEdit, 'station': self.stationLineEdit, 'location': self.locationLineEdit, 'channel': self.channelLineEdit, ... } """ raise NotImplementedError() def get_timeFromOtherButton(self): """ Return the button that triggers copying time from the other widget """ raise NotImplementedError() def get_locationFromOtherButton(self): """ Return the button that triggers copying location from the other widget """ raise NotImplementedError() def connectInput(self, key, one_input): # Different widgets emit different signals :P for signal_name in self.INPUT_CHANGE_SIGNALS: if hasattr(one_input, signal_name): LOGGER.debug("Listening to %s.%s" % (one_input, signal_name)) getattr(one_input, signal_name).connect(lambda: self.onInputChanged(key)) return def connectInputs(self): """ Set up listeners for the inputs """ for key, one_input in self.inputs.items(): self.connectInput(key, one_input) def onInputChanged(self, key): """ Called when any input is changed """ LOGGER.debug("Input changed: %s" % key) # Update the core options object self.options.set_options(self.getOptions()) # Emit a change event self.changed.emit(key) # Emit a coordinate change event if appropriate if self.isCoordinateInput(key): self.changedCoords.emit(key) def isCoordinateInput(self, key): """ Return true if the given key represents a coordinate input. Subclasses may override/extend this. """ for marker in ('latitude', 'longitude', 'radius', '_location', 'distance',): if marker in key: return True return False def getInputValue(self, key): """ Get the value of a single input. This is intended to Pythonify the Qt values that come natively from the widgets. (ie. return a Python date rather than a QDate). This is used internally by getOptions() """ one_input = self.inputs.get(key) if one_input: if isinstance(one_input, QtWidgets.QDateTimeEdit): # DateTime return one_input.dateTime().toString(QtCore.Qt.ISODate) elif isinstance(one_input, QtWidgets.QAbstractButton): # Radio/checkbox button return str(one_input.isChecked()) elif isinstance(one_input, QtWidgets.QComboBox): return one_input.currentText() elif hasattr(one_input, 'text'): return one_input.text() raise Exception("Couldn't identify the QWidget type for %s (%s)" % (key, one_input)) return None def setInputValue(self, key, value): """ Set the input value based on a string from the options """ one_input = self.inputs.get(key) if one_input: if isinstance(one_input, QtWidgets.QDateTimeEdit): # Ugh, complicated conversion from UTCDateTime dt = QtCore.QDateTime.fromString(value, QtCore.Qt.ISODate) one_input.setDateTime(dt) elif isinstance(one_input, QtWidgets.QDoubleSpinBox): # Float value one_input.setValue(float(value)) elif isinstance(one_input, QtWidgets.QComboBox): # Combo box index = one_input.findText(value) if index > -1: one_input.setCurrentIndex(index) elif isinstance(one_input, QtWidgets.QLineEdit): # Text input one_input.setText(value) elif isinstance(one_input, QtWidgets.QAbstractButton): # Radio/checkbox button one_input.setChecked(strtobool(str(value))) else: raise Exception("Don't know how to set an input for %s (%s)" % (key, one_input)) def setOptions(self): """ Put the current set of options into the mapped inputs """ # Get a dictionary of stringified options values for key, value in self.optionsToInputs(self.options.get_options(stringify=True)).items(): try: self.setInputValue(key, value) except Exception as e: LOGGER.warning("Unable to set input value for %s: %s", key, e) def optionsToInputs(self, values): """ Hook for subclasses to modify a dictionary of options to map to the input widgets """ return values def getOptions(self): """ Return a dictionary containing the input values, formatted as appropriate for a service call. """ inputValues = {} for key in self.inputs.keys(): try: inputValues[key] = self.getInputValue(key) except Exception as e: LOGGER.warning("Unable to get input value %s: %s", key, e) return self.inputsToOptions(inputValues) def inputsToOptions(self, values): """ Hook for subclasses to modify a dictionary of input values to map to the options """ return values def copyTimeOptions(self): """ Copy the time options from event_options/station_options """ LOGGER.info("Copying time to %s" % self.__class__) time_options = self.otherOptions.get_time_options() time_options.update(self.otherOptions.get_options(['time_choice'])) self.options.set_options(time_options) self.setOptions() self.changed.emit('time_choice') def copyLocationOptions(self): """ Copy the location options from event_options/station_options """ LOGGER.info("Copying location to %s" % self.__class__) loc_options = self.otherOptions.get_location_options() loc_options.update(self.otherOptions.get_options(['location_choice'])) self.options.set_options(loc_options) self.setOptions() self.changed.emit('location_choice') self.changedCoords.emit('location_choice') def setTime30Days(self): """ Set the start/end times to cover the last 30 days """ end = QtCore.QDateTime.currentDateTimeUtc() start = end.addDays(-30) self.starttimeDateTimeEdit.setDateTime(start) self.endtimeDateTimeEdit.setDateTime(end) self.changed.emit('time_choice') def setTime1Year(self): """ Set the start/end times to cover the last 30 days """ end = QtCore.QDateTime.currentDateTimeUtc() start = end.addYears(-1) self.starttimeDateTimeEdit.setDateTime(start) self.endtimeDateTimeEdit.setDateTime(end) self.changed.emit('time_choice')