def __init__(self, project, parent_widget=None): QDialog.__init__(self, parent_widget, Qt.Window) self.setupUi(self) self.project = project # Change PICK MODE to true for old school selector self.variables_table = VariablesTableView(parent_widget = self) self.original_nodes = set() self.cancel_validation_flag = {'value': False} self.model = None self.problem_variables = [] self.pb_create_new.setIcon(IconLibrary.icon('add')) self.initialize() self.table_container.addWidget(self.variables_table) # Automatically update the save button state on model data change self.connect(self.model, SIGNAL('model_changed'), self._update_apply_button) # Automatically enable or disable the validate selected vars button def on_sel_change(dummy_a, dummy_b): enabled = len(self.variables_table.selectedIndexes()) > 0 self.pb_validate_selected.setEnabled(enabled) sel_changed = SIGNAL('selectionChanged(const QItemSelection&,const QItemSelection&)') self.connect(self.variables_table.selectionModel(), sel_changed, on_sel_change) # Double clicking an item in the table brings up the editor def edit_wrapper(index): variable = self.model.variables[index.row()] if not variable['inherited']: self._edit_variable(variable) self.connect(self.variables_table, SIGNAL('doubleClicked(QModelIndex)'), edit_wrapper) def apply_and_close(): self._apply_variable_changes() self.accept() def cancel_validation(): self.cancel_validation_flag['value'] = True self.connect(self.pb_apply_and_close, SIGNAL("clicked()"), apply_and_close) self.connect(self.pb_apply, SIGNAL("clicked()"), self._apply_variable_changes) self.connect(self.pb_create_new, SIGNAL("clicked()"), self._add_variable) self.connect(self.pb_problems, SIGNAL("clicked()"), self._show_problem_variables) self.connect(self.pb_cancel_validation, SIGNAL("clicked()"), cancel_validation) signal = SIGNAL('customContextMenuRequested(const QPoint &)') self.connect(self.variables_table, signal, self._show_right_click_menu) signal = SIGNAL('currentIndexChanged(int)') self.connect(self.cbo_dataset_filter, signal, lambda x: self._set_dataset_filter()) self.validator = VariableValidator(self.project) self.editor = VariableEditor(self)
def __init__(self, project, parent_widget=None): QDialog.__init__(self, parent_widget, Qt.Window) self.setupUi(self) self.project = project # Change PICK MODE to true for old school selector self.variables_table = VariablesTableView(parent_widget=self) self.original_nodes = set() self.cancel_validation_flag = {'value': False} self.model = None self.problem_variables = [] self.pb_create_new.setIcon(IconLibrary.icon('add')) self.initialize() self.table_container.addWidget(self.variables_table) # Automatically update the save button state on model data change self.connect(self.model, SIGNAL('model_changed'), self._update_apply_button) # Automatically enable or disable the validate selected vars button def on_sel_change(dummy_a, dummy_b): enabled = len(self.variables_table.selectedIndexes()) > 0 self.pb_validate_selected.setEnabled(enabled) sel_changed = SIGNAL( 'selectionChanged(const QItemSelection&,const QItemSelection&)') self.connect(self.variables_table.selectionModel(), sel_changed, on_sel_change) # Double clicking an item in the table brings up the editor def edit_wrapper(index): variable = self.model.variables[index.row()] if not variable['inherited']: self._edit_variable(variable) self.connect(self.variables_table, SIGNAL('doubleClicked(QModelIndex)'), edit_wrapper) def apply_and_close(): self._apply_variable_changes() self.accept() def cancel_validation(): self.cancel_validation_flag['value'] = True self.connect(self.pb_apply_and_close, SIGNAL("clicked()"), apply_and_close) self.connect(self.pb_apply, SIGNAL("clicked()"), self._apply_variable_changes) self.connect(self.pb_create_new, SIGNAL("clicked()"), self._add_variable) self.connect(self.pb_problems, SIGNAL("clicked()"), self._show_problem_variables) self.connect(self.pb_cancel_validation, SIGNAL("clicked()"), cancel_validation) signal = SIGNAL('customContextMenuRequested(const QPoint &)') self.connect(self.variables_table, signal, self._show_right_click_menu) signal = SIGNAL('currentIndexChanged(int)') self.connect(self.cbo_dataset_filter, signal, lambda x: self._set_dataset_filter()) self.validator = VariableValidator(self.project) self.editor = VariableEditor(self)
class VariableLibrary(QDialog, Ui_VariableLibrary): ''' An editor for creating and modifying variables in the expression library. Causes mainwindow to emit signal 'variables_updated' whenever changes have been applied. ''' def __init__(self, project, parent_widget=None): QDialog.__init__(self, parent_widget, Qt.Window) self.setupUi(self) self.project = project # Change PICK MODE to true for old school selector self.variables_table = VariablesTableView(parent_widget=self) self.original_nodes = set() self.cancel_validation_flag = {'value': False} self.model = None self.problem_variables = [] self.pb_create_new.setIcon(IconLibrary.icon('add')) self.initialize() self.table_container.addWidget(self.variables_table) # Automatically update the save button state on model data change self.connect(self.model, SIGNAL('model_changed'), self._update_apply_button) # Automatically enable or disable the validate selected vars button def on_sel_change(dummy_a, dummy_b): enabled = len(self.variables_table.selectedIndexes()) > 0 self.pb_validate_selected.setEnabled(enabled) sel_changed = SIGNAL( 'selectionChanged(const QItemSelection&,const QItemSelection&)') self.connect(self.variables_table.selectionModel(), sel_changed, on_sel_change) # Double clicking an item in the table brings up the editor def edit_wrapper(index): variable = self.model.variables[index.row()] if not variable['inherited']: self._edit_variable(variable) self.connect(self.variables_table, SIGNAL('doubleClicked(QModelIndex)'), edit_wrapper) def apply_and_close(): self._apply_variable_changes() self.accept() def cancel_validation(): self.cancel_validation_flag['value'] = True self.connect(self.pb_apply_and_close, SIGNAL("clicked()"), apply_and_close) self.connect(self.pb_apply, SIGNAL("clicked()"), self._apply_variable_changes) self.connect(self.pb_create_new, SIGNAL("clicked()"), self._add_variable) self.connect(self.pb_problems, SIGNAL("clicked()"), self._show_problem_variables) self.connect(self.pb_cancel_validation, SIGNAL("clicked()"), cancel_validation) signal = SIGNAL('customContextMenuRequested(const QPoint &)') self.connect(self.variables_table, signal, self._show_right_click_menu) signal = SIGNAL('currentIndexChanged(int)') self.connect(self.cbo_dataset_filter, signal, lambda x: self._set_dataset_filter()) self.validator = VariableValidator(self.project) self.editor = VariableEditor(self) def initialize(self): # reset the variable library to reflect the current state of expression library in xml nodes_per_dataset = get_variable_nodes_per_dataset(self.project) variables = [] all_original_nodes = set() for dataset in nodes_per_dataset: for variable_node in nodes_per_dataset[dataset]: new_variable = variable_from_node(variable_node) new_variable['originalnode'] = variable_node variables.append(new_variable) all_original_nodes.add( variable_node) # save original values for comparison self.original_nodes = all_original_nodes self.cancel_validation_flag = {'value': False} # to avoid quirky behavior when applying changes, store the column widths and restore them # after the model update col_widths = [ self.variables_table.columnWidth(col) for col in range(5) ] # bind to self to prevent PyQt from "losing" the object ref self.model = VariablesTableModel(project=self.project, variables=variables, parent_widget=self.variables_table) self.variables_table.setModel(self.model) self.model.re_sort() self.connect(self.model, SIGNAL('layoutChanged()'), self.variables_table.resizeRowsToContents) self._update_apply_button() self.group_progress.setVisible(False) self.pb_problems.setIcon(IconLibrary.icon('warning_small')) self.pb_problems.setAttribute(Qt.WA_AlwaysShowToolTips) self._set_problem_variables([]) self._update_dataset_filter() # use the inital size if the column widths is all zeroes if sum(abs(w) for w in col_widths) == 0: col_widths = [195, 80, 45, 50, -1] for col in range(5): if col_widths[col] > 0: self.variables_table.setColumnWidth(col, col_widths[col]) self.variables_table.resizeRowsToContents() def update_validation_progress(self, ratio): ''' callback to show validation progress ''' ratio = 1.0 if ratio > 1.0 else ratio self.progress_validation.setValue(ratio * 100) self.progress_validation.repaint() def _update_apply_button(self): self.pb_apply.setEnabled(self.model.dirty) self.pb_apply_and_close.setEnabled(self.model.dirty) def _update_dataset_filter(self): '''repopulate the dataset filter combobox with existing datasets''' all_datasets = set() for dataset, _ in self.model.get_variables(): all_datasets.add(dataset) prev_selected_name = self.cbo_dataset_filter.currentText() self.cbo_dataset_filter.clear() self.cbo_dataset_filter.addItem('[All datasets]') for dataset_name in all_datasets: self.cbo_dataset_filter.addItem(dataset_name) refound_index = self.cbo_dataset_filter.findText(prev_selected_name) if refound_index > -1: self.cbo_dataset_filter.setCurrentIndex(refound_index) else: self.cbo_dataset_filter.setCurrentIndex(0) def _set_dataset_filter(self): index = self.cbo_dataset_filter.currentIndex() if index == 0: self.model.set_dataset_filter(None) else: self.model.set_dataset_filter( str(self.cbo_dataset_filter.currentText())) def _set_problem_variables(self, list_of_problem_variables): ''' stores the list of problematic variables and updates the problem button to reflect the change ''' self.problem_variables = list_of_problem_variables if self.problem_variables: self.pb_problems.setText('%d' % len(self.problem_variables)) else: self.pb_problems.setText('') txt = "Last validation didn't discover any problems with the tested variables" self.pb_problems.setToolTip(txt) self.pb_problems.setEnabled(not not self.problem_variables) if self.problem_variables: self.pb_problems.setText('%d' % len(self.problem_variables)) txt = 'Discovered problems with %d of the variables' % len( self.problem_variables) self.pb_problems.setToolTip(txt) else: self.pb_problems.setText('') def _show_problem_variables(self): ''' shows the current list of problem variables ''' if not self.problem_variables: MessageBox.information(self, 'All tested variables passed.') return msg = '<qt>' for dummy, err_msg in self.problem_variables: msg += '<br/>'.join(err_msg) msg += '<br/><br/>' msg += '</qt>' txt = 'Found problems with %d of the variables' % len( self.problem_variables) MessageBox.warning(self, txt, msg) def _apply_variable_changes(self): ''' apply the changes the user made to the expression library ''' # TODO: also check into the possibility that an inherited variable can have its name # changed (in this case don't overwrite the original variable's name. Instead create a new # variable with the new name.) dirty_variables = [ var for var in self.model.all_variables if var['dirty'] ] # partition dirty variables into create, delete and update sets create_set = [ var for var in dirty_variables if var['originalnode'] is None ] delete_set = [ var for var in dirty_variables if var['delete'] and var['originalnode'] is not None ] # the rest of the variables should just be updated update_set = [ var for var in dirty_variables if not var['originalnode'] in delete_set and var not in create_set ] # verify if we have a partition assert (set([str(var) for var in create_set]) | set([str(var) for var in delete_set]) | set([str(var) for var in update_set]) == set( [str(var) for var in dirty_variables])) assert (set([str(var) for var in create_set]) & set([str(var) for var in delete_set]) & set([str(var) for var in update_set]) == set()) # Apply the changes to each set of nodes expression_lib = self.project.find('general/expression_library') for variable in create_set: # print 'CREATE SET ', variable node = node_from_variable(variable) # print ' node', node self.project.insert_node(node, expression_lib) for variable in update_set: node = node_from_variable(variable) original_node = variable[ 'originalnode'] # reference to the actual XML node # print 'UPDATE SET %s (original %s)' %(variable['name'], original_node) self.project.make_local(original_node) for key in node.attrib: if not key == 'inherited': original_node.set(key, node.get(key)) if 'dataset' in original_node.attrib: del original_node.attrib['dataset'] original_node.text = node.text for variable in delete_set: # print 'DELETE SET %s' % (node) self.project.delete_node(variable['originalnode']) self.initialize() something_changed = bool(update_set or create_set or delete_set) update_mainwindow_savestate(something_changed) get_mainwindow_instance().emit(SIGNAL('variables_updated')) return True def _edit_variable(self, variable): self.editor.init_for_variable(variable, self.validator, self.model.get_variables()) if self.editor.exec_() == self.editor.Accepted: edited_var = self.editor.variable for key in edited_var: variable[key] = edited_var[key] if edited_var['dirty']: self.model.dirty = True self._update_apply_button() def _add_variable(self, base_on_variable=None): var = create_empty_variable() if base_on_variable is not None: for key in VariablesTableModel.VARIABLE_NON_METADATA_KEYS: var[key] = base_on_variable[key] name, dataset = var['name'], var['dataset'] var['name'] = get_unique_name( name, self.model.get_variable_names_in_dataset(dataset)) self.editor.init_for_variable(var, self.validator, self.model.get_variables()) if self.editor.exec_() == self.editor.Accepted: new_variable = self.editor.variable self.model.add_variable(new_variable) self._update_apply_button() def _view_dependencies(self, variable): chart = DependencyChart(self.project.xml_config) dialog = DependencyViewer(self) try: chart.graph_variable("temp", str(variable['definition']), False) dialog.show_graph('temp.png', variable['name']) dialog.show() except InvocationException: #dialog.show_error_message() chart.print_dependencies(str(variable['definition'])) def _show_right_click_menu(self, point): ''' handler for the when users right click on table variables_table ''' index = self.variables_table.indexAt(point) if not index.isValid: return self.variables_table.setCurrentIndex(index) var = self.model.variables[index.row()] menu = QMenu(self.variables_table) # Edit variable action p = ('edit', 'Edit %s' % var['name'], lambda x=var: self._edit_variable(x), self) edit_action = create_qt_action(*p) font = QFont() font.setBold(True) edit_action.setFont(font) # Clone variable action p = ('clone', 'Create new variable based on this', lambda x=var: self._add_variable(x), self) clone_action = create_qt_action(*p) def make_local(var=var): if var['inherited']: var['dirty'] = True var['inherited'] = None self.model.dirty = True self._update_apply_button() def delete_var(var=var): self.model.delete_variable(var) self._update_apply_button() p = ('make_local', 'Make local', make_local, self) make_local_action = create_qt_action(*p) p = ('delete', 'Delete %s' % var['name'], delete_var, self) delete_action = create_qt_action(*p) p = ('revert', 'Revert %s to inherited' % var['name'], delete_var, self) revert_action = create_qt_action(*p) # check to see if we have graphviz installed p = ('zoom', 'View dependencies', lambda x=var: self._view_dependencies(x), self) view_dependencies_action = create_qt_action(*p) if var['inherited']: # menu.addAction(edit_action) menu.addAction(make_local_action) menu.addAction(clone_action) menu.addSeparator() menu.addAction(view_dependencies_action) else: menu.addAction(edit_action) menu.addAction(clone_action) menu.addSeparator() # if the node in the table is local, but the original is inherited OR # if the original node is shadowing an inherited node, allow user to 'revert' # instead of 'delete'. Read more about prototype nodes in opus_project.py. if var['originalnode'] is None: menu.addAction(delete_action) else: prototype_node = self.project.get_prototype_node( var['originalnode']) if var['originalnode'].get('inherited') or \ (prototype_node is not None and prototype_node.get('inherited')): # This action will revert the node to the parent state. Show the values # that it will revert to tt = ('Revert %s to Name: %s, Definition: %s' % (var['name'], prototype_node.get('name'), prototype_node.text)) revert_action.setToolTip(tt) menu.addAction(revert_action) else: menu.addAction(delete_action) menu.addAction(view_dependencies_action) # Menu constructed, present to user if not menu.isEmpty(): menu.exec_(QCursor.pos()) # AUTO WIDGET EVENT HANDLERS @pyqtSlot() def on_pb_close_clicked(self): ''' event handler for when user clicks the close button ''' if self.model.dirty: question = 'Do you want to apply your changes before closing?' user_answer = common_dialogs.apply_before_close(question) if user_answer == common_dialogs.YES: if not self._apply_variable_changes(): return self.close() elif user_answer == common_dialogs.NO: self.close() else: return else: self.close() @pyqtSlot() def on_pb_validate_selected_clicked(self): ''' User clicked the validate selected button ''' # Get all the selected variables selected_rows = set() map(selected_rows.add, [i.row() for i in self.variables_table.selectedIndexes()]) # Setup GUI for batch run self.pb_cancel_validation.setEnabled(True) self._set_problem_variables([]) self.progress_validation.setValue(0) self.group_progress.setVisible(True) self.variables_table.setEnabled( False) # disable selecting variables during run self.group_progress.setTitle('Validating %d variables...' % len(selected_rows)) # Set the expression library in VariableFactory to the variables for this configuration. # We need to get this from the VariablesTableModel rather than from the xml configuration # since newly added variables may not yet have been saved to the xml configuration but we # still want to check them. VariableFactory().set_expression_library( self.model.get_variables_dict()) # Batch process the selected variables variables = [self.model.variables[i] for i in selected_rows] func = self.validator.check_data_errors var_key = 'dataerror' callback = self.update_validation_progress cancel_flag = self.cancel_validation_flag results = variable_batch_check(variables=variables, validator_func=func, variable_key=var_key, progress_callback=callback, cancel_flag=cancel_flag) # Setup GUI for investigating results self.pb_cancel_validation.setEnabled(False) self.progress_validation.setValue(100) self.variables_table.setEnabled(True) failed_variables = [(var, msg) for (var, flag, msg) in results if flag is False] self._set_problem_variables(failed_variables) self._show_problem_variables() self.group_progress.setVisible(False) if failed_variables: self.pb_problems.setFocus()
class VariableLibrary(QDialog, Ui_VariableLibrary): ''' An editor for creating and modifying variables in the expression library. Causes mainwindow to emit signal 'variables_updated' whenever changes have been applied. ''' def __init__(self, project, parent_widget=None): QDialog.__init__(self, parent_widget, Qt.Window) self.setupUi(self) self.project = project # Change PICK MODE to true for old school selector self.variables_table = VariablesTableView(parent_widget = self) self.original_nodes = set() self.cancel_validation_flag = {'value': False} self.model = None self.problem_variables = [] self.pb_create_new.setIcon(IconLibrary.icon('add')) self.initialize() self.table_container.addWidget(self.variables_table) # Automatically update the save button state on model data change self.connect(self.model, SIGNAL('model_changed'), self._update_apply_button) # Automatically enable or disable the validate selected vars button def on_sel_change(dummy_a, dummy_b): enabled = len(self.variables_table.selectedIndexes()) > 0 self.pb_validate_selected.setEnabled(enabled) sel_changed = SIGNAL('selectionChanged(const QItemSelection&,const QItemSelection&)') self.connect(self.variables_table.selectionModel(), sel_changed, on_sel_change) # Double clicking an item in the table brings up the editor def edit_wrapper(index): variable = self.model.variables[index.row()] if not variable['inherited']: self._edit_variable(variable) self.connect(self.variables_table, SIGNAL('doubleClicked(QModelIndex)'), edit_wrapper) def apply_and_close(): self._apply_variable_changes() self.accept() def cancel_validation(): self.cancel_validation_flag['value'] = True self.connect(self.pb_apply_and_close, SIGNAL("clicked()"), apply_and_close) self.connect(self.pb_apply, SIGNAL("clicked()"), self._apply_variable_changes) self.connect(self.pb_create_new, SIGNAL("clicked()"), self._add_variable) self.connect(self.pb_problems, SIGNAL("clicked()"), self._show_problem_variables) self.connect(self.pb_cancel_validation, SIGNAL("clicked()"), cancel_validation) signal = SIGNAL('customContextMenuRequested(const QPoint &)') self.connect(self.variables_table, signal, self._show_right_click_menu) signal = SIGNAL('currentIndexChanged(int)') self.connect(self.cbo_dataset_filter, signal, lambda x: self._set_dataset_filter()) self.validator = VariableValidator(self.project) self.editor = VariableEditor(self) def initialize(self): # reset the variable library to reflect the current state of expression library in xml nodes_per_dataset = get_variable_nodes_per_dataset(self.project) variables = [] all_original_nodes = set() for dataset in nodes_per_dataset: for variable_node in nodes_per_dataset[dataset]: new_variable = variable_from_node(variable_node) new_variable['originalnode'] = variable_node variables.append(new_variable) all_original_nodes.add(variable_node) # save original values for comparison self.original_nodes = all_original_nodes self.cancel_validation_flag = {'value': False} # to avoid quirky behavior when applying changes, store the column widths and restore them # after the model update col_widths = [self.variables_table.columnWidth(col) for col in range(5)] # bind to self to prevent PyQt from "losing" the object ref self.model = VariablesTableModel(project = self.project, variables = variables, parent_widget = self.variables_table) self.variables_table.setModel(self.model) self.model.re_sort() self.connect(self.model, SIGNAL('layoutChanged()'), self.variables_table.resizeRowsToContents) self._update_apply_button() self.group_progress.setVisible(False) self.pb_problems.setIcon(IconLibrary.icon('warning_small')) self.pb_problems.setAttribute(Qt.WA_AlwaysShowToolTips) self._set_problem_variables([]) self._update_dataset_filter() # use the inital size if the column widths is all zeroes if sum(abs(w) for w in col_widths) == 0: col_widths = [195, 80, 45, 50, -1] for col in range(5): if col_widths[col] > 0: self.variables_table.setColumnWidth(col, col_widths[col]) self.variables_table.resizeRowsToContents() def update_validation_progress(self, ratio): ''' callback to show validation progress ''' ratio = 1.0 if ratio > 1.0 else ratio self.progress_validation.setValue(ratio * 100) self.progress_validation.repaint() def _update_apply_button(self): self.pb_apply.setEnabled(self.model.dirty) self.pb_apply_and_close.setEnabled(self.model.dirty) def _update_dataset_filter(self): '''repopulate the dataset filter combobox with existing datasets''' all_datasets = set() for dataset, _ in self.model.get_variables(): all_datasets.add(dataset) prev_selected_name = self.cbo_dataset_filter.currentText() self.cbo_dataset_filter.clear() self.cbo_dataset_filter.addItem('[All datasets]') for dataset_name in all_datasets: self.cbo_dataset_filter.addItem(dataset_name) refound_index = self.cbo_dataset_filter.findText(prev_selected_name) if refound_index > -1: self.cbo_dataset_filter.setCurrentIndex(refound_index) else: self.cbo_dataset_filter.setCurrentIndex(0) def _set_dataset_filter(self): index = self.cbo_dataset_filter.currentIndex() if index == 0: self.model.set_dataset_filter(None) else: self.model.set_dataset_filter(str(self.cbo_dataset_filter.currentText())) def _set_problem_variables(self, list_of_problem_variables): ''' stores the list of problematic variables and updates the problem button to reflect the change ''' self.problem_variables = list_of_problem_variables if self.problem_variables: self.pb_problems.setText('%d' % len(self.problem_variables)) else: self.pb_problems.setText('') txt = "Last validation didn't discover any problems with the tested variables" self.pb_problems.setToolTip(txt) self.pb_problems.setEnabled(not not self.problem_variables) if self.problem_variables: self.pb_problems.setText('%d' % len(self.problem_variables)) txt = 'Discovered problems with %d of the variables' % len(self.problem_variables) self.pb_problems.setToolTip(txt) else: self.pb_problems.setText('') def _show_problem_variables(self): ''' shows the current list of problem variables ''' if not self.problem_variables: MessageBox.information(self, 'All tested variables passed.') return msg = '<qt>' for dummy, err_msg in self.problem_variables: msg += '<br/>'.join(err_msg) msg += '<br/><br/>' msg += '</qt>' txt = 'Found problems with %d of the variables' % len(self.problem_variables) MessageBox.warning(self, txt, msg) def _apply_variable_changes(self): ''' apply the changes the user made to the expression library ''' # TODO: also check into the possibility that an inherited variable can have its name # changed (in this case don't overwrite the original variable's name. Instead create a new # variable with the new name.) dirty_variables = [var for var in self.model.all_variables if var['dirty']] # partition dirty variables into create, delete and update sets create_set = [var for var in dirty_variables if var['originalnode'] is None] delete_set = [var for var in dirty_variables if var['delete'] and var['originalnode'] is not None] # the rest of the variables should just be updated update_set = [var for var in dirty_variables if not var['originalnode'] in delete_set and var not in create_set] # verify if we have a partition assert(set([str(var) for var in create_set]) | set([str(var) for var in delete_set]) | set([str(var) for var in update_set]) == set([str(var) for var in dirty_variables])) assert(set([str(var) for var in create_set]) & set([str(var) for var in delete_set]) & set([str(var) for var in update_set]) == set()) # Apply the changes to each set of nodes expression_lib = self.project.find('general/expression_library') for variable in create_set: # print 'CREATE SET ', variable node = node_from_variable(variable) # print ' node', node self.project.insert_node(node, expression_lib) for variable in update_set: node = node_from_variable(variable) original_node = variable['originalnode'] # reference to the actual XML node # print 'UPDATE SET %s (original %s)' %(variable['name'], original_node) self.project.make_local(original_node) for key in node.attrib: if not key == 'inherited': original_node.set(key, node.get(key)) if 'dataset' in original_node.attrib: del original_node.attrib['dataset'] original_node.text = node.text for variable in delete_set: # print 'DELETE SET %s' % (node) self.project.delete_node(variable['originalnode']) self.initialize() something_changed = bool(update_set or create_set or delete_set) update_mainwindow_savestate(something_changed) get_mainwindow_instance().emit(SIGNAL('variables_updated')) return True def _edit_variable(self, variable): self.editor.init_for_variable(variable, self.validator, self.model.get_variables()) if self.editor.exec_() == self.editor.Accepted: edited_var = self.editor.variable for key in edited_var: variable[key] = edited_var[key] if edited_var['dirty']: self.model.dirty = True self._update_apply_button() def _add_variable(self, base_on_variable = None): var = create_empty_variable() if base_on_variable is not None: for key in VariablesTableModel.VARIABLE_NON_METADATA_KEYS: var[key] = base_on_variable[key] name, dataset = var['name'], var['dataset'] var['name'] = get_unique_name(name, self.model.get_variable_names_in_dataset(dataset)) self.editor.init_for_variable(var, self.validator, self.model.get_variables()) if self.editor.exec_() == self.editor.Accepted: new_variable = self.editor.variable self.model.add_variable(new_variable) self._update_apply_button() def _view_dependencies(self, variable): chart = DependencyChart(self.project.xml_config) dialog = DependencyViewer(self) try: chart.graph_variable("temp", str(variable['definition']), False) dialog.show_graph('temp.png', variable['name']) dialog.show() except InvocationException: #dialog.show_error_message() chart.print_dependencies(str(variable['definition'])) def _show_right_click_menu(self, point): ''' handler for the when users right click on table variables_table ''' index = self.variables_table.indexAt(point) if not index.isValid: return self.variables_table.setCurrentIndex(index) var = self.model.variables[index.row()] menu = QMenu(self.variables_table) # Edit variable action p = ('edit', 'Edit %s' % var['name'], lambda x=var: self._edit_variable(x), self) edit_action = create_qt_action(*p) font = QFont() font.setBold(True) edit_action.setFont(font) # Clone variable action p = ('clone', 'Create new variable based on this', lambda x=var: self._add_variable(x), self) clone_action = create_qt_action(*p) def make_local(var = var): if var['inherited']: var['dirty'] = True var['inherited'] = None self.model.dirty = True self._update_apply_button() def delete_var(var = var): self.model.delete_variable(var) self._update_apply_button() p = ('make_local', 'Make local', make_local, self) make_local_action = create_qt_action(*p) p = ('delete', 'Delete %s' % var['name'], delete_var, self) delete_action = create_qt_action(*p) p = ('revert', 'Revert %s to inherited' % var['name'], delete_var, self) revert_action = create_qt_action(*p) # check to see if we have graphviz installed p = ('zoom', 'View dependencies', lambda x=var: self._view_dependencies(x), self) view_dependencies_action = create_qt_action(*p) if var['inherited']: # menu.addAction(edit_action) menu.addAction(make_local_action) menu.addAction(clone_action) menu.addSeparator() menu.addAction(view_dependencies_action) else: menu.addAction(edit_action) menu.addAction(clone_action) menu.addSeparator() # if the node in the table is local, but the original is inherited OR # if the original node is shadowing an inherited node, allow user to 'revert' # instead of 'delete'. Read more about prototype nodes in opus_project.py. if var['originalnode'] is None: menu.addAction(delete_action) else: prototype_node = self.project.get_prototype_node(var['originalnode']) if var['originalnode'].get('inherited') or \ (prototype_node is not None and prototype_node.get('inherited')): # This action will revert the node to the parent state. Show the values # that it will revert to tt = ('Revert %s to Name: %s, Definition: %s' % (var['name'], prototype_node.get('name'), prototype_node.text)) revert_action.setToolTip(tt) menu.addAction(revert_action) else: menu.addAction(delete_action) menu.addAction(view_dependencies_action) # Menu constructed, present to user if not menu.isEmpty(): menu.exec_(QCursor.pos()) # AUTO WIDGET EVENT HANDLERS @pyqtSlot() def on_pb_close_clicked(self): ''' event handler for when user clicks the close button ''' if self.model.dirty: question = 'Do you want to apply your changes before closing?' user_answer = common_dialogs.apply_before_close(question) if user_answer == common_dialogs.YES: if not self._apply_variable_changes(): return self.close() elif user_answer == common_dialogs.NO: self.close() else: return else: self.close() @pyqtSlot() def on_pb_validate_selected_clicked(self): ''' User clicked the validate selected button ''' # Get all the selected variables selected_rows = set() map(selected_rows.add, [i.row() for i in self.variables_table.selectedIndexes()]) # Setup GUI for batch run self.pb_cancel_validation.setEnabled(True) self._set_problem_variables([]) self.progress_validation.setValue(0) self.group_progress.setVisible(True) self.variables_table.setEnabled(False) # disable selecting variables during run self.group_progress.setTitle('Validating %d variables...' % len(selected_rows)) # Set the expression library in VariableFactory to the variables for this configuration. # We need to get this from the VariablesTableModel rather than from the xml configuration # since newly added variables may not yet have been saved to the xml configuration but we # still want to check them. VariableFactory().set_expression_library(self.model.get_variables_dict()) # Batch process the selected variables variables = [self.model.variables[i] for i in selected_rows] func = self.validator.check_data_errors var_key = 'dataerror' callback = self.update_validation_progress cancel_flag = self.cancel_validation_flag results = variable_batch_check(variables = variables, validator_func = func, variable_key = var_key, progress_callback = callback, cancel_flag = cancel_flag) # Setup GUI for investigating results self.pb_cancel_validation.setEnabled(False) self.progress_validation.setValue(100) self.variables_table.setEnabled(True) failed_variables = [(var, msg) for (var, flag, msg) in results if flag is False] self._set_problem_variables(failed_variables) self._show_problem_variables() self.group_progress.setVisible(False) if failed_variables: self.pb_problems.setFocus()