diff --git a/cdatgui/bases/background_delegate.py b/cdatgui/bases/background_delegate.py index d9b9569..b67d2a5 100644 --- a/cdatgui/bases/background_delegate.py +++ b/cdatgui/bases/background_delegate.py @@ -2,15 +2,15 @@ class BorderHighlightStyleDelegate(QtGui.QStyledItemDelegate): - def paint(self, painter, option, index): - bg = index.data(QtCore.Qt.BackgroundRole) - painter.fillRect(option.rect, bg) - super(BorderHighlightStyleDelegate, self).paint(painter, option, index) - if option.state & QtGui.QStyle.State_Selected: - painter.save() - color = QtGui.QColor(76, 177 ,255) - pen = QtGui.QPen(color, 2, QtCore.Qt.SolidLine, QtCore.Qt.SquareCap, QtCore.Qt.MiterJoin) - w = pen.width() / 2 - painter.setPen(pen) - painter.drawRect(option.rect.adjusted(w, w, -w, -w)) - painter.restore() + def paint(self, painter, option, index): + bg = index.data(QtCore.Qt.BackgroundRole) + painter.fillRect(option.rect, bg) + super(BorderHighlightStyleDelegate, self).paint(painter, option, index) + if option.state & QtGui.QStyle.State_Selected: + painter.save() + color = QtGui.QColor(76, 177, 255) + pen = QtGui.QPen(color, 2, QtCore.Qt.SolidLine, QtCore.Qt.SquareCap, QtCore.Qt.MiterJoin) + w = pen.width() / 2 + painter.setPen(pen) + painter.drawRect(option.rect.adjusted(w, w, -w, -w)) + painter.restore() 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..38c7766 --- /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): + super(AccessibleButtonDialog, self).__init__() + + self.setWindowModality(QtCore.Qt.ApplicationModal) + shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) + shortcut.activated.connect(self.cancel) + + self.save_button = QtGui.QPushButton('Save') + self.save_button.clicked.connect(self.save) + self.save_button.setEnabled(False) + self.save_button.setDefault(True) + self.cancel_button = QtGui.QPushButton('Cancel') + self.cancel_button.clicked.connect(self.cancel) + + 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 cancel(self): + self.close() + self.rejected.emit() + + def save(self): + self.close() + self.accepted.emit() + + +class ValidatingInputDialog(AccessibleButtonDialog): + def __init__(self): + super(ValidatingInputDialog, self).__init__() + + 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.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/value_slider.py b/cdatgui/bases/value_slider.py index ebc9a13..3338678 100644 --- a/cdatgui/bases/value_slider.py +++ b/cdatgui/bases/value_slider.py @@ -2,19 +2,23 @@ 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 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..c45dd12 --- /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] or self.textValue() + '_miniticks' 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..3a59ade 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.close) # 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.close) # 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..a27ea96 100644 --- a/cdatgui/cdat/plotter.py +++ b/cdatgui/cdat/plotter.py @@ -124,6 +124,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 +142,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 +157,15 @@ 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(): - self.plot() + self._gm = gm.name + self._type = vcs.graphicsmethodtype(gm) + self.source.gm_label.setText(self._gm) graphics_method = property(gm, set_gm) @@ -189,8 +188,16 @@ def set_vars(self, v): else: new_vars.append(var) self._vars = new_vars - if 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) @@ -200,8 +207,7 @@ def templ(self): def set_templ(self, template): # Check if gm supports templates self._template = template - if self.can_plot(): - self.plot() + self.source.tmpl_label.setText(self.template.name) template = property(templ, set_templ) @@ -213,6 +219,8 @@ def remove(self): self.removed.emit() def plot(self): + if not self.can_plot(): + raise Exception("Attempted plot when can_plot not valid") if self.variables is None: raise ValueError("No variables specified") @@ -229,7 +237,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 +269,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..0c515a9 100644 --- a/cdatgui/console/console_dock.py +++ b/cdatgui/console/console_dock.py @@ -11,7 +11,7 @@ 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) spreadsheet.emitAllPlots.connect(self.updateAllPlots) self.setWidget(self.console) diff --git a/cdatgui/console/console_widget.py b/cdatgui/console/console_widget.py index dfb638e..7f27887 100644 --- a/cdatgui/console/console_widget.py +++ b/cdatgui/console/console_widget.py @@ -9,6 +9,7 @@ from cdatgui.cdat.metadata import FileMetadataWrapper, VariableMetadataWrapper from cdatgui.variables import get_variables +from cdatgui.constants import reserved_words def is_cdms_var(v): @@ -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() @@ -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,13 @@ def codeExecuted(self, *varargs): self.display_plots.append(last_line) self.createdPlot.emit(last_line) + 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/constants.py b/cdatgui/constants.py new file mode 100644 index 0000000..fb3efa0 --- /dev/null +++ b/cdatgui/constants.py @@ -0,0 +1,5 @@ +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/editors/axis_editor.py b/cdatgui/editors/axis_editor.py index 4ee84e7..23e195f 100644 --- a/cdatgui/editors/axis_editor.py +++ b/cdatgui/editors/axis_editor.py @@ -1,15 +1,18 @@ from PySide import QtGui, QtCore -from cdatgui.bases.window_widget import BaseOkWindowWidget + +from cdatgui.bases.vcs_elements_dialog import VCSElementsDialog +from cdatgui.bases.window_widget import BaseSaveWindowWidget from cdatgui.editors.preview.axis_preview import AxisPreviewWidget from cdatgui.editors.widgets.dict_editor import DictEditorWidget import vcs -class AxisEditorWidget(BaseOkWindowWidget): +class AxisEditorWidget(BaseSaveWindowWidget): def __init__(self, axis, parent=None): super(AxisEditorWidget, self).__init__() self.axis = axis self.state = None + self.setSaveDialog(VCSElementsDialog('line')) # create layout so you can set the preview self.horizontal_layout = QtGui.QHBoxLayout() @@ -56,20 +59,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 +92,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 +124,52 @@ 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 + + # initialize combo value + if isinstance(axis_obj.ticks, str): + if axis_obj.ticks == '*': + ticks = 'default' + else: + ticks = axis_obj.ticks + self.preset_box.setCurrentIndex(self.preset_box.findText(ticks)) + self.updatePreset(ticks) + 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.saveTicks) + 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 +178,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 +201,21 @@ def updateTickmark(self, button): def updatePreset(self, preset): if preset == "default": self.object.ticks = "*" + self.save_button.setEnabled(False) + 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.save_button.setEnabled(True) + 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 +249,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() @@ -229,3 +283,6 @@ def updateTickSign(self): self.step_edit.setText(str(-val)) self.preview.update() + + def saveTicks(self, name): + self.object.save(name) diff --git a/cdatgui/editors/boxfill.py b/cdatgui/editors/boxfill.py index 6252146..f5ade0b 100644 --- a/cdatgui/editors/boxfill.py +++ b/cdatgui/editors/boxfill.py @@ -1,27 +1,19 @@ 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 +import numpy, vcs + +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 +29,19 @@ 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) + + self.graphicsMethodUpdated.connect(self.updateLegendButton) + + def updateLegendButton(self): + levs = self._gm.getlevels(*vcs.minmax(self.var)) + if len(levs) == 2 and numpy.allclose(levs, [1e+20] * 2): + self.legend_button.setEnabled(False) + else: + self.legend_button.setEnabled(True) @property def gm(self): @@ -123,14 +50,23 @@ 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.""" box_type = self.boxfill_types[radio.text()] self._gm.boxfill_type = box_type - self.graphicsMethodUpdated.emit(self._gm) + + if radio.text() == 'Custom': + self.levels_button.setEnabled(True) + self.updateLegendButton() + else: + self.levels_button.setEnabled(False) + + 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..2b69ae3 100644 --- a/cdatgui/editors/colormap.py +++ b/cdatgui/editors/colormap.py @@ -9,9 +9,11 @@ class QColormapEditor(QtGui.QColorDialog): choseColormap = QtCore.Signal(str) choseColorIndex = QtCore.Signal(int) + colormapCreated = QtCore.Signal(str) 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) @@ -94,10 +96,13 @@ def __init__(self, mode=COLORMAP_MODE, parent=None): def acceptClicked(self): # Make sure the colormap changes take effect - self.applyChanges() - self.choseColormap.emit(str(self.colormap.currentText())) - if self.mode == COLOR_MODE: - self.choseColorIndex.emit(self.cell) + if self.colormap.currentText() == 'default': + QtGui.QMessageBox.information(self, 'Cannot Modify', 'Cannot modify the default colormap') + else: + self.applyChanges() + self.choseColormap.emit(str(self.colormap.currentText())) + if self.mode == COLOR_MODE: + self.choseColorIndex.emit(self.cell) def selectedCell(self, ind): self.cell = ind @@ -134,6 +139,7 @@ def colorChanged(self): self.colors.set_cell(self.cell, cr, cg, cb, ca) def rejectChanges(self): + self.colors.reject() self.close() def save(self): @@ -150,6 +156,7 @@ def renamed(self): self.colormap.model().sort(0) self.colormap.setCurrentIndex(self.colormap.findText(newname)) self.newname.setText("") + self.colormapCreated.emit(newname) def blend(self): min_index, max_index = self.colors.get_color_range() diff --git a/cdatgui/editors/graphics_method_editor.py b/cdatgui/editors/graphics_method_editor.py new file mode 100644 index 0000000..ffe2ea7 --- /dev/null +++ b/cdatgui/editors/graphics_method_editor.py @@ -0,0 +1,127 @@ +from PySide import QtGui, QtCore +import vcs + +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() + + 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._gm = self.legend_editor.gm + 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 + self.graphicsMethodUpdated.emit() + + @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) + self.legend_editor.createAndSetObject(self.gm, self.var.var) + 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..c7f57d4 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,9 +47,12 @@ 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): + if len(colors) < len(levs) - 1: # Pad out colors to the right number of buckets diff = len(levs) - len(colors) colors += diff * colors[-1:] @@ -63,11 +67,14 @@ 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 @property def color_1(self): @@ -101,44 +108,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): @@ -182,13 +170,17 @@ def label_mode(self, v): self.labels = {} else: if vcs.isboxfill(self._gm): - self.labels = self._gm.autolabels(self._var) + min, max = vcs.minmax(self._var) + levels = self._gm.getlevels(min, max) + self.labels = self._gm.getlegendlabels(levels) @property def labels(self): if self._gm.legend is None: if vcs.isboxfill(self._gm): - return self._gm.autolabels(self._var) + min, max = vcs.minmax(self._var) + levels = self._gm.getlevels(min, max) + return self._gm.getlegendlabels(levels) return self._gm.legend @labels.setter @@ -199,7 +191,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..6a32620 --- /dev/null +++ b/cdatgui/editors/model/levels_base.py @@ -0,0 +1,27 @@ +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: + min, max = vcs.minmax(self._var) + levs = self._gm.getlevels(min, max).tolist() + 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 as e: + 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..bd09bee 100644 --- a/cdatgui/editors/model/vcsaxis.py +++ b/cdatgui/editors/model/vcsaxis.py @@ -1,11 +1,15 @@ 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 + self.name = None @property def axis(self): @@ -42,6 +46,12 @@ def ticks(self, val): minitick_count = self.minitick_count else: minitick_count = 0 + if isinstance(val, str): + if val == '*': + self.name = None + else: + self.name = val + if self._axis == "x1": self.gm.xticlabels1 = val if self._axis == "x2": @@ -92,8 +102,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 +127,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 +138,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): @@ -210,5 +228,26 @@ def ticks_as_dict(self): return ticks def save(self, name): - vcs.elements["list"][name] = self.ticks + if name is None: + raise Exception( + "Non str name cannot be used to save ticks") # something got through your rock solid wall of logic + + if isinstance(self.ticks, str): + vcs.elements["list"][name] = vcs.elements['list'][self.ticks] + else: + vcs.elements["list"][name] = self.ticks vcs.elements["list"][name + "_miniticks"] = self.miniticks + + self.ticks = name + self.miniticks = name + "_miniticks" + + 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 + + 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..495da88 --- /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/color_table.py b/cdatgui/editors/widgets/color_table.py index 5a94df2..41cf70a 100644 --- a/cdatgui/editors/widgets/color_table.py +++ b/cdatgui/editors/widgets/color_table.py @@ -63,7 +63,6 @@ def update_selection(self): self.singleColorSelected.emit(self.row_col_to_ind(sel.topRow(), sel.leftColumn())) return else: - print "Emitting None" self.singleColorSelected.emit(None) @@ -111,15 +110,24 @@ def cmap(self): @cmap.setter def cmap(self, value): - if not vcs.iscolormap(value): + try: value = vcs.getcolormap(value) + except KeyError: + raise KeyError('Invalid value {0} for colormap name'.format(value)) self._real_map = value + if self._cmap is not None: + del vcs.elements['colormap'][self._cmap.name] + self._cmap = vcs.createcolormap(Cp_name_src=self._real_map.name) self.update_table() def apply(self): for ind in range(256): self._real_map.index[ind] = self._cmap.index[ind] + del vcs.elements['colormap'][self._cmap.name] + + def reject(self): + del vcs.elements['colormap'][self._cmap.name] def reset(self): for ind in range(256): 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..bd004cc 100644 --- a/cdatgui/editors/widgets/legend_widget.py +++ b/cdatgui/editors/widgets/legend_widget.py @@ -1,5 +1,6 @@ from types import FunctionType +from cdatgui.editors.model.legend import VCSLegend from cdatgui.editors.widgets.dict_editor import DictEditorWidget from PySide import QtCore, QtGui from cdatgui.editors.model import legend @@ -9,6 +10,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): @@ -183,6 +185,8 @@ def __init__(self, parent=None): self.cur_button = None self.cur_index = None self.colormap_editor = None + self.gm = None + self.orig_gm = None # Create Labels colormap_label = QtGui.QLabel("Colormap:") @@ -190,7 +194,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 +235,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 +290,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,24 +315,63 @@ 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) - def setObject(self, legend): - self.object = legend + def createAndSetObject(self, gm, var): + self.orig_gm = gm + self.gm = vcs.creategraphicsmethod(vcs.graphicsmethodtype(gm), gm.name) + self.object = VCSLegend(self.gm, var) - 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() + + 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() - 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) + 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) + # select correct colormap index + if gm.colormap is not None: + self.colormap_dropdown.setCurrentIndex(self.colormap_dropdown.findText(gm.colormap)) + + self.preview.setLegendObject(self.object) self.preview.update() def updateColormap(self, cur_item, recreate=True): @@ -332,8 +379,7 @@ def updateColormap(self, cur_item, recreate=True): return self.object.colormap = cur_item - items = [self.colormap_dropdown.itemText(i) for i in range(self.colormap_dropdown.count())] - self.colormap_dropdown.setCurrentIndex(items.index(cur_item)) + self.colormap_dropdown.setCurrentIndex(self.colormap_dropdown.findText(cur_item)) self.preview.update() self.level_count = len(self.object.levels) @@ -393,7 +439,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 +448,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 +459,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 @@ -482,6 +529,7 @@ def createColormap(self, obj): self.colormap_editor.colormap.setCurrentIndex(items.index(self.colormap_dropdown.currentText())) self.colormap_editor.choseColormap.connect(partial(self.updateColormap, recreate=False)) self.colormap_editor.choseColorIndex.connect(partial(self.performActionAndClose, obj)) + self.colormap_editor.colormapCreated.connect(self.colormap_dropdown.addItem) self.colormap_editor.show() if self.start_timer.isActive(): self.start_timer.stop() @@ -534,6 +582,31 @@ 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() + + def accept(self): + orig_name = self.orig_gm.name + if orig_name in vcs.elements[vcs.graphicsmethodtype(self.orig_gm)]: + del vcs.elements[vcs.graphicsmethodtype(self.orig_gm)][orig_name] + + new_gm = vcs.creategraphicsmethod(vcs.graphicsmethodtype(self.orig_gm), self.gm.name, orig_name) + super(LegendEditorWidget, self).accept() + + def reject(self): + if self.gm.name in vcs.elements[vcs.graphicsmethodtype(self.orig_gm)]: + del vcs.elements[vcs.graphicsmethodtype(self.orig_gm)][self.gm.name] + super(LegendEditorWidget, self).reject() + 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..5d1047c --- /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..1a87b0e --- /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..fddf51c 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_window.py b/cdatgui/main_window.py index a325f1e..9590b48 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) 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..9be1247 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) + get_gms().replace(get_gms().indexOf(vcs.graphicsmethodtype(gm), gm), gm) self.current_plot.graphics_method = gm + 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.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.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 + else: + gm = vcs.getgraphicsmethod(gm_type, gm_name) + self.current_plot.graphics_method = gm - 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.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] + 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/tab.py b/cdatgui/spreadsheet/tab.py index 2b087ed..9563f22 100755 --- a/cdatgui/spreadsheet/tab.py +++ b/cdatgui/spreadsheet/tab.py @@ -411,9 +411,9 @@ 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) + + widget.grabWindowPixmap().accept(dirPath + '/' + + chr(c + ord('a')) + + str(r + 1) + '.' + format) def setSpan(self, row, col, rowSpan, colSpan): @@ -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) @@ -538,6 +537,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 +562,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..4ffa8d3 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 @@ -122,10 +123,13 @@ def dropEvent(self, event): if vcs_gm_mime in event.mimeData().formats(): event.accept() target.graphics_method(dropped) + if target.manager.can_plot(): + target.manager.plot() 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..d05c663 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..4217627 100644 --- a/cdatgui/variables/__init__.py +++ b/cdatgui/variables/__init__.py @@ -7,4 +7,4 @@ def get_variables(): global __variables__ if __variables__ is None: __variables__ = models.CDMSVariableListModel() - return __variables__ \ No newline at end of file + return __variables__ 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..ad9ec15 100644 --- a/cdatgui/variables/cdms_var_list.py +++ b/cdatgui/variables/cdms_var_list.py @@ -24,7 +24,8 @@ def remove_variable(self, variable): 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/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/models.py b/cdatgui/variables/models.py index d03f345..71a901b 100644 --- a/cdatgui/variables/models.py +++ b/cdatgui/variables/models.py @@ -11,7 +11,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) diff --git a/cdatgui/variables/variable_add.py b/cdatgui/variables/variable_add.py index 0cb5416..40c3a0a 100644 --- a/cdatgui/variables/variable_add.py +++ b/cdatgui/variables/variable_add.py @@ -1,13 +1,45 @@ -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 +from cdatgui.constants import 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 +47,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 +88,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 +104,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..1aff9dd 100644 --- a/cdatgui/variables/variable_widget.py +++ b/cdatgui/variables/variable_widget.py @@ -1,7 +1,7 @@ from functools import partial from cdatgui.bases import StaticDockWidget -from PySide import QtCore +from PySide import QtCore, QtGui from cdatgui.toolbars import AddEditRemoveToolbar from variable_add import AddDialog from cdms_var_list import CDMSVariableList @@ -9,7 +9,6 @@ class VariableWidget(StaticDockWidget): - selectedVariable = QtCore.Signal(object) def __init__(self, parent=None, flags=0): @@ -55,5 +54,24 @@ def edit_variable(self): 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) 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_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..b3451a1 --- /dev/null +++ b/tests/test_GmDialog.py @@ -0,0 +1,205 @@ +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() + qtbot.addWidget(editor.axis_editor) + assert editor.axis_editor + assert editor.axis_editor.axis == 'y1' + + editor.updated() + assert editor.axis_editor is None + + editor.editRight() + qtbot.addWidget(editor.axis_editor) + assert editor.axis_editor.axis == 'y2' + + editor.updated() + assert editor.axis_editor is None + + editor.editBottom() + qtbot.addWidget(editor.axis_editor) + 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() + qtbot.addWidget(editor.projection_editor) + 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() + qtbot.addWidget(save_dialog.dialog) + 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()) + qtbot.addWidget(d) + 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..5d1246e --- /dev/null +++ b/tests/test_ProjectionEditor.py @@ -0,0 +1,91 @@ +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() + qtbot.addWidget(new_editor) + 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_save_load.py b/tests/test_save_load.py index 6be43dc..6367a13 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,23 +28,26 @@ 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) - pm.graphics_method = vcs.getboxfill("default") - pm.template = vcs.gettemplate('default') - + pi = PlotInfo(vcs.init(), 0, 0) + qtbot.addWidget(pi) + pm = PlotManager(pi) + pm.graphics_method = vcs.createboxfill(source="default") + pm.template = vcs.createtemplate(source='default') f = cdms2.open(vcs.sample_data + "/clt.nc") fmw = FileMetadataWrapper(f) clt = fmw["clt"] pm.variables = [clt.var, None] - mocks.PlotInfo.canvas.close() + + pm.plot() + pi.canvas.close() export_script(path, [clt], [[pm]]) @@ -68,7 +71,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,15 +88,17 @@ 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) pm.template = vcs.gettemplate(display._template_origin) pm.variables = display.array pm_group.append(pm) + pm.plot() 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..a9676fc 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -158,7 +158,7 @@ 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