diff --git a/cdatgui/bases/background_delegate.py b/cdatgui/bases/background_delegate.py index d9b9569..d179225 100644 --- a/cdatgui/bases/background_delegate.py +++ b/cdatgui/bases/background_delegate.py @@ -7,7 +7,7 @@ def paint(self, painter, option, index): painter.fillRect(option.rect, bg) super(BorderHighlightStyleDelegate, self).paint(painter, option, index) if option.state & QtGui.QStyle.State_Selected: - painter.save() + painter.accept() color = QtGui.QColor(76, 177 ,255) pen = QtGui.QPen(color, 2, QtCore.Qt.SolidLine, QtCore.Qt.SquareCap, QtCore.Qt.MiterJoin) w = pen.width() / 2 diff --git a/cdatgui/bases/dynamic_grid_layout.py b/cdatgui/bases/dynamic_grid_layout.py index b605561..75222fb 100644 --- a/cdatgui/bases/dynamic_grid_layout.py +++ b/cdatgui/bases/dynamic_grid_layout.py @@ -49,7 +49,7 @@ def buildGrid(self, rect, force=False): return # clearing - self.clearWidget() + self.clearWidgets() # calculate full_columns = possible_columns - 1 @@ -77,8 +77,8 @@ def buildGrid(self, rect, force=False): self.cur_col_count = possible_columns - def clearWidget(self): - """clears widgets from the grid layout. Does not delete widgets""" + def clearWidgets(self): + """Clears widgets from the grid layout. Does not delete widgets""" for col, row_count in enumerate(self.counts): if row_count: for row in range(row_count): @@ -93,7 +93,9 @@ def getWidgets(self): return self.widgets def removeWidget(self, widget): - """removes widgets from gridlayout and updates list and counts""" + """Removes widgets from gridlayout and updates list and counts + Does Not Delete Widget + """ for i in self.widgets: if i == widget: diff --git a/cdatgui/bases/input_dialog.py b/cdatgui/bases/input_dialog.py new file mode 100644 index 0000000..76eec2d --- /dev/null +++ b/cdatgui/bases/input_dialog.py @@ -0,0 +1,75 @@ +from PySide import QtCore, QtGui + + +class AccessibleButtonDialog(QtGui.QWidget): + accepted = QtCore.Signal() + rejected = QtCore.Signal() + + def __init__(self, parent=None): + super(AccessibleButtonDialog, self).__init__(parent=parent) + + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.reject) + + self.save_button = QtGui.QPushButton('Save') + self.save_button.clicked.connect(self.accept) + self.save_button.setDefault(True) + self.cancel_button = QtGui.QPushButton('Cancel') + self.cancel_button.clicked.connect(self.reject) + + save_cancel_layout = QtGui.QHBoxLayout() + save_cancel_layout.addWidget(self.cancel_button) + save_cancel_layout.addWidget(self.save_button) + + self.vertical_layout = QtGui.QVBoxLayout() + self.vertical_layout.addLayout(save_cancel_layout) + + self.setLayout(self.vertical_layout) + + self.setMaximumSize(300, 100) + + def reject(self): + self.close() + self.rejected.emit() + + def accept(self): + self.close() + self.accepted.emit() + + +class ValidatingInputDialog(AccessibleButtonDialog): + def __init__(self, parent=None): + super(ValidatingInputDialog, self).__init__(parent=parent) + + self.label = QtGui.QLabel() + self.edit = QtGui.QLineEdit() + + self.edit.returnPressed.connect(self.save_button.click) + + edit_line_layout = QtGui.QHBoxLayout() + edit_line_layout.addWidget(self.label) + edit_line_layout.addWidget(self.edit) + + self.vertical_layout.insertLayout(0, edit_line_layout) + + self.save_button.setEnabled(False) + self.edit.setFocus() + + def setLabelText(self, text): + self.label.setText(text) + + def setValidator(self, validator): + self.edit.setValidator(validator) + + try: + validator.invalidInput.connect(lambda: self.save_button.setEnabled(False)) + validator.validInput.connect(lambda: self.save_button.setEnabled(True)) + except AttributeError: + pass + + def textValue(self): + return self.edit.text().strip() + + def setTextValue(self, text): + self.edit.setText(text) diff --git a/cdatgui/bases/list_model.py b/cdatgui/bases/list_model.py index f183aea..5dc8377 100644 --- a/cdatgui/bases/list_model.py +++ b/cdatgui/bases/list_model.py @@ -29,7 +29,7 @@ def replace(self, index, value): self.dataChanged.emit(ind, ind) def remove(self, ind): - self.removeRows(ind, 1) + return self.removeRows(ind, 1) def clear(self): self.removeRows(0, len(self.values)) @@ -40,9 +40,10 @@ def insertRows(self, row, count, values, parent=QtCore.QModelIndex()): self.endInsertRows() def removeRows(self, row, count, parent=QtCore.QModelIndex()): - self.beginRemoveRows(parent, row, row + count) + self.beginRemoveRows(parent, row, row + count - 1) self.values = self.values[:row] + self.values[row + count:] self.endRemoveRows() + return True def rowCount(self, modelIndex=None): return len(self.values) diff --git a/cdatgui/bases/range_widget.py b/cdatgui/bases/range_widget.py index 91e0421..dadfcce 100644 --- a/cdatgui/bases/range_widget.py +++ b/cdatgui/bases/range_widget.py @@ -1,8 +1,13 @@ from PySide import QtGui, QtCore + +from cdatgui.utils import label from value_slider import ValueSlider class RangeValidator(QtGui.QValidator): + validInput = QtCore.Signal() + invalidInput = QtCore.Signal() + def __init__(self, min, max, parse, parent=None): super(RangeValidator, self).__init__(parent=parent) self.min = min @@ -12,8 +17,10 @@ def __init__(self, min, max, parse, parent=None): def validate(self, i, pos): value = self.parser(i) if value is not None and value <= self.max and value >= self.min: + self.validInput.emit() return self.Acceptable else: + self.invalidInput.emit() return self.Intermediate @@ -31,46 +38,50 @@ def __build_slider__(val, values): class RangeWidget(QtGui.QWidget): - boundsEdited = QtCore.Signal() + validParams = QtCore.Signal() + invalidParams = QtCore.Signal() - def __init__(self, values, bottom=None, top=None, parent=None): + def __init__(self, values, bottom=None, top=None, axis_type=None, flipped=False, parent=None): """ - min: Minimum value for range - max: Maximum value for range + values: Axis values to be used for slider bottom: Lower bound top: Upper bound - formatter: Callable that converts a value to a string - parser: Callable that converts a string to a value + axis_type: Axis type to set up slider labels + flipped: the graph was flipped previously or not """ super(RangeWidget, self).__init__(parent=parent) + self.flipped = flipped self.values = values - l = QtGui.QHBoxLayout() + self.center = False if bottom is None: bottom = 0 if top is None: top = len(values) - 1 - min = 0 - max = len(values) - 1 + self.min = 0 + self.max = len(values) - 1 + self.prev_diff = top - bottom self.lowerBoundText = QtGui.QLineEdit(self.format(bottom)) + self.lowerBoundText.setFixedWidth(120) self.upperBoundText = QtGui.QLineEdit(self.format(top)) + self.upperBoundText.setFixedWidth(120) - self.lowerBoundText.setValidator(RangeValidator(min, top, self.parse)) - self.upperBoundText.setValidator(RangeValidator(bottom, max, self.parse)) + lower_validator = RangeValidator(self.min, self.max, self.parse) + upper_validator = RangeValidator(self.min, self.max, self.parse) - self.lowerBoundSlider = __build_slider__(bottom, values) - self.upperBoundSlider = __build_slider__(top, values) + lower_validator.validInput.connect(self.validParams.emit) + lower_validator.invalidInput.connect(self.invalidParams.emit) + upper_validator.validInput.connect(self.validParams.emit) + upper_validator.invalidInput.connect(self.invalidParams.emit) - slayout = QtGui.QVBoxLayout() - slayout.addWidget(self.lowerBoundSlider, 1) - slayout.addWidget(self.upperBoundSlider, 1) + self.lowerBoundText.setValidator(lower_validator) + self.upperBoundText.setValidator(upper_validator) - l.addWidget(self.lowerBoundText) - l.addLayout(slayout, 1) - l.addWidget(self.upperBoundText) + self.lowerBoundSlider = __build_slider__(bottom, values) + self.upperBoundSlider = __build_slider__(top, values) self.lowerBoundSlider.valueChanged.connect(self.updateLower) self.upperBoundSlider.valueChanged.connect(self.updateUpper) @@ -81,12 +92,69 @@ def __init__(self, values, bottom=None, top=None, parent=None): self.lowerBoundText.editingFinished.connect(self.adjustLower) self.upperBoundText.editingFinished.connect(self.adjustUpper) + self.upperTimer = QtCore.QTimer() + self.upperTimer.setInterval(1000) + self.upperTimer.timeout.connect(self.adjustUpper) + + self.lowerTimer = QtCore.QTimer() + self.lowerTimer.setInterval(1000) + self.lowerTimer.timeout.connect(self.adjustLower) + self.errorPalette = QtGui.QPalette() self.errorPalette.setColor(self.errorPalette.Text, QtCore.Qt.red) self.validPalette = QtGui.QPalette() self.validPalette.setColor(self.validPalette.Text, QtCore.Qt.black) - self.setLayout(l) + full_layout = QtGui.QHBoxLayout() + label_layout = QtGui.QVBoxLayout() + if axis_type == 'latitude': + label_layout.addWidget(label('Bottom:')) + label_layout.addWidget(label('Top:')) + elif axis_type == 'longitude': + label_layout.addWidget(label('Left:')) + label_layout.addWidget(label('Right:')) + elif axis_type == 'time': + label_layout.addWidget(label('Start:')) + label_layout.addWidget(label('End:')) + + lower_layout = QtGui.QHBoxLayout() + lower_layout.addWidget(self.lowerBoundText) + lower_layout.addWidget(self.lowerBoundSlider) + + upper_layout = QtGui.QHBoxLayout() + upper_layout.addWidget(self.upperBoundText) + upper_layout.addWidget(self.upperBoundSlider) + + slayout = QtGui.QVBoxLayout() + slayout.addLayout(lower_layout, 1) + slayout.addLayout(upper_layout, 1) + + full_layout.addLayout(label_layout) + full_layout.addLayout(slayout) + + if axis_type == 'latitude' or axis_type == 'longitude': + self.center = True + self.centerLineSlider = __build_slider__((bottom + top) / 2, values) + self.centerLineText = QtGui.QLineEdit() + self.centerLineText.setFixedWidth(120) + center_validator = RangeValidator(self.min, self.max, self.parse) + center_validator.validInput.connect(self.validParams.emit) + center_validator.invalidInput.connect(self.invalidParams.emit) + self.centerTimer = QtCore.QTimer() + self.centerTimer.setInterval(1000) + self.centerTimer.timeout.connect(self.adjustCenter) + self.centerLineText.setValidator(center_validator) + self.centerLineSlider.valueChanged.connect(self.updateCenter) + self.centerLineText.textEdited.connect(self.parseCenter) + self.centerLineText.editingFinished.connect(self.adjustCenter) + center_layout = QtGui.QHBoxLayout() + label_layout.insertWidget(1, label('Position:')) + center_layout.addWidget(self.centerLineText) + center_layout.addWidget(self.centerLineSlider) + slayout.insertLayout(1, center_layout) + self.recenterCenter() + + self.setLayout(full_layout) def getLimits(self): return self.lowerBoundSlider.minimum(), self.lowerBoundSlider.maximum() @@ -98,6 +166,7 @@ def parse(self, value): for i, v in enumerate(self.values): if v.startswith(value): return i + return None def getBounds(self): return self.lowerBoundSlider.value(), self.upperBoundSlider.value() @@ -107,35 +176,131 @@ def setBounds(self, low, high): self.upperBoundSlider.setValue(high) def updateLower(self, value): - if value > self.upperBoundSlider.value(): - self.lowerBoundSlider.setValue(self.upperBoundSlider.value()) - return - self.upperBoundText.validator().min = value + if value > self.upperBoundSlider.value() and not self.flipped: + self.flipped = True + elif value < self.upperBoundSlider.value() and self.flipped: + self.flipped = False + + self.upperBoundText.setText(self.format(self.upperBoundSlider.value())) + self.recenterCenter() self.boundsEdited.emit() + self.lowerBoundText.setPalette(self.validPalette) if value != self.parse(self.lowerBoundText.text()): self.lowerBoundText.setText(self.format(value)) - def updateUpper(self, value): - if value < self.lowerBoundSlider.value(): - self.upperBoundSlider.setValue(self.lowerBoundSlider.value()) + # update diff + self.prev_diff = self.upperBoundSlider.value() - self.lowerBoundSlider.value() + + def updateCenter(self, value=None): + recenter = False + + if self.prev_diff % 2 == 1: + diff = self.prev_diff - 1 + else: + diff = self.prev_diff + diff /= 2 + + if value - diff < self.min: + lower_val = self.min + self.centerLineText.validator().min = value + upper_val = lower_val + self.prev_diff + recenter = True + elif value + diff > self.max: + upper_val = self.max + self.centerLineText.validator().max = value + lower_val = upper_val - self.prev_diff + recenter = True + else: + lower_val = self.centerLineSlider.value() - diff + upper_val = self.centerLineSlider.value() + diff + + block = self.lowerBoundSlider.blockSignals(True) + self.lowerBoundSlider.setValue(lower_val) + + self.lowerBoundSlider.blockSignals(block) + self.lowerBoundText.setText(self.format(self.lowerBoundSlider.value())) + + self.centerLineText.setPalette(self.validPalette) + + block = self.upperBoundSlider.blockSignals(True) + self.upperBoundSlider.setValue(upper_val) + self.upperBoundText.setText(self.format(self.upperBoundSlider.value())) + self.upperBoundSlider.blockSignals(block) + + block = self.centerLineText.blockSignals(True) + self.centerLineText.setText(self.format(self.centerLineSlider.value())) + self.centerLineText.blockSignals(block) + + if recenter: + self.recenterCenter() + + self.boundsEdited.emit() + + def recenterCenter(self): + if not self.center: return - self.lowerBoundText.validator().max = value + + diff = abs(self.upperBoundSlider.value() - self.lowerBoundSlider.value()) + if diff % 2 == 1: + diff -= 1 + diff /= 2 + + block = self.centerLineSlider.blockSignals(True) + if not self.flipped: + self.centerLineSlider.setValue(self.lowerBoundSlider.value() + diff) + else: + self.centerLineSlider.setValue(self.lowerBoundSlider.value() - diff) + self.centerLineSlider.blockSignals(block) + + self.centerLineText.setText(self.format(self.centerLineSlider.value())) + self.centerLineText.setPalette(self.validPalette) + return diff + + def updateUpper(self, value): + if value < self.lowerBoundSlider.value() and not self.flipped: + self.flipped = True + elif value > self.lowerBoundSlider.value() and self.flipped: + self.flipped = False + + self.lowerBoundText.setText(self.format(self.lowerBoundSlider.value())) + self.recenterCenter() self.boundsEdited.emit() + self.upperBoundText.setPalette(self.validPalette) if value != self.parse(self.upperBoundText.text()): self.upperBoundText.setText(self.format(value)) + # update diff + self.prev_diff = self.upperBoundSlider.value() - self.lowerBoundSlider.value() + def parseLower(self, t): + self.lowerTimer.start() if self.lowerBoundText.hasAcceptableInput(): val = self.parse(t) + block = self.lowerBoundSlider.blockSignals(True) self.lowerBoundSlider.setValue(val) + self.lowerBoundSlider.blockSignals(block) self.lowerBoundText.setPalette(self.validPalette) else: self.lowerBoundText.setPalette(self.errorPalette) + def parseCenter(self, t): + self.centerTimer.start() + if self.centerLineText.hasAcceptableInput(): + val = self.parse(t) + block = self.centerLineSlider.blockSignals(True) + self.centerLineSlider.setValue(val) + self.centerLineSlider.blockSignals(block) + self.centerLineText.setPalette(self.validPalette) + else: + self.centerLineText.setPalette(self.errorPalette) + def parseUpper(self, t): + self.upperTimer.start() if self.upperBoundText.hasAcceptableInput(): val = self.parse(t) + block = self.upperBoundSlider.blockSignals(True) self.upperBoundSlider.setValue(val) + self.upperBoundSlider.blockSignals(block) self.upperBoundText.setPalette(self.validPalette) else: self.upperBoundText.setPalette(self.errorPalette) @@ -145,9 +310,18 @@ def adjustLower(self): # Normalize the value value = self.parse(self.lowerBoundText.text()) self.lowerBoundText.setText(self.format(value)) + self.updateLower(value) + + def adjustCenter(self): + if self.centerLineText.hasAcceptableInput(): + # Normalize the value + value = self.parse(self.centerLineText.text()) + self.centerLineText.setText(self.format(value)) + self.updateCenter(value) def adjustUpper(self): if self.upperBoundText.hasAcceptableInput(): # Normalize the value value = self.parse(self.upperBoundText.text()) self.upperBoundText.setText(self.format(value)) + self.updateUpper(value) diff --git a/cdatgui/bases/value_slider.py b/cdatgui/bases/value_slider.py index ebc9a13..7206b50 100644 --- a/cdatgui/bases/value_slider.py +++ b/cdatgui/bases/value_slider.py @@ -2,19 +2,28 @@ class ValueSlider(QtGui.QSlider): - realValueChanged = QtCore.Signal(object) - def __init__(self, values, parent=None): - super(ValueSlider, self).__init__(parent=parent) - self.values = values - self.setMinimum(0) - self.setMaximum(len(values) - 1) - self.valueChanged.connect(self.emitReal) + realValueChanged = QtCore.Signal(object) - def emitReal(self, val): - self.realValueChanged.emit(self.values[val]) + def __init__(self, values, parent=None): + super(ValueSlider, self).__init__(parent=parent) + self.values = values + self.setMinimum(0) + self.setMaximum(len(values) - 1) + self.valueChanged.connect(self.emitReal) - def realValue(self): - return self.values[self.value()] + def emitReal(self, val): + self.realValueChanged.emit(self.values[val]) - def setRealValue(self, realValue): - self.setValue(self.values.index(realValue)) \ No newline at end of file + def realValue(self): + return self.values[self.value()] + + def valueIndex(self, val=None): + if val is None: + return self.values.index(self.value()) + return self.values.index(val) + + def setRealValue(self, realValue): + if isinstance(realValue, list): + realValue = realValue[0] + val = min(range(len(self.values)), key=lambda i: abs(self.values[i] - realValue)) + self.setValue(val) diff --git a/cdatgui/bases/vcs_elements_dialog.py b/cdatgui/bases/vcs_elements_dialog.py new file mode 100644 index 0000000..850812c --- /dev/null +++ b/cdatgui/bases/vcs_elements_dialog.py @@ -0,0 +1,36 @@ +from cdatgui.bases.input_dialog import ValidatingInputDialog +from PySide import QtCore, QtGui +import vcs + + +class VcsElementsDialog(ValidatingInputDialog): + def __init__(self, element): + super(VcsElementsDialog, self).__init__() + self.element = element + self.setValidator(VcsElementsValidator()) + + def save(self): + if self.textValue() in vcs.elements[self.element]: + check = QtGui.QMessageBox.question(self, "Overwrite {0}?".format(self.element), + "{0} '{1}' already exists. Overwrite?".format(self.element.capitalize(), self.textValue()), + buttons=QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) + if check == QtGui.QDialogButtonBox.FirstButton: + self.close() + self.accepted.emit() + else: + self.close() + self.accepted.emit() + + +class VcsElementsValidator(QtGui.QValidator): + invalidInput = QtCore.Signal() + validInput = QtCore.Signal() + + def validate(self, inp, pos): + inp = inp.strip() + if not inp or inp == 'default': + self.invalidInput.emit() + return QtGui.QValidator.Intermediate + + self.validInput.emit() + return QtGui.QValidator.Acceptable diff --git a/cdatgui/bases/window_widget.py b/cdatgui/bases/window_widget.py index 0d83f1b..f1fc441 100644 --- a/cdatgui/bases/window_widget.py +++ b/cdatgui/bases/window_widget.py @@ -1,13 +1,20 @@ from PySide import QtCore, QtGui + class BaseSaveWindowWidget(QtGui.QWidget): - savePressed = QtCore.Signal(str) + accepted = QtCore.Signal(str) + rejected = QtCore.Signal() - def __init__(self): + def __init__(self, parent=None): super(BaseSaveWindowWidget, self).__init__() - + self.auto_close = True self.object = None self.preview = None + self.dialog = QtGui.QInputDialog() + self.dialog.setModal(QtCore.Qt.ApplicationModal) + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.reject) # Layout to add new elements self.vertical_layout = QtGui.QVBoxLayout() @@ -15,20 +22,20 @@ def __init__(self): # Save and Cancel Buttons cancel_button = QtGui.QPushButton() cancel_button.setText("Cancel") - cancel_button.clicked.connect(lambda: self.close()) + cancel_button.clicked.connect(self.reject) saveas_button = QtGui.QPushButton() saveas_button.setText("Save As") saveas_button.clicked.connect(self.saveAs) - save_button = QtGui.QPushButton() - save_button.setText("Save") - save_button.clicked.connect(self.save) + self.save_button = QtGui.QPushButton() + self.save_button.setText("Save") + self.save_button.clicked.connect(self.accept) save_cancel_row = QtGui.QHBoxLayout() save_cancel_row.addWidget(cancel_button) save_cancel_row.addWidget(saveas_button) - save_cancel_row.addWidget(save_button) + save_cancel_row.addWidget(self.save_button) save_cancel_row.insertStretch(1, 1) # Set up vertical_layout @@ -38,7 +45,6 @@ def __init__(self): def setPreview(self, preview): if self.preview: self.vertical_layout.removeWidget(self.preview) - print "P: ", self.preview self.preview.deleteLater() self.preview = preview @@ -46,15 +52,15 @@ def setPreview(self, preview): def saveAs(self): - self.win = QtGui.QInputDialog() + self.win = self.dialog self.win.setLabelText("Enter New Name:") - self.win.accepted.connect(self.save) + self.win.accepted.connect(self.accept) self.win.show() self.win.raise_() - def save(self): + def accept(self): try: name = self.win.textValue() @@ -63,18 +69,30 @@ def save(self): except: name = self.object.name - self.savePressed.emit(name) + self.accepted.emit(name) + if self.auto_close: + self.close() + + def setSaveDialog(self, dialog): + self.dialog = dialog + + def reject(self): + self.rejected.emit() self.close() class BaseOkWindowWidget(QtGui.QWidget): - okPressed = QtCore.Signal() + accepted = QtCore.Signal() + rejected = QtCore.Signal() - def __init__(self): + def __init__(self, parent=None): super(BaseOkWindowWidget, self).__init__() self.object = None self.preview = None + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.reject) # Layout to add new elements self.vertical_layout = QtGui.QVBoxLayout() @@ -82,11 +100,11 @@ def __init__(self): # Save and Cancel Buttons cancel_button = QtGui.QPushButton() cancel_button.setText("Cancel") - cancel_button.clicked.connect(lambda: self.close()) + cancel_button.clicked.connect(self.reject) ok_button = QtGui.QPushButton() ok_button.setText("OK") - ok_button.clicked.connect(self.okClicked) + ok_button.clicked.connect(self.accept) ok_cancel_row = QtGui.QHBoxLayout() ok_cancel_row.addWidget(cancel_button) @@ -105,6 +123,10 @@ def setPreview(self, preview): self.preview = preview self.vertical_layout.insertWidget(0, self.preview) - def okClicked(self): - self.okPressed.emit() + def accept(self): + self.accepted.emit() + self.close() + + def reject(self): + self.rejected.emit() self.close() diff --git a/cdatgui/cdat/plotter.py b/cdatgui/cdat/plotter.py index c8f4be9..580526d 100644 --- a/cdatgui/cdat/plotter.py +++ b/cdatgui/cdat/plotter.py @@ -105,6 +105,8 @@ def init_layout(self): self.newIcon = None self.setLayout(self.dataLayout) self.dataLayout = None + self.manager.graphics_method = (vcs.getboxfill('default'), False) + self.manager.template = (vcs.gettemplate('default'), False) self.initialized.emit() def variableSync(self, variables): @@ -124,6 +126,7 @@ def __init__(self, source): self._gm = None self._vars = None self._template = None + self._type = None def name(self): if self.can_plot() is False: @@ -141,13 +144,13 @@ def name(self): except AttributeError: vars.append(v.id) - gm_type = vcs.graphicsmethodtype(self._gm) vars = " x ".join(vars) - return "%s (%s)" % (vars, gm_type) + return "%s (%s)" % (vars, self._type) def load(self, display): self.dp = display - self._gm = vcs.getgraphicsmethod(display.g_type, display.g_name) + self._gm = display.g_name + self._type = display.g_type self._vars = display.array self._template = vcs.gettemplate(display._template_origin) @@ -156,17 +159,22 @@ def can_plot(self): @property def canvas(self): - # print "MANAGER CANVAS:", self.source.canvas return self.source.canvas def gm(self): - return self._gm + return vcs.getgraphicsmethod(self._type, self._gm) def set_gm(self, gm): - # check gm vs vars - self._gm = gm - if self.can_plot(): + plot = True + if isinstance(gm, tuple): + plot = gm[1] + gm = gm[0] + + self._gm = gm.name + self._type = vcs.graphicsmethodtype(gm) + if plot and self.can_plot(): self.plot() + self.source.gm_label.setText(self._gm) graphics_method = property(gm, set_gm) @@ -174,6 +182,10 @@ def get_vars(self): return self._vars def set_vars(self, v): + plot = True + if len(v) > 2: + plot = v[2] + v = v[:2] try: self._vars = (v[0], v[1]) except TypeError: @@ -189,9 +201,19 @@ def set_vars(self, v): else: new_vars.append(var) self._vars = new_vars - if self.can_plot(): + if plot and self.can_plot(): self.plot() + valid_vars = [] + for v in self._vars: + try: + if v.all(): + valid_vars.append(v) + except AttributeError: + continue + + self.source.variableSync(valid_vars) + variables = property(get_vars, set_vars) def templ(self): @@ -199,10 +221,17 @@ def templ(self): def set_templ(self, template): # Check if gm supports templates + plot = True + if isinstance(template, tuple): + plot = template[1] + template = template[0] + self._template = template - if self.can_plot(): + if plot and self.can_plot(): self.plot() + self.source.tmpl_label.setText(self.template.name) + template = property(templ, set_templ) def remove(self): @@ -229,7 +258,21 @@ def plot(self): self.dp.g_type = vcs.graphicsmethodtype(self.graphics_method) # Update the canvas - self.canvas.update() + # self.canvas.update() use this once update is not broken + args = [] + for var in self.variables: + if var is not None: + args.append(var) + if self.template is not None: + args.append(self.template.name) + else: + args.append("default") + if self.graphics_method is not None: + args.append(vcs.graphicsmethodtype(self.graphics_method)) + args.append(self.graphics_method.name) + + self.canvas.clear(preserve_display=True, render=False) + self.dp = self.canvas.plot(*args) else: args = [] @@ -247,4 +290,5 @@ def plot(self): if self.template is None: self._template = vcs.gettemplate(self.dp._template_origin) if self.graphics_method is None: - self._gm = vcs.getgraphicsmethod(self.dp.g_type, self.dp.g_name) + self._gm = self.dp.g_name + self._type = self.dp.g_type diff --git a/cdatgui/cdat/vcswidget.py b/cdatgui/cdat/vcswidget.py index d64c44f..69d46ca 100644 --- a/cdatgui/cdat/vcswidget.py +++ b/cdatgui/cdat/vcswidget.py @@ -31,6 +31,7 @@ def __init__(self, parent=None): self.visibilityChanged.connect(self.manageCanvas) self.displays = [] + self.timer = None self.to_plot = [] def plot(self, *args, **kwargs): @@ -58,6 +59,8 @@ def update(self): self.canvas.update() def manageCanvas(self, showing): + self.timer.deleteLater() + self.timer = None """Make sure that the canvas isn't opened till we're really ready.""" if showing and self.canvas is None: self.canvas = vcs.init(backend=self.mRenWin) @@ -75,15 +78,25 @@ def manageCanvas(self, showing): self.canvas.onClosing((0, 0)) self.canvas = None + def doTimer(self, func): + if self.timer is not None: + self.timer.stop() + self.timer.deleteLater() + self.timer = None + self.timer = QtCore.QTimer(parent=self) + self.timer.setSingleShot(True) + self.timer.timeout.connect(func) + self.timer.start(0) + def showEvent(self, e): "Handle twitchy VTK resources appropriately." super(QVCSWidget, self).showEvent(e) - QtCore.QTimer.singleShot(0, self.becameVisible) + self.doTimer(self.becameVisible) def hideEvent(self, e): "Handle twitchy VTK resources appropriately." super(QVCSWidget, self).hideEvent(e) - QtCore.QTimer.singleShot(0, self.becameHidden) + self.doTimer(self.becameHidden) def deleteLater(self): """ @@ -93,6 +106,8 @@ def deleteLater(self): deallocating. Overriding PyQt deleteLater to free up resources """ + if self.timer is not None: + self.timer.stop() if self.canvas: self.canvas.onClosing((0, 0)) self.canvas = None diff --git a/cdatgui/console/console_dock.py b/cdatgui/console/console_dock.py index 3db307a..3e58087 100644 --- a/cdatgui/console/console_dock.py +++ b/cdatgui/console/console_dock.py @@ -11,8 +11,10 @@ def __init__(self, spreadsheet, parent=None): self.console.createdPlot.connect(self.added_plot) self.console.createdPlot.connect(spreadsheet.tabController.currentWidget().totalPlotsChanged) - self.console.updatedVar.connect(spreadsheet.tabController.currentWidget().totalPlotsChanged) + self.console.updatedVar.connect(spreadsheet.tabController.currentWidget().replotPlottersUpdateVars) + self.console.checkDisplayPlots.connect(spreadsheet.tabController.currentWidget().checkDisplayPlots) spreadsheet.emitAllPlots.connect(self.updateAllPlots) + spreadsheet.tabController.currentWidget().plotsRemoved.connect(self.clearPlots) self.setWidget(self.console) def added_plot(self, displayplot): @@ -32,3 +34,9 @@ def updateAllPlots(self, cells): plots.extend(plotter) self.plots = plots self.console.updateAllPlots(self.plots) + + def clearPlots(self, removed_dps): + print "clearing plots" + for plot in self.plots: + if plot.dp in removed_dps: + print "removed plot", plot diff --git a/cdatgui/console/console_widget.py b/cdatgui/console/console_widget.py index dfb638e..6ac1a99 100644 --- a/cdatgui/console/console_widget.py +++ b/cdatgui/console/console_widget.py @@ -8,7 +8,7 @@ from qtconsole.rich_jupyter_widget import RichJupyterWidget from cdatgui.cdat.metadata import FileMetadataWrapper, VariableMetadataWrapper -from cdatgui.variables import get_variables +from cdatgui.variables import get_variables, reserved_words def is_cdms_var(v): @@ -22,6 +22,7 @@ def is_displayplot(v): class ConsoleWidget(QtGui.QWidget): createdPlot = QtCore.Signal(object) updatedVar = QtCore.Signal() + checkDisplayPlots = QtCore.Signal(list) def __init__(self, parent=None): super(ConsoleWidget, self).__init__() @@ -37,10 +38,6 @@ def __init__(self, parent=None): self.shell_vars = {} self.gm_count = {} self.letters = list(string.ascii_uppercase) - self.reserved_words = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global', 'or', 'with', - 'assert', 'else', 'if', 'pass', 'yield', 'break', 'except', 'import', 'print', 'class', - 'exec', 'in', 'raise', 'continue', 'finally', 'is', 'return', 'def', 'for', 'lambda', - 'try'] # Create ipython widget self.kernel_manager = QtInProcessKernelManager() @@ -50,7 +47,7 @@ def __init__(self, parent=None): self.kernel_client = self.kernel_manager.client() self.kernel_client.start_channels() - self.kernel_client.execute("import vcs, cdms2", silent=True) + self.kernel_client.execute("import vcs, cdms2, cdutil, numpy", silent=True) self.jupyter_widget = RichJupyterWidget() self.jupyter_widget.kernel_manager = self.kernel_manager @@ -148,6 +145,7 @@ def updateAllPlots(self, plots): def codeExecuted(self, *varargs): namespace = self.kernel.shell.user_ns cur_keys = set(namespace) + variable_updated = False # get last output out_dict = namespace["Out"] @@ -170,7 +168,7 @@ def codeExecuted(self, *varargs): self.variable_list.add_variable(cdms_var) else: self.variable_list.update_variable(cdms_var, key) - self.updatedVar.emit() + variable_updated = True elif is_displayplot(value) and value not in self.display_plots: self.display_plots.append(value) @@ -180,10 +178,15 @@ def codeExecuted(self, *varargs): self.display_plots.append(last_line) self.createdPlot.emit(last_line) + self.checkDisplayPlots.emit(self.display_plots) + + if variable_updated: + self.updatedVar.emit() + def fixInvalidVariables(self, var): var = re.sub(' +', '_', var) var = re.sub("[^a-zA-Z0-9_]+", '', var) - if var in self.reserved_words or not re.match("^[a-zA-Z_]", var): + if var in reserved_words() or not re.match("^[a-zA-Z_]", var): var = 'cdat_' + var return var diff --git a/cdatgui/editors/axis_editor.py b/cdatgui/editors/axis_editor.py index 4ee84e7..7c22d4d 100644 --- a/cdatgui/editors/axis_editor.py +++ b/cdatgui/editors/axis_editor.py @@ -56,20 +56,19 @@ def __init__(self, axis, parent=None): self.tickmark_button_group.buttonClicked.connect(self.updateTickmark) # create preset combo box - # This in only being tracked for debugging - preset_box = QtGui.QComboBox() - preset_box.addItem("default") - preset_box.addItems(vcs.listelements("list")) - preset_box.currentIndexChanged[str].connect(self.updatePreset) + self.preset_box = QtGui.QComboBox() + self.preset_box.addItem("default") + self.preset_box.addItems(vcs.listelements("list")) + self.preset_box.currentIndexChanged[str].connect(self.updatePreset) preset_row.addWidget(preset_label) - preset_row.addWidget(preset_box) + preset_row.addWidget(self.preset_box) # create slider for Ticks self.ticks_slider = QtGui.QSlider() - self.ticks_slider.setRange(1, 100) + self.ticks_slider.setRange(2, 100) self.ticks_slider.setOrientation(QtCore.Qt.Horizontal) - self.ticks_slider.sliderMoved.connect(self.updateTicks) + self.ticks_slider.valueChanged.connect(self.updateTicks) # create step edit box step_validator = QtGui.QDoubleValidator() @@ -90,18 +89,18 @@ def __init__(self, axis, parent=None): ticks_row.addWidget(self.step_edit) # create show mini ticks check box - show_mini_check_box = QtGui.QCheckBox() - show_mini_check_box.stateChanged.connect(self.updateShowMiniTicks) + self.show_mini_check_box = QtGui.QCheckBox() + self.show_mini_check_box.stateChanged.connect(self.updateShowMiniTicks) # create mini tick spin box - mini_tick_box = QtGui.QSpinBox() - mini_tick_box.setRange(0, 255) - mini_tick_box.valueChanged.connect(self.updateMiniTicks) + self.mini_tick_box = QtGui.QSpinBox() + self.mini_tick_box.setRange(0, 255) + self.mini_tick_box.valueChanged.connect(self.updateMiniTicks) mini_ticks_row.addWidget(show_mini_label) - mini_ticks_row.addWidget(show_mini_check_box) + mini_ticks_row.addWidget(self.show_mini_check_box) mini_ticks_row.addWidget(mini_per_tick_label) - mini_ticks_row.addWidget(mini_tick_box) + mini_ticks_row.addWidget(self.mini_tick_box) self.adjuster_layout = QtGui.QVBoxLayout() @@ -122,21 +121,42 @@ def setPreview(self, preview): self.preview.setMinimumWidth(150) self.preview.setMaximumWidth(350) - if self.axis == "y": + if self.axis[0] == "y": self.horizontal_layout.insertWidget(0, self.preview) - elif self.axis == "x": + elif self.axis[0] == "x": self.adjuster_layout.insertWidget(0, self.preview) def setAxisObject(self, axis_obj): self.object = axis_obj self.preview.setAxisObject(self.object) + if self.object.numticks: + if self.object.is_positive(): + self.negative_check.setChecked(False) + else: + self.negative_check.setChecked(True) + self.updateTicks(self.object.numticks) + block = self.ticks_slider.blockSignals(True) + self.ticks_slider.setValue(self.object.numticks) + self.ticks_slider.blockSignals(block) + self.show_mini_check_box.setChecked(self.object.show_miniticks) + self.mini_tick_box.setValue(self.object.minitick_count) + + # set initial mode + for button in self.tickmark_button_group.buttons(): + if isinstance(self.object.ticks, dict) and button.text() == 'Even': + button.click() + elif self.object.ticks == '*' and button.text() == 'Manual': + button.setEnabled(False) + self.preview.update() + self.accepted.connect(self.object.save) + self.rejected.connect(self.object.cancel) # Update mode essentially def updateTickmark(self, button): - if self.axis == "x": + if self.axis[0] == "x": index = 2 - elif self.axis == "y": + elif self.axis[0] == "y": index = 1 while self.adjuster_layout.count() > index + 1: widget = self.adjuster_layout.takeAt(index).widget() @@ -145,16 +165,22 @@ def updateTickmark(self, button): if button.text() == "Auto": self.adjuster_layout.insertWidget(index, self.preset_widget) self.preset_widget.setVisible(True) + self.updatePreset(self.preset_box.currentText()) elif button.text() == "Even": self.adjuster_layout.insertWidget(index, self.ticks_widget) self.ticks_widget.setVisible(True) self.state = "count" + self.mini_tick_box.setEnabled(True) + self.show_mini_check_box.setEnabled(True) + for button in self.tickmark_button_group.buttons(): + if button.text() == 'Manual': + button.setEnabled(True) elif button.text() == "Manual": self.adjuster_layout.insertWidget(index, self.scroll_area) self.dict_widget.setDict(self.object.ticks_as_dict()) - self.dict_widget.setVisible(True) + self.scroll_area.setVisible(True) self.object.mode = button.text().lower() self.preview.update() @@ -162,8 +188,19 @@ def updateTickmark(self, button): def updatePreset(self, preset): if preset == "default": self.object.ticks = "*" + self.mini_tick_box.setEnabled(False) + self.show_mini_check_box.setEnabled(False) + for button in self.tickmark_button_group.buttons(): + if button.text() == 'Manual': + button.setEnabled(False) else: self.object.ticks = preset + self.mini_tick_box.setEnabled(True) + self.show_mini_check_box.setEnabled(True) + for button in self.tickmark_button_group.buttons(): + if button.text() == 'Manual': + button.setEnabled(True) + self.preview.update() def updateShowMiniTicks(self, state): @@ -197,7 +234,9 @@ def updateStep(self): self.negative_check.setCheckState(QtCore.Qt.Unchecked) self.object.step = cur_val self.state = "step" + block = self.ticks_slider.blockSignals(True) self.ticks_slider.setValue(self.object.numticks) + self.ticks_slider.blockSignals(block) self.preview.update() diff --git a/cdatgui/editors/boxfill.py b/cdatgui/editors/boxfill.py index 6252146..510d654 100644 --- a/cdatgui/editors/boxfill.py +++ b/cdatgui/editors/boxfill.py @@ -1,27 +1,17 @@ from PySide import QtGui, QtCore from collections import OrderedDict -from level_editor import LevelEditor -from widgets.legend_widget import LegendEditorWidget -from model.legend import VCSLegend -from axis_editor import AxisEditorWidget -from model.vcsaxis import VCSAxis +from .graphics_method_editor import GraphicsMethodEditorWidget -class BoxfillEditor(QtGui.QWidget): - """Configures a boxfill graphics method.""" - graphicsMethodUpdated = QtCore.Signal(object) +class BoxfillEditor(GraphicsMethodEditorWidget): + """Configures a boxfill graphics method.""" def __init__(self, parent=None): """Initialize the object.""" super(BoxfillEditor, self).__init__(parent=parent) - self._gm = None - self.var = None - self.tmpl = None - - layout = QtGui.QVBoxLayout() - self.setLayout(layout) + self.orig_type = None self.boxfill_types = OrderedDict( Linear="linear", Logarithmic="log10", @@ -37,84 +27,10 @@ def __init__(self, parent=None): button_layout.addWidget(radiobutton) self.type_group.addButton(radiobutton) - layout.addLayout(button_layout) - - levels_button = QtGui.QPushButton("Edit Levels") - levels_button.clicked.connect(self.editLevels) - legend_button = QtGui.QPushButton("Edit Legend") - legend_button.clicked.connect(self.editLegend) - - left_axis = QtGui.QPushButton("Edit Left Ticks") - left_axis.clicked.connect(self.editLeft) - right_axis = QtGui.QPushButton("Edit Right Ticks") - right_axis.clicked.connect(self.editRight) - bottom_axis = QtGui.QPushButton("Edit Bottom Ticks") - bottom_axis.clicked.connect(self.editBottom) - top_axis = QtGui.QPushButton("Edit Top Ticks") - top_axis.clicked.connect(self.editTop) - - layout.addWidget(levels_button) - layout.addWidget(legend_button) - layout.addWidget(left_axis) - layout.addWidget(right_axis) - layout.addWidget(top_axis) - layout.addWidget(bottom_axis) - - self.level_editor = None - self.legend_editor = None - self.axis_editor = None self.type_group.buttonClicked.connect(self.setBoxfillType) - def editAxis(self, axis): - if self.axis_editor is None: - self.axis_editor = AxisEditorWidget(axis[0]) - self.axis_editor.okPressed.connect(self.updated) - axis = VCSAxis(self._gm, self.tmpl, axis, self.var) - self.axis_editor.setAxisObject(axis) - self.axis_editor.show() - self.axis_editor.raise_() - - def editLeft(self): - self.editAxis("y1") - - def editRight(self): - self.editAxis("y2") - - def editBottom(self): - self.editAxis("x1") - - def editTop(self): - self.editAxis("x2") - - def editLevels(self): - """Edit the levels of this GM.""" - if self.level_editor is None: - self.level_editor = LevelEditor() - self.level_editor.levelsUpdated.connect(self.updated) - self.level_editor.gm = self.gm - self.level_editor.var = self.var.var - self.level_editor.show() - self.level_editor.raise_() - - def editLegend(self): - if self.legend_editor is None: - self.legend_editor = LegendEditorWidget() - self.legend_editor.okPressed.connect(self.updated) - legend = VCSLegend(self.gm, self.var.var) - self.legend_editor.setObject(legend) - self.legend_editor.show() - self.legend_editor.raise_() - - def updated(self): - if self.legend_editor is not None: - self.legend_editor = None - if self.axis_editor is not None: - self.axis_editor = None - if self.level_editor is not None: - self.level_editor = None - print "Emitting updated" - self.graphicsMethodUpdated.emit(self._gm) - print "Updated" + self.button_layout.insertLayout(0, button_layout) + self.levels_button.setEnabled(False) @property def gm(self): @@ -123,14 +39,21 @@ def gm(self): @gm.setter def gm(self, value): - """GM setter.""" self._gm = value + self.orig_type = self._gm.boxfill_type type_real_vals = self.boxfill_types.values() index = type_real_vals.index(value.boxfill_type) - self.type_group.buttons()[index].setChecked(True) + button = self.type_group.buttons()[index] + button.click() + self.setBoxfillType(button) def setBoxfillType(self, radio): """Take in a radio button and set the GM boxfill_type.""" + if radio.text() == 'Custom': + self.levels_button.setEnabled(True) + else: + self.levels_button.setEnabled(False) box_type = self.boxfill_types[radio.text()] + self._gm.boxfill_type = box_type - self.graphicsMethodUpdated.emit(self._gm) + diff --git a/cdatgui/editors/cdat1d.py b/cdatgui/editors/cdat1d.py new file mode 100644 index 0000000..385bd4f --- /dev/null +++ b/cdatgui/editors/cdat1d.py @@ -0,0 +1,87 @@ +from PySide import QtGui, QtCore +from .graphics_method_editor import GraphicsMethodEditorWidget +from .secondary.editor.marker import MarkerEditorWidget +from .secondary.editor.line import LineEditorWidget +import vcs + +class Cdat1dEditor(GraphicsMethodEditorWidget): + """Configures a meshfill graphics method.""" + + def __init__(self, parent=None): + """Initialize the object.""" + super(Cdat1dEditor, self).__init__(parent=parent) + + self.button_layout.takeAt(0).widget().deleteLater() + self.button_layout.takeAt(0).widget().deleteLater() + + self.flip_check = QtGui.QCheckBox() + self.flip_check.stateChanged.connect(self.flipGraph) + + flip_layout = QtGui.QHBoxLayout() + flip_layout.addWidget(QtGui.QLabel("Flip")) + flip_layout.addWidget(self.flip_check) + flip_layout.addStretch(1) + + marker_button = QtGui.QPushButton("Edit Marker") + marker_button.clicked.connect(self.editMarker) + + line_button = QtGui.QPushButton("Edit Line") + line_button.clicked.connect(self.editLine) + + self.button_layout.insertWidget(0, line_button) + self.button_layout.insertWidget(0, marker_button) + self.button_layout.insertLayout(0, flip_layout) + + self.marker_editor = None + self.line_editor = None + + def editMarker(self): + if self.marker_editor: + self.marker_editor.close() + self.marker_editor.deleteLater() + self.marker_editor = MarkerEditorWidget() + self.marker_editor.accepted.connect(self.updateMarker) + mark_obj = vcs.createmarker(mtype=self.gm.marker, color=self.gm.markercolor, size=self.gm.markersize) + self.marker_editor.setMarkerObject(mark_obj) + self.marker_editor.raise_() + self.marker_editor.show() + + def editLine(self): + if self.line_editor: + self.line_editor.close() + self.line_editor.deleteLater() + self.line_editor = LineEditorWidget() + self.line_editor.accepted.connect(self.updateLine) + if self.gm.linewidth < 1: + self.gm.linewidth = 1 + line_obj = vcs.createline(ltype=self.gm.line, color=self.gm.linecolor, width=self.gm.linewidth) + self.line_editor.setLineObject(line_obj) + self.line_editor.raise_() + self.line_editor.show() + + def updateMarker(self, name): + self.gm.marker = self.marker_editor.object.type[0] + self.gm.markercolor = self.marker_editor.object.color[0] + self.gm.markersize = self.marker_editor.object.size[0] + + def updateLine(self, name): + self.gm.line = self.line_editor.object.type[0] + self.gm.linecolor = self.line_editor.object.color[0] + self.gm.linewidth = self.line_editor.object.width[0] + + def flipGraph(self, state): + if state == QtCore.Qt.Checked: + self.gm.flip = True + elif state == QtCore.Qt.Unchecked: + self.gm.flip = False + + @property + def gm(self): + """GM property.""" + return self._gm + + @gm.setter + def gm(self, value): + """GM setter.""" + self._gm = value + self.flip_check.setChecked(value.flip) diff --git a/cdatgui/editors/colormap.py b/cdatgui/editors/colormap.py index e12c91b..de40be1 100644 --- a/cdatgui/editors/colormap.py +++ b/cdatgui/editors/colormap.py @@ -12,6 +12,7 @@ class QColormapEditor(QtGui.QColorDialog): def __init__(self, mode=COLORMAP_MODE, parent=None): QtGui.QColorDialog.__init__(self, parent) + self.setWindowModality(QtCore.Qt.ApplicationModal) self.parent = parent self.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) self.setOption(QtGui.QColorDialog.NoButtons) diff --git a/cdatgui/editors/graphics_method_editor.py b/cdatgui/editors/graphics_method_editor.py new file mode 100644 index 0000000..0c36403 --- /dev/null +++ b/cdatgui/editors/graphics_method_editor.py @@ -0,0 +1,128 @@ +from PySide import QtGui, QtCore +import vcs + +from cdatgui.editors.model.legend import VCSLegend +from cdatgui.editors.widgets.legend_widget import LegendEditorWidget +from cdatgui.editors.projection_editor import ProjectionEditor +from level_editor import LevelEditor +from axis_editor import AxisEditorWidget +from model.vcsaxis import VCSAxis + + +class GraphicsMethodEditorWidget(QtGui.QWidget): + """Configures a boxfill graphics method.""" + + graphicsMethodUpdated = QtCore.Signal(object) + + def __init__(self, parent=None): + """Initialize the object.""" + super(GraphicsMethodEditorWidget, self).__init__(parent=parent) + self._gm = None + self.var = None + self.tmpl = None + + self.button_layout = QtGui.QVBoxLayout() + self.setLayout(self.button_layout) + + self.levels_button = QtGui.QPushButton("Edit Levels") + self.levels_button.clicked.connect(self.editLevels) + self.levels_button.setDefault(False) + self.levels_button.setAutoDefault(False) + self.legend_button = QtGui.QPushButton("Edit Legend") + self.legend_button.clicked.connect(self.editLegend) + self.legend_button.setAutoDefault(False) + left_axis = QtGui.QPushButton("Edit Left Ticks") + left_axis.clicked.connect(self.editLeft) + right_axis = QtGui.QPushButton("Edit Right Ticks") + right_axis.clicked.connect(self.editRight) + bottom_axis = QtGui.QPushButton("Edit Bottom Ticks") + bottom_axis.clicked.connect(self.editBottom) + top_axis = QtGui.QPushButton("Edit Top Ticks") + top_axis.clicked.connect(self.editTop) + projection = QtGui.QPushButton('Edit Projection') + projection.clicked.connect(self.editProjection) + + self.button_layout.addWidget(self.levels_button) + self.button_layout.addWidget(self.legend_button) + self.button_layout.addWidget(left_axis) + self.button_layout.addWidget(right_axis) + self.button_layout.addWidget(top_axis) + self.button_layout.addWidget(bottom_axis) + self.button_layout.addWidget(projection) + + self.level_editor = None + self.legend_editor = None + self.axis_editor = None + self.projection_editor = None + + def editAxis(self, axis): + self.axis_editor = AxisEditorWidget(axis) + self.axis_editor.accepted.connect(self.updated) + self.axis_editor.rejected.connect(self.updated) + axis = VCSAxis(self.gm, self.tmpl, axis, self.var) + self.axis_editor.setAxisObject(axis) + self.axis_editor.show() + self.axis_editor.raise_() + + def editLeft(self): + self.editAxis("y1") + + def editRight(self): + self.editAxis("y2") + + def editBottom(self): + self.editAxis("x1") + + def editTop(self): + self.editAxis("x2") + + def editLevels(self): + """Edit the levels of this GM.""" + self.level_editor = LevelEditor() + self.level_editor.levelsUpdated.connect(self.updated) + self.level_editor.gm = self.gm + self.level_editor.var = self.var.var + self.level_editor.show() + self.level_editor.raise_() + + def updated(self): + if self.legend_editor is not None: + self.legend_editor.deleteLater() + self.legend_editor = None + if self.axis_editor is not None: + self.axis_editor.deleteLater() + self.axis_editor = None + if self.level_editor is not None: + self.level_editor.deleteLater() + self.level_editor = None + if self.projection_editor is not None: + self.projection_editor.deleteLater() + self.projection_editor = None + + @property + def gm(self): + """GM property.""" + return self._gm + + @gm.setter + def gm(self, value): + """GM setter.""" + self._gm = value + + def editLegend(self): + self.legend_editor = LegendEditorWidget() + self.legend_editor.accepted.connect(self.updated) + self.legend_editor.rejected.connect(self.updated) + legend = VCSLegend(self.gm, self.var.var) + self.legend_editor.setObject(legend) + self.legend_editor.show() + self.legend_editor.raise_() + + def editProjection(self): + self.projection_editor = ProjectionEditor() + self.projection_editor.rejected.connect(self.updated) + self.projection_editor.accepted.connect(self.updated) + self.projection_editor.setProjectionObject(vcs.getprojection(self.gm.projection)) + self.projection_editor.gm = self.gm + self.projection_editor.show() + self.projection_editor.raise_() diff --git a/cdatgui/editors/isofill.py b/cdatgui/editors/isofill.py new file mode 100644 index 0000000..8dacb87 --- /dev/null +++ b/cdatgui/editors/isofill.py @@ -0,0 +1,9 @@ +from .graphics_method_editor import GraphicsMethodEditorWidget + + +class IsofillEditor(GraphicsMethodEditorWidget): + """Configures a Isofill graphics method.""" + + def __init__(self, parent=None): + """Initialize the object.""" + super(IsofillEditor, self).__init__(parent=parent) diff --git a/cdatgui/editors/isoline.py b/cdatgui/editors/isoline.py new file mode 100644 index 0000000..046a57a --- /dev/null +++ b/cdatgui/editors/isoline.py @@ -0,0 +1,88 @@ +from PySide import QtGui, QtCore + +from .graphics_method_editor import GraphicsMethodEditorWidget +from .widgets.multi_line_editor import MultiLineEditor +from .model.isoline_model import IsolineModel +from .widgets.multi_text_editor import MultiTextEditor + + +class IsolineEditor(GraphicsMethodEditorWidget): + """Configures a meshfill graphics method.""" + + def __init__(self, parent=None): + """Initialize the object.""" + super(IsolineEditor, self).__init__(parent=parent) + self._var = None + + self.edit_label_button = QtGui.QPushButton('Edit Labels') + self.edit_label_button.clicked.connect(self.editText) + + edit_line_button = QtGui.QPushButton('Edit Lines') + edit_line_button.clicked.connect(self.editLines) + + self.label_check = QtGui.QCheckBox() + self.label_check.stateChanged.connect(self.updateLabel) + + label = QtGui.QLabel('Label') + + label_layout = QtGui.QHBoxLayout() + label_layout.addWidget(label) + label_layout.addWidget(self.label_check) + label_layout.addStretch(1) + + self.button_layout.insertWidget(0, edit_line_button) + self.button_layout.insertWidget(0, self.edit_label_button) + self.button_layout.insertLayout(0, label_layout) + + self.text_edit_widget = None + self.line_edit_widget = None + + self.legend_button.setEnabled(False) + self.legend_button.hide() + + def editText(self): + if self.text_edit_widget: + self.text_edit_widget.close() + self.text_edit_widget.deleteLater() + self.text_edit_widget = MultiTextEditor() + self.text_edit_widget.setObject(IsolineModel(self._gm, self._var)) + self.text_edit_widget.show() + self.text_edit_widget.raise_() + + def editLines(self): + if self.line_edit_widget: + self.line_edit_widget.close() + self.line_edit_widget.deleteLater() + self.line_edit_widget = MultiLineEditor() + self.line_edit_widget.setObject(IsolineModel(self._gm, self._var)) + self.line_edit_widget.show() + self.line_edit_widget.raise_() + + def updateLabel(self, state): + if state == QtCore.Qt.Unchecked: + self._gm.label = False + self.edit_label_button.setEnabled(False) + elif state == QtCore.Qt.Checked: + self._gm.label = True + self.edit_label_button.setEnabled(True) + + @property + def var(self): + return self._var + + @var.setter + def var(self, v): + self._var = v + + @property + def gm(self): + """GM property.""" + return self._gm + + @gm.setter + def gm(self, value): + """GM setter.""" + self._gm = value + self.label_check.setChecked(self._gm.label) + self.edit_label_button.setEnabled(self._gm.label) + diff --git a/cdatgui/editors/level_editor.py b/cdatgui/editors/level_editor.py index a358cc4..4fee5de 100644 --- a/cdatgui/editors/level_editor.py +++ b/cdatgui/editors/level_editor.py @@ -1,14 +1,16 @@ """Provides a widget to manipulate the levels for a graphics method.""" +from bisect import bisect_left from cdatgui.cdat.vcswidget import QVCSWidget from PySide import QtCore, QtGui from .widgets.adjust_values import AdjustValues +from cdatgui.bases.window_widget import BaseOkWindowWidget import vcsaddons import vcs import numpy -class LevelEditor(QtGui.QWidget): +class LevelEditor(BaseOkWindowWidget): """Uses DictEditor to select levels for a GM and displays a histogram.""" levelsUpdated = QtCore.Signal() @@ -19,33 +21,29 @@ def __init__(self, parent=None): self._var = None self._gm = None + self.setWindowModality(QtCore.Qt.ApplicationModal) + self.canvas = QVCSWidget() self.value_sliders = AdjustValues() self.value_sliders.valuesChanged.connect(self.update_levels) - layout = QtGui.QVBoxLayout() - layout.addWidget(self.canvas) - layout.addWidget(self.value_sliders) - self.setLayout(layout) + self.vertical_layout.insertWidget(0,self.canvas) + self.vertical_layout.insertWidget(1, self.value_sliders) + self.setLayout(self.vertical_layout) self.histo = vcsaddons.histograms.Ghg() - self.reset = QtGui.QPushButton(u"Cancel") - self.reset.clicked.connect(self.reset_levels) - - self.apply = QtGui.QPushButton(u"Apply") - self.apply.clicked.connect(self.levelsUpdated.emit) - self.orig_levs = None - button_layout = QtGui.QHBoxLayout() - layout.addLayout(button_layout) - button_layout.addWidget(self.reset) - button_layout.addWidget(self.apply) - + self.rejected.connect(self.reset_levels) + self.accepted.connect(self.updated_levels) def reset_levels(self): + self.close() self.gm.levels = self.orig_levs - self.update_levels(self.gm.levels) + self.levelsUpdated.emit() + + def updated_levels(self): + self.close() self.levelsUpdated.emit() def update_levels(self, levs, clear=False): @@ -64,18 +62,35 @@ def var(self): @var.setter def var(self, value): self._var = value - flat = self._var.flatten() + flat = self._var.data + flat = sorted(numpy.unique(flat.flatten())) + var_min, var_max = vcs.minmax(flat) + # Check if we're using auto levels - if self._gm is None or not self.has_set_gm_levels(): + if vcs.graphicsmethodtype(self._gm) =='isoline' and not self.isoline_has_set_gm_levels(): + levs = vcs.utils.mkscale(var_min, var_max) + elif self._gm is None or not self.has_set_gm_levels(): # Update the automatic levels with this variable levs = vcs.utils.mkscale(var_min, var_max) else: # Otherwise, just use what the levels are levs = self._gm.levels + if isinstance(levs[0], list): + levs = [item[0] for item in levs] + try: + step = (levs[-1] - levs[0])/1000 + values = list(numpy.arange(levs[0], levs[-1]+step, step)) + except: + step = (levs[-1][0] - levs[0][0])/1000 + values = list(numpy.arange(levs[0][0], levs[-1][0]+step, step)) + + for lev in levs: + if lev not in values: + values.insert(bisect_left(values, lev), lev) self.canvas.clear() - self.value_sliders.update(var_min, var_max, levs) + self.value_sliders.update(values, levs) self.update_levels(levs, clear=True) @property @@ -89,10 +104,19 @@ def gm(self, value): if self.has_set_gm_levels() and self.var is not None: levs = self._gm.levels flat = self._var.flatten() - var_min, var_max = vcs.minmax(flat) - self.value_sliders.update(var_min, var_max, levs) + self.value_sliders.update(flat, levs) self.update_levels(levs, clear=True) - def has_set_gm_levels(self): - return len(self._gm.levels) != 2 or not numpy.allclose(self._gm.levels, [1e+20] * 2) + try: + length = len(self._gm.levels[0]) + except: + length = len(self._gm.levels) + try: + return length != 2 or not numpy.allclose(self._gm.levels, [1e+20] * 2) + except ValueError: + return True + + def isoline_has_set_gm_levels(self): + length = len(self._gm.levels[0]) + return length != 2 or not numpy.allclose(self._gm.levels, [0.0, 1e+20]) diff --git a/cdatgui/editors/meshfill.py b/cdatgui/editors/meshfill.py new file mode 100644 index 0000000..de4f487 --- /dev/null +++ b/cdatgui/editors/meshfill.py @@ -0,0 +1,20 @@ +from .graphics_method_editor import GraphicsMethodEditorWidget + +class MeshfillEditor(GraphicsMethodEditorWidget): + """Configures a meshfill graphics method.""" + + def __init__(self, parent=None): + """Initialize the object.""" + super(MeshfillEditor, self).__init__(parent=parent) + + @property + def gm(self): + """GM property.""" + return self._gm + + + @gm.setter + def gm(self, value): + """GM setter.""" + self._gm = value + self._gm.fillareaindices = [1] diff --git a/cdatgui/editors/model/isoline_model.py b/cdatgui/editors/model/isoline_model.py new file mode 100644 index 0000000..799ff8c --- /dev/null +++ b/cdatgui/editors/model/isoline_model.py @@ -0,0 +1,41 @@ +from .levels_base import LevelsBaseModel +from cdatgui.vcsmodel import get_textstyles +import vcs + + +class IsolineModel(LevelsBaseModel): + def __init__(self, gm, var, canvas=None): + self._gm = gm + self._var = var + self._canvas = canvas + self.count = 1 + + @property + def line(self): + while len(self._gm.line) < len(self._gm.levels): + self._gm.line.append(self._gm.line[-1]) + while len(self._gm.line) > len(self._gm.levels): + self._gm.line.remove(self._gm.line[-1]) + return self._gm.line + + @property + def linecolors(self): + return self._gm.linecolors + + @property + def linewidths(self): + return self._gm.linewidths + + @property + def text(self): + if not self._gm.text: + self._gm.text = ['default'] + while len(self._gm.text) < len(self._gm.levels): + self._gm.text.append(self._gm.text[-1]) + while len(self._gm.text) > len(self._gm.levels): + self._gm.text.remove(self._gm.text[-1]) + return self._gm.text + + @property + def textcolors(self): + return self._gm.textcolors diff --git a/cdatgui/editors/model/legend.py b/cdatgui/editors/model/legend.py index d8542de..3a18c07 100644 --- a/cdatgui/editors/model/legend.py +++ b/cdatgui/editors/model/legend.py @@ -1,5 +1,6 @@ import vcs import numpy +from .levels_base import LevelsBaseModel def get_colormaps(): @@ -7,7 +8,7 @@ def get_colormaps(): return sorted(vcs.elements["colormap"].keys()) -class VCSLegend(object): +class VCSLegend(LevelsBaseModel): def __init__(self, gm, var, canvas=None): self._gm = gm self._var = var @@ -37,7 +38,7 @@ def rgba_from_index(self, index): def vcs_colors(self): """Used internally, don't worry about it.""" levs = self.levels - if self._gm.fillareacolors: + if self._gm.fillareacolors and self._gm.fillareacolors != [1]: colors = self._gm.fillareacolors return colors else: @@ -46,7 +47,10 @@ def vcs_colors(self): levs = levs[1:] if self.ext_right: levs = levs[:-1] - colors = vcs.getcolors(levs, colors=range(self.color_1, self.color_2)) + if self.color_1 is None and self.color_2 is None: + colors = vcs.getcolors(levs, split=0) + else: + colors = vcs.getcolors(levs, colors=range(self.color_1, self.color_2)) levs = real_levs if len(colors) < len(levs): # Pad out colors to the right number of buckets @@ -63,11 +67,25 @@ def set_color(self, index, color): @property def fill_style(self): """Use for custom fill's radio buttons.""" - return self._gm.fillareastyle + if hasattr(self._gm, 'fillareastyle'): + return self._gm.fillareastyle + return None @fill_style.setter def fill_style(self, style): self._gm.fillareastyle = style.lower() + self._gm.fillareacolors = self.vcs_colors + self.adjust_to_level_length(self._gm.fillareaindices) + # this should just be temporary until merge of missing level branch + self.adjust_to_level_length(self._gm.fillareaopacity) + self.adjust_to_level_length(self._gm.fillareacolors) + + def adjust_to_level_length(self, lst): + # +1 for invisible level + while len(lst) < len(self.levels)+1: + lst.append(lst[-1]) + while len(lst) > len(self.levels)+1: + lst.pop() @property def color_1(self): @@ -101,44 +119,25 @@ def color_2(self, c): @property def ext_left(self): - return self._gm.ext_1 + if hasattr(self._gm, "ext_1"): + return self._gm.ext_1 + return None @ext_left.setter def ext_left(self, v): - self._gm.ext_1 = v + if hasattr(self._gm, "ext_1"): + self._gm.ext_1 = v @property def ext_right(self): - return self._gm.ext_2 + if hasattr(self._gm, "ext_2"): + return self._gm.ext_2 + return None @ext_right.setter def ext_right(self, v): - self._gm.ext_2 = v - - @property - def levels(self): - """Used internally, don't worry about it.""" - levs = list(self._gm.levels) - # Check if they're autolevels - if numpy.allclose(levs, 1e20): - if vcs.isboxfill(self._gm) == 1: - nlevs = self.color_2 - self.color_1 + 1 - minval, maxval = vcs.minmax(self._var) - levs = vcs.mkscale(minval, maxval) - if len(levs) == 1: - levs.append(levs[0] + .00001) - delta = (levs[-1] - levs[0]) / nlevs - levs = list(numpy.arange(levs[0], levs[-1] + delta, delta)) - else: - levs = vcs.mkscale(*vcs.minmax(self._var)) - - # Now adjust for ext_1 nad ext_2 - if self.ext_left: - levs.insert(0, -1e20) - if self.ext_right: - levs.append(1e20) - - return levs + if hasattr(self._gm, "ext_2"): + self._gm.ext_2 = v @property def level_names(self): @@ -199,7 +198,7 @@ def level_color(self, i): return self.vcs_colors[i] def set_level_color(self, i, v): - if self._gm.fillareacolors is None: + if self._gm.fillareacolors is None or self._gm.fillareacolors == [1]: self._gm.fillareacolors = self.vcs_colors if len(self._gm.fillareacolors) < len(self.levels): self._gm.fillareacolors += (len(self.levels) - len(self._gm.fillareacolors)) * self._gm.fillareacolors[-1:] diff --git a/cdatgui/editors/model/levels_base.py b/cdatgui/editors/model/levels_base.py new file mode 100644 index 0000000..6b0624a --- /dev/null +++ b/cdatgui/editors/model/levels_base.py @@ -0,0 +1,32 @@ +import numpy, vcs + + +class LevelsBaseModel(object): + + @property + def levels(self): + """Used internally, don't worry about it.""" + levs = list(self._gm.levels) + # Check if they're autolevels + if numpy.allclose(levs, 1e20): + if vcs.isboxfill(self._gm) == 1: + nlevs = self.color_2 - self.color_1 + 1 + minval, maxval = vcs.minmax(self._var) + levs = vcs.mkscale(minval, maxval) + if len(levs) == 1: + levs.append(levs[0] + .00001) + delta = (levs[-1] - levs[0]) / nlevs + levs = list(numpy.arange(levs[0], levs[-1] + delta, delta)) + else: + levs = vcs.mkscale(*vcs.minmax(self._var)) + + # Now adjust for ext_1 nad ext_2 + try: + if self.ext_left: + levs.insert(0, -1e20) + if self.ext_right: + levs.append(1e20) + except AttributeError: + pass + + return levs \ No newline at end of file diff --git a/cdatgui/editors/model/vcsaxis.py b/cdatgui/editors/model/vcsaxis.py index 8acfc74..b34f458 100644 --- a/cdatgui/editors/model/vcsaxis.py +++ b/cdatgui/editors/model/vcsaxis.py @@ -1,9 +1,12 @@ import vcs + class VCSAxis(object): def __init__(self, gm, tmpl, axis, var): - self.gm = gm - self.tmpl = tmpl + self.gm = vcs.creategraphicsmethod(vcs.graphicsmethodtype(gm), gm.name) + self.orig_gm_name = gm.name + self.tmpl = vcs.createtemplate(source=tmpl) + self.orig_tmpl_name = tmpl.name self._axis = axis self.var = var @@ -92,8 +95,12 @@ def mode(self): @mode.setter def mode(self, value): - if value == "auto" and isinstance(self.ticks, dict): - self.ticks = "*" + # if value == "auto" and isinstance(self.ticks, dict): + # self.ticks = "*" + if value == 'even' and isinstance(self.ticks, str): + if self.ticks != "*": + step = self.step + self.step = step @property def numticks(self): @@ -113,6 +120,10 @@ def numticks(self, num): step = (right - left) / float(num) self.ticks = {right + n * step: right + n * step for n in range(-1 * num)} + def is_positive(self): + left, right = vcs.minmax(self.axis) + return left in self.ticks + @property def step(self): ticks = self.ticks @@ -120,7 +131,7 @@ def step(self): ticks = vcs.elements["list"][ticks] ticks = sorted(ticks) left, right = vcs.minmax(self.axis) - return (right - left) / len(ticks) + return (right - left) / (len(ticks) - 1) # pretty sure this need to be - @step.setter def step(self, value): @@ -209,6 +220,18 @@ def ticks_as_dict(self): ticks = vcs.elements["list"][ticks] return ticks - def save(self, name): - vcs.elements["list"][name] = self.ticks - vcs.elements["list"][name + "_miniticks"] = self.miniticks + def save(self): + gtype = vcs.graphicsmethodtype(self.gm) + del vcs.elements[gtype][self.orig_gm_name] + vcs.elements[gtype][self.orig_gm_name] = self.gm + del vcs.elements['template'][self.orig_tmpl_name] + vcs.elements['template'][self.orig_tmpl_name] = self.tmpl + + # vcs.elements["list"][name] = self.ticks + # vcs.elements["list"][name + "_miniticks"] = self.miniticks + + def cancel(self): + gtype = vcs.graphicsmethodtype(self.gm) + del vcs.elements[gtype][self.gm.name] + del vcs.elements['template'][self.tmpl.name] + diff --git a/cdatgui/editors/preview/axis_preview.py b/cdatgui/editors/preview/axis_preview.py index 16a0338..2a6cf2a 100644 --- a/cdatgui/editors/preview/axis_preview.py +++ b/cdatgui/editors/preview/axis_preview.py @@ -7,6 +7,7 @@ def __init__(self, parent=None): super(AxisPreviewWidget, self).__init__(parent=parent) self.axis = None self.visibilityChanged.connect(self.visibility_toggled) + self.template_name = None def visibility_toggled(self, showing): if showing: @@ -17,7 +18,10 @@ def update(self): if self.canvas is None: return self.canvas.clear(render=False) + if self.template_name: + del vcs.elements['template'][self.template_name] template = vcs.createtemplate(source=self.axis.tmpl) + self.template_name = template.name template.blank() axis_orientation = self.axis._axis[0] diff --git a/cdatgui/editors/preview/legend_preview.py b/cdatgui/editors/preview/legend_preview.py index 0c984be..169c3ec 100644 --- a/cdatgui/editors/preview/legend_preview.py +++ b/cdatgui/editors/preview/legend_preview.py @@ -7,6 +7,7 @@ def __init__(self, parent=None): super(LegendPreviewWidget, self).__init__(parent=parent) self.legend = None self.visibilityChanged.connect(self.visibility_toggled) + self.template_name = None def visibility_toggled(self, showing): if showing: @@ -16,7 +17,10 @@ def update(self): if self.canvas is None: return self.canvas.clear(render=False) + if self.template_name: + del vcs.elements['template'][self.template_name] template = vcs.createtemplate() + self.template_name = template.name template.blank() template.legend.priority = 1 @@ -33,8 +37,11 @@ def update(self): template.legend.textorientation = text_orientation.name template.drawColorBar(self.legend.vcs_colors, self.legend.levels, self.legend.labels, ext_1=self.legend.ext_left, - ext_2=self.legend.ext_right, x=self.canvas, cmap=self.legend.colormap, - style=[self.legend.fill_style], index=self.legend._gm.fillareaindices, + ext_2=self.legend.ext_right, + x=self.canvas, + cmap=self.legend.colormap, + style=[self.legend.fill_style], + index=self.legend._gm.fillareaindices, opacity=self.legend._gm.fillareaopacity) self.canvas.backend.renWin.Render() diff --git a/cdatgui/editors/projection_editor.py b/cdatgui/editors/projection_editor.py new file mode 100644 index 0000000..e06b790 --- /dev/null +++ b/cdatgui/editors/projection_editor.py @@ -0,0 +1,179 @@ +from PySide import QtCore, QtGui +import vcs, sys +from cdatgui.bases.window_widget import BaseSaveWindowWidget +from cStringIO import StringIO +from cdatgui.utils import label +from cdatgui.bases.vcs_elements_dialog import VcsElementsDialog, VcsElementsValidator + + +class ProjectionEditor(BaseSaveWindowWidget): + def __init__(self): + super(ProjectionEditor, self).__init__() + dialog = VcsElementsDialog('projection') + dialog.setValidator(VcsElementsValidator()) + self.setSaveDialog(dialog) + self.orig_projection = None + self.cur_projection_name = None + self.gm = None + self.accepted.connect(self.savingNewProjection) + self.editors = [] + self.auto_close = False + self.newprojection_name = None + + self.proj_combo = QtGui.QComboBox() + self.proj_combo.addItems(vcs.listelements('projection')) + self.proj_combo.currentIndexChanged[str].connect(self.updateCurrentProjection) + + types = ["linear", + "utm", + "state plane", + "albers equal area", + "lambert conformal c", + "mercator", + "polar stereographic", + "polyconic", + "equid conic", + "transverse mercator", + "stereographic", + "lambert azimuthal", + "azimuthal", + "gnomonic", + "orthographic", + "gen. vert. near per", + "sinusoidal", + "equirectangular", + "miller cylindrical", + "van der grinten", + "hotin oblique merc", + "robinson", + "space oblique merc", + "alaska conformal", + "interrupted goode", + "mollweide", + "interrupt mollweide", + "hammer", + "wagner iv", + "wagner vii", + "oblated equal area" + ] + + self.type_combo = QtGui.QComboBox() + self.type_combo.addItems(types) + self.type_combo.currentIndexChanged[str].connect(self.updateProjectionType) + + name_label = label("Select Projection:") + type_label = label("Type:") + + name_row = QtGui.QHBoxLayout() + name_row.addWidget(name_label) + name_row.addWidget(self.proj_combo) + + type_row = QtGui.QHBoxLayout() + type_row.addWidget(type_label) + type_row.addWidget(self.type_combo) + + well = QtGui.QFrame() + well.setFrameShape(QtGui.QFrame.StyledPanel) + well.setFrameShadow(QtGui.QFrame.Sunken) + self.well_layout = QtGui.QVBoxLayout() + well.setLayout(self.well_layout) + self.well_layout.addLayout(type_row) + + self.vertical_layout.insertLayout(0, name_row) + self.vertical_layout.insertWidget(1, well) + + def setProjectionObject(self, obj): + self.orig_projection = obj + self.cur_projection_name = obj.name + self.object = vcs.createprojection(source=obj) + self.newprojection_name = self.object.name + + self.updateAttributes() + + def updateAttributes(self): + + if self.cur_projection_name == 'default': + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + + for i in range(1, self.well_layout.count()): + row = self.well_layout.takeAt(1).layout() + row.takeAt(0).widget().deleteLater() + row.takeAt(0).widget().deleteLater() + row.deleteLater() + self.editors = [] + + # set name + block = self.proj_combo.blockSignals(True) + self.proj_combo.setCurrentIndex(self.proj_combo.findText(self.cur_projection_name)) + self.proj_combo.blockSignals(block) + + # set type + block = self.type_combo.blockSignals(True) + self.type_combo.setCurrentIndex(self.type_combo.findText(self.object.type)) + self.type_combo.blockSignals(block) + + for name in self.object.attributes: + value = getattr(self.object, name) + + edit_attr = QtGui.QLineEdit() + edit_attr.setText(str(value)) + self.editors.append((edit_attr, name)) + + row = QtGui.QHBoxLayout() + row.addWidget(label(name.capitalize() + ":")) + row.addWidget(edit_attr) + + # self.vertical_layout.insertLayout(self.vertical_layout.count() - 1, row) + self.well_layout.addLayout(row) + + def updateCurrentProjection(self, proj): + proj = str(proj) + self.cur_projection_name = proj + if self.newprojection_name in vcs.listelements('projection'): + del vcs.elements['projection'][self.newprojection_name] + self.object = vcs.createprojection(source=vcs.getprojection(proj)) + self.newprojection_name = self.object.name + self.updateAttributes() + + def updateProjectionType(self, type): + self.object.type = str(type) + self.updateAttributes() + + def updateProjection(self): + for editor, attr in self.editors: + if isinstance(editor, QtGui.QComboBox): + text = editor.currentText() + else: + text = editor.text() + try: + text = float(text) + except ValueError: + QtGui.QMessageBox.critical(self, "Invalid Type", + "Value '{0}' for {1} is not valid.".format(text, attr.capitalize())) + return False + + setattr(self.object, attr, text) + return True + + def savingNewProjection(self, name): + if not self.updateProjection(): + return + + if name == self.newprojection_name: + del vcs.elements['projection'][self.cur_projection_name] + vcs.createprojection(self.cur_projection_name, self.object) + name = self.cur_projection_name + else: + if name in vcs.listelements('projection'): + del vcs.elements['projection'][name] + vcs.createprojection(name, vcs.elements['projection'][self.newprojection_name]) + + self.gm.projection = name + self.close() + + def close(self): + if self.newprojection_name in vcs.elements['projection']: + del vcs.elements['projection'][self.newprojection_name] + super(ProjectionEditor, self).close() diff --git a/cdatgui/editors/secondary/editor/line.py b/cdatgui/editors/secondary/editor/line.py index 934340f..1806ea8 100644 --- a/cdatgui/editors/secondary/editor/line.py +++ b/cdatgui/editors/secondary/editor/line.py @@ -1,13 +1,19 @@ from PySide import QtGui, QtCore from cdatgui.bases.window_widget import BaseSaveWindowWidget from cdatgui.editors.secondary.preview.line import LinePreviewWidget +import vcs +from cdatgui.vcsmodel import get_lines class LineEditorWidget(BaseSaveWindowWidget): + saved = QtCore.Signal(str) def __init__(self): super(LineEditorWidget, self).__init__() self.setPreview(LinePreviewWidget()) + self.accepted.connect(self.saveNewLine) + self.orig_name = None + self.newline_name = None # create labels type_label = QtGui.QLabel("Type:") @@ -17,43 +23,78 @@ def __init__(self): row = QtGui.QHBoxLayout() # create type combo box - type_box = QtGui.QComboBox() - type_box.addItems(["solid", "dash", "dot", "dash-dot", "long-dash"]) - type_box.currentIndexChanged[str].connect(self.updateType) + self.type_box = QtGui.QComboBox() + self.type_box.addItems(["solid", "dash", "dot", "dash-dot", "long-dash"]) + self.type_box.currentIndexChanged[str].connect(self.updateType) # create color spin box - color_box = QtGui.QSpinBox() - color_box.setRange(0, 255) - color_box.valueChanged.connect(self.updateColor) + self.color_box = QtGui.QSpinBox() + self.color_box.setRange(0, 255) + self.color_box.valueChanged.connect(self.updateColor) # create color spin box - width_box = QtGui.QSpinBox() - width_box.setRange(1, 300) - width_box.valueChanged.connect(self.updateWidth) + self.width_box = QtGui.QSpinBox() + self.width_box.setRange(1, 300) + self.width_box.valueChanged.connect(self.updateWidth) row.addWidget(type_label) - row.addWidget(type_box) + row.addWidget(self.type_box) row.addWidget(color_label) - row.addWidget(color_box) + row.addWidget(self.color_box) row.addWidget(width_label) - row.addWidget(width_box) + row.addWidget(self.width_box) self.vertical_layout.insertLayout(1, row) def setLineObject(self, line_obj): + self.setWindowTitle('Edit {0} line'.format(line_obj.name)) + self.orig_name = line_obj.name + + if line_obj.name == 'default': + self.save_button.setEnabled(False) + + line_obj = vcs.createline(source=line_obj.name) + self.newline_name = line_obj.name + self.object = line_obj self.preview.setLineObject(self.object) + self.type_box.setCurrentIndex(self.type_box.findText(self.object.type[0])) + self.color_box.setValue(self.object.color[0]) + self.width_box.setValue(self.object.width[0]) + def updateType(self, cur_item): - self.object.type = str(cur_item) + self.object.type = [str(cur_item)] self.preview.update() def updateColor(self, color): - self.object.color = color + self.object.color = [color] self.preview.update() def updateWidth(self, width): - self.object.width = width + self.object.width = [width] self.preview.update() + + def saveNewLine(self, name): + name = str(name) + + if name == self.newline_name: + if self.orig_name in vcs.elements['line']: + del vcs.elements['line'][self.orig_name] + + vcs.createline(self.orig_name, source=name) + get_lines().updated(self.orig_name) + self.saved.emit(self.orig_name) + else: + if name in vcs.elements['line']: + del vcs.elements['line'][name] + vcs.createline(name, source=self.newline_name) + get_lines().updated(name) + self.saved.emit(name) + + def close(self): + if self.newline_name in vcs.elements['line']: + del vcs.elements['line'][self.newline_name] + super(LineEditorWidget, self).close() diff --git a/cdatgui/editors/secondary/editor/marker.py b/cdatgui/editors/secondary/editor/marker.py index d80a1e4..c390bcf 100644 --- a/cdatgui/editors/secondary/editor/marker.py +++ b/cdatgui/editors/secondary/editor/marker.py @@ -17,8 +17,8 @@ def __init__(self): row = QtGui.QHBoxLayout() # create type combo box - type_box = QtGui.QComboBox() - type_box.addItems(["dot", "plus", "star", "circle", "cross", "diamond", "triangle_up", "triangle_down", + self.type_box = QtGui.QComboBox() + self.type_box.addItems(["dot", "plus", "star", "circle", "cross", "diamond", "triangle_up", "triangle_down", "triangle_left", "triangle_right", "square", "diamond_fill", "triangle_up_fill", "triangle_down_fill", "triangle_left_fill", "triangle_right_fill", "square_fill",'hurricane', 'w00', 'w01', 'w02', 'w03', 'w04', 'w05', 'w06', 'w07', 'w08', 'w09', 'w10', 'w11', 'w12', @@ -29,41 +29,44 @@ def __init__(self): 'w65', 'w66', 'w67', 'w68', 'w69', 'w70', 'w71', 'w72', 'w73', 'w74', 'w75', 'w76', 'w77', 'w78', 'w79', 'w80', 'w81', 'w82', 'w83', 'w84', 'w85', 'w86', 'w87', 'w88', 'w89', 'w90', 'w91', 'w92', 'w93', 'w94', 'w95', 'w96', 'w97', 'w98', 'w99', 'w100', 'w101', 'w102']) - type_box.currentIndexChanged[str].connect(self.updateType) + self.type_box.currentIndexChanged[str].connect(self.updateType) # create color spin box - color_box = QtGui.QSpinBox() - color_box.setRange(0, 255) - color_box.valueChanged.connect(self.updateColor) + self.color_box = QtGui.QSpinBox() + self.color_box.setRange(0, 255) + self.color_box.valueChanged.connect(self.updateColor) # create size spin box - size_box = QtGui.QSpinBox() - size_box.setRange(1, 300) - size_box.valueChanged.connect(self.updateSize) + self.size_box = QtGui.QSpinBox() + self.size_box.setRange(1, 300) + self.size_box.valueChanged.connect(self.updateSize) row.addWidget(type_label) - row.addWidget(type_box) + row.addWidget(self.type_box) row.addWidget(color_label) - row.addWidget(color_box) + row.addWidget(self.color_box) row.addWidget(size_label) - row.addWidget(size_box) + row.addWidget(self.size_box) self.vertical_layout.insertLayout(1, row) def setMarkerObject(self, mark_obj): self.object = mark_obj self.preview.setMarkerObject(self.object) + self.type_box.setCurrentIndex(self.type_box.findText(self.object.type[0])) + self.color_box.setValue(self.object.color[0]) + self.size_box.setValue(self.object.size[0]) def updateType(self, cur_item): - self.object.type = str(cur_item) + self.object.type = [str(cur_item)] self.preview.update() def updateColor(self, color): - self.object.color = color + self.object.color = [color] self.preview.update() def updateSize(self, size): - self.object.size = size + self.object.size = [size] self.preview.update() \ No newline at end of file diff --git a/cdatgui/editors/secondary/editor/text.py b/cdatgui/editors/secondary/editor/text.py index a410afa..a8ff8bd 100755 --- a/cdatgui/editors/secondary/editor/text.py +++ b/cdatgui/editors/secondary/editor/text.py @@ -2,12 +2,18 @@ from cdatgui.editors.secondary.preview.text import TextStylePreviewWidget from PySide import QtCore, QtGui from cdatgui.bases.window_widget import BaseSaveWindowWidget +from cdatgui.vcsmodel import get_textstyles class TextStyleEditorWidget(BaseSaveWindowWidget): + saved = QtCore.Signal(str) + def __init__(self): super(TextStyleEditorWidget, self).__init__() self.setPreview(TextStylePreviewWidget()) + self.accepted.connect(self.saveNewText) + self.orig_names = [] + self.newtextcombined_name = None # Set up vertical align self.va_group = QtGui.QButtonGroup() @@ -90,12 +96,20 @@ def __init__(self): self.vertical_layout.insertLayout(2, font_size_row) def setTextObject(self, text_object): - self.textObject = text_object - self.preview.setTextObject(self.textObject) - self.setWindowTitle('Edit Style "%s"' % self.textObject.name) + self.orig_names = [text_object.name, text_object.Tt_name, text_object.To_name] + + if text_object.Tt_name == 'default' and text_object.To_name == 'default': + self.save_button.setEnabled(False) + + text_object = vcs.createtextcombined(Tt_source=text_object.Tt_name, To_source=text_object.To_name) + self.newtextcombined_name = text_object.name + + self.object = text_object + self.preview.setTextObject(self.object) + self.setWindowTitle('Edit Style "%s"' % self.object.name.split(':::')[0]) # set initial values - cur_valign = self.textObject.valign + cur_valign = self.object.valign for button in self.va_group.buttons(): if cur_valign == 0 and button.text() == "Top": button.setChecked(True) @@ -104,7 +118,7 @@ def setTextObject(self, text_object): elif cur_valign == 4 and button.text() == "Bot": button.setChecked(True) - cur_halign = self.textObject.halign + cur_halign = self.object.halign for button in self.ha_group.buttons(): if cur_halign == 0 and button.text() == "Left": button.setChecked(True) @@ -113,66 +127,107 @@ def setTextObject(self, text_object): elif cur_halign == 2 and button.text() == "Right": button.setChecked(True) - self.angle_slider.setSliderPosition(self.textObject.angle) - - self.size_box.setValue(self.textObject.height) + self.angle_slider.setSliderPosition(self.object.angle) + self.size_box.setValue(self.object.height) def updateButton(self, button): if button.text() == "Top": - self.textObject.valign = "top" - + self.object.valign = "top" elif button.text() == "Mid": - self.textObject.valign = "half" - + self.object.valign = "half" elif button.text() == "Bot": - self.textObject.valign = "bottom" - + self.object.valign = "bottom" elif button.text() == "Left": - self.textObject.halign = "left" - + self.object.halign = "left" elif button.text() == "Center": - self.textObject.halign = "center" - + self.object.halign = "center" elif button.text() == "Right": - self.textObject.halign = "right" - + self.object.halign = "right" self.preview.update() def updateAngle(self, angle): - - self.textObject.angle = angle % 360 # angle cannot be higher than 360 - + self.object.angle = angle % 360 # angle cannot be higher than 360 self.preview.update() def updateFont(self, font): - - self.textObject.font = str(font) - + self.object.font = str(font) self.preview.update() def updateSize(self, size): - - self.textObject.height = size - + self.object.height = size self.preview.update() - def saveAs(self): - - self.win = QtGui.QInputDialog() - - self.win.setLabelText("Enter New Name:") - self.win.accepted.connect(self.save) - - self.win.show() - self.win.raise_() - - def save(self): - - try: - name = self.win.textValue() - self.win.close() - except: - name = self.textObject.name - - self.savePressed.emit(name) - self.close() + def saveNewText(self, name): + name = str(name) + tt_name, to_name = self.newtextcombined_name.split(':::') + + if name != self.newtextcombined_name: + to = vcs.elements['textorientation'][to_name] + tt = vcs.elements['texttable'][tt_name] + + # deleting if already exists. This will only happen if they want to overwrite + if name in vcs.elements['texttable']: + del vcs.elements['texttable'][name] + if name in vcs.elements['textorientation']: + del vcs.elements['textorientation'][name] + + # inserting new object + new_tt = vcs.createtexttable(name, source=tt) + new_to = vcs.createtextorientation(name, source=to) + vcs.elements['textorientation'][name] = new_to + vcs.elements['texttable'][name] = new_tt + + # removing old object from key + vcs.elements['textorientation'].pop(to_name) + vcs.elements['texttable'].pop(tt_name) + + tc = vcs.createtextcombined() + tc.Tt = new_tt + tc.To = new_to + + # inserting into model + get_textstyles().updated(name) + + # adding to list + self.saved.emit(name) + + else: + # recover original info + old_tt = vcs.elements['texttable'][self.orig_names[1]] + old_to = vcs.elements['textorientation'][self.orig_names[2]] + + # get new info + new_tt = vcs.elements['texttable'][tt_name] + new_to = vcs.elements['textorientation'][to_name] + + # delete old tt and to + old_tt_name = old_tt.name + old_to_name = old_to.name + + del vcs.elements['texttable'][self.orig_names[1]] + del vcs.elements['textorientation'][self.orig_names[2]] + + # create new tt and to objects with old name and new attributes + brand_new_tt = vcs.createtexttable(old_tt_name, source=new_tt) + brand_new_to = vcs.createtextorientation(old_to_name, source=new_to) + + tc = vcs.createtextcombined() + tc.Tt = brand_new_tt + tc.To = brand_new_to + vcs.elements['textcombined'][self.orig_names[0]] = tc + + # inserting into model + get_textstyles().updated(old_tt_name) + + # adding to list + self.saved.emit(old_tt_name) + + def close(self): + tt_name, to_name = self.newtextcombined_name.split(':::') + if self.newtextcombined_name in vcs.elements['textcombined']: + del vcs.elements['textcombined'][self.newtextcombined_name] + if to_name in vcs.listelements('textorientation'): + del vcs.elements['textorientation'][to_name] + if tt_name in vcs.listelements('texttable'): + del vcs.elements['texttable'][tt_name] + super(TextStyleEditorWidget, self).close() diff --git a/cdatgui/editors/secondary/preview/text.py b/cdatgui/editors/secondary/preview/text.py index 2e73445..e21c24b 100755 --- a/cdatgui/editors/secondary/preview/text.py +++ b/cdatgui/editors/secondary/preview/text.py @@ -9,7 +9,7 @@ def __init__(self, parent=None): def setTextObject(self, textobject): self.textobj = textobject - tmpobj = vcs.createtext(Tt_source=self.textobj.Tt, To_source=self.textobj.To) + tmpobj = vcs.createtext(Tt_source=self.textobj.Tt_name, To_source=self.textobj.To_name) tmpobj.string = ["%s Preview" % self.textobj.name] tmpobj.x = [.5] tmpobj.y = [.5] diff --git a/cdatgui/editors/vector.py b/cdatgui/editors/vector.py new file mode 100644 index 0000000..c38a8e6 --- /dev/null +++ b/cdatgui/editors/vector.py @@ -0,0 +1,37 @@ +from PySide import QtGui +from .graphics_method_editor import GraphicsMethodEditorWidget +from .secondary.editor.marker import MarkerEditorWidget +from .secondary.editor.line import LineEditorWidget +import vcs + +class VectorEditor(GraphicsMethodEditorWidget): + """Configures a meshfill graphics method.""" + + def __init__(self, parent=None): + """Initialize the object.""" + super(VectorEditor, self).__init__(parent=parent) + + self.button_layout.takeAt(0).widget().deleteLater() + self.button_layout.takeAt(0).widget().deleteLater() + + line_button = QtGui.QPushButton("Edit Line") + line_button.clicked.connect(self.editLine) + + self.button_layout.insertWidget(0, line_button) + + self.marker_editor = None + self.line_editor = None + + def editLine(self): + if not self.line_editor: + self.line_editor = LineEditorWidget() + self.line_editor.accepted.connect(self.updateLine) + line_obj = vcs.createline(ltype=self.gm.line, color=self.gm.linecolor, width=self.gm.linewidth) + self.line_editor.setLineObject(line_obj) + self.line_editor.raise_() + self.line_editor.show() + + def updateLine(self, name): + self.gm.line = self.line_editor.object.type[0] + self.gm.linecolor = self.line_editor.object.color[0] + self.gm.line = self.line_editor.object.width[0] diff --git a/cdatgui/editors/widgets/adjust_values.py b/cdatgui/editors/widgets/adjust_values.py index 241133a..bd71544 100644 --- a/cdatgui/editors/widgets/adjust_values.py +++ b/cdatgui/editors/widgets/adjust_values.py @@ -1,6 +1,8 @@ +import numpy from PySide.QtCore import * from PySide.QtGui import * from functools import partial +from cdatgui.bases.value_slider import ValueSlider class AdjustValues(QWidget): @@ -9,9 +11,9 @@ class AdjustValues(QWidget): def __init__(self, parent=None): super(AdjustValues, self).__init__(parent=parent) - self.min_val = 0 self.max_val = 1 + self.values = None self.slides = [] # Insert Sliders self.wrap = QVBoxLayout() @@ -29,23 +31,23 @@ def __init__(self, parent=None): self.setLayout(self.wrap) def add_level(self): - self.insert_line() + new_slide = self.insert_line() + new_slide.setRealValue(new_slide.values[-1]) + if self.clearing is False: self.send_values() - def update(self, minval, maxval, values): - if minval >= maxval: - raise ValueError("Minimum value %d >= maximum value %d" % (minval, maxval)) - self.min_val = minval - self.max_val = maxval + def update(self, values, levs): + block = self.blockSignals(True) + self.values = values self.clearing = True for ind in range(len(self.rows)): self.remove_level(self.rows[0]) - - for ind, value in enumerate(values): - self.insert_line() - self.slides[ind].setValue(value) + for ind, value in enumerate(levs): + cur_slide = self.insert_line() + cur_slide.setRealValue(value) self.clearing = False + self.blockSignals(block) def adjust_slides(self, slide, cur_val): cur_index = self.slides.index(slide) @@ -53,21 +55,21 @@ def adjust_slides(self, slide, cur_val): for i, s in enumerate(self.slides): if i < cur_index: - if s.sliderPosition() > slide.sliderPosition(): + if s.sliderPosition() >= slide.sliderPosition(): s.setValue(slide.sliderPosition()) else: - if s.sliderPosition() < slide.sliderPosition(): + if s.sliderPosition() <= slide.sliderPosition(): s.setValue(slide.sliderPosition()) def send_values(self): positions = [] for slide in self.slides: - positions.append(slide.sliderPosition()) + positions.append(slide.realValue()) self.valuesChanged.emit(positions) def change_label(self, lab, slide, cur_val): - lab.setText(str(slide.sliderPosition())) + lab.setText(str(slide.realValue())) def remove_level(self, row): child = row.takeAt(0) @@ -87,7 +89,9 @@ def remove_level(self, row): def insert_line(self): row = QHBoxLayout() lab = QLabel(str(self.max_val)) - slide = QSlider(Qt.Horizontal) + lab.setMinimumWidth(50) + slide = ValueSlider(self.values) + slide.setOrientation(Qt.Horizontal) # remove button rem_button = QPushButton() @@ -100,8 +104,7 @@ def insert_line(self): row.addWidget(slide) # set slide attributes - slide.setRange(self.min_val, self.max_val) - slide.setValue(self.max_val) + slide.setTickInterval(len(self.values) / 20) slide.setTickPosition(QSlider.TicksAbove) slide.valueChanged.connect(partial(self.change_label, lab, slide)) slide.valueChanged.connect(partial(self.adjust_slides, slide)) @@ -113,3 +116,5 @@ def insert_line(self): # add to list self.slides.append(slide) self.rows.append(row) + + return slide diff --git a/cdatgui/editors/widgets/dict_editor.py b/cdatgui/editors/widgets/dict_editor.py index b4ca49f..1e8950f 100644 --- a/cdatgui/editors/widgets/dict_editor.py +++ b/cdatgui/editors/widgets/dict_editor.py @@ -213,7 +213,7 @@ def dict(self): return dict(zip(keys, values)) def clear(self): - self.grid.clearWidget() + self.grid.clearWidgets() self.clearing = True while self.key_value_rows: diff --git a/cdatgui/editors/widgets/legend_widget.py b/cdatgui/editors/widgets/legend_widget.py index 3e6603a..02594fd 100644 --- a/cdatgui/editors/widgets/legend_widget.py +++ b/cdatgui/editors/widgets/legend_widget.py @@ -9,6 +9,7 @@ from cdatgui.utils import pattern_thumbnail from cdatgui.bases.dynamic_grid_layout import DynamicGridLayout from functools import partial +import vcs class ForceResizeScrollArea(QtGui.QScrollArea): @@ -190,7 +191,7 @@ def __init__(self, parent=None): end_color_label = QtGui.QLabel("End Color:") extend_left_label = QtGui.QLabel("Extend Left") extend_right_label = QtGui.QLabel("Extend Right") - custom_fill_label = QtGui.QLabel("Custom Fill") + self.custom_fill_label = QtGui.QLabel("Custom Fill") labels_label = QtGui.QLabel("Labels:") # Timers @@ -231,10 +232,10 @@ def __init__(self, parent=None): self.end_color_button.clicked.connect(partial(self.createColormap, self.end_color_spin)) # Create extend check boxes - extend_left_check = QtGui.QCheckBox() - extend_left_check.stateChanged.connect(self.updateExtendLeft) - extend_right_check = QtGui.QCheckBox() - extend_right_check.stateChanged.connect(self.updateExtendRight) + self.extend_left_check = QtGui.QCheckBox() + self.extend_left_check.stateChanged.connect(self.updateExtendLeft) + self.extend_right_check = QtGui.QCheckBox() + self.extend_right_check.stateChanged.connect(self.updateExtendRight) # Create custom fill icon self.custom_fill_icon = QtGui.QToolButton() @@ -286,19 +287,23 @@ def __init__(self, parent=None): start_color_layout.addWidget(start_color_label) start_color_layout.addWidget(self.start_color_spin) start_color_layout.addWidget(self.start_color_button) + self.start_color_widget = QtGui.QWidget() + self.start_color_widget.setLayout(start_color_layout) end_color_layout.addWidget(end_color_label) end_color_layout.addWidget(self.end_color_spin) end_color_layout.addWidget(self.end_color_button) + self.end_color_widget = QtGui.QWidget() + self.end_color_widget.setLayout(end_color_layout) - extend_layout.addWidget(extend_left_check) + extend_layout.addWidget(self.extend_left_check) extend_layout.addWidget(extend_left_label) - extend_layout.addWidget(extend_right_check) + extend_layout.addWidget(self.extend_right_check) extend_layout.addWidget(extend_right_label) extend_layout.insertStretch(2, 1) custom_fill_layout.addWidget(self.custom_fill_icon) - custom_fill_layout.addWidget(custom_fill_label) + custom_fill_layout.addWidget(self.custom_fill_label) # Add preview self.setPreview(LegendPreviewWidget()) @@ -307,8 +312,8 @@ def __init__(self, parent=None): # Insert layouts self.vertical_layout.insertLayout(1, colormap_layout) - self.vertical_layout.insertLayout(2, start_color_layout) - self.vertical_layout.insertLayout(3, end_color_layout) + self.vertical_layout.insertWidget(2, self.start_color_widget) + self.vertical_layout.insertWidget(3, self.end_color_widget) self.vertical_layout.insertLayout(4, extend_layout) self.vertical_layout.insertLayout(5, custom_fill_layout) self.vertical_layout.insertLayout(6, labels_layout) @@ -316,13 +321,46 @@ def __init__(self, parent=None): def setObject(self, legend): self.object = legend - self.start_color_spin.setValue(self.object.color_1) - self.updateButtonColor(self.start_color_button, self.object.color_1) - self.start_color_button.setFixedSize(100, 25) + try: + self.start_color_spin.setValue(self.object.color_1) + self.updateButtonColor(self.start_color_button, self.object.color_1) + self.start_color_button.setFixedSize(100, 25) + except TypeError: + self.start_color_widget.setEnabled(False) + self.start_color_widget.hide() - self.end_color_spin.setValue(self.object.color_2) - self.updateButtonColor(self.end_color_button, self.object.color_2) - self.end_color_button.setFixedSize(100, 25) + try: + self.end_color_spin.setValue(self.object.color_2) + self.updateButtonColor(self.end_color_button, self.object.color_2) + self.end_color_button.setFixedSize(100, 25) + except TypeError: + self.end_color_widget.setEnabled(False) + self.end_color_widget.hide() + + # disable the extend left and right if the gm does not have any - might not actually be needed + if self.object.ext_left is not None: + self.extend_left_check.setChecked(self.object.ext_left) + else: + self.extend_left_check.setEnabled(False) + self.extend_left_check.hide() + + if self.object.ext_right is not None: + self.extend_right_check.setChecked(self.object.ext_right) + else: + self.extend_right_check.setEnabled(False) + self.extend_right_check.hide() + + # disable the custom fill option if the fill style is not custom + if vcs.isboxfill(self.object._gm): + if self.object._gm.boxfill_type == 'custom': + self.enableCustom(self.object._gm.fillareastyle != 'solid') + else: + self.disableCustom() + + elif self.object.fill_style: + self.enableCustom(self.object.fill_style != 'solid') + else: + self.disableCustom() self.preview.setLegendObject(legend) self.preview.update() @@ -393,7 +431,7 @@ def updateArrowType(self): self.fill_style_widget.setVisible(True) self.vertical_layout.insertLayout(6, self.custom_vertical_layout) self.custom_vertical_layout.addWidget(self.createCustomFillBox()) - self.initateFillStyle(self.fill_button_group.button(-2)) + self.initateFillStyle() else: self.object.fill_style = "Solid" self.fill_style_widget.setVisible(False) @@ -402,10 +440,10 @@ def updateArrowType(self): self.preview.update() - def initateFillStyle(self, old_button): + def initateFillStyle(self): """Used when creating custom fill to initalize fill style to Solid""" for button in self.fill_button_group.buttons(): - if button.text() == old_button.text(): + if button.text() == self.object.fill_style.capitalize(): button.click() def updateCustomFillBox(self): @@ -413,7 +451,8 @@ def updateCustomFillBox(self): self.deleteCustomFillBox() self.custom_vertical_layout.addWidget(self.createCustomFillBox()) self.vertical_layout.insertLayout(6, self.custom_vertical_layout) - self.initateFillStyle(self.fill_button_group.checkedButton()) + # self.initateFillStyle(self.fill_button_group.checkedButton()) + self.initateFillStyle() def createCustomFillBox(self): # create layout for custom fill @@ -534,6 +573,18 @@ def handleEndColorInvalidInput(self): self.end_color_button.setStyleSheet( self.end_color_button.styleSheet() + "border: 1px solid red;") + def enableCustom(self, show=False): + self.custom_fill_icon.setEnabled(True) + self.custom_fill_icon.show() + self.custom_fill_label.show() + if show and self.custom_fill_icon.arrowType() == QtCore.Qt.RightArrow: + self.updateArrowType() + + def disableCustom(self): + self.custom_fill_icon.setEnabled(False) + self.custom_fill_icon.hide() + self.custom_fill_label.hide() + if __name__ == "__main__": import cdms2, vcs diff --git a/cdatgui/editors/widgets/multi_line_editor.py b/cdatgui/editors/widgets/multi_line_editor.py new file mode 100644 index 0000000..473c134 --- /dev/null +++ b/cdatgui/editors/widgets/multi_line_editor.py @@ -0,0 +1,104 @@ +from PySide import QtCore, QtGui +from functools import partial + +from cdatgui.editors.secondary.editor.line import LineEditorWidget +from cdatgui.bases.window_widget import BaseOkWindowWidget +from cdatgui.bases.dynamic_grid_layout import DynamicGridLayout +import vcs +from cdatgui.vcsmodel import get_lines +from cdatgui.bases.vcs_elements_dialog import VcsElementsDialog, VcsElementsValidator + + +class MultiLineEditor(BaseOkWindowWidget): + def __init__(self): + super(MultiLineEditor, self).__init__() + self.isoline_model = None + self.line_editor = None + self.line_combos = [] + self.dynamic_grid = DynamicGridLayout(400) + self.vertical_layout.insertLayout(0, self.dynamic_grid) + self.setWindowTitle("Edit Lines") + self.resize(300, self.height()) + + def setObject(self, object, *args): + self.isoline_model = object + widgets = [] + + # clear grid + grid_widgets = self.dynamic_grid.getWidgets() + self.dynamic_grid.clearWidgets() + + for widget in grid_widgets: + self.dynamic_grid.removeWidget(widget) + widget.deleteLater() + + # repopulate + for ind, lev in enumerate(self.isoline_model.levels): + row = QtGui.QHBoxLayout() + line_label = QtGui.QLabel(str(lev)) + + line_combo = QtGui.QComboBox() + line_combo.setModel(get_lines()) + + # set to current line + item = self.isoline_model.line[ind] + line_combo.setCurrentIndex(get_lines().elements.index(item)) + self.line_combos.append(line_combo) + + edit_line = QtGui.QPushButton('Edit Line') + edit_line.clicked.connect(partial(self.editLine, ind)) + + line_combo.currentIndexChanged.connect(partial(self.changeLine, ind)) + + # add everything to layout + row.addWidget(line_label) + row.addWidget(line_combo) + row.addWidget(edit_line) + + row_widget = QtGui.QWidget() + row_widget.setLayout(row) + widgets.append(row_widget) + + self.dynamic_grid.addNewWidgets(widgets) + + def editLine(self, index): + if self.line_editor: + self.line_editor.close() + self.line_editor.deleteLater() + self.line_editor = LineEditorWidget() + dialog = VcsElementsDialog('line') + dialog.setValidator(VcsElementsValidator()) + self.line_editor.setSaveDialog(dialog) + + line = self.isoline_model.line[index] + line_obj = vcs.getline(line) + + self.line_editor.setLineObject(line_obj) + self.line_editor.saved.connect(partial(self.update, index)) + + self.line_editor.show() + self.line_editor.raise_() + + def changeLine(self, row_index, combo_index): + """Changed line to an already existing line in the line model""" + self.isoline_model.line[row_index] = get_lines().elements[combo_index] + + def update(self, index, name): + """Updated line from line editor""" + self.isoline_model.line[index] = str(name) + self.line_combos[index].setCurrentIndex(self.line_combos[index].findText(name)) + + def accept(self): + self.updateGM() + self.accepted.emit() + self.close() + + def updateGM(self): + colors = [] + widths = [] + for line in self.isoline_model.line: + colors.append(vcs.getline(line).color[0]) + widths.append(vcs.getline(line).width[0]) + + self.isoline_model._gm.linecolors = colors + self.isoline_model._gm.linewidths = widths diff --git a/cdatgui/editors/widgets/multi_text_editor.py b/cdatgui/editors/widgets/multi_text_editor.py new file mode 100644 index 0000000..bf6d6b0 --- /dev/null +++ b/cdatgui/editors/widgets/multi_text_editor.py @@ -0,0 +1,90 @@ +from PySide import QtCore, QtGui +from functools import partial + +from cdatgui.editors.secondary.editor.text import TextStyleEditorWidget +from cdatgui.bases.window_widget import BaseOkWindowWidget +from cdatgui.bases.dynamic_grid_layout import DynamicGridLayout +import vcs +from cdatgui.vcsmodel import get_textstyles +from cdatgui.bases.vcs_elements_dialog import VcsElementsDialog, VcsElementsValidator + + +class MultiTextEditor(BaseOkWindowWidget): + def __init__(self): + super(MultiTextEditor, self).__init__() + self.isoline_model = None + self.text_editor = None + self.text_combos = [] + self.dynamic_grid = DynamicGridLayout(400) + self.vertical_layout.insertLayout(0, self.dynamic_grid) + self.setWindowTitle("Edit Texts") + self.resize(300, self.height()) + + def setObject(self, object): + self.isoline_model = object + widgets = [] + + # clear grid + grid_widgets = self.dynamic_grid.getWidgets() + self.dynamic_grid.clearWidgets() + + for widget in grid_widgets: + self.dynamic_grid.removeWidget(widget) + widget.deleteLater() + + # repopulate + for ind, lev in enumerate(self.isoline_model.levels): + row = QtGui.QHBoxLayout() + text_label = QtGui.QLabel(str(lev)) + + text_combo = QtGui.QComboBox() + text_combo.setModel(get_textstyles()) + + # set to current text + item = self.isoline_model.text[ind] + text_combo.setCurrentIndex(get_textstyles().elements.index(item)) + self.text_combos.append(text_combo) + + edit_text = QtGui.QPushButton('Edit Text') + edit_text.clicked.connect(partial(self.editText, ind)) + + text_combo.currentIndexChanged[str].connect(partial(self.changeText, ind)) + + # add everything to layout + row.addWidget(text_label) + row.addWidget(text_combo) + row.addWidget(edit_text) + + row_widget = QtGui.QWidget() + row_widget.setLayout(row) + widgets.append(row_widget) + + self.dynamic_grid.addNewWidgets(widgets) + + def editText(self, index): + if self.text_editor: + self.text_editor.close() + self.text_editor.deleteLater() + self.text_editor = TextStyleEditorWidget() + dialog = VcsElementsDialog('texttable') + dialog.setValidator(VcsElementsValidator()) + self.text_editor.setSaveDialog(dialog) + + text = self.isoline_model.text[index] + text_obj = get_textstyles().get_el(text) + + self.text_editor.setTextObject(text_obj) + self.text_editor.saved.connect(partial(self.update, index)) + + self.text_editor.show() + self.text_editor.raise_() + + def changeText(self, row_index, combo_index): + self.isoline_model.text[row_index] = get_textstyles().elements[get_textstyles().elements.index(combo_index)] + + def update(self, index, name): + self.text_combos[index].setCurrentIndex(self.text_combos[index].findText(name)) + + def accept(self): + self.accepted.emit() + self.close() diff --git a/cdatgui/editors/widgets/template/labels.py b/cdatgui/editors/widgets/template/labels.py index b499df2..5c2426a 100644 --- a/cdatgui/editors/widgets/template/labels.py +++ b/cdatgui/editors/widgets/template/labels.py @@ -132,7 +132,7 @@ def __init__(self, parent=None): self._template = None self.member_groups = {group: TemplateLabelGroup(group) for group in members} self.style_editor = TextStyleEditorWidget() - self.style_editor.savePressed.connect(self.save_style) + self.style_editor.accepted.connect(self.save_style) for group, widget in self.member_groups.iteritems(): widget.labelUpdated.connect(self.labelUpdated.emit) widget.moveLabel.connect(self.moveLabel.emit) diff --git a/cdatgui/graphics/__init__.py b/cdatgui/graphics/__init__.py index a19622c..a65d2fc 100644 --- a/cdatgui/graphics/__init__.py +++ b/cdatgui/graphics/__init__.py @@ -1,10 +1,14 @@ from models import VCSGraphicsMethodModel - __gms__ = None + def get_gms(): global __gms__ if __gms__ is None: __gms__ = VCSGraphicsMethodModel() return __gms__ + + +gms_with_non_implemented_editors = ['scatter', '1d', '3d_dual_scalar', '3d_scalar', '3d_vector', 'isoline', 'scatter', + 'taylordiagram', 'xvsy', 'xyvsy', 'yxvsx'] diff --git a/cdatgui/graphics/dialog.py b/cdatgui/graphics/dialog.py index e2dd3a8..57478e4 100644 --- a/cdatgui/graphics/dialog.py +++ b/cdatgui/graphics/dialog.py @@ -1,48 +1,142 @@ from PySide import QtGui, QtCore + +from cdatgui.bases.vcs_elements_dialog import VcsElementsDialog from cdatgui.editors.boxfill import BoxfillEditor +from cdatgui.editors.isofill import IsofillEditor +from cdatgui.editors.meshfill import MeshfillEditor +from cdatgui.editors.isoline import IsolineEditor +from cdatgui.editors.cdat1d import Cdat1dEditor +from cdatgui.editors.vector import VectorEditor import vcs -class BoxfillDialog(QtGui.QDialog): +class GraphicsMethodDialog(QtGui.QDialog): editedGM = QtCore.Signal(object) createdGM = QtCore.Signal(object) def __init__(self, gm, var, tmpl, parent=None): - super(BoxfillDialog, self).__init__(parent=parent) + super(GraphicsMethodDialog, self).__init__(parent=parent) + self.setWindowModality(QtCore.Qt.ApplicationModal) + self.newgm_name = None + self.origgm_name = gm.name + layout = QtGui.QVBoxLayout() - self.gm = gm - self.editor = BoxfillEditor() - self.editor.gm = gm + + self.gmtype = vcs.graphicsmethodtype(gm) + if self.gmtype == "boxfill": + self.editor = BoxfillEditor() + self.create = vcs.createboxfill + elif self.gmtype == "isofill": + self.editor = IsofillEditor() + self.create = vcs.createisofill + elif self.gmtype == "meshfill": + self.editor = MeshfillEditor() + self.create = vcs.createmeshfill + elif self.gmtype == "isoline": + self.editor = IsolineEditor() + self.create = vcs.createisoline + elif self.gmtype == "1d": + self.editor = Cdat1dEditor() + self.create = vcs.create1d + elif self.gmtype == "vector": + self.editor = VectorEditor() + self.create = vcs.createvector + else: + raise NotImplementedError("No editor exists for type %s" % self.gmtype) self.editor.var = var self.editor.tmpl = tmpl + self.gm = self.createNewGM(gm) + self.newgm_name = self.gm.name + self.editor.gm = self.gm + + self.setWindowTitle('Editing ' + gm.name) + layout.addWidget(self.editor) - buttons = QtGui.QHBoxLayout() + self.buttons = QtGui.QHBoxLayout() cancel = QtGui.QPushButton("Cancel") + cancel.setAutoDefault(True) cancel.clicked.connect(self.reject) + + self.buttons.addWidget(cancel) + self.buttons.addStretch() + + layout.addLayout(self.buttons) + + self.setLayout(layout) + + def createNewGM(self, gm): + """This is here so it can be overridden when inherited""" + return self.create(source=gm) + + def reject(self): + super(GraphicsMethodDialog, self).reject() + + if isinstance(self.editor, BoxfillEditor): + self.gm.boxfill_type = self.editor.orig_type + + if self.newgm_name in vcs.elements[vcs.graphicsmethodtype(self.gm)].keys(): + del vcs.elements[vcs.graphicsmethodtype(self.gm)][self.newgm_name] + + +class GraphicsMethodSaveDialog(GraphicsMethodDialog): + def __init__(self, gm, var, tmpl, parent=None): + super(GraphicsMethodSaveDialog, self).__init__(gm, var, tmpl, parent) + self.dialog = None + save_as = QtGui.QPushButton("Save As") save_as.clicked.connect(self.customName) save = QtGui.QPushButton("Save") + save.setDefault(True) save.clicked.connect(self.accept) - self.accepted.connect(self.save) - save.setDefault(True) + self.buttons.addWidget(save_as) + self.buttons.addWidget(save) - buttons.addWidget(cancel) - buttons.addStretch() - buttons.addWidget(save_as) - buttons.addWidget(save) - layout.addLayout(buttons) + self.accepted.connect(self.save) - self.setLayout(layout) + if self.origgm_name == 'default': + save.setEnabled(False) def customName(self): - name = QtGui.QInputDialog.getText(self, u"Save As", u"Name for Boxfill:") - self.save(name) + self.dialog = VcsElementsDialog('boxfill') + self.dialog.setLabelText('Name for {0}'.format(unicode(self.gmtype))) + self.dialog.setWindowTitle('Save As') + self.dialog.accepted.connect(self.grabGm) + self.dialog.show() + self.dialog.raise_() + + def grabGm(self): + self.save(self.dialog.textValue()) def save(self, name=None): if name is None: - self.editedGM.emit(self.gm) + del vcs.elements[self.gmtype][self.origgm_name] + gm = vcs.creategraphicsmethod(self.gmtype, self.newgm_name, self.origgm_name) + self.editedGM.emit(gm) else: - gm = vcs.createboxfill(name, self.gm) + if name in vcs.listelements(self.gmtype): + del vcs.elements[self.gmtype][name] + gm = self.create(name, self.newgm_name) self.createdGM.emit(gm) + + del vcs.elements[self.gmtype][self.newgm_name] + + self.close() + + +class GraphicsMethodOkDialog(GraphicsMethodDialog): + def __init__(self, gm, var, tmpl, parent=None): + super(GraphicsMethodOkDialog, self).__init__(gm, var, tmpl, parent) + + ok_button = QtGui.QPushButton('OK') + ok_button.clicked.connect(self.accept) + + self.accepted.connect(self.okClicked) + self.buttons.addWidget(ok_button) + + def okClicked(self): + del vcs.elements[self.gmtype][self.origgm_name] + gm = vcs.creategraphicsmethod(self.gmtype, self.newgm_name, self.origgm_name) + self.editedGM.emit(gm) + del vcs.elements[self.gmtype][self.newgm_name] diff --git a/cdatgui/graphics/graphics_method_widget.py b/cdatgui/graphics/graphics_method_widget.py index df4d746..e6599bc 100644 --- a/cdatgui/graphics/graphics_method_widget.py +++ b/cdatgui/graphics/graphics_method_widget.py @@ -1,10 +1,169 @@ -from PySide import QtCore +import copy +from PySide import QtCore, QtGui from cdatgui.bases import StaticDockWidget from cdatgui.toolbars import AddEditRemoveToolbar from vcs_gm_list import GraphicsMethodList +from cdatgui.bases.input_dialog import ValidatingInputDialog +from cdatgui.graphics import get_gms +from cdatgui.graphics.dialog import GraphicsMethodOkDialog +from cdatgui.utils import label +from cdatgui.cdat.metadata import FileMetadataWrapper +from . import gms_with_non_implemented_editors +import vcs, cdms2, os + + +class NameValidator(QtGui.QValidator): + invalidInput = QtCore.Signal() + validInput = QtCore.Signal() + + def __init__(self): + super(NameValidator, self).__init__() + self.gm_type = None + + def validate(self, inp, pos): + if not self.gm_type: + raise Exception("Must set gm_type") + if not inp or inp in vcs.listelements(self.gm_type): + self.invalidInput.emit() + return QtGui.QValidator.Intermediate + else: + self.validInput.emit() + return QtGui.QValidator.Acceptable + + +class EditGmDialog(GraphicsMethodOkDialog): + def __init__(self, gtype, ginstance, store=True): + """Store designates whether the gm is to be saved on okPressed for use when creating new gms""" + self.gtype = gtype + self.ginstance = ginstance + f = cdms2.open(os.path.join(vcs.sample_data, 'clt.nc')) + f = FileMetadataWrapper(f) + var = f('clt') + + gm = vcs.creategraphicsmethod(str(gtype), str(ginstance)) + tmpl = vcs.createtemplate() + + self.edit_tmpl_name = tmpl.name + self.edit_gm_name = gm.name + super(EditGmDialog, self).__init__(gm, var, tmpl) + + self.setWindowTitle('Editing ' + self.ginstance) + + self.rejected.connect(self.resetGM) + self.rejected.connect(self.resetTmpl) + + if not store: + self.accepted.connect(self.createGM) + + def createNewGM(self, gm): + return gm + + def okClicked(self): + self.hide() + + def resetGM(self): + if self.edit_gm_name: + del vcs.elements[self.gtype][self.edit_gm_name] + self.edit_gm_name = None + + def resetTmpl(self): + if self.edit_tmpl_name: + del vcs.elements['template'][self.edit_tmpl_name] + self.edit_tmpl_name = None + + def createGM(self): + cur_index = get_gms().indexOf(self.gtype, vcs.getgraphicsmethod(self.gtype, self.ginstance)) + del vcs.elements[self.gtype][self.ginstance] + if self.edit_gm_name: + gm = vcs.creategraphicsmethod(self.gtype, self.edit_gm_name, self.ginstance) + get_gms().replace(cur_index, gm) + self.resetTmpl() + self.resetGM() + + +class CreateGM(ValidatingInputDialog): + def __init__(self, currently_selected, parent=None): + super(CreateGM, self).__init__() + + self.edit_dialog = None + + validator = NameValidator() + self.setValidator(validator) + self.setLabelText('Name:') + + self.gm_type_combo = QtGui.QComboBox() + self.gm_type_combo.setModel(get_gms()) + if currently_selected: + self.gm_type_combo.setCurrentIndex(self.gm_type_combo.findText(currently_selected[0])) + self.gm_type_combo.currentIndexChanged.connect(self.setGMRoot) + self.edit.validator().gm_type = self.gm_type_combo.currentText() + + # Create the instance combo first so the setGMRoot function can update it properly + self.gm_instance_combo = QtGui.QComboBox() + self.gm_instance_combo.setModel(get_gms()) + self.gm_instance_combo.setRootModelIndex(get_gms().index(self.gm_type_combo.currentIndex(), 0)) + if currently_selected and len(currently_selected) > 1: + self.gm_instance_combo.setCurrentIndex(self.gm_instance_combo.findText(currently_selected[1])) + else: + self.gm_instance_combo.setCurrentIndex(self.gm_instance_combo.findText('default')) + + type_layout = QtGui.QHBoxLayout() + type_layout.addWidget(label('Graphics Method Type:')) + type_layout.addWidget(self.gm_type_combo) + + instance_layout = QtGui.QHBoxLayout() + instance_layout.addWidget(label('Graphics Method:')) + instance_layout.addWidget(self.gm_instance_combo) + + self.vertical_layout.insertLayout(0, instance_layout) + self.vertical_layout.insertLayout(0, type_layout) + + # add customize button + if not (currently_selected and currently_selected[0] in gms_with_non_implemented_editors): + button_layout = self.vertical_layout.itemAt(self.vertical_layout.count() - 1).layout() + customize_button = QtGui.QPushButton('Customize') + customize_button.clicked.connect(self.editGM) + button_layout.insertWidget(1, customize_button) + + self.accepted.connect(self.createGM) + + def setGMRoot(self, index): + if self.edit_dialog is not None: + self.edit_dialog.deleteLater() + self.edit_dialog = None + self.edit.validator().gm_type = self.gm_type_combo.currentText() + self.gm_instance_combo.setRootModelIndex(get_gms().index(index, 0)) + self.gm_instance_combo.setCurrentIndex(self.gm_instance_combo.findText('default')) + self.edit.validator().validate(self.edit.text(), 0) + + def createGM(self): + if self.edit_dialog and self.edit_dialog.edit_gm_name: + gm = vcs.creategraphicsmethod(str(self.gm_type_combo.currentText()), + self.edit_dialog.edit_gm_name, + str(self.textValue())) + get_gms().add_gm(gm) + del vcs.elements[self.gm_type_combo.currentText()][self.edit_dialog.edit_gm_name] + + else: + gm = vcs.creategraphicsmethod(str(self.gm_type_combo.currentText()), + str(self.gm_instance_combo.currentText()), + str(self.textValue())) + get_gms().add_gm(gm) + + if self.edit_dialog: + self.edit_dialog.deleteLater() + self.edit_dialog = None + + def editGM(self): + if not self.edit_dialog: + self.edit_dialog = EditGmDialog(self.gm_type_combo.currentText(), self.gm_instance_combo.currentText()) + + self.edit_dialog.show() + self.edit_dialog.raise_() class GraphicsMethodWidget(StaticDockWidget): + editedGM = QtCore.Signal() def __init__(self, parent=None, flags=0): super(GraphicsMethodWidget, self).__init__("Graphics Methods", parent=parent, flags=flags) @@ -14,20 +173,52 @@ def __init__(self, parent=None, flags=0): self.add_gm, self.edit_gm, self.remove_gm)) + + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) self.list = GraphicsMethodList() + self.list.changedSelection.connect(self.selection_change) self.setWidget(self.list) + self.add_gm_widget = None + self.edit_dialog = None + self.ginstance = None + self.gtype = None def selection_change(self): selected = self.list.get_selected() + self.ginstance = None if selected is None: return - self.selectedGraphicsMethod.emit(selected) + if selected: + self.gtype = selected[0] + if len(selected) > 1 and self.gtype not in gms_with_non_implemented_editors: + self.ginstance = selected[1] + self.titleBarWidget().edit.setEnabled(True) + self.titleBarWidget().remove.setEnabled(True) + else: + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) + return + if self.ginstance == 'default': + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) + return + elif not self.ginstance: + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) + return def add_gm(self): - pass + self.add_gm_widget = CreateGM(self.list.get_selected()) + self.add_gm_widget.show() + self.add_gm_widget.raise_() def edit_gm(self): - pass + self.edit_dialog = EditGmDialog(self.gtype, self.ginstance, False) + self.edit_dialog.accepted.connect(self.editedGM.emit) + self.edit_dialog.show() + self.edit_dialog.raise_() def remove_gm(self): - pass + model_index = get_gms().indexOf(self.gtype, vcs.getgraphicsmethod(self.gtype, self.ginstance)) + get_gms().removeRows(model_index.row(), 1, parent=model_index.parent()) diff --git a/cdatgui/graphics/models.py b/cdatgui/graphics/models.py index 76d6233..3c9493f 100644 --- a/cdatgui/graphics/models.py +++ b/cdatgui/graphics/models.py @@ -10,14 +10,28 @@ def __init__(self, parent=None): self.gm_types = sorted(vcs.graphicsmethodlist()) self.gms = {gmtype: [el for el in vcs.elements[gmtype].values() if el.name[:1] != "__"] for gmtype in vcs.graphicsmethodlist()} + self.next_id = 0 + self.keys = {} def add_gm(self, gm): parent_row = self.gm_types.index(vcs.graphicsmethodtype(gm)) self.insertRows(self.rowCount(), 1, [gm], self.index(parent_row, 0)) + def removeRows(self, row, count, parent=QtCore.QModelIndex()): + if not parent.isValid(): + # Can't remove graphics method types + return False + self.beginRemoveRows(parent, row, row + count - 1) + del self.gms[self.gm_types[parent.row()]][row:row + count] + self.endRemoveRows() + return True + def indexOf(self, gmtype, gm): parent = self.gm_types.index(gmtype) - actual = self.gms[gmtype].index(gm) + for list_gm in self.gms[gmtype]: + if list_gm.name == gm.name: + actual = self.gms[gmtype].index(list_gm) + break return self.index(actual, 0, parent=self.index(parent, 0)) def replace(self, index, gm): @@ -45,24 +59,31 @@ def get_dropped(self, md): def index(self, row, col, parent=QtCore.QModelIndex()): if parent.isValid(): - # Grab the child of parent - gm_type = self.gm_types[parent.row()] - pointer = self.gms[gm_type][row] + key = (parent.row(), row) else: - pointer = self.gm_types[row] - - return self.createIndex(row, col, pointer) + key = (row, None) + if key in self.keys: + internalid = self.keys[key] + else: + internalid = self.next_id + self.keys[key] = internalid + self.next_id += 1 + return self.createIndex(row, col, internalid) def parent(self, qmi): - if qmi.internalPointer() in self.gm_types: - # Root level item, no work required + if not qmi.isValid(): return QtCore.QModelIndex() - for ind, gtype in enumerate(self.gm_types): - if qmi.internalPointer() in self.gms[gtype]: - return self.index(ind, 0) + for key in self.keys: + if qmi.internalId() == self.keys[key]: + break + else: + return QtCore.QModelIndex() - return QtCore.QModelIndex() + if key[1] is None: + return QtCore.QModelIndex() + else: + return self.index(key[0], 0) def columnCount(self, qmi=QtCore.QModelIndex()): return 1 @@ -72,8 +93,8 @@ def insertRows(self, row, count, gms, parent=QtCore.QModelIndex()): raise ValueError("Can't insert new Graphics Method Types") parent_name = self.data(parent) - self.beginInsertRows(parent, row, row + count) - self.gms = self.gms[parent_name][:row] + gms + self.gms[parent_name][row:] + self.beginInsertRows(parent, row, row + count - 1) + self.gms[parent_name] = self.gms[parent_name][:row] + gms + self.gms[parent_name][row:] self.endInsertRows() def rowCount(self, modelIndex=QtCore.QModelIndex()): @@ -86,14 +107,15 @@ def rowCount(self, modelIndex=QtCore.QModelIndex()): def data(self, index, role=QtCore.Qt.DisplayRole): if role != QtCore.Qt.DisplayRole: return None - parent = index.parent() - if parent.isValid(): - # index is a GM - gtype = parent.internalPointer() - gm = self.gms[gtype][index.row()] - return unicode(gm.name) - else: - return unicode(self.gm_types[index.row()]) + + for key in self.keys: + if self.keys[key] == index.internalId(): + break + gm_type = self.gm_types[key[0]] + if key[1] is None: + return unicode(gm_type) + gm = self.gms[gm_type][key[1]] + return unicode(gm.name) def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): return u"Graphics Method" diff --git a/cdatgui/graphics/vcs_gm_list.py b/cdatgui/graphics/vcs_gm_list.py index 6745cb0..cb258c8 100644 --- a/cdatgui/graphics/vcs_gm_list.py +++ b/cdatgui/graphics/vcs_gm_list.py @@ -4,6 +4,8 @@ class GraphicsMethodList(QtGui.QTreeView): + changedSelection = QtCore.Signal() + def __init__(self, parent=None): super(GraphicsMethodList, self).__init__(parent=parent) self.setModel(get_gms()) @@ -12,18 +14,14 @@ def __init__(self, parent=None): self.setIndentation(10) def get_selected(self): - items = self.selectedItems() - sel = None - + items = self.selectedIndexes() for selected in items: - if selected.parent() is None: - continue - - p = selected.parent() + if not selected.parent().isValid(): + return [selected.data()] - t = self.types[p.text(0)] - gm = t[selected.text(0)] - sel = gm - break + return [selected.parent().data(), selected.data()] + return None - return sel + def selectionChanged(self, selected, deselected): + super(GraphicsMethodList, self).selectionChanged(selected, deselected) + self.changedSelection.emit() diff --git a/cdatgui/main_menu.py b/cdatgui/main_menu.py index 46217a1..10ac939 100644 --- a/cdatgui/main_menu.py +++ b/cdatgui/main_menu.py @@ -1,9 +1,11 @@ from PySide import QtGui, QtCore +from functools import partial + from cdatgui.cdat.importer import import_script from cdatgui.cdat.exporter import export_script from cdatgui.variables.manager import manager import os -import numpy +from cdatgui.variables.manipulations.manipulation import Manipulations class MainMenu(QtGui.QMenuBar): @@ -14,6 +16,7 @@ def __init__(self, spreadsheet, var, gm, tmpl, parent=None): self.var = var self.gm = gm self.tmpl = tmpl + self.manipulations = Manipulations() file_menu = self.addMenu("File") openscript = file_menu.addAction("Open Script") @@ -24,6 +27,55 @@ def __init__(self, spreadsheet, var, gm, tmpl, parent=None): save.setShortcut(QtGui.QKeySequence.Save) save.triggered.connect(self.save_script) + self.edit_data_menu = self.addMenu("Edit Data") + self.edit_data_menu.setEnabled(False) + seasonal_climatology = self.edit_data_menu.addAction("Seasonal Climatologies") + seasonal_climatology.triggered.connect(partial(self.manipulations.launchClimatologyDialog, 'seasonal')) + + monthly_climatology = self.edit_data_menu.addAction("Monthly Climatologies") + monthly_climatology.triggered.connect(partial(self.manipulations.launchClimatologyDialog, 'monthly')) + + regrid = self.edit_data_menu.addAction("Regrid") + regrid.triggered.connect(self.manipulations.launchRegridDialog) + + average = self.edit_data_menu.addAction("Average") + average.triggered.connect(self.manipulations.launchAverageDialog) + + summation = self.edit_data_menu.addAction("Summation") + summation.triggered.connect(self.manipulations.launchSumDialog) + + std = self.edit_data_menu.addAction("Standard Deviation") + std.triggered.connect(self.manipulations.launchSTDDialog) + + departure = self.edit_data_menu.addAction("Departures") + departure.triggered.connect(self.manipulations.launchDepartureDialog) + + correlation = self.edit_data_menu.addAction("Correlation") + correlation.triggered.connect( + partial(self.manipulations.launchCorrelationOrCovarianceDialog, correlation.text())) + + covariance = self.edit_data_menu.addAction("Covariance") + covariance.triggered.connect(partial(self.manipulations.launchCorrelationOrCovarianceDialog, covariance.text())) + + lagged_correlation = self.edit_data_menu.addAction("Lagged Correlation") + lagged_correlation.triggered.connect( + partial(self.manipulations.launchCorrelationOrCovarianceDialog, lagged_correlation.text())) + + lagged_covariance = self.edit_data_menu.addAction("Lagged Covariance") + lagged_covariance.triggered.connect( + partial(self.manipulations.launchCorrelationOrCovarianceDialog, lagged_covariance.text())) + + linear_regression = self.edit_data_menu.addAction("Linear Regression") + linear_regression.triggered.connect(self.manipulations.launchLinearRegressionDialog) + + geometric_mean = self.edit_data_menu.addAction("Geometric Mean") + geometric_mean.triggered.connect(self.manipulations.launchGeometricMeanDialog) + + weighted_mean = self.edit_data_menu.addAction("Weighted Mean") + + variance = self.edit_data_menu.addAction("Variance") + variance.triggered.connect(self.manipulations.launchVarianceDialog) + def open_script(self): filePath = QtGui.QFileDialog.getOpenFileName(self, u"Open Script", @@ -50,9 +102,9 @@ def open_script(self): # Now we need to sync up the plotters with the displays for cell, display_plots in zip(cells, displays): cell.load(display_plots) - # TODO: - # Should also load graphics methods and templates into - # appropriate widgets if they're named + # TODO: + # Should also load graphics methods and templates into + # appropriate widgets if they're named def save_script(self): filePath = QtGui.QFileDialog.getSaveFileName(self, u"Save Script", "/", diff --git a/cdatgui/main_window.py b/cdatgui/main_window.py index a325f1e..4845034 100644 --- a/cdatgui/main_window.py +++ b/cdatgui/main_window.py @@ -23,9 +23,11 @@ def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()): self.add_left_dock(var_widget) gm_widget = GraphicsMethodWidget(parent=self) + gm_widget.editedGM.connect(self.spreadsheet.tabController.currentWidget().replotPlottersUpdateVars) self.add_left_dock(gm_widget) tmpl_widget = TemplateWidget(parent=self) + tmpl_widget.editedTmpl.connect(self.spreadsheet.tabController.currentWidget().replotPlottersUpdateVars) self.add_left_dock(tmpl_widget) inspector = InspectorWidget(self.spreadsheet, parent=self) @@ -33,9 +35,11 @@ def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()): con = ConsoleDockWidget(self.spreadsheet, parent=self) self.add_right_dock(con) + mm = MainMenu(self.spreadsheet, var_widget, gm_widget, tmpl_widget) + self.setMenuBar(mm) - self.setMenuBar(MainMenu(self.spreadsheet, var_widget, - gm_widget, tmpl_widget)) + var_widget.variableListNotEmpty.connect(lambda: mm.edit_data_menu.setEnabled(True)) + var_widget.variableListEmpty.connect(lambda: mm.edit_data_menu.setEnabled(False)) def add_left_dock(self, widget): self.addDockWidget(DockWidgetArea.LeftDockWidgetArea, widget) diff --git a/cdatgui/persistence/db.py b/cdatgui/persistence/db.py index a6b728b..7c808c4 100644 --- a/cdatgui/persistence/db.py +++ b/cdatgui/persistence/db.py @@ -39,6 +39,7 @@ def db_version(filename): def connect(): global __dbconn__ if __dbconn__ is None: + # TODO: Use vcs.getdotdir() or whatever it is dotdir = os.path.expanduser("~/.uvcdat") path = os.path.expanduser(os.path.join(dotdir, "cdatgui_%s.db" % cdatgui.info.version)) @@ -89,3 +90,11 @@ def add_data_source(uri): matching.last_accessed = datetime.date.today() matching.times_used += 1 db.commit() + +def remove_data_source(uri): + db = connect() + + matching = db.query(DataSource).filter_by(uri=uri).first() + if matching is not None: + db.delete(matching) + db.commit() diff --git a/cdatgui/sidebar/inspector_widget.py b/cdatgui/sidebar/inspector_widget.py index 0628013..771a637 100644 --- a/cdatgui/sidebar/inspector_widget.py +++ b/cdatgui/sidebar/inspector_widget.py @@ -6,7 +6,8 @@ from cdatgui.templates import get_templates from cdatgui.variables.edit_variable_widget import EditVariableDialog from cdatgui.templates.dialog import TemplateEditorDialog -from cdatgui.graphics.dialog import BoxfillDialog +from cdatgui.graphics.dialog import GraphicsMethodSaveDialog +from cdatgui.graphics import gms_with_non_implemented_editors import vcs @@ -59,12 +60,13 @@ def select(self, ind): class InspectorWidget(StaticDockWidget): - plotters_updated = QtCore.Signal(list) + plotters_updated = QtCore.Signal() def __init__(self, spreadsheet, parent=None): super(InspectorWidget, self).__init__("Inspector", parent=parent) self.allowed_sides = [QtCore.Qt.DockWidgetArea.RightDockWidgetArea] spreadsheet.selectionChanged.connect(self.selection_change) + self.plotters_updated.connect(spreadsheet.tabController.currentWidget().replotPlottersUpdateVars) self.cells = [] self.current_plot = None self.plots = PlotterListModel() @@ -168,13 +170,15 @@ def editSecondVar(self, var): self.editVariable(var) def editGraphicsMethod(self, gm): - get_gms().replace(get_gms().indexOf("boxfill", gm), gm) - self.current_plot.graphics_method = gm + get_gms().replace(get_gms().indexOf(vcs.graphicsmethodtype(gm), gm), gm) + self.current_plot.graphics_method = (gm, False) + self.plotters_updated.emit() def makeGraphicsMethod(self, gm): get_gms().add_gm(gm) self.gm_instance_combo.setCurrentIndex(self.gm_instance_combo.count() - 1) - self.current_plot.graphics_method = gm + self.current_plot.graphics_method = (gm, False) + self.plotters_updated.emit() def editGM(self): gm_type = self.gm_type_combo.currentText() @@ -182,32 +186,24 @@ def editGM(self): gm = vcs.getgraphicsmethod(gm_type, gm_name) if self.gm_editor: - self.gm_editor.reject() + self.gm_editor.close() self.gm_editor.deleteLater() - self.gm_editor = BoxfillDialog(gm, self.var_combos[0].currentObj(), self.template_combo.currentObj()) + self.gm_editor = GraphicsMethodSaveDialog(gm, self.var_combos[0].currentObj(), self.template_combo.currentObj()) self.gm_editor.createdGM.connect(self.makeGraphicsMethod) self.gm_editor.editedGM.connect(self.editGraphicsMethod) self.gm_editor.show() self.gm_editor.raise_() - def makeTmpl(self, template): - get_templates().add_template(template) - - def editTmpl(self, template): - ind = get_templates().indexOf(template) - if ind.isValid(): - get_templates.replace(ind.row(), template) - def editTemplate(self, tmpl): - if self.tmpl_editor: - self.tmpl_editor.reject() - self.tmpl_editor.deleteLater() self.tmpl_editor = TemplateEditorDialog(tmpl) - self.tmpl_editor.createdTemplate.connect(self.makeTmpl) - self.tmpl_editor.editedTemplate.connect(self.editTmpl) + self.tmpl_editor.doneEditing.connect(self.setTemplateCombo) self.tmpl_editor.show() self.tmpl_editor.raise_() + def setTemplateCombo(self, tmpl_name): + self.template_combo.setCurrentIndex(self.template_combo.findText(tmpl_name)) + self.plotters_updated.emit() + def deletePlot(self, plot): ind = self.plot_combo.currentIndex() self.plots.remove(ind) @@ -215,22 +211,50 @@ def deletePlot(self, plot): def setGMRoot(self, index): self.gm_instance_combo.setRootModelIndex(get_gms().index(index, 0)) + self.edit_gm_button.setEnabled(False) def setTemplate(self, template): - self.current_plot.template = template + self.current_plot.template = (template, False) + self.plotters_updated.emit() def updateGM(self, index): + if self.gm_type_combo.currentText() not in gms_with_non_implemented_editors: + self.edit_gm_button.setEnabled(True) gm_type = self.gm_type_combo.currentText() gm_name = self.gm_instance_combo.currentText() + if gm_type in ['vector', '3d_vector', '3d_dual_scalar']: + self.var_combos[1].setEnabled(True) + enabled = True + else: + block = self.var_combos[1].blockSignals(True) + self.var_combos[1].setCurrentIndex(-1) + self.var_combos[1].blockSignals(block) + self.var_combos[1].setEnabled(False) + enabled = False + self.current_plot._vars = (self.current_plot.variables[0], None) + + if enabled and self.var_combos[1].currentIndex() == -1: + gm = vcs.getgraphicsmethod(gm_type, gm_name) + self.current_plot.graphics_method = (gm, False) + else: + gm = vcs.getgraphicsmethod(gm_type, gm_name) + self.current_plot.graphics_method = (gm, False) - gm = vcs.getgraphicsmethod(gm_type, gm_name) - self.current_plot.graphics_method = gm + self.plotters_updated.emit() def setFirstVar(self, var): - self.current_plot.variables = [var, self.current_plot.variables[1]] + self.current_plot.variables = [var, self.current_plot.variables[1], False] + self.plotters_updated.emit() def setSecondVar(self, var): - self.current_plot.variables = [self.current_plot.variables[0], var] + old_vars = self.current_plot.variables + try: + self.current_plot.variables = [self.current_plot.variables[0], var.var, False] + except ValueError: + old_vars.append(False) + self.current_plot.variables = old_vars + + self.plotters_updated.emit() def selectPlot(self, plot): plotIndex = self.plot_combo.currentIndex() @@ -261,7 +285,7 @@ def selectPlot(self, plot): block = self.template_combo.blockSignals(True) self.template_combo.setCurrentIndex(self.template_combo.findText(plot.template.name)) self.template_combo.blockSignals(block) - if self.gm_type_combo.currentText() == "boxfill": + if self.gm_instance_combo.currentText() != '' and self.gm_type_combo.currentText() not in gms_with_non_implemented_editors: self.edit_gm_button.setEnabled(True) else: self.edit_gm_button.setEnabled(False) @@ -275,7 +299,6 @@ def selectPlot(self, plot): v.setEnabled(False) def selection_change(self, selected): - plots = [] self.cells = [] self.plots.clear() for cell in selected: diff --git a/cdatgui/spreadsheet/cell.py b/cdatgui/spreadsheet/cell.py index 3aaaa3a..bdc801d 100755 --- a/cdatgui/spreadsheet/cell.py +++ b/cdatgui/spreadsheet/cell.py @@ -118,11 +118,11 @@ def dumpToFile(self, filename): pixmap = self.grabWindowPixmap() ext = os.path.splitext(filename)[1].lower() if not ext: - pixmap.save(filename, 'PNG') + pixmap.accept(filename, 'PNG') elif ext == '.pdf': self.saveToPDF(filename) else: - pixmap.save(filename) + pixmap.accept(filename) def saveToPDF(self, filename): printer = QtGui.QPrinter() diff --git a/cdatgui/spreadsheet/sheet.py b/cdatgui/spreadsheet/sheet.py index fc11820..f0d3593 100755 --- a/cdatgui/spreadsheet/sheet.py +++ b/cdatgui/spreadsheet/sheet.py @@ -232,7 +232,7 @@ def paint(self, painter, option, index): """ QtGui.QItemDelegate.paint(self, painter, option, index) if ((index.row(), index.column()) == self.table.activeCell): - painter.save() + painter.accept() painter.setPen(QtGui.QPen(QtGui.QBrush( QtGui.QColor(0.8549 * 255, 0.6971 * 255, 0.2255 * 255)), self.padding)) r = self.table.visualRect(index) diff --git a/cdatgui/spreadsheet/tab.py b/cdatgui/spreadsheet/tab.py index 2b087ed..6485645 100755 --- a/cdatgui/spreadsheet/tab.py +++ b/cdatgui/spreadsheet/tab.py @@ -411,10 +411,10 @@ def exportSheetToImages(self, dirPath, format='png'): for c in xrange(cCount): widget = self.getCell(r, c) if widget: - widget.grabWindowPixmap().save(dirPath + '/' + - chr(c + ord('a')) + - str(r + 1) + - '.' + format) + widget.grabWindowPixmap().accept(dirPath + '/' + + chr(c + ord('a')) + + str(r + 1) + + '.' + format) def setSpan(self, row, col, rowSpan, colSpan): """ setSpan(row, col, rowSpan, colSpan: int) -> None @@ -442,7 +442,7 @@ class StandardWidgetSheetTab(QtGui.QWidget, StandardWidgetSheetTabInterface): displaying the spreadsheet. """ - + plotsRemoved = QtCore.Signal(list) selectionChanged = QtCore.Signal(list) emitAllPlots = QtCore.Signal(list) @@ -492,7 +492,6 @@ def createContainers(self): widget = QCDATWidget(r, c) widget.plotAdded.connect(self.selectionChange) widget.emitAllPlots.connect(self.totalPlotsChanged) - # widget.emitAllPlots.connect(self.totalPlotsChanged) cellWidget = QCellContainer(widget) self.setCellByWidget(r, c, cellWidget) @@ -514,6 +513,37 @@ def removeContainers(self, new_rows=None, new_cols=None): for col in cols: self.setCellByWidget(row, col, None) + def getCellWidgets(self): + row_count, col_count = self.getDimension() + cell_widgets = [] + for r in xrange(row_count): + for c in xrange(col_count): + w = self.getCellWidget(r, c) + if w is not None and w.widget() is not None: + cell_widgets.append(w) + return cell_widgets + + def checkDisplayPlots(self, tracked_plots): + # print "checking display plots" + cell_widgets = self.getCellWidgets() + removed_plots = [] + for cell in cell_widgets: + qcdat = cell.widget() + tracked_plots = qcdat.getPlotters() + # print "tracked plots", tracked_plots + d_names = qcdat.canvas.display_names + # print 'current d_names', d_names + for plot in tracked_plots: + # print "plot dp", plot.dp + # if plot.dp: + # print "plot dp name", plot.dp.name + if plot.dp and plot.dp.name not in d_names: + removed_plots.append(plot) + + if removed_plots: + # print "plots removed" + self.plotsRemoved.emit(removed_plots) + def rowSpinBoxChanged(self): """ rowSpinBoxChanged() -> None Handle the number of row changed @@ -538,6 +568,23 @@ def colSpinBoxChanged(self): self.createContainers() self.totalPlotsChanged() + def replotPlottersUpdateVars(self): + + total_tabs = [] + plots = [] + for row in range(self.toolBar.rowSpinBox.value()): + for col in range(self.toolBar.colSpinBox.value()): + total_tabs.append(self.sheet.cellWidget(row, col)) + for cell in total_tabs: + cell = cell.containedWidget + # cell is now a QCDATWidget + plotter = cell.getPlotters() + plots.extend(plotter) + for plot in plots: + if plot.can_plot(): + plot.plot() + self.emitAllPlots.emit(total_tabs) + def totalPlotsChanged(self): total_tabs = [] for row in range(self.toolBar.rowSpinBox.value()): @@ -546,7 +593,6 @@ def totalPlotsChanged(self): self.emitAllPlots.emit(total_tabs) ### Belows are API Wrappers to connect to self.sheet - def getDimension(self): """ getDimension() -> tuple Get the sheet dimensions diff --git a/cdatgui/spreadsheet/tabcontroller.py b/cdatgui/spreadsheet/tabcontroller.py index 0f0242a..0691d34 100755 --- a/cdatgui/spreadsheet/tabcontroller.py +++ b/cdatgui/spreadsheet/tabcontroller.py @@ -82,12 +82,11 @@ def __init__(self, parent=None): self.tabBar().tabTextChanged.connect(self.changeTabText) self.addAction(self.showNextTabAction()) self.addAction(self.showPrevTabAction()) - self.executedPipelines = [[],{},{}] + self.executedPipelines = [[], {}, {}] self.monitoredPipelines = {} self.spreadsheetFileName = None self.tabCloseRequested.connect(self.delete_sheet_by_index) - def create_first_sheet(self): self.addTabWidget(StandardWidgetSheetTab(self), 'Sheet 1') @@ -202,11 +201,13 @@ def uvcdatPreferencesAction(self): themeMenu = prefMenu.addMenu("Icons Theme") defaultThemeAction = themeMenu.addAction("Default") defaultThemeAction.setCheckable(True) - defaultThemeAction.setStatusTip("Use the default theme (the application must be restarted for changes to take effect)") + defaultThemeAction.setStatusTip( + "Use the default theme (the application must be restarted for changes to take effect)") minimalThemeAction = themeMenu.addAction("Minimal") minimalThemeAction.setCheckable(True) - minimalThemeAction.setStatusTip("Use the minimal theme (the application must be restarted for changes to take effect)") + minimalThemeAction.setStatusTip( + "Use the minimal theme (the application must be restarted for changes to take effect)") themegroup = QtGui.QActionGroup(self) themegroup.addAction(defaultThemeAction) themegroup.addAction(minimalThemeAction) @@ -284,7 +285,7 @@ def newSheetActionTriggered(self, checked=False): """ self.setCurrentIndex(self.addTabWidget(StandardWidgetSheetTab(self), - 'Sheet %d' % (self.count()+1))) + 'Sheet %d' % (self.count() + 1))) self.currentWidget().sheet.stretchCells() def tabInserted(self, index): @@ -314,7 +315,7 @@ def deleteSheetActionTriggered(self, checked=False): Actual code to delete the current sheet """ - if self.count()>0: + if self.count() > 0: widget = self.currentWidget() self.tabWidgets.remove(widget) self.removeTab(self.currentIndex()) @@ -328,7 +329,7 @@ def clearTabs(self): Clear and reset the controller """ - while self.count()>0: + while self.count() > 0: self.deleteSheetActionTriggered() for i in reversed(range(len(self.tabWidgets))): t = self.tabWidgets[i] @@ -344,7 +345,7 @@ def insertTab(self, idx, tabWidget, tabText): QTabWidget or a QStackedWidget """ - if self.operatingWidget!=self: + if self.operatingWidget != self: ret = self.operatingWidget.insertWidget(idx, tabWidget) self.operatingWidget.setCurrentIndex(ret) return ret @@ -379,14 +380,14 @@ def moveTab(self, tabIdx, destination): Move a tab at tabIdx to a different position at destination """ - if (tabIdx<0 or tabIdx>self.count() or - destination<0 or destination>self.count()): + if (tabIdx < 0 or tabIdx > self.count() or + destination < 0 or destination > self.count()): return tabText = self.tabText(tabIdx) tabWidget = self.widget(tabIdx) self.removeTab(tabIdx) self.insertTab(destination, tabWidget, tabText) - if tabIdx==self.currentIndex(): + if tabIdx == self.currentIndex(): self.setCurrentIndex(destination) def splitTab(self, tabIdx, pos=None): @@ -394,7 +395,7 @@ def splitTab(self, tabIdx, pos=None): Split a tab to be a stand alone window and move to position pos """ - if tabIdx<0 or tabIdx>self.count() or self.count()==0: + if tabIdx < 0 or tabIdx > self.count() or self.count() == 0: return tabWidget = self.widget(tabIdx) self.removeTab(tabIdx) @@ -411,9 +412,9 @@ def mergeTab(self, frame, tabIdx): Merge a tab dock widget back to the controller at position tabIdx """ - if tabIdx<0 or tabIdx>self.count(): + if tabIdx < 0 or tabIdx > self.count(): return - if tabIdx==self.count(): tabIdx = -1 + if tabIdx == self.count(): tabIdx = -1 tabWidget = frame.widget() frame.setWidget(None) while frame in self.floatingTabWidgets: @@ -437,8 +438,8 @@ def insertTabWidget(self, index, tabWidget, sheetLabel): Insert a tab widget to the controller at some location """ - if sheetLabel==None: - sheetLabel = 'Sheet %d' % (len(self.tabWidgets)+1) + if sheetLabel == None: + sheetLabel = 'Sheet %d' % (len(self.tabWidgets) + 1) if not tabWidget in self.tabWidgets: self.tabWidgets.append(tabWidget) tabWidget.setWindowTitle(sheetLabel) @@ -454,7 +455,7 @@ def tabWidgetUnderMouse(self): if t.underMouse(): result = t else: - t.showHelpers(False, QtCore.QPoint(-1,-1)) + t.showHelpers(False, QtCore.QPoint(-1, -1)) return result def showNextTab(self): @@ -462,8 +463,8 @@ def showNextTab(self): Bring the next tab up """ - if self.operatingWidget.currentIndex()0: - index = self.operatingWidget.currentIndex()-1 + if self.operatingWidget.currentIndex() > 0: + index = self.operatingWidget.currentIndex() - 1 self.operatingWidget.setCurrentIndex(index) def tabPopupMenu(self): @@ -481,10 +482,10 @@ def tabPopupMenu(self): """ menu = QtGui.QMenu(self) - en = self.operatingWidget.currentIndex()0 + en = self.operatingWidget.currentIndex() > 0 self.showPrevTabAction().setEnabled(en) menu.addAction(self.showPrevTabAction()) menu.addSeparator() @@ -492,7 +493,7 @@ def tabPopupMenu(self): t = self.operatingWidget.widget(idx) action = menu.addAction(t.windowTitle()) action.setData(idx) - if t==self.operatingWidget.currentWidget(): + if t == self.operatingWidget.currentWidget(): action.setIcon(QtGui.QIcon(':/images/ok.png')) return menu diff --git a/cdatgui/spreadsheet/vtk_classes.py b/cdatgui/spreadsheet/vtk_classes.py index 8653263..40b89f0 100755 --- a/cdatgui/spreadsheet/vtk_classes.py +++ b/cdatgui/spreadsheet/vtk_classes.py @@ -7,11 +7,11 @@ from functools import partial from cdatgui.variables import get_variables - cdms_mime = "application/x-cdms-variable-list" vcs_gm_mime = "application/x-vcs-gm" vcs_template_mime = "application/x-vcs-template" + class QCDATWidget(QtGui.QFrame): # TODO: Add a signal for addedPlots plotAdded = QtCore.Signal() @@ -36,6 +36,7 @@ def __init__(self, row, col, parent=None): self.setAcceptDrops(True) self.mRenWin = vtk.vtkRenderWindow() + self.mRenWin.StencilCapableOn() self.iren = QVTKRenderWindowInteractor(parent=self, rw=self.mRenWin) self.canvas = None @@ -126,6 +127,7 @@ def dropEvent(self, event): self.iren.show() self.dragTarget.hide() self.plotAdded.emit() + self.emitAllPlots.emit() def dragEnterEvent(self, event): accepted = set([cdms_mime, vcs_gm_mime, vcs_template_mime]) diff --git a/cdatgui/templates/dialog.py b/cdatgui/templates/dialog.py index ee8c000..977a08b 100644 --- a/cdatgui/templates/dialog.py +++ b/cdatgui/templates/dialog.py @@ -2,6 +2,9 @@ from cdatgui.editors.template import TemplateEditor import vcs import copy +from cdatgui.bases.vcs_elements_dialog import VcsElementsDialog + +from cdatgui.templates import get_templates def sync_template(self, src): @@ -60,11 +63,13 @@ def sync_template(self, src): class TemplateEditorDialog(QtGui.QDialog): - createdTemplate = QtCore.Signal(object) - editedTemplate = QtCore.Signal(object) + doneEditing = QtCore.Signal(str) def __init__(self, tmpl, parent=None): super(TemplateEditorDialog, self).__init__(parent=parent) + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.close) self.real_tmpl = tmpl self.tmpl = vcs.createtemplate(source=tmpl) l = QtGui.QVBoxLayout() @@ -91,15 +96,40 @@ def __init__(self, tmpl, parent=None): l.addLayout(buttons) self.setLayout(l) + self.dialog = None def customName(self): - name = QtGui.QInputDialog.getText(self, u"Save As", u"Name for template:") - self.save(name) + # name = QtGui.QInputDialog.getText(self, u"Save As", u"Name for template:") + self.dialog = VcsElementsDialog('template') + self.dialog.setLabelText('Name:') + self.dialog.setWindowTitle('Save As') + + self.dialog.accepted.connect(self.grabName) + self.dialog.show() + self.dialog.raise_() + + def grabName(self): + self.save(self.dialog.textValue()) def save(self, name=None): if name is None: sync_template(self.real_tmpl, self.tmpl) - self.editedTemplate.emit(self.real_tmpl) + self.editTmpl(self.real_tmpl) + name = self.real_tmpl.name else: - template = vcs.createtemplate(name, self.tmpl.name) - self.createdTemplate.emit(template) + if name in vcs.listelements('template'): + del vcs.elements['template'][name] + template = vcs.createtemplate(str(name), self.tmpl.name) + self.makeTmpl(template) + + self.doneEditing.emit(name) + self.close() + + def editTmpl(self, template): + ind = get_templates().indexOf(template) + if ind.isValid(): + get_templates().replace(ind.row(), template) + + def makeTmpl(self, template): + get_templates().add_template(template) + diff --git a/cdatgui/templates/models.py b/cdatgui/templates/models.py index 41a26c2..bfc57f5 100644 --- a/cdatgui/templates/models.py +++ b/cdatgui/templates/models.py @@ -10,7 +10,8 @@ class VCSTemplateListModel(QtCore.QAbstractListModel): def __init__(self, tmpl_filter=None, parent=None): super(VCSTemplateListModel, self).__init__(parent=parent) if tmpl_filter is not None: - self.templates = [template for template in vcs.elements["template"].values() if tmpl_filter.search(template.name) is None] + self.templates = [template for template in vcs.elements["template"].values() if + tmpl_filter.search(template.name) is None] else: self.templates = vcs.elements["template"].values() @@ -23,6 +24,13 @@ def template_key(tmpl): self.templates = sorted(self.templates, key=template_key) + def replace(self, ind, tmpl): + if ind < len(self.templates): + self.templates[ind] = tmpl + self.dataChanged.emit(ind, ind) + else: + raise IndexError("Index %d out of range." % ind) + def get(self, ind): return self.templates[ind] @@ -51,10 +59,15 @@ def get_dropped(self, md): return vcs.elements["template"][template_name] def insertRows(self, row, count, templates, parent=QtCore.QModelIndex()): - self.beginInsertRows(parent, row, row + count) + self.beginInsertRows(parent, row, row + count - 1) self.templates = self.templates[:row] + templates + self.templates[row:] self.endInsertRows() + def removeRows(self, row, count, parent=QtCore.QModelIndex()): + self.beginRemoveRows(parent, row, row + count - 1) + self.templates.pop(row) + self.endRemoveRows() + def rowCount(self, modelIndex=None): return len(self.templates) diff --git a/cdatgui/templates/template_list.py b/cdatgui/templates/template_list.py index 05f2afc..ca491c9 100644 --- a/cdatgui/templates/template_list.py +++ b/cdatgui/templates/template_list.py @@ -1,16 +1,30 @@ -from PySide import QtGui -from models import VCSTemplateListModel +from PySide import QtGui, QtCore +from . import get_templates import re class TemplateList(QtGui.QListView): + changedSelection = QtCore.Signal() + def __init__(self, parent=None): super(TemplateList, self).__init__(parent=parent) - self.setModel(VCSTemplateListModel()) + self.setModel(get_templates()) self.setDragEnabled(True) + def currentRow(self): + if self.selectedIndexes(): + return self.selectedIndexes()[0].row() + return -1 + + def remove(self, tmpl): + self.model().removeRows(self.model().indexOf(tmpl).row(), 1) + def get_selected(self): ind = self.currentRow() if ind == -1: return None return self.model().templates[ind] + + def selectionChanged(self, *args, **kwargs): + super(TemplateList, self).selectionChanged(*args, **kwargs) + self.changedSelection.emit() diff --git a/cdatgui/templates/template_widget.py b/cdatgui/templates/template_widget.py index 43a9261..c6ece24 100644 --- a/cdatgui/templates/template_widget.py +++ b/cdatgui/templates/template_widget.py @@ -1,10 +1,14 @@ from PySide import QtCore from cdatgui.bases import StaticDockWidget +from cdatgui.templates import get_templates from cdatgui.toolbars import AddEditRemoveToolbar from template_list import TemplateList +from cdatgui.templates.dialog import TemplateEditorDialog +import vcs class TemplateWidget(StaticDockWidget): + editedTmpl = QtCore.Signal() def __init__(self, parent=None): super(TemplateWidget, self).__init__("Templates", parent=parent) @@ -15,19 +19,54 @@ def __init__(self, parent=None): self.edit_template, self.remove_template)) self.list = TemplateList() + self.list.changedSelection.connect(self.selection_change) + self.dialog = None self.setWidget(self.list) + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) def selection_change(self): selected = self.list.get_selected() - if selected is None: - return - self.selectedTemplate.emit(selected) + if selected is None or selected.name == 'default': + self.titleBarWidget().edit.setEnabled(False) + self.titleBarWidget().remove.setEnabled(False) + else: + self.titleBarWidget().edit.setEnabled(True) + self.titleBarWidget().remove.setEnabled(True) def add_template(self): - pass + # get template object + sel = self.list.get_selected() + if sel is None: + sel = vcs.gettemplate('default') + + self.dialog = TemplateEditorDialog(sel) + self.dialog.doneEditing.connect(self.template_edited) + + # remove save button + v_layout = self.dialog.layout() + v_layout.itemAt(v_layout.count() - 1).takeAt(3).widget().deleteLater() + + self.dialog.show() + self.dialog.raise_() def edit_template(self): - pass + sel = self.list.get_selected() + self.dialog = TemplateEditorDialog(sel) + + #remove save as button + v_layout = self.dialog.layout() + v_layout.itemAt(v_layout.count() - 1).takeAt(2).widget().deleteLater() + + self.dialog.doneEditing.connect(self.template_edited) + self.dialog.show() + self.dialog.raise_() def remove_template(self): - pass + sel = self.list.get_selected() + self.list.remove(sel) + + def template_edited(self, *args, **kargs): + self.editedTmpl.emit() + + diff --git a/cdatgui/variables/__init__.py b/cdatgui/variables/__init__.py index d76cd4c..56c95b2 100644 --- a/cdatgui/variables/__init__.py +++ b/cdatgui/variables/__init__.py @@ -7,4 +7,11 @@ def get_variables(): global __variables__ if __variables__ is None: __variables__ = models.CDMSVariableListModel() - return __variables__ \ No newline at end of file + return __variables__ + + +def reserved_words(): + return ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global', 'or', 'with', + 'assert', 'else', 'if', 'pass', 'yield', 'break', 'except', 'import', 'print', 'class', + 'exec', 'in', 'raise', 'continue', 'finally', 'is', 'return', 'def', 'for', 'lambda', + 'try'] diff --git a/cdatgui/variables/axes_widgets.py b/cdatgui/variables/axes_widgets.py index a930632..57b56ec 100644 --- a/cdatgui/variables/axes_widgets.py +++ b/cdatgui/variables/axes_widgets.py @@ -6,6 +6,10 @@ class QAxisList(QtGui.QWidget): axisEdited = QtCore.Signal(object) + manipulationComboIndexesChanged = QtCore.Signal() + validParams = QtCore.Signal() + invalidParams = QtCore.Signal() + """ Widget containing a list of axis widgets for the selected variable """ def __init__(self, cdmsFile=None, var=None, parent=None): @@ -13,6 +17,9 @@ def __init__(self, cdmsFile=None, var=None, parent=None): self.axisWidgets = [] # List of QAxis widgets self.cdmsFile = cdmsFile # cdms file associated with the variable self._var = None + self.squeeze = True + + self.manipulation_combos = [] # Init & set the layout self.vbox = QtGui.QVBoxLayout() @@ -31,10 +38,14 @@ def __init__(self, cdmsFile=None, var=None, parent=None): self.roi_layout.addLayout(self.roi_vbox) roi_preview = QtGui.QVBoxLayout() - self.roi_sample = ROIPreview((200, 200)) + # keep this proportional + self.roi_sample = ROIPreview((500, 500)) roi_preview.addWidget(self.roi_sample) - self.roi_layout.addLayout(roi_preview) + vbox.addLayout(roi_preview) vbox.addLayout(self.roi_layout) + + success = vbox.setAlignment(roi_preview, QtCore.Qt.AlignCenter) + self.roi_vbox.setAlignment(QtCore.Qt.AlignCenter) self.var = var def clear(self): @@ -50,6 +61,8 @@ def getKwargs(self): for widget in self.axisWidgets: key, bounds = widget.getSelector() kwargs[key] = bounds + if self.squeeze: + kwargs['squeeze'] = True return kwargs def getLatLon(self): @@ -64,32 +77,16 @@ def getLatLon(self): lon_ax = axis return lat_ax, lon_ax - def getROI(self): - if self._var is None: - return - - for w in self.axisWidgets: - if w.axis.isLatitude(): - lat_bot, lat_top = w.getBotTop() - if w.axis.isLongitude(): - lon_bot, lon_top = w.getBotTop() - - return (lat_bot, lat_top), (lon_bot, lon_top) - - def setROI(self, latitude=None, longitude=None): - if latitude is not None and self.latitude is not None: - self.latitude.setBotTop(*latitude) - if longitude is not None and self.longitude is not None: - self.longitude.setBotTop(*longitude) - def updateROI(self, axis): min_lat, max_lat = self.latitude.getBotTop() min_lon, max_lon = self.longitude.getBotTop() - self.roi_sample.setLatRange(min_lat, max_lat) - self.roi_sample.setLonRange(min_lon, max_lon) + self.roi_sample.setLatRange(min_lat, max_lat, self.latitude.range.flipped) + self.roi_sample.setLonRange(min_lon, max_lon, self.longitude.range.flipped) def getVar(self): - return self._var.get_original()(**self.getKwargs()) + orig = self._var.get_original() + new_var = orig(**self.getKwargs()) + return new_var def setVar(self, var): """ Iterate through the variable's axes and create and initialize an Axis @@ -98,6 +95,8 @@ def setVar(self, var): self.clear() self._var = var + latitude_layout = None + longitude_layout = None if var is None: return @@ -106,27 +105,46 @@ def setVar(self, var): var_axes = {ax.id: ax for ax in var.getAxisList()} for axis in orig.getAxisList(): - w = AxisBoundsChooser(var_axes[axis.id], source_axis=axis) - if axis.isLatitude(): - self.latitude = w - elif axis.isLongitude(): - self.longitude = w - else: - self.vbox.addWidget(w) - w.boundsEdited.connect(self.axisEdited.emit) - self.axisWidgets.append(w) + if axis.id in var_axes: + w = AxisBoundsChooser(var_axes[axis.id], source_axis=axis) + # add manipulations + manipulations_combo = QtGui.QComboBox() + manipulations_combo.currentIndexChanged.connect(lambda y: self.manipulationComboIndexesChanged.emit()) + self.manipulation_combos.append((axis.id, manipulations_combo)) + + for item in ['Default', 'Summation', 'Average', 'Standard Deviation', 'Geometric Mean']: + manipulations_combo.addItem(item) + + axis_bounds_layout = QtGui.QHBoxLayout() + axis_bounds_layout.addWidget(manipulations_combo) + axis_bounds_layout.addWidget(w) + w.validParams.connect(self.validParams.emit) + w.invalidParams.connect(self.invalidParams.emit) + if axis.isLatitude(): + self.latitude = w + latitude_layout = axis_bounds_layout + elif axis.isLongitude(): + self.longitude = w + longitude_layout = axis_bounds_layout + if axis.isCircular(): + self.roi_sample.setCircular(True) + else: + self.vbox.addLayout(axis_bounds_layout) + w.boundsEdited.connect(self.axisEdited.emit) + self.axisWidgets.append(w) if self.latitude is not None: if self.longitude is not None: - self.roi_vbox.addWidget(self.latitude) - self.roi_vbox.addWidget(self.longitude) + self.roi_vbox.addLayout(latitude_layout) + self.roi_vbox.addLayout(longitude_layout) self.layout().addLayout(self.roi_layout) self.latitude.boundsEdited.connect(self.updateROI) self.longitude.boundsEdited.connect(self.updateROI) + self.latitude.boundsEdited.emit(10) # dummy var to set initial values + self.longitude.boundsEdited.emit(10) else: - self.layout().addWidget(self.latitude) + self.layout().addLayout(latitude_layout) elif self.longitude is not None: - self.layout().addWidget(self.longitude) - + self.layout().addLayout(longitude_layout) var = property(getVar, setVar) diff --git a/cdatgui/variables/axis_bounds.py b/cdatgui/variables/axis_bounds.py index d11cd75..5ff3f53 100644 --- a/cdatgui/variables/axis_bounds.py +++ b/cdatgui/variables/axis_bounds.py @@ -1,13 +1,18 @@ from PySide import QtGui, QtCore + +import numpy + from cdatgui.bases import RangeWidget from cdatgui.utils import header_label -from cdatgui.cdat.axis import axis_values, selector_value +from cdatgui.cdat.axis import axis_values, selector_value, format_degrees, format_time_axis, parse_degrees, format_axis from functools import partial import genutil class AxisBoundsChooser(QtGui.QWidget): boundsEdited = QtCore.Signal(object) + validParams = QtCore.Signal() + invalidParams = QtCore.Signal() def __init__(self, axis, source_axis=None, parent=None): super(AxisBoundsChooser, self).__init__(parent=parent) @@ -15,22 +20,67 @@ def __init__(self, axis, source_axis=None, parent=None): self.axis = source_axis else: self.axis = axis + if all(earlier >= later for earlier, later in zip(axis, axis[1:])): + flipped = True + else: + flipped = False l = QtGui.QVBoxLayout() l.addWidget(header_label(axis.id)) if source_axis is not None: + self.values = [val for val in source_axis] + minimum, maximum = (float(num) for num in genutil.minmax(source_axis)) bottom, top = (float(num) for num in genutil.minmax(axis)) - for i, v in enumerate(source_axis): - if v == bottom: - bot_ind = i - if v == top: - top_ind = i - self.range = RangeWidget(axis_values(source_axis), bottom=bot_ind, top=top_ind) + + # adjust size for circular behavior + if source_axis.isCircular(): + diff = self.values[-1] - self.values[-2] + for i in range(len(self.values) / 2): + self.values.insert(0, self.values[0] - diff) + self.values.append(self.values[-1] + diff) + + formatted_vals = [] + for val in self.values: + formatted_vals.append(format_degrees(val)) + + for i, v in enumerate(self.values): + if v == bottom: + bot_ind = i + if v == top: + top_ind = i + if flipped: + tmp = bot_ind + bot_ind = top_ind + top_ind = tmp + + self.range = RangeWidget(formatted_vals, bottom=bot_ind, top=top_ind, axis_type=source_axis.id, + flipped=flipped) + else: + for i, v in enumerate(self.values): + if v == bottom: + bot_ind = i + if v == top: + top_ind = i + if flipped: + tmp = bot_ind + bot_ind = top_ind + top_ind = tmp + + formatter = format_axis(axis) + values = [] + for value in self.values: + values.append(formatter(value)) + + self.range = RangeWidget(values, bottom=bot_ind, top=top_ind, + axis_type=source_axis.id, flipped=flipped) else: minimum, maximum = (float(num) for num in genutil.minmax(axis)) self.range = RangeWidget(axis_values(axis)) + self.range.validParams.connect(self.validParams.emit) + self.range.invalidParams.connect(self.invalidParams.emit) + l.addWidget(self.range) self.setLayout(l) @@ -40,12 +90,12 @@ def __init__(self, axis, source_axis=None, parent=None): def getMinMax(self): indices = self.range.getBounds() - values = [self.axis[index] for index in indices] + values = [self.values[index] for index in indices] return values def getBotTop(self): indices = self.range.getBounds() - values = [self.axis[index] for index in indices] + values = [self.values[index] for index in indices] return values def setBotTop(self, bottom, top): @@ -66,7 +116,11 @@ def setBotTop(self, bottom, top): self.range.setBounds(lower_ind, upper_ind) def getSelector(self): - lower, upper = self.range.getBounds() - lower = selector_value(lower, self.axis) - upper = selector_value(upper, self.axis) - return self.axis.id, (lower, upper) + bound1, bound2 = self.range.getBounds() + if self.axis.isTime(): + bound1 = self.range.values[bound1] + bound2 = self.range.values[bound2] + else: + bound1 = parse_degrees(self.range.values[bound1]) + bound2 = parse_degrees(self.range.values[bound2]) + return self.axis.id, (bound1, bound2) diff --git a/cdatgui/variables/cdms_file_chooser.py b/cdatgui/variables/cdms_file_chooser.py index 3322faf..5c1e8ee 100644 --- a/cdatgui/variables/cdms_file_chooser.py +++ b/cdatgui/variables/cdms_file_chooser.py @@ -7,7 +7,7 @@ class CDMSFileChooser(QtGui.QDialog): def __init__(self, parent=None, f=0): super(CDMSFileChooser, self).__init__(parent=parent, f=f) - + self.setWindowModality(QtCore.Qt.ApplicationModal) self.tabs = VerticalTabWidget() layout = QtGui.QVBoxLayout() diff --git a/cdatgui/variables/cdms_file_tree.py b/cdatgui/variables/cdms_file_tree.py index ebd1fe6..1fc6e9e 100644 --- a/cdatgui/variables/cdms_file_tree.py +++ b/cdatgui/variables/cdms_file_tree.py @@ -1,5 +1,7 @@ from PySide import QtGui, QtCore import os.path +from collections import OrderedDict + from cdatgui.utils import icon import urlparse @@ -11,7 +13,7 @@ class CDMSFileItem(QtGui.QTreeWidgetItem): - def __init__(self, text, parent=None): + def __init__(self, text, uri, parent=None): global label_font, label_icon_size, label_icon if label_font is None: @@ -21,12 +23,14 @@ def __init__(self, text, parent=None): label_icon = icon("bluefile.png") super(CDMSFileItem, self).__init__(parent=parent) + + self.uri = uri self.setSizeHint(0, label_icon_size) self.setIcon(0, label_icon) self.setText(1, text) self.setFont(1, label_font) self.setExpanded(True) - self.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) class CDMSFileTree(QtGui.QTreeWidget): @@ -52,7 +56,7 @@ def add_file(self, cdmsfile): file_name = os.path.basename(parsed.path) - file_item = CDMSFileItem(file_name) + file_item = CDMSFileItem(file_name, cdmsfile.uri) for var in cdmsfile.variables: var_item = QtGui.QTreeWidgetItem() @@ -64,11 +68,20 @@ def add_file(self, cdmsfile): def get_selected(self): items = self.selectedItems() - variables = [] + variables = OrderedDict() for item in items: - var_name = item.text(1) - file_index = self.indexOfTopLevelItem(item.parent()) - cdmsfile = self.files_ordered[file_index] - variables.append(cdmsfile(var_name)) - - return variables + new_vars = [] + if isinstance(item, CDMSFileItem): + for index in range(item.childCount()): + new_vars.append(item.child(index)) + else: + new_vars.append(item) + for var in new_vars: + var_name = var.text(1) + file_index = self.indexOfTopLevelItem(var.parent()) + cdmsfile = self.files_ordered[file_index] + var_meta_item = cdmsfile(var_name) + if var.text(1) not in variables.values(): + variables[var_meta_item] = var.text(1) + + return variables.keys() diff --git a/cdatgui/variables/cdms_var_list.py b/cdatgui/variables/cdms_var_list.py index 76f5a6f..56a131f 100644 --- a/cdatgui/variables/cdms_var_list.py +++ b/cdatgui/variables/cdms_var_list.py @@ -19,12 +19,13 @@ def remove_variable(self, variable): ind = variable else: # It's a variable - for ind, var in enumerate(self.model().variables): - if var.id == variable.id: + for ind, var in enumerate(self.model().values): + if var[0] == variable.id: break else: raise ValueError("Variable %s not in Variable List" % (variable.id)) - self.model().remove_variable(ind) + res = self.model().remove_variable(ind) + return res def add_variable(self, cdmsvar): self.model().add_variable(cdmsvar) diff --git a/cdatgui/variables/edit_variable_widget.py b/cdatgui/variables/edit_variable_widget.py index fa39b12..252c749 100644 --- a/cdatgui/variables/edit_variable_widget.py +++ b/cdatgui/variables/edit_variable_widget.py @@ -14,20 +14,27 @@ # # ############################################################################### from PySide import QtCore, QtGui -from region import ROISelectionDialog + from axes_widgets import QAxisList +from cdatgui.utils import label +from cdatgui.variables.manipulations.manipulation import Manipulations class EditVariableDialog(QtGui.QDialog): - createdVariable = QtCore.Signal(object) editedVariable = QtCore.Signal(object) - def __init__(self, var, parent=None): + def __init__(self, var, var_list, parent=None): QtGui.QDialog.__init__(self, parent=parent) - + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.reject) + self.valid = True self.var = var + self.var_list = var_list self.modified = False + self.manipulations = Manipulations() + self.manipulations.remove.connect(self.removeVar) self.setWindowTitle('Edit Variable "%s"' % var.id) @@ -41,24 +48,23 @@ def __init__(self, var, parent=None): self.dims.setLayout(self.dimsLayout) v.addWidget(self.dims) - self.roiSelector = ROISelectionDialog(self) - self.roiSelector.setWindowFlags(self.roiSelector.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.roiSelector.doneConfigure.connect(self.setRoi) - self.axisList = QAxisList(None, var, self) + self.axisList.invalidParams.connect(self.disableApplySave) + self.axisList.validParams.connect(self.enableApplySave) + self.axisList.manipulationComboIndexesChanged.connect( + lambda: self.enableApplySave() if self.valid else self.disableApplySave()) v.addWidget(self.axisList) h = QtGui.QHBoxLayout() - self.selectRoiButton = QtGui.QPushButton('Select Region Of Interest') - self.selectRoiButton.setDefault(False) - self.selectRoiButton.setHidden(True) - for axis in self.var.getAxisList(): - if axis.isLatitude() or axis.isLongitude(): - self.selectRoiButton.setHidden(False) - break - h.addWidget(self.selectRoiButton) + squeeze_label = label('Squeeze') + squeeze_check = QtGui.QCheckBox() + squeeze_check.setChecked(True) + squeeze_check.stateChanged.connect(self.setSqueeze) + squeeze_layout = QtGui.QHBoxLayout() + squeeze_layout.addWidget(squeeze_label) + squeeze_layout.addWidget(squeeze_check) + h.addLayout(squeeze_layout) s = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) @@ -82,28 +88,54 @@ def __init__(self, var, parent=None): # Define button self.btnApplyEdits.clicked.connect(self.applyEditsClicked) self.btnSaveEditsAs.clicked.connect(self.saveEditsAsClicked) - self.selectRoiButton.clicked.connect(self.selectRoi) self.axisList.axisEdited.connect(self.set_modified) - def set_modified(self, axis): + def setSqueeze(self, state): + if state == QtCore.Qt.Checked: + self.axisList.squeeze = True + elif state == QtCore.Qt.Unchecked: + self.axisList.squeeze = False + self.set_modified() + + def removeVar(self, var): + self.var_list.remove_variable(var) + self.reject() + + def enableApplySave(self): + self.valid = True self.btnApplyEdits.setEnabled(True) + self.btnSaveEditsAs.setEnabled(True) - def selectRoi(self): - (lat0, lat1), (lon0, lon1) = self.axisList.getROI() - self.roiSelector.setROI((lon0, lat0, lon1, lat1)) - self.roiSelector.show() + def disableApplySave(self): + self.valid = False + self.btnApplyEdits.setEnabled(False) + self.btnSaveEditsAs.setEnabled(False) - def setRoi(self): - roi = self.roiSelector.getROI() - lon0, lat0, lon1, lat1 = roi - self.axisList.setROI((lat0, lat1), (lon0, lon1)) + def set_modified(self, axis=None): + self.btnApplyEdits.setEnabled(True) def applyEditsClicked(self): newvar = self.axisList.var + newvar = self.applyManipulations(newvar) newvar.id = self.var.id self.editedVariable.emit(newvar) self.close() + def applyManipulations(self, var): + new_var = var + for axis_id, combo in self.axisList.manipulation_combos: + manipulation = combo.currentText() + if manipulation == 'Summation': + new_var = self.manipulations.sum(new_var, axis_id) + elif manipulation == 'Standard Deviation': + new_var = self.manipulations.std(new_var, axis_id) + elif manipulation == 'Geometric Mean': + new_var = self.manipulations.geometricMean(new_var, axis_id) + elif manipulation == 'Average': + new_var = self.manipulations.average(new_var, axis_id) + + return new_var + def saveEditsAsClicked(self): text, ok = QtGui.QInputDialog.getText(self, u"Save Variable As...", u"New Variable Name:") if ok: diff --git a/cdatgui/variables/manager.py b/cdatgui/variables/manager.py index ef6e672..5268f7b 100644 --- a/cdatgui/variables/manager.py +++ b/cdatgui/variables/manager.py @@ -1,5 +1,5 @@ import cdms2 -from cdatgui.persistence.db import get_data_sources, add_data_source +from cdatgui.persistence.db import get_data_sources, add_data_source, remove_data_source import cdatgui.cdat.metadata from PySide import QtCore @@ -50,3 +50,9 @@ def add_file(self, file): self.usedFile.emit(fmw) return fmw + + def remove_file(self, file): + if file.uri not in self.files: + raise Exception("File not in manager.") + del self.files[file.uri] + remove_data_source(file.uri) diff --git a/cdatgui/variables/manipulations/__init__.py b/cdatgui/variables/manipulations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cdatgui/variables/manipulations/manipulation.py b/cdatgui/variables/manipulations/manipulation.py new file mode 100644 index 0000000..8c581e0 --- /dev/null +++ b/cdatgui/variables/manipulations/manipulation.py @@ -0,0 +1,1136 @@ +from functools import partial + +from PySide import QtCore, QtGui +from cdatgui.bases.input_dialog import ValidatingInputDialog, AccessibleButtonDialog +from cdatgui.utils import label +from cdatgui.variables import get_variables +from cdatgui.variables.variable_add import FileNameValidator +import cdutil, cdtime, genutil, cdms2 + + +def getTimes(var): + time = var.getTime() + + months = set() + times = [] + for t in time: + reltime = cdtime.reltime(t, time.units) + comptime = reltime.tocomponent() + months.add(comptime.month) + if 12 in months and 1 in months and 2 in months: + times.append('DJF') + if 3 in months and 4 in months and 5 in months: + times.append('MAM') + if 6 in months and 7 in months and 8 in months: + times.append('JJA') + if 9 in months and 10 in months and 11 in months: + times.append('SON') + + for i in range(1, 13): + if i not in months: + break + else: + times.append('YEAR') + times.append('ANNUALCYCLE') + + for ind, item in enumerate(['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', + 'NOV', 'DEC']): + if ind + 1 in months: + times.append(item) + return times + + +class VariableSelectorDialog(AccessibleButtonDialog): + def __init__(self, parent=None): + super(VariableSelectorDialog, self).__init__(parent=parent) + self.variable_combo = QtGui.QComboBox() + self.variable_combo.setModel(get_variables()) + + variable_label = label("Variable:") + + variable_layout = QtGui.QHBoxLayout() + variable_layout.addWidget(variable_label) + variable_layout.addWidget(self.variable_combo) + + self.vertical_layout.insertLayout(0, variable_layout) + + self.save_button.setText('Save as') + + def getVarName(self): + return self.variable_combo.currentText() + + def getVar(self): + return get_variables().get_variable(self.variable_combo.currentText()) + + def accept(self): + self.accepted.emit() + + +class LinearRegressionDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(LinearRegressionDialog, self).__init__(parent=parent) + self.regressionType = None + self.variable_combo.currentIndexChanged.connect(self.populateLists) + + self.tab_widget = QtGui.QTabWidget() + self.tab_widget.currentChanged.connect(self.updateRegressionType) + + self.second_var_combo = QtGui.QComboBox() + second_label = label('Regression over:') + second_layout = QtGui.QHBoxLayout() + second_layout.addWidget(second_label) + second_layout.addWidget(self.second_var_combo) + second_var_widget = QtGui.QWidget() + second_var_widget.setLayout(second_layout) + self.tab_widget.addTab(second_var_widget, 'Variable') + + self.axis_combo = QtGui.QComboBox() + axis_label = label('Axis:') + axis_layout = QtGui.QHBoxLayout() + axis_layout.addWidget(axis_label) + axis_layout.addWidget(self.axis_combo) + axis_widget = QtGui.QWidget() + axis_widget.setLayout(axis_layout) + self.tab_widget.addTab(axis_widget, 'Axis') + + self.populateAxisCombo() + self.populateSecondVarCombo() + self.updateRegressionType() + self.vertical_layout.insertWidget(1, self.tab_widget) + + def getSecondItemName(self): + if self.regressionType == 'axis': + return self.axis_combo.currentText() + elif self.regressionType == 'variable': + return self.second_var_combo.currentText() + else: + return None + + def updateRegressionType(self): + self.regressionType = self.tab_widget.tabText(self.tab_widget.currentIndex()).lower() + + if self.regressionType == 'axis': + if self.axis_combo.count() == 0: + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + elif self.regressionType == 'variable': + if self.second_var_combo.count() == 0: + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + elif self.regressionType == 'none': + self.save_button.setEnabled(True) + + def populateLists(self): + self.populateAxisCombo() + self.populateSecondVarCombo() + + if self.regressionType == 'variable': + if self.second_var_combo.count() == 0: + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + elif self.regressionType == 'axis': + if self.axis_combo.count() == 0: + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + elif self.regressionType == 'none': + self.save_button.setEnabled(True) + + def populateSecondVarCombo(self): + for row in range(self.second_var_combo.count()): + self.second_var_combo.removeItem(0) + + for var_label, list_var in get_variables().values: + if var_label != self.getVarName() and list_var.shape == self.getVar().shape: + self.second_var_combo.addItem(var_label) + + def populateAxisCombo(self): + for _ in range(self.axis_combo.count()): + self.axis_combo.removeItem(0) + + for axis in self.getVar().getAxisList(): + self.axis_combo.addItem(axis.id) + + def getAxis(self): + if self.regressionType == 'axis': + return str(self.axis_combo.currentText()) + else: + return None + + def getSecondVar(self): + if self.regressionType == 'variable': + return get_variables().get_variable(self.second_var_combo.currentText()) + else: + return None + + def getSecondVarName(self): + return self.second_var_combo.currentText() + + +class DoubleVarDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(DoubleVarDialog, self).__init__(parent=parent) + + self.second_label = label('Correlate with:') + self.second_var_combo = QtGui.QComboBox() + + self.variable_combo.currentIndexChanged.connect(self.populateSecondVarList) + + self.populateSecondVarList() + + second_var_layout = QtGui.QHBoxLayout() + second_var_layout.addWidget(self.second_label) + second_var_layout.addWidget(self.second_var_combo) + + self.vertical_layout.insertLayout(1, second_var_layout) + + def setSecondLabel(self, text): + self.second_label.setText(text) + + def populateSecondVarList(self): + for _ in range(self.second_var_combo.count()): + self.second_var_combo.removeItem(0) + + for var_label, list_var in get_variables().values: + if var_label != self.getVarName() and list_var.shape == self.getVar().shape: + self.second_var_combo.addItem(var_label) + + if self.second_var_combo.count() == 0: + self.save_button.setEnabled(False) + else: + self.save_button.setEnabled(True) + + def getSecondVar(self): + return get_variables().get_variable(self.second_var_combo.currentText()) + + def getSecondVarName(self): + return self.second_var_combo.currentText() + + +class AxisSelectorDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(AxisSelectorDialog, self).__init__(parent=parent) + + self.variable_combo.currentIndexChanged.connect(self.changeAxis) + + axis_label = label('Axis:') + + self.axis_combo = QtGui.QComboBox() + + axis_layout = QtGui.QHBoxLayout() + axis_layout.addWidget(axis_label) + axis_layout.addWidget(self.axis_combo) + self.vertical_layout.insertLayout(1, axis_layout) + + self.populateAxisCombo() + + def changeAxis(self, index): + for _ in range(self.axis_combo.count()): + self.axis_combo.removeItem(0) + self.populateAxisCombo() + + def populateAxisCombo(self): + for axis in self.getVar().getAxisList(): + self.axis_combo.addItem(axis.id) + + def getAxis(self): + return str(self.axis_combo.currentText()) + + +class DepartureDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(DepartureDialog, self).__init__(parent=parent) + + self.variable_combo.currentIndexChanged.connect(self.populateDepartures) + + self.departure_combo = QtGui.QComboBox() + self.populateDepartures() + self.departure_combo.currentIndexChanged.connect(self.toggleBounds) + + departure_label = label("Departure:") + + departure_layout = QtGui.QHBoxLayout() + departure_layout.addWidget(departure_label) + departure_layout.addWidget(self.departure_combo) + + self.vertical_layout.insertLayout(1, departure_layout) + + self.bounds_combo = QtGui.QComboBox() + self.bounds_combo.addItems(['Daily', 'Monthly', 'Yearly']) + + bounds_label = label('Bounds:') + + bounds_layout = QtGui.QHBoxLayout() + bounds_layout.addWidget(bounds_label) + bounds_layout.addWidget(self.bounds_combo) + + self.vertical_layout.insertLayout(2, bounds_layout) + + def toggleBounds(self, index): + if self.departure_combo.currentText() in ['DJF', 'MAM', 'JJA', 'SON', 'YEAR']: + self.bounds_combo.setEnabled(True) + else: + self.bounds_combo.setEnabled(False) + + def getDeparture(self): + return self.departure_combo.currentText() + + def getBounds(self): + return self.bounds_combo.currentText() + + def populateDepartures(self): + times = getTimes(self.getVar()) + for _ in range(self.departure_combo.count()): + self.departure_combo.removeItem(0) + for item in times: + self.departure_combo.addItem(item) + + +class ClimatologyDialog(VariableSelectorDialog): + def __init__(self, climo_type, parent=None): + super(ClimatologyDialog, self).__init__(parent=parent) + + self.variable_combo.currentIndexChanged.connect(self.populateClimos) + self.climo_type = climo_type + self.climo_combo = QtGui.QComboBox() + self.populateClimos() + clim_label = label("Climatology:") + + clim_layout = QtGui.QHBoxLayout() + clim_layout.addWidget(clim_label) + clim_layout.addWidget(self.climo_combo) + + self.vertical_layout.insertLayout(1, clim_layout) + + if climo_type == 'seasonal': + self.bounds_combo = QtGui.QComboBox() + self.bounds_combo.addItems(['Daily', 'Monthly', 'Yearly']) + + bounds_label = label('Bounds:') + + bounds_layout = QtGui.QHBoxLayout() + bounds_layout.addWidget(bounds_label) + bounds_layout.addWidget(self.bounds_combo) + + self.vertical_layout.insertLayout(2, bounds_layout) + + def getClimatology(self): + return self.climo_combo.currentText() + + def getBounds(self): + return self.bounds_combo.currentText() + + def populateClimos(self): + seasons = ['DJF', 'MAM', 'JJA', 'SON', 'YEAR'] + times = getTimes(self.getVar()) + for _ in range(self.climo_combo.count()): + self.climo_combo.removeItem(0) + for item in times: + if self.climo_type == 'seasonal' and item in seasons: + self.climo_combo.addItem(item) + elif self.climo_type == 'monthly' and item not in seasons: + self.climo_combo.addItem(item) + + +class RegridDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(RegridDialog, self).__init__(parent=parent) + + self.source_grid_combo = QtGui.QComboBox() + for v_label, var in get_variables().values: + if var.getGrid() is not None: + self.source_grid_combo.addItem( + '{0} {1}'.format(v_label, var.getGrid().shape)) + + source_grid_label = label("Source Grid:") + + source_grid_layout = QtGui.QHBoxLayout() + source_grid_layout.addWidget(source_grid_label) + source_grid_layout.addWidget(self.source_grid_combo) + + self.regrid_tool_combo = QtGui.QComboBox() + self.regrid_tool_combo.addItems(['regrid2', 'esmf', 'libcf']) + self.regrid_tool_combo.currentIndexChanged.connect(self.updateRegridMethod) + + regrid_tool_label = label("Regrid Tool:") + + regrid_tool_layout = QtGui.QHBoxLayout() + regrid_tool_layout.addWidget(regrid_tool_label) + regrid_tool_layout.addWidget(self.regrid_tool_combo) + + self.regrid_method_combo = QtGui.QComboBox() + self.regrid_method_combo.addItems(['conserve', 'linear', 'patch']) + self.regrid_method_combo.setEnabled(False) + + regrid_method_label = label("Regrid Method:") + + regrid_method_layout = QtGui.QHBoxLayout() + regrid_method_layout.addWidget(regrid_method_label) + regrid_method_layout.addWidget(self.regrid_method_combo) + + self.vertical_layout.insertLayout(1, source_grid_layout) + self.vertical_layout.insertLayout(2, regrid_tool_layout) + self.vertical_layout.insertLayout(3, regrid_method_layout) + + def updateRegridMethod(self, index): + if self.regrid_tool_combo.currentText() == 'esmf': + self.regrid_method_combo.setEnabled(True) + else: + self.regrid_method_combo.setEnabled(False) + + def getSourceVarName(self): + return self.source_grid_combo.currentText().split(" ")[0] + + def getRegridTool(self): + return self.regrid_tool_combo.currentText() + + def getRegridMethod(self): + if self.regrid_method_combo.isEnabled(): + return self.regrid_method_combo.currentText() + return None + + +class AxisListWidget(QtGui.QListWidget): + changedSelection = QtCore.Signal() + + def selectionChanged(self, selected, deselected): + super(AxisListWidget, self).selectionChanged(selected, deselected) + self.changedSelection.emit() + + +class AverageDialog(VariableSelectorDialog): + def __init__(self, parent=None): + super(AverageDialog, self).__init__(parent=parent) + self.variable_combo.currentIndexChanged.connect(self.update) + + self.axis_list = AxisListWidget() + self.axis_list.setSelectionMode(QtGui.QAbstractItemView.MultiSelection) + self.axis_list.changedSelection.connect(self.selectionChanged) + + axis_label = label('Axis:') + + axis_layout = QtGui.QHBoxLayout() + axis_layout.addWidget(axis_label) + axis_layout.addWidget(self.axis_list) + + self.bounds_combo = QtGui.QComboBox() + self.bounds_combo.addItems(['Auto', 'Daily', 'Monthly', 'Yearly']) + self.bounds_combo.setEnabled(False) + + bounds_label = label('Time bounds:') + + bounds_layout = QtGui.QHBoxLayout() + bounds_layout.addWidget(bounds_label) + bounds_layout.addWidget(self.bounds_combo) + + self.vertical_layout.insertLayout(1, axis_layout) + self.vertical_layout.insertLayout(2, bounds_layout) + + self.update() + self.save_button.setEnabled(False) + + def update(self): + var = self.getVar() + time_axis = var.getTime() + try: + bounds = time_axis.getBounds() + if bounds is not None: + self.bounds_combo.insertItem(0, 'Original bounds') + except AttributeError: + pass + + for _ in range(self.axis_list.count()): + widgetitem = self.axis_list.takeItem(0) + del widgetitem + self.populateAxisList() + + def populateAxisList(self): + for axis in self.getVar().getAxisList(): + self.axis_list.addItem(axis.id) + + def selectionChanged(self): + if len(self.axis_list.selectedIndexes()) > 0: + self.save_button.setEnabled(True) + else: + self.save_button.setEnabled(False) + + try: + for ind in self.axis_list.selectedIndexes(): + if ind.data().lower() == self.getVar().getTime().id: + self.bounds_combo.setEnabled(True) + return + except AttributeError: + pass + + self.bounds_combo.setEnabled(False) + + def getAxis(self): + selected_axis = [] + for ind in self.axis_list.selectedIndexes(): + selected_axis.append(str(self.getVar().getAxisIndex(str(ind.data())))) + return ''.join(selected_axis) + + def getAxisCharacters(self): + selected_axis = [] + for ind in self.axis_list.selectedIndexes(): + selected_axis.append(str(ind.data()[0].lower())) + return ''.join(selected_axis) + + def getBounds(self): + return self.bounds_combo.currentText() + + +class DoubleNameDialog(ValidatingInputDialog): + def __init__(self, parent=None): + super(DoubleNameDialog, self).__init__(parent=parent) + + self.setLabelText('Slope Name:') + + self.second_name_edit = QtGui.QLineEdit() + second_name_label = label('Intercept Name:') + second_name_layout = QtGui.QHBoxLayout() + second_name_layout.addWidget(second_name_label) + second_name_layout.addWidget(self.second_name_edit) + self.vertical_layout.insertLayout(1, second_name_layout) + + def secondTextValue(self): + return self.second_name_edit.text().strip() + + def setSecondNameValidator(self, validator): + self.second_name_edit.setValidator(validator) + + try: + validator.invalidInput.connect(lambda: self.save_button.setEnabled(False)) + validator.validInput.connect(lambda: self.save_button.setEnabled(True)) + except AttributeError: + pass + + def setSecondTextValue(self, text): + self.second_name_edit.setText(text) + + +class Manipulations(QtCore.QObject): + remove = QtCore.Signal(object) + + def __init__(self): + super(Manipulations, self).__init__() + self.dialog = None + self.name_dialog = None + + def launchClimatologyDialog(self, ctype): + self.dialog = ClimatologyDialog(ctype) + self.dialog.accepted.connect(partial(self.getClimoSuggestedName, ctype)) + self.dialog.rejected.connect(self.dialog.deleteLater) + self.dialog.setMinimumSize(300, 200) + self.dialog.setWindowTitle('Climatology') + self.dialog.show() + self.dialog.raise_() + + def launchAverageDialog(self): + self.dialog = AverageDialog() + self.dialog.accepted.connect(self.getAverageSuggestedName) + self.dialog.rejected.connect(self.dialog.deleteLater) + self.dialog.setWindowTitle('Average') + self.dialog.show() + self.dialog.raise_() + + def launchRegridDialog(self): + self.dialog = RegridDialog() + self.dialog.accepted.connect(self.getRegridSuggestedName) + self.dialog.rejected.connect(self.dialog.deleteLater) + self.dialog.setWindowTitle('Regrid') + self.dialog.show() + self.dialog.raise_() + + def getAverageSuggestedName(self): + text = '{0}_average_over_{1}'.format(self.dialog.getVarName(), self.dialog.getAxisCharacters()) + + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.average) + + def getRegridSuggestedName(self): + text = '{0}_regrid_from_{1}_{2}'.format(self.dialog.getVarName(), self.dialog.getSourceVarName(), + self.dialog.getRegridTool()) + if self.dialog.getRegridMethod(): + text += '_' + str(self.dialog.getRegridMethod()) + + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.regrid) + + def getClimoSuggestedName(self, ctype): + if ctype == 'seasonal': + text = '{0}_{1}_{2}_climatology'.format(self.dialog.getClimatology(), self.dialog.getBounds().lower(), + self.dialog.getVarName()) + else: + text = '{0}_{1}_climatology'.format(self.dialog.getClimatology(), self.dialog.getVarName()) + + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.climatology) + + def launchNameDialog(self, suggested_name, callback): + self.name_dialog = ValidatingInputDialog() + self.name_dialog.setValidator(FileNameValidator()) + self.name_dialog.setWindowTitle("Save As") + self.name_dialog.setLabelText('Enter New Name:') + + self.name_dialog.setTextValue(suggested_name) + self.name_dialog.edit.selectAll() + self.name_dialog.accepted.connect(callback) + self.name_dialog.rejected.connect(self.name_dialog.deleteLater) + self.name_dialog.show() + self.name_dialog.raise_() + + def regrid(self): + var_name = self.dialog.getVarName() + var = get_variables().get_variable(var_name) + source_var_name = self.dialog.getSourceVarName() + source_var = get_variables().get_variable(source_var_name) + regrid_tool = self.dialog.getRegridTool() + kargs = {'regrid_tool': regrid_tool} + if regrid_tool == 'esmf': + kargs['regrid_method'] = self.dialog.getRegridMethod() + elif regrid_tool == 'libcf': + kargs['regrid_method'] = 'linear' + + # spits out filemetadatawrapper :D + new_var = var.regrid(source_var.getGrid(), **kargs) + new_var.id = self.name_dialog.textValue() + + get_variables().add_variable(new_var) + + self.dialog.close() + self.dialog.deleteLater() + + self.name_dialog.close() + self.name_dialog.deleteLater() + + def average(self, var=None, axis_id=None): + if var is None and axis_id is None: + from_edit_var = False + var_name = self.dialog.getVarName() + var = get_variables().get_variable(var_name) + + axis = self.dialog.getAxis() + # determine if time is part of selected indices + time = False + for ind in axis: + ind = int(ind) + if var.getAxis(ind) == var.getTime(): + time = True + + if time: + time_axis = var.getTime() + bounds = self.dialog.getBounds() + if bounds == 'Auto': + time_axis.setBounds(time_axis.genGenericBounds()) + elif bounds != 'Original bounds': + self.setBounds(time_axis, bounds) + elif (var is None and axis_id is not None) or (var is not None and axis_id is None): + raise Exception("You provided one of two variables needed for averaging. Aborting") + else: + from_edit_var = True + axis_index = var.getAxisIndex(axis_id) + axis = str(axis_index) + time = False + for ind in axis: + ind = int(ind) + if var.getAxis(ind) == var.getTime(): + time = True + if time: + time_axis = var.getTime() + time_axis.setBounds(time_axis.genGenericBounds()) + + # set all axis to autolevels if isLevel + for axis_obj in var.getAxisList(): + if axis_obj.isLevel(): + axis_obj.setBounds(axis_obj.genGenericBounds()) + + # get axis list to re add axis to var after summation + axis_list = var.getAxisList() + for ind in axis: + axis_list.pop(int(ind)) + + new_var = cdutil.averager(var, axis=axis) + + for index, axis in enumerate(axis_list): + new_var.setAxis(index, axis) + + if from_edit_var: + new_var.id = str(var.id) + else: + new_var.id = str(self.name_dialog.textValue()) + get_variables().add_variable(new_var) + + if self.dialog: + self.dialog.close() + self.dialog.deleteLater() + + if self.name_dialog: + self.name_dialog.close() + self.name_dialog.deleteLater() + + if from_edit_var: + return new_var + + def climatology(self): + climo = self.dialog.getClimatology() + var_name = self.dialog.getVarName() + var = get_variables().get_variable(var_name) + var = var.var + + if climo in ['DJF', 'MAM', 'JJA', 'SON', 'YEAR']: + bounds = self.dialog.getBounds() + time_axis = var.getTime() + self.setBounds(time_axis, bounds) + else: + time_axis = var.getTime() + cdutil.setAxisTimeBoundsMonthly(time_axis) + + if climo == "DJF": + new_var = cdutil.DJF.climatology(var) + elif climo == "MAM": + new_var = cdutil.MAM.climatology(var) + elif climo == "JJA": + new_var = cdutil.JJA.climatology(var) + elif climo == "SON": + new_var = cdutil.SON.climatology(var) + elif climo == "YEAR": + new_var = cdutil.YEAR.climatology(var) + elif climo == 'JAN': + new_var = cdutil.JAN.climatology(var) + elif climo == 'FEB': + new_var = cdutil.FEB.climatology(var) + elif climo == 'MAR': + new_var = cdutil.MAR.climatology(var) + elif climo == 'APR': + new_var = cdutil.APR.climatology(var) + elif climo == 'MAY': + new_var = cdutil.MAY.climatology(var) + elif climo == 'JUN': + new_var = cdutil.JUN.climatology(var) + elif climo == 'JUL': + new_var = cdutil.JUL.climatology(var) + elif climo == 'AUG': + new_var = cdutil.AUG.climatology(var) + elif climo == 'SEP': + new_var = cdutil.SEP.climatology(var) + elif climo == 'OCT': + new_var = cdutil.OCT.climatology(var) + elif climo == 'NOV': + new_var = cdutil.NOV.climatology(var) + elif climo == 'DEC': + new_var = cdutil.DEC.climatology(var) + elif climo == 'ANNUALCYCLE': + new_var = cdutil.ANNUALCYCLE.climatology(var) + else: + raise Exception(climo + " is not a valid climatology") + + new_var.id = self.name_dialog.textValue() + get_variables().add_variable(new_var) + + # cleanup + self.dialog.close() + self.dialog.deleteLater() + + self.name_dialog.close() + self.name_dialog.deleteLater() + + def setBounds(self, time_axis, bounds): + if bounds == "Daily": + cdutil.setAxisTimeBoundsDaily(time_axis) + elif bounds == "Monthly": + cdutil.setAxisTimeBoundsMonthly(time_axis) + elif bounds == "Yearly": + cdutil.setAxisTimeBoundsYearly(time_axis) + else: + raise ValueError("No bounds function for %s" % bounds) + + def launchSumDialog(self): + self.dialog = AxisSelectorDialog() + self.dialog.accepted.connect(self.getSumSuggestedName) + self.dialog.setWindowTitle('Summation') + self.dialog.show() + self.dialog.raise_() + + def launchSTDDialog(self): + self.dialog = AxisSelectorDialog() + self.dialog.accepted.connect(self.getSTDSuggestedName) + self.dialog.setWindowTitle('Standard Deviation') + self.dialog.show() + self.dialog.raise_() + + def adjustNameForDuplicates(self, text): + count = 1 + while get_variables().variable_exists(text): + if count == 1: + text = text + '_' + str(count) + else: + text = text[:-2] + '_' + str(count) + count += 1 + return text + + def getSumSuggestedName(self): + text = '{0}_sum_over_{1}'.format(self.dialog.getVarName(), self.dialog.getAxis()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.sum) + + def sum(self, var=None, axis_id=None): + if var is None and axis_id is None: + from_edit_var = False + var = self.dialog.getVar() + axis = self.dialog.getAxis() + axis_index = var.getAxisIndex(axis) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + elif (var is None and axis_id is not None) or (var is not None and axis_id is None): + raise Exception("You provided one of two variables needed for summation. Aborting") + else: + from_edit_var = True + axis_index = var.getAxisIds().index(axis_id) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + # get axis list to re add axis to var after summation + axis_list = var.getAxisList() + axis_list.pop(axis_index) + + # create var + new_var = var.sum(axis_index) + + # add old axis + for index, axis in enumerate(axis_list): + new_var.setAxis(index, axis) + + if from_edit_var: + new_var.id = str(var.id) + else: + new_var.id = str(self.name_dialog.textValue()) + get_variables().add_variable(new_var) + + if self.dialog: + self.dialog.close() + self.dialog.deleteLater() + if self.name_dialog is not None: + self.name_dialog.close() + self.name_dialog.deleteLater() + + if from_edit_var: + return new_var + + def getSTDSuggestedName(self): + text = '{0}_std_over_{1}'.format(self.dialog.getVarName(), self.dialog.getAxis()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.std) + + def getDepartureSuggestedName(self): + if self.dialog.bounds_combo.isEnabled(): + text = '{0}_{1}_departure_for_{2}'.format(self.dialog.getVarName(), self.dialog.getBounds(), + self.dialog.getDeparture()) + else: + text = '{0}_departure_for_{1}'.format(self.dialog.getVarName(), self.dialog.getDeparture()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.departure) + + def std(self, var=None, axis_id=None): + if var is None and axis_id is None: + from_edit_var = False + var = self.dialog.getVar() + axis = self.dialog.getAxis() + axis_index = var.getAxisIndex(axis) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + elif (var is None and axis_id is not None) or (var is not None and axis_id is None): + raise Exception("You provided one of two variables needed for summation. Aborting") + else: + from_edit_var = True + axis_index = var.getAxisIds().index(axis_id) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + # get axis list to re add axis to var after summation + axis_list = var.getAxisList() + axis_list.pop(axis_index) + + # create var + new_var = var.std(axis_index) + new_var = cdms2.MV2.asVariable(new_var) + + # add old axis + for index, axis in enumerate(axis_list): + new_var.setAxis(index, axis) + + if from_edit_var: + new_var.id = str(var.id) + else: + new_var.id = str(self.name_dialog.textValue()) + get_variables().add_variable(new_var) + + if self.dialog: + self.dialog.close() + self.dialog.deleteLater() + if self.name_dialog is not None: + self.name_dialog.close() + self.name_dialog.deleteLater() + + if from_edit_var: + return new_var + + def launchDepartureDialog(self): + self.dialog = DepartureDialog() + self.dialog.accepted.connect(self.getDepartureSuggestedName) + self.dialog.setWindowTitle('Departure') + self.dialog.show() + self.dialog.raise_() + + def departure(self): + departure = self.dialog.getDeparture() + var_name = self.dialog.getVarName() + var = get_variables().get_variable(var_name) + var = var.var + + if departure in ['DJF', 'MAM', 'JJA', 'SON', 'YEAR']: + bounds = self.dialog.getBounds() + time_axis = var.getTime() + self.setBounds(time_axis, bounds) + else: + time_axis = var.getTime() + cdutil.setAxisTimeBoundsMonthly(time_axis) + + if departure == "DJF": + new_var = cdutil.DJF.departures(var) + elif departure == "MAM": + new_var = cdutil.MAM.departures(var) + elif departure == "JJA": + new_var = cdutil.JJA.departures(var) + elif departure == "SON": + new_var = cdutil.SON.departures(var) + elif departure == "YEAR": + new_var = cdutil.YEAR.departures(var) + elif departure == 'JAN': + new_var = cdutil.JAN.departures(var) + elif departure == 'FEB': + new_var = cdutil.FEB.departures(var) + elif departure == 'MAR': + new_var = cdutil.MAR.departures(var) + elif departure == 'APR': + new_var = cdutil.APR.departures(var) + elif departure == 'MAY': + new_var = cdutil.MAY.departures(var) + elif departure == 'JUN': + new_var = cdutil.JUN.departures(var) + elif departure == 'JUL': + new_var = cdutil.JUL.departures(var) + elif departure == 'AUG': + new_var = cdutil.AUG.departures(var) + elif departure == 'SEP': + new_var = cdutil.SEP.departures(var) + elif departure == 'OCT': + new_var = cdutil.OCT.departures(var) + elif departure == 'NOV': + new_var = cdutil.NOV.departures(var) + elif departure == 'DEC': + new_var = cdutil.DEC.departures(var) + elif departure == 'ANNUALCYCLE': + new_var = cdutil.ANNUALCYCLE.departures(var) + else: + raise Exception('Did not provide a valid climatology') + + new_var.id = self.name_dialog.textValue() + + get_variables().add_variable(new_var) + + # cleanup + self.dialog.close() + self.dialog.deleteLater() + + if self.name_dialog is not None: + self.name_dialog.close() + self.name_dialog.deleteLater() + + def launchCorrelationOrCovarianceDialog(self, operation): + if operation == 'Correlation': + func = genutil.statistics.correlation + elif operation == 'Covariance': + func = genutil.statistics.covariance + elif operation == 'Lagged Correlation': + func = genutil.statistics.laggedcorrelation + elif operation == 'Lagged Covariance': + func = genutil.statistics.laggedcovariance + else: + raise NotImplementedError('No function for operation ' + operation) + operation = operation.replace(' ', '_') + + self.dialog = DoubleVarDialog() + self.dialog.setWindowTitle(operation) + self.dialog.accepted.connect(partial(self.getCorrelationOrCovarianceSuggestedName, func, operation.lower())) + + self.dialog.setSecondLabel(operation + ' with:') + self.dialog.show() + self.dialog.raise_() + + def getCorrelationOrCovarianceSuggestedName(self, func, operation): + text = '{0}_{1}_with_{2}'.format(self.dialog.getVarName(), operation, self.dialog.getSecondVarName()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, partial(self.correlationOrCovariance, func)) + + def correlationOrCovariance(self, func): + var1 = self.dialog.getVar() + var2 = self.dialog.getSecondVar() + + new_var = func(var1, var2) + + new_var.id = self.name_dialog.textValue() + + get_variables().add_variable(new_var) + + self.dialog.close() + self.dialog.deleteLater() + + if self.name_dialog: + self.name_dialog.close() + self.name_dialog.deleteLater() + + def launchLinearRegressionDialog(self): + self.dialog = LinearRegressionDialog() + self.dialog.accepted.connect(self.getLinearRegressionSuggestedName) + self.dialog.setWindowTitle('Linear Regression') + self.dialog.show() + self.dialog.raise_() + + def getLinearRegressionSuggestedName(self): + if self.dialog.getSecondItemName() is not None: + slope_text = '{0}_over_{1}_slope'.format(self.dialog.getVarName(), self.dialog.getSecondItemName()) + intercept_text = '{0}_over_{1}_intercept'.format(self.dialog.getVarName(), self.dialog.getSecondItemName()) + else: + slope_text = '{0}_linear_regression_slope'.format(self.dialog.getVarName()) + intercept_text = '{0}_linear_regression_intercept'.format(self.dialog.getVarName()) + + slope_text = self.adjustNameForDuplicates(slope_text) + intercept_text = self.adjustNameForDuplicates(intercept_text) + + self.launchDoubleNameDialog(slope_text, intercept_text, self.linearRegression) + + def launchDoubleNameDialog(self, slope_suggested_name, intercept_suggested_name, callback): + self.name_dialog = DoubleNameDialog() + self.name_dialog.setValidator(FileNameValidator()) + self.name_dialog.setSecondNameValidator(FileNameValidator()) + self.name_dialog.setWindowTitle("Save As") + + self.name_dialog.setTextValue(slope_suggested_name) + self.name_dialog.setSecondTextValue(intercept_suggested_name) + self.name_dialog.edit.selectAll() + self.name_dialog.accepted.connect(callback) + self.name_dialog.rejected.connect(self.name_dialog.deleteLater) + self.name_dialog.show() + self.name_dialog.raise_() + + def linearRegression(self): + var = self.dialog.getVar() + if self.dialog.regressionType == 'axis': + axis = self.dialog.getAxis() + axis_ind = var.getAxisIndex(axis) + slope, intercept = genutil.statistics.linearregression(var, axis=axis_ind) + elif self.dialog.regressionType == 'variable': + var2 = self.dialog.getSecondVar() + slope, intercept = genutil.statistics.linearregression(var, x=var2) + elif self.dialog.regressionType == 'none': + slope, intercept = genutil.statistics.linearregression(var) + else: + raise Exception("Uh oh! Invalid regression type " + self.dialog.regressionType) + + slope.id = self.name_dialog.textValue() + intercept.id = self.name_dialog.secondTextValue() + + get_variables().add_variable(slope) + get_variables().add_variable(intercept) + + self.dialog.close() + self.dialog.deleteLater() + + if self.name_dialog: + self.name_dialog.close() + self.name_dialog.deleteLater() + + def geometricMean(self, var=None, axis_id=None): + if var is None and axis_id is None: + from_edit_var = False + var = self.dialog.getVar() + axis = self.dialog.getAxis() + axis_index = var.getAxisIndex(axis) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + elif (var is None and axis_id is not None) or (var is not None and axis_id is None): + raise Exception("You provided one of two variables needed for summation. Aborting") + else: + from_edit_var = True + axis_index = var.getAxisIds().index(axis_id) + if axis_index == -1: + raise Exception("Invalid axis cannot perform summation") + # get axis list to re add axis to var after summation + axis_list = var.getAxisList() + axis_list.pop(axis_index) + + # create var + new_var = genutil.statistics.geometricmean(var, axis_index) + new_var = cdms2.MV2.asVariable(new_var) + + # add old axis + for index, axis in enumerate(axis_list): + new_var.setAxis(index, axis) + + if from_edit_var: + new_var.id = str(var.id) + else: + new_var.id = str(self.name_dialog.textValue()) + get_variables().add_variable(new_var) + + if self.dialog: + self.dialog.close() + self.dialog.deleteLater() + if self.name_dialog is not None: + self.name_dialog.close() + self.name_dialog.deleteLater() + + if from_edit_var: + return new_var + + def launchGeometricMeanDialog(self): + self.dialog = AxisSelectorDialog() + self.dialog.accepted.connect(self.getGeometricMeanSuggestedName) + self.dialog.setWindowTitle('Geometric Mean') + self.dialog.show() + self.dialog.raise_() + + def getGeometricMeanSuggestedName(self): + text = '{0}_geometricmean_over_{1}'.format(self.dialog.getVarName(), self.dialog.getAxis()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.geometricMean) + + def launchVarianceDialog(self): + self.dialog = AxisSelectorDialog() + self.dialog.accepted.connect(self.getVarianceSuggestedName) + self.dialog.setWindowTitle('Variance') + self.dialog.show() + self.dialog.raise_() + + def getVarianceSuggestedName(self): + text = '{0}_variance_over_{1}'.format(self.dialog.getVarName(), self.dialog.getAxis()) + text = self.adjustNameForDuplicates(text) + self.launchNameDialog(text, self.variance) + + def variance(self): + var = self.dialog.getVar() + axis = self.dialog.getAxis() + axis_index = var.getAxisIndex(axis) + + new_var = var.var(axis_index) + + new_var.id = self.name_dialog.textValue() + get_variables().add_variable(new_var) + + self.dialog.close() + self.dialog.deleteLater() + + self.name_dialog.close() + self.name_dialog.deleteLater() diff --git a/cdatgui/variables/models.py b/cdatgui/variables/models.py index d03f345..ccd33ed 100644 --- a/cdatgui/variables/models.py +++ b/cdatgui/variables/models.py @@ -1,5 +1,6 @@ from PySide import QtCore from cdatgui.bases.list_model import ListModel +import cdms2 class CDMSVariableListModel(ListModel): @@ -11,7 +12,6 @@ def get_variable(self, var_name_or_index): return self.get(var_name_or_index) else: for v in self.values: - # print "Stored", type(v) if v[0] == var_name_or_index: return v[1] raise ValueError("No variable found with ID %s" % var_name_or_index) @@ -21,8 +21,11 @@ def get(self, ind): def get_variable_label(self, var): for label, value in self.values: - if value == var: - return label + try: + if value == var: + return label + except (ValueError, cdms2.error.CDMSError): + pass def append(self, variable): super(CDMSVariableListModel, self).append((variable.id, variable)) @@ -43,9 +46,13 @@ def replace(self, index, value): else: raise IndexError("Index %d out of range." % index) - def variable_exists(self, variable): + def variable_exists(self, variable_or_id): + if isinstance(variable_or_id, str): + v_id = variable_or_id + else: + v_id = variable_or_id.id for var in self.values: - if var[0] == variable.id: + if var[0] == v_id: return True return False diff --git a/cdatgui/variables/region/preview.py b/cdatgui/variables/region/preview.py index 046a007..d777f31 100644 --- a/cdatgui/variables/region/preview.py +++ b/cdatgui/variables/region/preview.py @@ -1,8 +1,8 @@ from PySide import QtGui, QtCore from cdatgui.utils import data_file - __continents__ = None +__duplicated_continents__ = None def continents(): @@ -12,34 +12,126 @@ def continents(): return __continents__ -def continents_in_latlon(lat_range, lon_range, size=(200, 200)): +def duplicatedContinents(): + global __duplicated_continents__ + if __duplicated_continents__ is None: + __duplicated_continents__ = getDuplicatedContinents() + return __duplicated_continents__ + + +def calculate_y(lat_range): c = continents() - low_x, high_x = lon_range + low_y, high_y = lat_range # Adjust into first quadrant - low_x += 180 - high_x += 180 low_y += 90 high_y += 90 - # Adjust into 4th quadrant + # adjust into 4th quadrant low_y = 180 - low_y high_y = 180 - high_y - low_x_pct = low_x / 360. - high_x_pct = high_x / 360. low_y_pct = low_y / 180. high_y_pct = high_y / 180. - image_x1 = low_x_pct * c.width() - image_x2 = high_x_pct * c.width() - # The "high" y is closer to 0, which means it's the start corner - image_y1 = high_y_pct * c.height() - # The "low" y is further from 0, which means it's the end corner - image_y2 = low_y_pct * c.height() + if high_y_pct < low_y_pct: + # The "high" y is closer to 0, which means it's the start corner + image_y1 = high_y_pct * c.height() + # The "low" y is further from 0, which means it's the end corner + image_y2 = low_y_pct * c.height() + flip = False + else: + image_y1 = low_y_pct * c.height() + image_y2 = high_y_pct * c.height() + flip = True + + return image_y1, image_y2, flip + + +def calculate_x(lon_range, circular): + low_x, high_x = lon_range + + if circular: + c = duplicatedContinents() + low_x += 360 + high_x += 355 + low_x_pct = low_x / 715. + high_x_pct = high_x / 715. + + else: + c = continents() - cropped = c.copy(image_x1, image_y1, image_x2 - image_x1, image_y2 - image_y1) + # Adjust into first quadrant + low_x += 180 + high_x += 180 + + low_x_pct = low_x / 360. + high_x_pct = high_x / 360. + + # print "lon range adjusted", low_x, high_x + if low_x < high_x: + image_x1 = low_x_pct * c.width() + image_x2 = high_x_pct * c.width() + flip = False + else: + image_x1 = high_x_pct * c.width() + image_x2 = low_x_pct * c.width() + flip = True + + return image_x1, image_x2, flip + + +def getDuplicatedContinents(): + c = continents() + wider_image = QtGui.QImage(QtCore.QSize(c.width() * 2, c.height()), QtGui.QImage.Format_RGB16) + wider_image.fill('red') + painter = QtGui.QPainter() + painter.begin(wider_image) + + painter.drawImage(QtCore.QRectF( + QtCore.QPointF(0, 0), + QtCore.QPointF(c.width() / 2, c.height())), + c, + QtCore.QRectF( + QtCore.QPointF(c.width() / 2, 0), + QtCore.QPoint(c.width(), c.height()))) + + painter.drawImage(QtCore.QRectF( + QtCore.QPointF(c.width() / 2, 0), + QtCore.QPointF(c.width() + c.width() / 2, c.height())), + c, + QtCore.QRectF( + QtCore.QPointF(0, 0), + QtCore.QPoint(c.width(), c.height()))) + + painter.drawImage(QtCore.QRectF( + QtCore.QPointF(c.width() + (c.width() / 2), 0), + QtCore.QPointF(c.width() * 2, c.height())), + c, + QtCore.QRectF( + QtCore.QPointF(0, 0), + QtCore.QPointF(c.width() / 2, c.height()))) + + painter.end() + + return wider_image + + +def continents_in_latlon(lat_range, lon_range, size=(200, 200), circular=False, lat_flipped=False, lon_flipped=False): + if circular: + c = getDuplicatedContinents() + else: + c = continents() + + image_x1, image_x2, x_flip = calculate_x(lon_range, circular) + image_y1, image_y2, y_flip = calculate_y(lat_range) + + sub_y = image_y2 - image_y1 + sub_x = image_x2 - image_x1 + + cropped = c.copy(image_x1, image_y1, sub_x, sub_y) + cropped = cropped.mirrored(lon_flipped, lat_flipped) if 0 in (cropped.width(), cropped.height()): return QtGui.QPixmap.fromImage(cropped) if cropped.height() > cropped.width(): @@ -60,16 +152,27 @@ def __init__(self, size, lat_range=(-90, 90), lon_range=(-180, 180), parent=None self.setMinimumHeight(height) self.setMaximumHeight(height) self.size = size + self.circular = False + self.lat_flipped = False + self.lon_flipped = False self.lat_range = lat_range self.lon_range = lon_range self.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=size)) - def setLatRange(self, low, high): + def setLatRange(self, low, high, flipped): + self.lat_flipped = flipped self.lat_range = (low, high) - self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=self.size)) + self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=self.size, circular=self.circular, + lat_flipped=self.lat_flipped, lon_flipped=self.lon_flipped)) - def setLonRange(self, low, high): + def setLonRange(self, low, high, flipped): + self.lon_flipped = flipped self.lon_range = (low, high) - self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=self.size)) + self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=self.size, circular=self.circular, + lat_flipped=self.lat_flipped, lon_flipped=self.lon_flipped)) + + def setCircular(self, circ): + self.circular = circ + self.setPixmap(continents_in_latlon(self.lat_range, self.lon_range, size=self.size, circular=self.circular)) diff --git a/cdatgui/variables/region/selector.py b/cdatgui/variables/region/selector.py index 2469f2c..f97c5f8 100644 --- a/cdatgui/variables/region/selector.py +++ b/cdatgui/variables/region/selector.py @@ -13,8 +13,7 @@ class QtROISelectorMapFrame(object): - - def __init__( self, name, map_dir, map_file, grid_extent, latlon_bounds, map_scale ): + def __init__(self, name, map_dir, map_file, grid_extent, latlon_bounds, map_scale): self.name = name self.map_dir = map_dir self.map_file = map_file @@ -27,49 +26,55 @@ def getMapGridExtent(self): return self.grid_extent def getMapFilePath(self): - return os.path.join( self.map_dir, self.map_file ) + return os.path.join(self.map_dir, self.map_file) def getPixmap(self): worldMapFile = self.getMapFilePath() return QtGui.QPixmap(worldMapFile) - def getCornerPoint( self, index ): - return QtCore.QPointF( self.latlon_bounds[ 2*index ], self.latlon_bounds[ 2*index + 1 ] ) + def getCornerPoint(self, index): + return QtCore.QPointF(self.latlon_bounds[2 * index], self.latlon_bounds[2 * index + 1]) - def createView( self, parent ): + def createView(self, parent): self.scene = QtGui.QGraphicsScene(parent) - self.item = QtGui.QGraphicsPixmapItem( self.getPixmap(), None, self.scene ) - self.item.setFlags( QtGui.QGraphicsItem.ItemIsMovable ) - self.item.setAcceptedMouseButtons ( QtCore.Qt.LeftButton ) - self.item.setPos( 0, 0 ) - self.view = MapGraphicsView( self.item, self.grid_extent, self.getCornerPoint( 0 ), self.getCornerPoint( 1 ), parent ) - self.view.setScene( self.scene ) - self.view.scale( self.mapScale[0], self.mapScale[1] ) - self.roiRect = QtGui.QGraphicsRectItem( self.grid_extent[0], self.grid_extent[1], (self.grid_extent[2]-self.grid_extent[0]), (self.grid_extent[3]-self.grid_extent[1]), self.item, self.scene ) - self.roiRect.setBrush( QtGui.QBrush( QtCore.Qt.NoBrush ) ) - pen = QtGui.QPen( QtCore.Qt.green ) - pen.setWidth( 2 ); - self.roiRect.setPen( pen ) + self.item = QtGui.QGraphicsPixmapItem(self.getPixmap(), None, self.scene) + self.item.setFlags(QtGui.QGraphicsItem.ItemIsMovable) + self.item.setAcceptedMouseButtons(QtCore.Qt.LeftButton) + self.item.setPos(0, 0) + self.view = MapGraphicsView(self.item, self.grid_extent, self.getCornerPoint(0), self.getCornerPoint(1), parent) + self.view.setScene(self.scene) + self.view.scale(self.mapScale[0], self.mapScale[1]) + self.roiRect = QtGui.QGraphicsRectItem(self.grid_extent[0], self.grid_extent[1], + (self.grid_extent[2] - self.grid_extent[0]), + (self.grid_extent[3] - self.grid_extent[1]), self.item, self.scene) + self.roiRect.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) + pen = QtGui.QPen(QtCore.Qt.green) + pen.setWidth(2); + self.roiRect.setPen(pen) self.roiRect.setZValue(1) - def getView( self, parent ): - if not self.view: self.createView( parent ) + def getView(self, parent): + if not self.view: self.createView(parent) return self.view - def setRect( self, x0, y0, dx, dy ): - self.roiRect.setRect ( x0, y0, dx, dy ) + def setRect(self, x0, y0, dx, dy): + self.roiRect.setRect(x0, y0, dx, dy) self.view.update() -MapFrames = [ QtROISelectorMapFrame( 'Double Map', defaultMapDir, 'WorldMap2.jpg', ( 0, 0, 2048, 512 ), ( -180, -90 , 540.0, 90.0), (1.2,1.2) ), - QtROISelectorMapFrame( 'Gridded Map', defaultMapDir, 'WorldMap.jpg', ( 106, 72, 2902, 1470 ), ( -180, -90 , 180.0, 90.0), (0.4,0.4) ) ] -class MapGraphicsView(QtGui.QGraphicsView): +MapFrames = [ + QtROISelectorMapFrame('Double Map', defaultMapDir, 'WorldMap2.jpg', (0, 0, 2048, 512), (-180, -90, 540.0, 90.0), + (1.2, 1.2)), + QtROISelectorMapFrame('Gridded Map', defaultMapDir, 'WorldMap.jpg', (106, 72, 2902, 1470), (-180, -90, 180.0, 90.0), + (0.4, 0.4))] + +class MapGraphicsView(QtGui.QGraphicsView): ROISelected = QtCore.Signal(QtCore.QPointF, QtCore.QPointF, QtCore.QPointF, QtCore.QPointF) def __init__(self, imageGraphicsItem, imageContentExtents, pt0, pt1, parent=None): super(MapGraphicsView, self).__init__(parent) - self.Extent = ( pt0.x(), pt0.y(), pt1.x(), pt1.y() ) + self.Extent = (pt0.x(), pt0.y(), pt1.x(), pt1.y()) self.setDragMode(QtGui.QGraphicsView.RubberBandDrag) self.imageOriginOffset = QtCore.QPointF(imageContentExtents[0], @@ -78,23 +83,23 @@ def __init__(self, imageGraphicsItem, imageContentExtents, pt0, pt1, parent=None imageExtentOffset = QtCore.QPointF(imageContentExtents[2], imageContentExtents[3]) \ if imageContentExtents is not None else \ - QtCore.QPointF( imageGraphicsItem.pixmap().width(), - imageGraphicsItem.pixmap().height() ) + QtCore.QPointF(imageGraphicsItem.pixmap().width(), + imageGraphicsItem.pixmap().height()) imagePixelDims = imageExtentOffset - self.imageOriginOffset self.imageLatLonScale = ((self.Extent[2] - self.Extent[0]) / imagePixelDims.x(), - (self.Extent[3] - self.Extent[1]) / imagePixelDims.y()) + (self.Extent[3] - self.Extent[1]) / imagePixelDims.y()) self.roiCorner0 = None self.roiCorner1 = None self.imageGraphicsItem = imageGraphicsItem -# self.setRenderHint(QPainter.Antialiasing) -# self.setRenderHint(QPainter.TextAntialiasing) + # self.setRenderHint(QPainter.Antialiasing) + # self.setRenderHint(QPainter.TextAntialiasing) def wheelEvent(self, event): factor = 1.41 ** (-event.delta() / 240.0) self.scale(factor, factor) - def loadImage( self, image ): + def loadImage(self, image): p = QtGui.QPixmap.fromImage(image) self.imageGraphicsItem.setPixmap(p) self.update() @@ -103,47 +108,48 @@ def GetPointCoords(self): imagePtScaled = None ptS = None cursor_pos = QtGui.QCursor.pos() - point = self.mapFromGlobal( cursor_pos ) + point = self.mapFromGlobal(cursor_pos) pos0 = self.imageGraphicsItem.pos() pos1 = self.mapToScene(point) -# if self.geometry().contains(point): + # if self.geometry().contains(point): ptS = pos1 - pos0 mapPt = ptS - self.imageOriginOffset - imagePtScaled = QtCore.QPointF( (mapPt.x() * self.imageLatLonScale[0]) + self.Extent[0], self.Extent[3] - (mapPt.y() * self.imageLatLonScale[1] ) ) + imagePtScaled = QtCore.QPointF((mapPt.x() * self.imageLatLonScale[0]) + self.Extent[0], + self.Extent[3] - (mapPt.y() * self.imageLatLonScale[1])) if imagePtScaled.x() < self.Extent[0]: - imagePtScaled.setX( self.Extent[0] ) + imagePtScaled.setX(self.Extent[0]) if imagePtScaled.x() > self.Extent[2]: - imagePtScaled.setX( self.Extent[2] ) + imagePtScaled.setX(self.Extent[2]) if imagePtScaled.y() < self.Extent[1]: - imagePtScaled.setY( self.Extent[1] ) + imagePtScaled.setY(self.Extent[1]) if imagePtScaled.y() > self.Extent[3]: - imagePtScaled.setY( self.Extent[3] ) - return ( imagePtScaled, ptS ) + imagePtScaled.setY(self.Extent[3]) + return (imagePtScaled, ptS) - def GetScenePointFromGeoPoint(self, geoPt ): + def GetScenePointFromGeoPoint(self, geoPt): if geoPt.x() < self.Extent[0]: - geoPt.setX( self.Extent[0] ) + geoPt.setX(self.Extent[0]) if geoPt.x() > self.Extent[2]: - geoPt.setX( self.Extent[2] ) + geoPt.setX(self.Extent[2]) if geoPt.y() < self.Extent[1]: - geoPt.setY( self.Extent[1] ) + geoPt.setY(self.Extent[1]) if geoPt.y() > self.Extent[3]: - geoPt.setY( self.Extent[3] ) + geoPt.setY(self.Extent[3]) return QtCore.QPointF((geoPt.x() - self.Extent[0]) / self.imageLatLonScale[0] + self.imageOriginOffset.x(), - (self.Extent[3] - geoPt.y()) / self.imageLatLonScale[1] + self.imageOriginOffset.y()) + (self.Extent[3] - geoPt.y()) / self.imageLatLonScale[1] + self.imageOriginOffset.y()) def mousePressEvent(self, event): self.roiCorner0, self.scenePt0 = self.GetPointCoords() QtGui.QGraphicsView.mousePressEvent(self, event) def orderX(self, pt0, pt1): - if(pt0.x() > pt1.x()): + if (pt0.x() > pt1.x()): tmp = pt1.x() pt1.setX(pt0.x()) pt0.setX(tmp) def orderY(self, pt0, pt1): - if(pt0.y() > pt1.y()): + if (pt0.y() > pt1.y()): tmp = pt1.y() pt1.setY(pt0.y()) pt0.setY(tmp) @@ -154,19 +160,21 @@ def orderCoords(self, pt0, pt1): def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: - ( self.roiCorner1, self.scenePt1 ) = self.GetPointCoords() + (self.roiCorner1, self.scenePt1) = self.GetPointCoords() if self.roiCorner0 != None and self.roiCorner1 != None: - self.orderCoords( self.roiCorner0, self.roiCorner1 ) + self.orderCoords(self.roiCorner0, self.roiCorner1) self.ROISelected.emit(self.roiCorner0, self.roiCorner1, self.scenePt0, self.scenePt1) if self.scenePt1 != None: - self.emit( QtCore.SIGNAL("PointSelected"), self.scenePt1, self.roiCorner1 ) + self.emit(QtCore.SIGNAL("PointSelected"), self.scenePt1, self.roiCorner1) QtGui.QGraphicsView.mouseReleaseEvent(self, event) + class ROISelectionDialog(QtGui.QDialog): doneConfigure = QtCore.Signal() - def __init__(self, parent=None, **args ): + + def __init__(self, parent=None, **args): super(ROISelectionDialog, self).__init__(parent) - init_frame_index = args.get("mapFrameIndex",0) + init_frame_index = args.get("mapFrameIndex", 0) self.ROICornerLon0 = QtGui.QLineEdit() self.ROICornerLat0 = QtGui.QLineEdit() @@ -179,49 +187,49 @@ def __init__(self, parent=None, **args ): self.tabbedWidget = QtGui.QTabWidget() layout = QtGui.QVBoxLayout() - layout.addWidget( self.tabbedWidget ) - self.connect( self.tabbedWidget, QtCore.SIGNAL("currentChanged(int)"), self.adjustROIRect ) + layout.addWidget(self.tabbedWidget) + self.connect(self.tabbedWidget, QtCore.SIGNAL("currentChanged(int)"), self.adjustROIRect) for mapFrame in MapFrames: - view = mapFrame.getView( self ) + view = mapFrame.getView(self) view.ROISelected.connect(self.UpdateGeoCoords) - self.tabbedWidget.addTab( view, mapFrame.name ) + self.tabbedWidget.addTab(view, mapFrame.name) w = QtGui.QWidget() panelLayout = QtGui.QHBoxLayout() - w.setLayout( panelLayout ) + w.setLayout(panelLayout) ROICorner0Label = QtGui.QLabel("Top Left:") ROICorner1Label = QtGui.QLabel("Bottom Right:") - self.connect( self.ROICornerLon0, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect ) - self.connect( self.ROICornerLat0, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect ) - self.connect( self.ROICornerLon1, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect ) - self.connect( self.ROICornerLat1, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect ) + self.connect(self.ROICornerLon0, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect) + self.connect(self.ROICornerLat0, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect) + self.connect(self.ROICornerLon1, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect) + self.connect(self.ROICornerLat1, QtCore.SIGNAL("editingFinished()"), self.adjustROIRect) - LatLabel0 = QtGui.QLabel( "Lat: ") - LonLabel0 = QtGui.QLabel( "Lon: ") + LatLabel0 = QtGui.QLabel("Lat: ") + LonLabel0 = QtGui.QLabel("Lon: ") grid0 = QtGui.QHBoxLayout() - grid0.addWidget( LonLabel0 ) - grid0.addWidget( self.ROICornerLon0 ) - grid0.addWidget( LatLabel0 ) - grid0.addWidget( self.ROICornerLat0 ) + grid0.addWidget(LonLabel0) + grid0.addWidget(self.ROICornerLon0) + grid0.addWidget(LatLabel0) + grid0.addWidget(self.ROICornerLat0) w0 = QtGui.QGroupBox("Top Left:") - w0.setLayout( grid0 ) - panelLayout.addWidget( w0 ) + w0.setLayout(grid0) + panelLayout.addWidget(w0) LatLabel1 = QtGui.QLabel("Lat: ") LonLabel1 = QtGui.QLabel("Lon: ") grid1 = QtGui.QHBoxLayout() - grid1.addWidget( LonLabel1 ) - grid1.addWidget( self.ROICornerLon1 ) - grid1.addWidget( LatLabel1 ) - grid1.addWidget( self.ROICornerLat1 ) + grid1.addWidget(LonLabel1) + grid1.addWidget(self.ROICornerLon1) + grid1.addWidget(LatLabel1) + grid1.addWidget(self.ROICornerLat1) w1 = QtGui.QGroupBox("Top Right:") - w1.setLayout( grid1 ) - panelLayout.addWidget( w1 ) + w1.setLayout(grid1) + panelLayout.addWidget(w1) panelLayout.addStretch(1) self.okButton = QtGui.QPushButton('&OK', self) @@ -232,133 +240,133 @@ def __init__(self, parent=None, **args ): self.cancelButton.setFixedWidth(100) panelLayout.addWidget(self.cancelButton) self.connect(self.okButton, QtCore.SIGNAL('clicked(bool)'), self.okTriggered) - self.connect(self.cancelButton, QtCore.SIGNAL('clicked(bool)'), self.close ) + self.connect(self.cancelButton, QtCore.SIGNAL('clicked(bool)'), self.close) layout.addWidget(w) self.setLayout(layout) - self.initROIBounds( init_frame_index ) - self.setCurrentMapFrame( init_frame_index ) + self.initROIBounds(init_frame_index) + self.setCurrentMapFrame(init_frame_index) - def setCurrentMapFrame(self, index ): - self.tabbedWidget.setCurrentIndex ( index ) + def setCurrentMapFrame(self, index): + self.tabbedWidget.setCurrentIndex(index) self.adjustROIRect() - def initROIBounds( self, index ): - mapFrame = MapFrames[ index ] - ROIcorner0 = mapFrame.getCornerPoint( 0 ) - ROIcorner1 = mapFrame.getCornerPoint( 1 ) - self.ROICornerLon0.setText ( "%.1f" % ROIcorner0.x() ) - self.ROICornerLat0.setText ( "%.1f" % ROIcorner0.y() ) - self.ROICornerLon1.setText ( "%.1f" % ROIcorner1.x() ) - self.ROICornerLat1.setText ( "%.1f" % ROIcorner1.y() ) + def initROIBounds(self, index): + mapFrame = MapFrames[index] + ROIcorner0 = mapFrame.getCornerPoint(0) + ROIcorner1 = mapFrame.getCornerPoint(1) + self.ROICornerLon0.setText("%.1f" % ROIcorner0.x()) + self.ROICornerLat0.setText("%.1f" % ROIcorner0.y()) + self.ROICornerLon1.setText("%.1f" % ROIcorner1.x()) + self.ROICornerLat1.setText("%.1f" % ROIcorner1.y()) def getView(self): return self.tabbedWidget.currentWidget() - def setROI( self, roi ): + def setROI(self, roi): view = self.getView() - geoPt0 = QtCore.QPointF( roi[0], roi[1] ) - geoPt1 = QtCore.QPointF( roi[2], roi[3] ) - scenePt0 = view.GetScenePointFromGeoPoint( geoPt0 ) - scenePt1 = view.GetScenePointFromGeoPoint( geoPt1 ) - self.UpdateROICoords( roi, scenePt0, scenePt1 ) - - def UpdateGeoCoords(self, geoPt0, geoPt1, scenePt0, scenePt1 ): - self.ROICornerLon0.setText ( "%.1f" % geoPt0.x() ) - self.ROICornerLat0.setText ( "%.1f" % geoPt0.y() ) - self.ROICornerLon1.setText ( "%.1f" % geoPt1.x() ) - self.ROICornerLat1.setText ( "%.1f" % geoPt1.y() ) - self.UpdateROIRect( scenePt0, scenePt1 ) - - def UpdateROICoords(self, roi, scenePt0, scenePt1 ): - self.ROICornerLon0.setText ( "%.1f" % roi[0] ) - self.ROICornerLat0.setText ( "%.1f" % roi[1] ) - self.ROICornerLon1.setText ( "%.1f" % roi[2] ) - self.ROICornerLat1.setText ( "%.1f" % roi[3] ) - self.UpdateROIRect( scenePt0, scenePt1 ) + geoPt0 = QtCore.QPointF(roi[0], roi[1]) + geoPt1 = QtCore.QPointF(roi[2], roi[3]) + scenePt0 = view.GetScenePointFromGeoPoint(geoPt0) + scenePt1 = view.GetScenePointFromGeoPoint(geoPt1) + self.UpdateROICoords(roi, scenePt0, scenePt1) + + def UpdateGeoCoords(self, geoPt0, geoPt1, scenePt0, scenePt1): + self.ROICornerLon0.setText("%.1f" % geoPt0.x()) + self.ROICornerLat0.setText("%.1f" % geoPt0.y()) + self.ROICornerLon1.setText("%.1f" % geoPt1.x()) + self.ROICornerLat1.setText("%.1f" % geoPt1.y()) + self.UpdateROIRect(scenePt0, scenePt1) + + def UpdateROICoords(self, roi, scenePt0, scenePt1): + self.ROICornerLon0.setText("%.1f" % roi[0]) + self.ROICornerLat0.setText("%.1f" % roi[1]) + self.ROICornerLon1.setText("%.1f" % roi[2]) + self.ROICornerLat1.setText("%.1f" % roi[3]) + self.UpdateROIRect(scenePt0, scenePt1) def getCurrentMapFrame(self): index = self.tabbedWidget.currentIndex() - return MapFrames[ index ] + return MapFrames[index] - def UpdateROIRect(self, scenePt0, scenePt1 ): + def UpdateROIRect(self, scenePt0, scenePt1): currentFrame = self.getCurrentMapFrame() - currentFrame.setRect ( scenePt0.x(), scenePt0.y(), scenePt1.x()-scenePt0.x(), scenePt1.y()-scenePt0.y() ) + currentFrame.setRect(scenePt0.x(), scenePt0.y(), scenePt1.x() - scenePt0.x(), scenePt1.y() - scenePt0.y()) def okTriggered(self): self.emit(QtCore.SIGNAL('doneConfigure()')) self.close() def getROI(self): - return [ float(self.ROICornerLon0.text()), float(self.ROICornerLat0.text()), float(self.ROICornerLon1.text()), float(self.ROICornerLat1.text()) ] + return [float(self.ROICornerLon0.text()), float(self.ROICornerLat0.text()), float(self.ROICornerLon1.text()), + float(self.ROICornerLat1.text())] - def adjustROIRect( self, index = 0 ): + def adjustROIRect(self, index=0): try: - geoPt0 = QtCore.QPointF( float(self.ROICornerLon0.text()), float(self.ROICornerLat0.text()) ) - geoPt1 = QtCore.QPointF( float(self.ROICornerLon1.text()), float(self.ROICornerLat1.text()) ) - if( geoPt1.x() < geoPt0.x() ): - geoPt1.setX( geoPt0.x() ) - if( geoPt1.y() < geoPt0.y() ): - geoPt1.setY( geoPt0.y() ) + geoPt0 = QtCore.QPointF(float(self.ROICornerLon0.text()), float(self.ROICornerLat0.text())) + geoPt1 = QtCore.QPointF(float(self.ROICornerLon1.text()), float(self.ROICornerLat1.text())) + if (geoPt1.x() < geoPt0.x()): + geoPt1.setX(geoPt0.x()) + if (geoPt1.y() < geoPt0.y()): + geoPt1.setY(geoPt0.y()) view = self.getView() - scenePt0 = view.GetScenePointFromGeoPoint( geoPt0 ) - scenePt1 = view.GetScenePointFromGeoPoint( geoPt1 ) - self.UpdateROIRect( scenePt0, scenePt1 ) - except: pass + scenePt0 = view.GetScenePointFromGeoPoint(geoPt0) + scenePt1 = view.GetScenePointFromGeoPoint(geoPt1) + self.UpdateROIRect(scenePt0, scenePt1) + except: + pass class ExampleForm(QtGui.QDialog): - def __init__(self, parent=None): super(ExampleForm, self).__init__(parent) self.lonRangeType = 1 - self.fullRoi = [ [ 0.0, -90.0, 360.0, 90.0 ], [ -180.0, -90.0, 180.0, 90.0 ] ] - self.roi = self.fullRoi[ self.lonRangeType ] + self.fullRoi = [[0.0, -90.0, 360.0, 90.0], [-180.0, -90.0, 180.0, 90.0]] + self.roi = self.fullRoi[self.lonRangeType] layout = QtGui.QVBoxLayout() - self.roiLabel = QtGui.QLabel( "ROI: %s" % str( self.roi ) ) + self.roiLabel = QtGui.QLabel("ROI: %s" % str(self.roi)) layout.addWidget(self.roiLabel) roiButton_layout = QtGui.QHBoxLayout() - layout.addLayout(roiButton_layout ) + layout.addLayout(roiButton_layout) self.selectRoiButton = QtGui.QPushButton('Select ROI', self) - roiButton_layout.addWidget( self.selectRoiButton ) - self.connect( self.selectRoiButton, QtCore.SIGNAL('clicked(bool)'), self.selectRoi ) + roiButton_layout.addWidget(self.selectRoiButton) + self.connect(self.selectRoiButton, QtCore.SIGNAL('clicked(bool)'), self.selectRoi) self.resetRoiButton = QtGui.QPushButton('Reset ROI', self) - roiButton_layout.addWidget( self.resetRoiButton ) - self.connect( self.resetRoiButton, QtGui.SIGNAL('clicked(bool)'), self.resetRoi ) + roiButton_layout.addWidget(self.resetRoiButton) + self.connect(self.resetRoiButton, QtGui.SIGNAL('clicked(bool)'), self.resetRoi) - self.roiSelector = ROISelectionDialog( self.parent() ) - if self.roi: self.roiSelector.setROI( self.roi ) - self.connect(self.roiSelector, QtCore.SIGNAL('doneConfigure()'), self.setRoi ) + self.roiSelector = ROISelectionDialog(self.parent()) + if self.roi: self.roiSelector.setROI(self.roi) + self.connect(self.roiSelector, QtCore.SIGNAL('doneConfigure()'), self.setRoi) self.setLayout(layout) self.setWindowTitle("ROI Selector") - def selectRoi( self ): - if self.roi: self.roiSelector.setROI( self.roi ) + def selectRoi(self): + if self.roi: self.roiSelector.setROI(self.roi) self.roiSelector.show() - def resetRoi( self ): - roi0 = self.fullRoi[ self.lonRangeType ] - self.roiSelector.setROI( roi0 ) - self.roiLabel.setText( "ROI: %s" % str( roi0 ) ) - for i in range( len( self.roi ) ): self.roi[i] = roi0[i] + def resetRoi(self): + roi0 = self.fullRoi[self.lonRangeType] + self.roiSelector.setROI(roi0) + self.roiLabel.setText("ROI: %s" % str(roi0)) + for i in range(len(self.roi)): self.roi[i] = roi0[i] def setRoi(self): self.roi = self.roiSelector.getROI() - self.roiLabel.setText( "ROI: %s" % str( self.roi ) ) + self.roiLabel.setText("ROI: %s" % str(self.roi)) + if __name__ == '__main__': app = QtCore.QApplication(sys.argv) form = ExampleForm() rect = QtCore.QApplication.desktop().availableGeometry() - form.resize( 300, 150 ) + form.resize(300, 150) form.show() app.exec_() - - diff --git a/cdatgui/variables/variable_add.py b/cdatgui/variables/variable_add.py index 0cb5416..cba9940 100644 --- a/cdatgui/variables/variable_add.py +++ b/cdatgui/variables/variable_add.py @@ -1,13 +1,44 @@ -from PySide import QtGui -from cdms_file_tree import CDMSFileTree +from PySide import QtGui, QtCore + +import re +from functools import partial + from cdatgui.toolbars import AddEditRemoveToolbar from cdms_file_chooser import CDMSFileChooser +from cdms_file_tree import CDMSFileTree from manager import manager +from . import get_variables, reserved_words +from cdatgui.bases.input_dialog import ValidatingInputDialog + + +class dummyVar(object): + def __init__(self, id): + self.id = id + + +class FileNameValidator(QtGui.QValidator): + invalidInput = QtCore.Signal() + validInput = QtCore.Signal() + + def __init__(self): + super(FileNameValidator, self).__init__() + + def validate(self, name, pos): + if name in reserved_words() or not re.search("^[a-zA-Z_]", name) or name == '' \ + or re.search(' +', name) or re.search("[^a-zA-Z0-9_]+", name) \ + or get_variables().variable_exists(dummyVar(name)): + self.invalidInput.emit() + return QtGui.QValidator.Intermediate + self.validInput.emit() + return QtGui.QValidator.Acceptable class AddDialog(QtGui.QDialog): def __init__(self, parent=None, f=0): super(AddDialog, self).__init__(parent=parent, f=f) + self.setWindowModality(QtCore.Qt.ApplicationModal) + self.renameVar = [] + self.dialog = None wrap = QtGui.QVBoxLayout() @@ -15,19 +46,21 @@ def __init__(self, parent=None, f=0): wrap.addLayout(horiz, 10) - buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | - QtGui.QDialogButtonBox.Cancel) - - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) + self.buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | + QtGui.QDialogButtonBox.Cancel) + import_button = QtGui.QPushButton("Import As") + import_button.clicked.connect(self.rename_file) + self.buttons.addButton(import_button, QtGui.QDialogButtonBox.ActionRole) + self.buttons.accepted.connect(self.verify_selected_files) + self.buttons.rejected.connect(self.reject) - ok = buttons.button(QtGui.QDialogButtonBox.StandardButton.Ok) - cancel = buttons.button(QtGui.QDialogButtonBox.StandardButton.Cancel) + ok = self.buttons.button(QtGui.QDialogButtonBox.StandardButton.Ok) + cancel = self.buttons.button(QtGui.QDialogButtonBox.StandardButton.Cancel) ok.setDefault(True) cancel.setDefault(False) - wrap.addWidget(buttons) + wrap.addWidget(self.buttons) self.setLayout(wrap) @@ -54,7 +87,12 @@ def addFileToTree(self, file): self.tree.add_file(file) def selected_variables(self): - return self.tree.get_selected() + if self.renameVar: + var_list = self.renameVar + self.renameVar = [] + return var_list + else: + return self.tree.get_selected() def add_file(self): self.chooser.show() # pragma: no cover @@ -65,4 +103,57 @@ def added_files(self): self.tree.add_file(cdmsfile) def remove_file(self): - pass # pragma: no cover + sel = self.tree.selectedItems() + for item in sel: + i = item.parent().takeChild(item.parent().indexOfChild(item)) + del i + + file_count = self.tree.topLevelItemCount() + i = 0 + while i < file_count: + if not self.tree.topLevelItem(i).childCount(): + file = self.tree.takeTopLevelItem(i) + manager().remove_file(file) + del file + file_count -= 1 + else: + i += 1 + + def rename_file(self): + var = self.tree.get_selected() + if len(var) > 1 or len(var) < 1: + QtGui.QMessageBox.warning(self, "Error", "Please select one variable to import as") + return + var = var[0] + + self.dialog = ValidatingInputDialog() + self.dialog.setValidator(FileNameValidator()) + self.dialog.accepted.connect(partial(self.setRenameVar, var)) + self.dialog.setWindowTitle("Import As") + self.dialog.setLabelText("Enter New Name:") + + self.dialog.show() + self.dialog.raise_() + + def setRenameVar(self, var): + self.renameVar.append(var) + self.renameVar[-1].id = self.dialog.textValue() + self.accepted.emit() + self.dialog.close() + self.tree.clearSelection() + + def isValidName(self, name): + if name in reserved_words() or not re.search("^[a-zA-Z_]", name) or name == '' \ + or re.search(' +', name) or re.search("[^a-zA-Z0-9_]+", name) \ + or get_variables().variable_exists(dummyVar(name)): + return False + return True + + def verify_selected_files(self): + if not self.renameVar: + vars = self.tree.get_selected() + for var in vars: + if not self.isValidName(var.id): + QtGui.QMessageBox.warning(self, "Error", "Invalid name for selected var(s)") + return + self.accept() diff --git a/cdatgui/variables/variable_widget.py b/cdatgui/variables/variable_widget.py index bf01686..5ae8597 100644 --- a/cdatgui/variables/variable_widget.py +++ b/cdatgui/variables/variable_widget.py @@ -1,16 +1,18 @@ from functools import partial from cdatgui.bases import StaticDockWidget -from PySide import QtCore +from PySide import QtCore, QtGui from cdatgui.toolbars import AddEditRemoveToolbar +from cdatgui.variables import get_variables from variable_add import AddDialog from cdms_var_list import CDMSVariableList from edit_variable_widget import EditVariableDialog class VariableWidget(StaticDockWidget): - selectedVariable = QtCore.Signal(object) + variableListNotEmpty = QtCore.Signal() + variableListEmpty = QtCore.Signal() def __init__(self, parent=None, flags=0): super(VariableWidget, self).__init__(u"Variables", parent, flags) @@ -36,6 +38,7 @@ def add_variable(self): new_variables = self.add_dialog.selected_variables() for var in new_variables: self.variable_widget.add_variable(var) + self.variableListNotEmpty.emit() def load(self, vars): for var in vars: @@ -49,11 +52,32 @@ def edit_variable(self): index = indexes[0].row() variable = self.variable_widget.get_variable(index) label = self.variable_widget.get_variable_label(variable) - e = EditVariableDialog(variable, self) + e = EditVariableDialog(variable, self.variable_widget, self) e.editedVariable.connect(partial(self.variable_widget.update_variable, label=label)) e.createdVariable.connect(self.variable_widget.add_variable) e.show() def remove_variable(self): - # Confirm removal dialog - pass # pragma: nocover + indices = self.variable_widget.selectedIndexes() + indices = sorted(indices, key=lambda x: x.row()) + if len(indices) < 1: + return + names = [] + for index in indices: + names.append(index.data()) + if len(names) > 1: + var_text = 'variables' + else: + var_text = 'variable' + var_name_text = "" + for name in names: + var_name_text += name + ", " + var_name_text = var_name_text[:-2] + answer = QtGui.QMessageBox.question(self, "Confirmation", + "Are you sure you want to delete {0} {1}?".format(var_text, var_name_text), + buttons=QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) + if answer == QtGui.QMessageBox.StandardButton.Ok: + for count, index in enumerate(indices): + self.variable_widget.remove_variable(index.row() - count) + if not get_variables().values: + self.variableListEmpty.emit() diff --git a/cdatgui/vcsmodel/elements.py b/cdatgui/vcsmodel/elements.py index f4c9604..b3162a6 100644 --- a/cdatgui/vcsmodel/elements.py +++ b/cdatgui/vcsmodel/elements.py @@ -51,6 +51,25 @@ def updated(self, el_name): if insert_ind == -1: new_els.append(el_name) insert_ind = len(self.elements) - self.beginInsertRows(QtCore.QModelIndex(), insert_ind, 1) + self.beginInsertRows(QtCore.QModelIndex(), insert_ind, insert_ind) self.elements = new_els self.endInsertRows() + + def remove(self, el_name): + new_els = [] + remove_ind = -1 + remove_me = el_name + for ind, name in enumerate(self.elements): + if remove_me is not None and name == remove_me: + remove_ind = ind + remove_me = None + else: + new_els.append(name) + + if remove_ind == -1: + return + + self.beginRemoveRows(QtCore.QModelIndex(), remove_ind, remove_ind) + self.elements = new_els + self.endRemoveRows() + diff --git a/cdatgui/vcsmodel/secondary.py b/cdatgui/vcsmodel/secondary.py index 7e96444..d0ccbbd 100644 --- a/cdatgui/vcsmodel/secondary.py +++ b/cdatgui/vcsmodel/secondary.py @@ -4,6 +4,7 @@ class LineElementsModel(VCSElementsModel): el_type = "line" + def __init__(self): super(LineElementsModel, self).__init__() self.isa = vcs.isline @@ -32,9 +33,11 @@ def get_el(self, name): tc = vcs.gettextcombined(tc) if tc.To_name == name and tc.Tt_name == name: return tc + tc = vcs.createtextcombined() tc.Tt = tt tc.To = to + return tc def isa(self, obj): @@ -46,6 +49,7 @@ def tooltip(self, name, obj): class FillareaElementsModel(VCSElementsModel): el_type = "fillarea" + def __init__(self): super(FillareaElementsModel, self).__init__() self.isa = vcs.isfillarea @@ -57,6 +61,7 @@ def tooltip(self, name, obj): class MarkerElementsModel(VCSElementsModel): el_type = "marker" + def __init__(self): super(MarkerElementsModel, self).__init__() self.isa = vcs.ismarker diff --git a/tests/mocks/PlotInfo.py b/tests/mocks/PlotInfo.py index 9fc7859..cd42bb0 100644 --- a/tests/mocks/PlotInfo.py +++ b/tests/mocks/PlotInfo.py @@ -1,2 +1,2 @@ import vcs -canvas = vcs.init() \ No newline at end of file +canvas = vcs.init() diff --git a/tests/test_AxisEditor.py b/tests/test_AxisEditor.py index 04c7217..3baa8df 100644 --- a/tests/test_AxisEditor.py +++ b/tests/test_AxisEditor.py @@ -7,14 +7,13 @@ @pytest.fixture def editors(): - box = vcs.createboxfill() - tmpl = vcs.createtemplate() var = cdms2.open(vcs.sample_data + "/clt.nc")("clt") - axis = VCSAxis(box, tmpl, "y1", var) + axis = VCSAxis(vcs.createboxfill(), vcs.createtemplate(), "y1", var) edit_1 = AxisEditorWidget("x") edit_1.setAxisObject(axis) + axis = VCSAxis(vcs.createboxfill(), vcs.createtemplate(), "y1", var) edit_2 = AxisEditorWidget("y") edit_2.setAxisObject(axis) @@ -30,27 +29,41 @@ def test_presets(qtbot, editors): assert editor.object.ticks == "lat5" -def test_miniticks(editors): +def test_miniticks(qtbot, editors): for index, editor in enumerate(editors): # don't need this line once default is fixed + editor.preset_box.setCurrentIndex(editor.preset_box.findText('lat5')) editor.updatePreset("lat5") + assert editor.object.ticks == "lat5" button_list = editor.tickmark_button_group.buttons() for button in button_list: + print "button", button.text() + assert editor.object.gm.name in vcs.listelements('boxfill') editor.updateTickmark(button) + print "after update tickmark" + assert editor.object.gm.name in vcs.listelements('boxfill') show_mini_check_box = editor.adjuster_layout.itemAt(3 - index).layout().itemAt(1).widget() show_mini_check_box.setCheckState(QtCore.Qt.Checked) assert editor.object.show_miniticks + print "after update show mini" + assert editor.object.gm.name in vcs.listelements('boxfill') + mini_ticks_box = editor.adjuster_layout.itemAt(3 - index).layout().itemAt(3).widget() mini_ticks_box.setValue(4) assert editor.object.minitick_count == 4 + print "after update mini count" + assert editor.object.gm.name in vcs.listelements('boxfill') + show_mini_check_box.setCheckState(QtCore.Qt.Unchecked) assert not editor.object.show_miniticks + assert editor.object.gm.name in vcs.listelements('boxfill') + editor.reject() def test_step_ticks_negative(qtbot, editors): @@ -104,7 +117,7 @@ def test_step_ticks_negative(qtbot, editors): editor.negative_check.setCheckState(QtCore.Qt.Checked) -def test_dict(editors): +def test_dict(qtbot, editors): for editor in editors: dict = {30: "30N", 20: "20N", 10: "10N", 0: "0"} editor.updateAxisWithDict(dict) @@ -112,7 +125,7 @@ def test_dict(editors): assert editor.object.ticks == dict -def test_reset_preview(editors): +def test_reset_preview(qtbot, editors): for editor in editors: with pytest.raises(Exception): editor.setPreview(AxisPreviewWidget()) diff --git a/tests/test_BaseWindow.py b/tests/test_BaseWindow.py index 5e2e98c..9f1fffd 100644 --- a/tests/test_BaseWindow.py +++ b/tests/test_BaseWindow.py @@ -28,13 +28,13 @@ def save_as(name): def test_save(qtbot, window): base = window - base.savePressed.connect(save) - base.save() + base.accepted.connect(save) + base.accept() def test_save_as(qtbot, window): base = window - base.savePressed.connect(save_as) + base.accepted.connect(save_as) base.saveAs() base.win.setTextValue("pizza") base.win.accepted.emit() diff --git a/tests/test_Climatologies.py b/tests/test_Climatologies.py new file mode 100644 index 0000000..854e17e --- /dev/null +++ b/tests/test_Climatologies.py @@ -0,0 +1,58 @@ +import pytest, vcs, cdms2 + +from cdatgui.cdat.metadata import FileMetadataWrapper +from cdatgui.main_menu import MainMenu +from cdatgui.main_window import MainWindow +from cdatgui.variables import variable_widget, get_variables + + +@pytest.fixture +def menu(): + win = MainWindow() + menu_bar = win.menuBar() + assert isinstance(menu_bar, MainMenu) + return menu_bar, win + + +def test_enableAndDisable(qtbot, menu): + win = menu[1] + menu = menu[0] + var_widget = win.findChildren(variable_widget.VariableWidget)[0] + + assert menu.edit_data_menu.isEnabled() == False + + # simulate proper signal emit + var_widget.variableListNotEmpty.emit() + assert menu.edit_data_menu.isEnabled() == True + + var_widget.variableListEmpty.emit() + assert menu.edit_data_menu.isEnabled() == False + +''' +def test_createClimo(qtbot, menu): + win = menu[1] + menu = menu[0] + + f = cdms2.open(vcs.sample_data + '/clt.nc') + f = FileMetadataWrapper(f) + clt = f('clt') + get_variables().add_variable(clt) + old_count = len(get_variables().values) + checked_var_list = [clt] + + menu.createClimatology('seasonal') + for climo_index in range(menu.dialog.climo_combo.count()): + for bounds_index in range(menu.dialog.bounds_combo.count()): + menu.dialog.climo_combo.setCurrentIndex(climo_index) + menu.dialog.bounds_combo.setCurrentIndex(bounds_index) + menu.dialog.accept() + menu.name_dialog.accept() + + assert len(get_variables().values) == old_count + 1 + print get_variables().values + # assert + assert isinstance(get_variables().values[-1], cdms2.tvariable.TransientVariable) + assert get_variables().values[-1] not in checked_var_list + checked_var_list.append(get_variables().values[-1]) + old_count += 1 +''' diff --git a/tests/test_CreateGmWidget.py b/tests/test_CreateGmWidget.py new file mode 100644 index 0000000..b83b436 --- /dev/null +++ b/tests/test_CreateGmWidget.py @@ -0,0 +1,109 @@ +import pytest, vcs, cdms2, os +# from cdatgui.graphics.dialog import * +# from cdatgui.cdat.metadata import FileMetadataWrapper +# from cdatgui.editors import boxfill, isoline, cdat1d +from cdatgui.graphics import get_gms +from cdatgui.graphics.graphics_method_widget import CreateGM, EditGmDialog +from PySide import QtCore, QtGui + + +@pytest.fixture +def creategm_dialog(): + cgm = CreateGM(['boxfill', 'a_boxfill']) + return cgm + + +@pytest.fixture +def editgm_dialog_store(): + egmd = EditGmDialog('boxfill', 'a_boxfill') + return egmd + + +@pytest.fixture +def editgm_dialog_nostore(): + egmd = EditGmDialog('boxfill', 'a_boxfill', False) + return egmd + + +def test_createGM(qtbot, creategm_dialog): + assert creategm_dialog.gm_type_combo.currentText() == 'boxfill' + assert creategm_dialog.gm_instance_combo.currentText() == 'a_boxfill' + + # assure its passing in the correct value to the customize dialog + # print "editing gm" + creategm_dialog.editGM() + assert creategm_dialog.edit_dialog.gtype == 'boxfill' + assert creategm_dialog.edit_dialog.ginstance == 'a_boxfill' + # print "emitting accept" + # import pdb; pdb.set_trace() + creategm_dialog.edit_dialog.accepted.emit() + # print "after emitting accept" + + creategm_dialog.gm_type_combo.setCurrentIndex(creategm_dialog.gm_type_combo.findText('isoline')) + assert creategm_dialog.gm_instance_combo.currentText() == 'default' + assert creategm_dialog.gm_instance_combo.rootModelIndex().row() == 6 + assert creategm_dialog.edit_dialog is None + # print "adjusting root" + + # assert the validator type gets updated + assert creategm_dialog.edit.validator().gm_type == 'isoline' + + assert creategm_dialog.save_button.isEnabled() == False + creategm_dialog.setTextValue('new_isoline') + assert creategm_dialog.save_button.isEnabled() == True + creategm_dialog.createGM() + + assert vcs.getisoline('new_isoline') in get_gms().gms['isoline'] + + +def test_createGM_using_customize_gm(qtbot, creategm_dialog): + creategm_dialog.editGM() + assert creategm_dialog.edit_dialog.gtype == 'boxfill' + assert creategm_dialog.edit_dialog.ginstance == 'a_boxfill' + print "loaded edit gm" + creategm_dialog.edit_dialog.gm.projection = 'robinson' + creategm_dialog.edit_dialog.reject() + creategm_dialog.setTextValue('new_boxfill') + creategm_dialog.createGM() + print "rejected gm create" + + assert vcs.getboxfill('new_boxfill') in get_gms().gms['boxfill'] + assert vcs.getboxfill('new_boxfill').projection == 'linear' + + creategm_dialog.editGM() + assert creategm_dialog.edit_dialog.gtype == 'boxfill' + assert creategm_dialog.edit_dialog.ginstance == 'a_boxfill' + + creategm_dialog.edit_dialog.gm.projection = 'robinson' + print "EDITED GM", creategm_dialog.edit_dialog.gm.list() + creategm_dialog.edit_dialog.accepted.emit() + + # test for reopening + creategm_dialog.editGM() + assert creategm_dialog.edit_dialog.gm.projection == 'robinson' + print "REOPENED NON EDITED GM", creategm_dialog.edit_dialog.gm.list() + creategm_dialog.edit_dialog.accepted.emit() + + creategm_dialog.setTextValue('new_boxfill2') + creategm_dialog.createGM() + + assert vcs.getboxfill('new_boxfill2') in get_gms().gms['boxfill'] + assert vcs.getboxfill('new_boxfill2').projection == 'robinson' + + +def test_EditGmDialogStore(qtbot, editgm_dialog_store): + editgm_dialog_store.rejected.emit() + assert editgm_dialog_store.edit_tmpl_name not in vcs.listelements('template') + assert editgm_dialog_store.edit_gm_name not in vcs.listelements('boxfill') + + +def test_EditGmDialogNoStore(qtbot, editgm_dialog_nostore): + dialog = editgm_dialog_nostore + orig_gm = vcs.getboxfill(dialog.ginstance) + dialog.createGM() + new_gm = vcs.getboxfill(dialog.ginstance) + assert new_gm != orig_gm + assert new_gm in get_gms().gms[dialog.gtype] + assert orig_gm not in get_gms().gms[dialog.gtype] + assert editgm_dialog_nostore.edit_tmpl_name not in vcs.listelements('template') + assert editgm_dialog_nostore.edit_gm_name not in vcs.listelements('boxfill') diff --git a/tests/test_GmDialog.py b/tests/test_GmDialog.py new file mode 100644 index 0000000..6e795d5 --- /dev/null +++ b/tests/test_GmDialog.py @@ -0,0 +1,199 @@ +import pytest, vcs, cdms2, os + +from cdatgui.editors.model.legend import VCSLegend +from cdatgui.graphics.dialog import * +from cdatgui.cdat.metadata import FileMetadataWrapper +from cdatgui.editors import boxfill, isoline, cdat1d +from PySide import QtCore, QtGui + + +@pytest.fixture +def boxfill_dialog(): + s = get_var() + d = GraphicsMethodDialog(vcs.getboxfill('default'), s, vcs.createtemplate()) + d.createdGM.connect(saveAs) + return d + + +@pytest.fixture +def isoline_dialog(): + s = get_var() + d = GraphicsMethodDialog(vcs.getisoline('default'), s, vcs.createtemplate()) + return d + + +@pytest.fixture +def oned_dialog(): + s = get_var() + d = GraphicsMethodDialog(vcs.get1d('default'), s, vcs.createtemplate()) + return d + + +@pytest.fixture +def save_dialog(): + s = get_var() + d = GraphicsMethodSaveDialog(vcs.getmeshfill('a_meshfill'), s, vcs.createtemplate()) + return d + + +@pytest.fixture +def ok_dialog(): + s = get_var() + d = GraphicsMethodOkDialog(vcs.getboxfill('a_boxfill'), s, vcs.createtemplate()) + return d + + +def get_var(): + f = cdms2.open(os.path.join(vcs.sample_data, 'clt.nc')) + f = FileMetadataWrapper(f) + s = f('clt') + return s + + +def saveAs(gm): + assert gm.name == 'test' + + +def test_boxfillDialog(qtbot, boxfill_dialog): + """Test boxfill gm editor as well as basic dialog functionality and GraphicsMethodEditor functionality""" + editor = boxfill_dialog.editor + + assert isinstance(editor, boxfill.BoxfillEditor) + assert editor.levels_button.isEnabled() == False + + for button in editor.type_group.buttons(): + if button.text() == 'Custom': + button.click() + break + + assert editor.levels_button.isEnabled() == True + assert editor.gm.boxfill_type == 'custom' + + editor.levels_button.click() + qtbot.addWidget(editor.level_editor) + assert editor.level_editor + editor.level_editor.close() + + for button in editor.type_group.buttons(): + if button.text() == 'Logarithmic': + button.click() + break + + assert editor.levels_button.isEnabled() == False + # save_button = boxfill_dialog.layout().itemAt(1).layout().itemAt(3).widget() + # assert save_button.isEnabled() == False + # boxfill_dialog.save(('test', True)) + + # test ticks dialogs + editor.editLeft() + assert editor.axis_editor + assert editor.axis_editor.axis == 'y1' + + editor.updated() + assert editor.axis_editor is None + + editor.editRight() + assert editor.axis_editor.axis == 'y2' + + editor.updated() + assert editor.axis_editor is None + + editor.editBottom() + assert editor.axis_editor.axis == 'x1' + + editor.updated() + assert editor.axis_editor is None + + editor.editTop() + qtbot.addWidget(editor.axis_editor) + assert editor.axis_editor.axis == 'x2' + + editor.updated() + + # testing legend crashes with autolabels error. autolabels not supposed to implemented? + # editor.editLegend() + # assert editor.legend_editor + # assert isinstance(editor.object, VCSLegend) + + # editor.updated() + + editor.editProjection() + assert editor.projection_editor + assert editor.projection_editor.cur_projection_name == 'linear' + editor.projection_editor.close() + + editor.updated() + assert editor.axis_editor is None + assert editor.projection_editor is None + + boxfill_dialog.reject() + + +def test_isolineDialog(qtbot, isoline_dialog): + editor = isoline_dialog.editor + assert isinstance(editor, isoline.IsolineEditor) + + assert not editor.text_edit_widget + assert not editor.line_edit_widget + + assert editor.label_check.isChecked() == False + assert editor.edit_label_button.isEnabled() == False + + editor.updateLabel(QtCore.Qt.Checked) + assert editor.gm.label == True + assert editor.edit_label_button.isEnabled() == True + + editor.editText() + qtbot.addWidget(editor.text_edit_widget) + assert editor.text_edit_widget + + editor.editLines() + qtbot.addWidget(editor.line_edit_widget) + assert editor.line_edit_widget + + editor.updateLabel(QtCore.Qt.Unchecked) + assert editor.gm.label == False + assert editor.edit_label_button.isEnabled() == False + + +def test_1dDialog(qtbot, oned_dialog): + # really only testing this because it has a marker button. + editor = oned_dialog.editor + assert isinstance(editor, cdat1d.Cdat1dEditor) + + editor.flipGraph(QtCore.Qt.Checked) + assert editor.gm.flip == True + + editor.editMarker() + qtbot.addWidget(editor.marker_editor) + assert editor.marker_editor + + editor.editLine() + qtbot.addWidget(editor.line_editor) + assert editor.line_editor + + oned_dialog.reject() + assert oned_dialog.newgm_name not in vcs.listelements('1d') + + +def test_saveDialog(qtbot, save_dialog): + assert save_dialog.origgm_name == 'a_meshfill' + save_button = save_dialog.layout().itemAt(1).layout().itemAt(3).widget() + assert save_button.isEnabled() == True + save_dialog.customName() + assert isinstance(save_dialog.dialog, VcsElementsDialog) + + +def test_okDialog(qtbot, ok_dialog): + assert ok_dialog.origgm_name == 'a_boxfill' + ok_dialog.accept() + assert ok_dialog.newgm_name not in vcs.listelements('boxfill') + + +def test_saveButtonDisabled(qtbot): + s = get_var() + d = GraphicsMethodSaveDialog(vcs.getisoline('default'), s, vcs.createtemplate()) + save_button = d.layout().itemAt(1).layout().itemAt(3).widget() + assert save_button.isEnabled() == False + + # boxfill_dialog.save(('test', True)) diff --git a/tests/test_LineEditor.py b/tests/test_LineEditor.py index 9fe7863..142e44f 100644 --- a/tests/test_LineEditor.py +++ b/tests/test_LineEditor.py @@ -1,22 +1,48 @@ import pytest import vcs, cdms2 from cdatgui.editors.secondary.editor.line import LineEditorWidget +from cdatgui.vcsmodel import get_lines + @pytest.fixture def editor(): editor = LineEditorWidget() - line = vcs.createline() + line = vcs.getline('cyan') editor.setLineObject(line) return editor + def test_type(qtbot, editor): editor.updateType('dash') assert editor.object.type == ['dash'] + editor.accept() + assert vcs.elements['line']['cyan'].type == ['dash'] + assert editor.newline_name not in vcs.listelements('line') + + def test_color(qtbot, editor): editor.updateColor(55) assert editor.object.color == [55] + editor.saveAs() + editor.win.setTextValue('check') + editor.accept() + assert 'check' in vcs.listelements('line') + assert vcs.elements['line']['check'].color == [55] + + del vcs.elements['line']['check'] + assert 'check' not in vcs.listelements('line') + assert editor.newline_name not in vcs.listelements('line') + + def test_width(qtbot, editor): editor.updateWidth(250) assert editor.object.width == [250] + + editor.accept() + assert vcs.elements['line']['cyan'].width == [250] + assert editor.newline_name not in vcs.listelements('line') + + get_lines().remove('check') + get_lines().remove(editor.newline_name) diff --git a/tests/test_MultiLineEditor.py b/tests/test_MultiLineEditor.py new file mode 100644 index 0000000..0cd4b1f --- /dev/null +++ b/tests/test_MultiLineEditor.py @@ -0,0 +1,115 @@ +import pytest, vcs, cdms2, os +from cdatgui.editors.widgets.multi_line_editor import MultiLineEditor +from cdatgui.editors.widgets.multi_text_editor import MultiTextEditor +from cdatgui.cdat.metadata import FileMetadataWrapper +from cdatgui.editors.model.isoline_model import IsolineModel +from cdatgui.vcsmodel import get_lines, get_textstyles + + +@pytest.fixture +def line_editor(): + """ + Multi Editors are only being tested with set levels due to odd behavior with autolabels + that is waiting for merge + """ + editor = MultiLineEditor() + gm = vcs.getisoline('a_isoline') + gm.levels = range(10, 80, 10) + editor.setObject(IsolineModel(gm, get_var())) + return editor, gm + + +@pytest.fixture +def text_editor(): + """ + Multi Editors are only being tested with set levels due to odd behavior with autolabels + that is waiting for merge + """ + editor = MultiTextEditor() + gm = vcs.getisoline('a_isoline') + gm.levels = range(10, 80, 10) + editor.setObject(IsolineModel(gm, get_var())) + return editor, gm + + +def get_var(): + f = cdms2.open(os.path.join(vcs.sample_data, 'clt.nc')) + f = FileMetadataWrapper(f) + s = f('clt') + return s + + +def test_MultiLineEditor(qtbot, line_editor): + editor = line_editor[0] + gm = line_editor[1] + for combo in editor.line_combos: + assert combo.currentIndex() != -1 + + editor.line_combos[2].setCurrentIndex(10) + print editor.line_combos[2].model().elements + print editor.isoline_model.line + assert editor.isoline_model.line[2] == 'pink' + + editor.editLine(6) + qtbot.addWidget(editor.line_editor) + assert editor.line_editor + assert isinstance(editor.line_editor.object, vcs.line.Tl) + + # simulate editing a line and updating it + l = vcs.createline('dummy') + l.color = [55] + l.type = ['dash-dot'] + l.width = [8] + get_lines().updated('dummy') + + editor.update(4, 'dummy') + assert editor.line_combos[4].currentText() == 'dummy' + + editor.accept() + + # check and see if the isoline was updated when combo changed and ok was pressed + assert gm.linecolors[2] == 254 + assert gm.linewidths[2] == 2 + assert vcs.getline(gm.line[2]).name == 'pink' + assert vcs.getline(gm.line[2]).type == ['dash'] + + assert gm.linecolors[4] == 55 + assert gm.linewidths[4] == 8 + assert vcs.getline(gm.line[4]).name == 'dummy' + assert vcs.getline(gm.line[4]).type == ['dash-dot'] + + +def test_MultiTextEditor(qtbot, text_editor): + editor = text_editor[0] + gm = text_editor[1] + for combo in editor.text_combos: + assert combo.currentIndex() != -1 + + editor.text_combos[2].setCurrentIndex(3) + assert editor.isoline_model.text[2] == 'qa' + + editor.editText(1) + qtbot.addWidget(editor.text_editor) + assert editor.text_editor + assert isinstance(editor.text_editor.object, vcs.textcombined.Tc) + + # simulate editing a line and updating it + tc = vcs.createtextcombined('dummy') + tc.angle = 30 + tc.font = 'Chinese' + tc.halign = 1 + tc.valign = 1 + tc.height = 24 + get_textstyles().updated('dummy') + + editor.update(3, 'dummy') + assert editor.text_combos[3].currentText() == 'dummy' + + editor.accept() + + # check and see if the isoline was updated when combo changed and ok was pressed + assert vcs.gettextcombined(gm.text[3], gm.text[3]).name == 'dummy:::dummy' + assert vcs.gettextcombined(gm.text[3], gm.text[3]).font == 8 # 'Chinese' + assert vcs.gettextcombined(gm.text[3], gm.text[3]).halign == 1 + assert vcs.gettextcombined(gm.text[3], gm.text[3]).valign == 1 + assert vcs.gettextcombined(gm.text[3], gm.text[3]).height == 24 diff --git a/tests/test_ProjectionEditor.py b/tests/test_ProjectionEditor.py new file mode 100644 index 0000000..8320c58 --- /dev/null +++ b/tests/test_ProjectionEditor.py @@ -0,0 +1,90 @@ +import vcs, cdms2, pytest +from PySide import QtCore, QtGui +from cdatgui.editors.projection_editor import ProjectionEditor + + +@pytest.fixture +def editor(): + edit = ProjectionEditor() + gm = vcs.createboxfill() + proj_obj = vcs.getprojection('linear') + edit.setProjectionObject(proj_obj) + edit.gm = gm + return edit + + +def test_changingNameAndType(qtbot, editor): + orig_ortho = vcs.elements['projection']['orthographic'] + assert editor.vertical_layout.count() == 3 + assert editor.proj_combo.currentText() == 'linear' + assert editor.type_combo.currentText() == 'linear' + + editor.proj_combo.setCurrentIndex(5) + assert editor.cur_projection_name == 'orthographic' + assert editor.type_combo.currentText() == 'orthographic' + assert len(editor.editors) == 5 + + editor.type_combo.setCurrentIndex(editor.type_combo.findText('hotin oblique merc')) + assert len(editor.editors) == 11 + assert editor.cur_projection_name == 'orthographic' + assert editor.type_combo.currentText() == 'hotin oblique merc' + + editor.accept() + + assert vcs.elements['projection']['orthographic'] != orig_ortho + assert vcs.elements['projection']['orthographic'].type == 'hotin oblique merc' + assert editor.newprojection_name not in vcs.listelements('projection') + + +def test_saveAs(qtbot, editor): + orig_poly = vcs.elements['projection']['polyconic'] + assert editor.vertical_layout.count() == 3 + assert editor.proj_combo.currentText() == 'linear' + assert editor.type_combo.currentText() == 'linear' + + editor.proj_combo.setCurrentIndex(7) + assert editor.cur_projection_name == 'polyconic' + assert editor.type_combo.currentText() == 'polyconic' + assert len(editor.editors) == 6 + + editor.saveAs() + qtbot.addWidget(editor.win) + editor.win.setTextValue('test') + editor.win.accepted.emit() + + assert vcs.elements['projection']['polyconic'] == orig_poly + assert 'test' in vcs.listelements('projection') + assert editor.newprojection_name not in vcs.listelements('projection') + + +def test_close(qtbot, editor): + assert editor.newprojection_name in vcs.listelements('projection') + assert editor.cur_projection_name == 'linear' + assert editor.object.type == 'linear' + + editor.updateCurrentProjection('mollweide') + assert editor.cur_projection_name == 'mollweide' + assert editor.object.type == 'mollweide' + editor.close() + + assert editor.newprojection_name not in vcs.listelements('projection') + assert vcs.getprojection('linear').name == 'linear' + assert vcs.getprojection('linear').type == 'linear' + + +def test_settingAttributes(qtbot, editor): + old_proj_name = editor.gm.projection + editor.type_combo.setCurrentIndex(editor.type_combo.findText('robinson')) + editor.editors[0][0].setText('12') + + editor.accept() + + old_proj = vcs.getprojection(old_proj_name) + assert old_proj.type == 'robinson' + assert old_proj.sphere == 12.0 + + new_editor = ProjectionEditor() + new_editor.setProjectionObject(old_proj) + new_editor.gm = editor.gm + assert new_editor.editors[0][0].text() == '12.0' + assert new_editor.editors[0][1] == 'sphere' diff --git a/tests/test_TextStyleEditor.py b/tests/test_TextStyleEditor.py index 9e8a3f8..415dde0 100755 --- a/tests/test_TextStyleEditor.py +++ b/tests/test_TextStyleEditor.py @@ -2,110 +2,107 @@ import vcs from PySide import QtCore, QtGui from cdatgui.editors.secondary.editor.text import TextStyleEditorWidget +from cdatgui.vcsmodel import get_textstyles @pytest.fixture -def editors(): +def editor(): + print "in editor" + for name in ['header', 'header2', 'header3']: + try: + del vcs.elements['textcombined']['{0}:::{1}'.format(name, name)] + del vcs.elements['texttable'][name] + del vcs.elements['textorientation'][name] + except: + print "didnt delete all" + edit1 = TextStyleEditorWidget() - t = vcs.createtext() - t.name = "header" + t = vcs.createtext('header') edit1.setTextObject(t) - edit2 = TextStyleEditorWidget() - t = vcs.createtext() - t.name = "header" - t.valign = 0 - t.halign = 1 - edit2.setTextObject(t) - - edit3 = TextStyleEditorWidget() - t = vcs.createtext() - t.name = "header" - t.valign = 4 - t.halign = 2 - edit3.setTextObject(t) - - return edit1, edit2, edit3 + return edit1 def save_check(name): - assert name == "header" + assert name == 'header' -def test_save(qtbot, editors): - for editor in editors: +def test_alignment(qtbot, editor): - editor.savePressed.connect(save_check) - editor.save() + # test valign + editor.updateButton(editor.va_group.buttons()[0]) + assert editor.object.valign == 0 + editor.updateButton(editor.va_group.buttons()[2]) + assert editor.object.valign == 4 -def test_alignment(editors): - for editor in editors: - # test valign - editor.updateButton(editor.va_group.buttons()[0]) - assert editor.textObject.valign == 0 + editor.updateButton(editor.va_group.buttons()[1]) + assert editor.object.valign == 2 - editor.updateButton(editor.va_group.buttons()[2]) - assert editor.textObject.valign == 4 + # test halign + editor.updateButton(editor.ha_group.buttons()[2]) + assert editor.object.halign == 2 - editor.updateButton(editor.va_group.buttons()[1]) - assert editor.textObject.valign == 2 + editor.updateButton(editor.ha_group.buttons()[1]) + assert editor.object.halign == 1 - # test halign - editor.updateButton(editor.ha_group.buttons()[2]) - assert editor.textObject.halign == 2 + editor.updateButton(editor.ha_group.buttons()[0]) + assert editor.object.halign == 0 - editor.updateButton(editor.ha_group.buttons()[1]) - assert editor.textObject.halign == 1 + # test save as well + editor.saved.connect(save_check) + editor.accept() - editor.updateButton(editor.ha_group.buttons()[0]) - assert editor.textObject.halign == 0 +def test_angle(editor): + assert editor.object.angle == 0 -def test_angle(editors): - for editor in editors: + editor.updateAngle(50) + assert editor.object.angle == 50 - assert editor.textObject.angle == 0 + editor.updateAngle(440) + assert editor.object.angle == 80 - editor.updateAngle(50) - assert editor.textObject.angle == 50 + editor.accept() - editor.updateAngle(440) - assert editor.textObject.angle == 80 +def test_font(editor): + editor.updateFont("Helvetica") + assert editor.object.font == 4 -def test_font(editors): - for editor in editors: - editor.updateFont("Helvetica") - assert editor.textObject.font == 4 + editor.updateFont("Chinese") + assert editor.object.font == 8 - editor.updateFont("Chinese") - assert editor.textObject.font == 8 + editor.accept() -def test_size(editors): - for editor in editors: - assert editor.textObject.height == 14 +def test_size(editor): + assert editor.object.height == 14 - editor.updateSize(50) - assert editor.textObject.height == 50 + editor.updateSize(50) + assert editor.object.height == 50 + + editor.accept() def saveas_check(name): assert name == "test.txt" -def test_saveas(qtbot, editors): - for editor in editors: +def test_saveas(qtbot, editor): - editor.savePressed.connect(saveas_check) - editor.saveAs() + editor.saved.connect(saveas_check) + editor.saveAs() - try: - print editor.win - except: - print "Did not create save as dialog" - assert 0 + try: + print editor.win + except: + print "Did not create save as dialog" + assert 0 + + editor.win.setTextValue("test.txt") + qtbot.keyPress(editor.win, QtCore.Qt.Key_Enter) + assert "test.txt" in vcs.listelements('texttable') + assert "test.txt" in vcs.listelements('textorientation') + assert "test.txt" in get_textstyles().elements - editor.win.setTextValue("test.txt") - qtbot.keyPress(editor.win, QtCore.Qt.Key_Enter) diff --git a/tests/test_bases.py b/tests/test_bases.py index 3ba4905..868c3c6 100644 --- a/tests/test_bases.py +++ b/tests/test_bases.py @@ -193,12 +193,14 @@ def test_range_widget(qtbot): qtbot.addWidget(w) assert w.getBounds() == (10, 40) - w.updateLower(41) + w.lowerBoundSlider.setValue(40) + # w.updateLower(41) assert w.getBounds() == (40, 40) w.lowerBoundText.setText("12") w.lowerBoundText.textEdited.emit("12") assert w.getBounds() == (12, 40) - w.updateUpper(11) + w.upperBoundSlider.setValue(12) + # w.updateUpper(12) assert w.getBounds() == (12, 12) w.upperBoundText.setText("30") w.upperBoundText.textEdited.emit("30") diff --git a/tests/test_save_load.py b/tests/test_save_load.py index 6be43dc..883aa3f 100644 --- a/tests/test_save_load.py +++ b/tests/test_save_load.py @@ -1,6 +1,6 @@ import pytest # noqa from cdatgui.cdat.importer import import_script -from cdatgui.cdat.plotter import PlotManager +from cdatgui.cdat.plotter import PlotManager, PlotInfo from cdatgui.cdat.exporter import diff, export_script from cdatgui.cdat.metadata import VariableMetadataWrapper, FileMetadataWrapper import mocks @@ -28,14 +28,16 @@ def test_load_script(canvas): assert len(script.templates) == 3 -def test_save_and_load_script(tmpdir): +def test_save_and_load_script(tmpdir, qtbot): save_file = tmpdir.join("simple_vis.py") # File shouldn't exist assert save_file.exists() is False path = str(save_file.realpath()) - pm = PlotManager(mocks.PlotInfo) + pi = PlotInfo(vcs.init(), 0, 0) + qtbot.addWidget(pi) + pm = PlotManager(pi) pm.graphics_method = vcs.getboxfill("default") pm.template = vcs.gettemplate('default') @@ -44,7 +46,7 @@ def test_save_and_load_script(tmpdir): clt = fmw["clt"] pm.variables = [clt.var, None] - mocks.PlotInfo.canvas.close() + pi.canvas.close() export_script(path, [clt], [[pm]]) @@ -68,7 +70,7 @@ def test_save_and_load_script(tmpdir): assert len(obj.templates) == 1 -def test_save_loaded_script(tmpdir): +def test_save_loaded_script(tmpdir, qtbot): _ = vcs.init() dirpath = os.path.dirname(__file__) load_file = os.path.join(dirpath, "data", "clt_u_v_iso.py") @@ -85,7 +87,9 @@ def test_save_loaded_script(tmpdir): for display_group in canvas_displays: pm_group = [] for display in display_group: - pm = PlotManager(mocks.PlotInfo) + pi = PlotInfo(vcs.init(), 0, 0) + qtbot.addWidget(pi) + pm = PlotManager(pi) # Determine which of the graphics methods created in loaded gm = vcs.getgraphicsmethod(display.g_type, display.g_name) pm.graphics_method = closest(gm, loaded.graphics_methods) @@ -93,7 +97,6 @@ def test_save_loaded_script(tmpdir): pm.variables = display.array pm_group.append(pm) plot_managers.append(pm_group) - mocks.PlotInfo.canvas.close() export_script(str(save_file), loaded.variables.values(), plot_managers) diff --git a/tests/test_variables.py b/tests/test_variables.py index a5ade10..b4804c7 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -158,13 +158,14 @@ def test_add_dialog(qtbot, var_manager): def test_variable_widget(qtbot): - w = cdatgui.variables.VariableWidget() + w = cdatgui.variables.variable_widget.VariableWidget() qtbot.addWidget(w) w.add_dialog = mocks.VariableAddDialog # Fake the signal to check for new variables - w.add_variable() + with qtbot.waitSignal(w.variableListNotEmpty, timeout=1000, raising=True): + w.add_variable() # Make sure that we can select a variable with qtbot.waitSignal(w.selectedVariable,