class ShortcutConfig(QWidget): # {{{ changed_signal = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self._layout = l = QGridLayout() self.setLayout(self._layout) self.header = QLabel(_('Double click on any entry to change the' ' keyboard shortcuts associated with it')) l.addWidget(self.header, 0, 0, 1, 3) self.view = QTreeView(self) self.view.setAlternatingRowColors(True) self.view.setHeaderHidden(True) self.view.setAnimated(True) l.addWidget(self.view, 1, 0, 1, 3) self.delegate = Delegate() self.view.setItemDelegate(self.delegate) self.delegate.sizeHintChanged.connect(self.editor_opened, type=Qt.QueuedConnection) self.delegate.changed_signal.connect(self.changed_signal) self.search = SearchBox2(self) self.search.initialize('shortcuts_search_history', help_text=_('Search for a shortcut by name')) self.search.search.connect(self.find) l.addWidget(self.search, 2, 0, 1, 1) self.nb = QPushButton(QIcon(I('arrow-down.png')), _('&Next'), self) self.pb = QPushButton(QIcon(I('arrow-up.png')), _('&Previous'), self) self.nb.clicked.connect(self.find_next) self.pb.clicked.connect(self.find_previous) l.addWidget(self.nb, 2, 1, 1, 1) l.addWidget(self.pb, 2, 2, 1, 1) l.setColumnStretch(0, 100) def restore_defaults(self): self._model.restore_defaults() self.changed_signal.emit() def commit(self): if self.view.state() == self.view.EditingState: self.delegate.accept_changes() self._model.commit() def initialize(self, keyboard): self._model = ConfigModel(keyboard, parent=self) self.view.setModel(self._model) def editor_opened(self, index): self.view.scrollTo(index, self.view.EnsureVisible) @property def is_editing(self): return self.view.state() == self.view.EditingState def find(self, query): if not query: return try: idx = self._model.find(query) except ParseException: self.search.search_done(False) return self.search.search_done(True) if not idx.isValid(): info_dialog(self, _('No matches'), _('Could not find any shortcuts matching %s')%query, show=True, show_copy_button=False) return self.highlight_index(idx) def highlight_index(self, idx): self.view.scrollTo(idx) self.view.selectionModel().select(idx, self.view.selectionModel().ClearAndSelect) self.view.setCurrentIndex(idx) self.view.setFocus(Qt.OtherFocusReason) def find_next(self, *args): idx = self.view.currentIndex() if not idx.isValid(): idx = self._model.index(0, 0) idx = self._model.find_next(idx, unicode(self.search.currentText())) self.highlight_index(idx) def find_previous(self, *args): idx = self.view.currentIndex() if not idx.isValid(): idx = self._model.index(0, 0) idx = self._model.find_next(idx, unicode(self.search.currentText()), backwards=True) self.highlight_index(idx) def highlight_group(self, group_name): idx = self.view.model().index_for_group(group_name) if idx is not None: self.view.expand(idx) self.view.scrollTo(idx, self.view.PositionAtTop) self.view.selectionModel().select(idx, self.view.selectionModel().ClearAndSelect) self.view.setCurrentIndex(idx) self.view.setFocus(Qt.OtherFocusReason)
class ShortcutConfig(QWidget): # {{{ changed_signal = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self._layout = l = QVBoxLayout(self) self.header = QLabel( _('Double click on any entry to change the' ' keyboard shortcuts associated with it')) l.addWidget(self.header) self.view = QTreeView(self) self.view.setAlternatingRowColors(True) self.view.setHeaderHidden(True) self.view.setAnimated(True) l.addWidget(self.view) self.delegate = Delegate() self.view.setItemDelegate(self.delegate) self.delegate.sizeHintChanged.connect( self.editor_opened, type=Qt.ConnectionType.QueuedConnection) self.delegate.changed_signal.connect(self.changed_signal) self.search = SearchBox2(self) self.search.initialize('shortcuts_search_history', help_text=_('Search for a shortcut by name')) self.search.search.connect(self.find) self._h = h = QHBoxLayout() l.addLayout(h) h.addWidget(self.search) self.nb = QPushButton(QIcon(I('arrow-down.png')), _('&Next'), self) self.pb = QPushButton(QIcon(I('arrow-up.png')), _('&Previous'), self) self.nb.clicked.connect(self.find_next) self.pb.clicked.connect(self.find_previous) h.addWidget(self.nb), h.addWidget(self.pb) h.setStretch(0, 100) def restore_defaults(self): self._model.restore_defaults() self.changed_signal.emit() def commit(self): if self.view.state() == self.view.EditingState: self.delegate.accept_changes() self._model.commit() def initialize(self, keyboard): self._model = ConfigModel(keyboard, parent=self) self.view.setModel(self._model) def editor_opened(self, index): self.view.scrollTo(index, self.view.EnsureVisible) @property def is_editing(self): return self.view.state() == self.view.EditingState def find(self, query): if not query: return try: idx = self._model.find(query) except ParseException: self.search.search_done(False) return self.search.search_done(True) if not idx.isValid(): info_dialog(self, _('No matches'), _('Could not find any shortcuts matching %s') % query, show=True, show_copy_button=False) return self.highlight_index(idx) def highlight_index(self, idx): self.view.scrollTo(idx) self.view.selectionModel().select( idx, self.view.selectionModel().ClearAndSelect) self.view.setCurrentIndex(idx) self.view.setFocus(Qt.FocusReason.OtherFocusReason) def find_next(self, *args): idx = self.view.currentIndex() if not idx.isValid(): idx = self._model.index(0, 0) idx = self._model.find_next(idx, unicode_type(self.search.currentText())) self.highlight_index(idx) def find_previous(self, *args): idx = self.view.currentIndex() if not idx.isValid(): idx = self._model.index(0, 0) idx = self._model.find_next(idx, unicode_type(self.search.currentText()), backwards=True) self.highlight_index(idx) def highlight_group(self, group_name): idx = self.view.model().index_for_group(group_name) if idx is not None: self.view.expand(idx) self.view.scrollTo(idx, self.view.PositionAtTop) self.view.selectionModel().select( idx, self.view.selectionModel().ClearAndSelect) self.view.setCurrentIndex(idx) self.view.setFocus(Qt.FocusReason.OtherFocusReason)
class Navigation(QWidget): """a navigation widget, displaying all known projects and samples """ changed_projects = pyqtSignal(str, str) changed_allele = pyqtSignal(str, int, str) change_view = pyqtSignal(int) refresh = pyqtSignal(str) def __init__(self, log, settings): self.log = log self.settings = settings super().__init__() self.init_UI() self.create_model() self.tree.expand(self.open_node) self.tree.clicked.connect(self.onClicked) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.open_menu) def init_UI(self): """creates the UI """ self.log.debug("Setting up Navigation area UI...") self.tree = QTreeView() self.tree.doubleClicked.connect(self.onDoubleClick) self.grid = QGridLayout() self.setLayout(self.grid) self.header_lbl = QLabel("Projects and Samples:", self) self.header_lbl.setStyleSheet(general.label_style_2nd) self.grid.addWidget(self.header_lbl, 0,0) self.grid.addWidget(self.tree, 1, 0) header = self.tree.header() header.hide() self.log.debug("\t=> Done!") def create_model(self): """creates or recreates the model """ self.log.debug("Updating Navigation area...") root_node = Node("All") openNode = Node("Open", root_node) closedNode = Node("Closed", root_node) project_nodes = {} query = """select distinct sample_id_int, projects.project_name, project_status, allele_status, allele_nr from projects left join alleles on projects.project_name = alleles.project_name order by project_status desc, projects.project_name desc, sample_id_int """ q = QtSql.QSqlQuery() q.exec_(query) while q.next(): sample = q.value(0) project = q.value(1) pstatus = q.value(2) astatus = q.value(3) nr = q.value(4) try: pnode = project_nodes[project] except KeyError: if pstatus == "Closed": topnode = closedNode else: topnode = openNode pnode = Node(project, topnode, "Project") project_nodes[project] = pnode if sample: if nr != 1: sample = "{} ({})".format(sample, nr) Node(sample, pnode, "Sample", astatus) self.model = NavigationModel(root_node) self.tree.setModel(self.model) self.open_node = self.model.index(0, 0, QModelIndex()) self.closed_node = self.model.index(1, 0, QModelIndex()) self.root_node = self.open_node.parent() self.log.debug("\t=> Done!") @pyqtSlot(str, str) def expand_project(self, project, status = None): """expands the node of the given project, returns its index and parent-node's index """ self.log.debug("Expanding project {} ({})...".format(project, status)) if not status in (None, "Open", "Closed"): return None, None myindex = self.open_node myparent_node = self.open_node if status == "Open": self.log.debug("Open") parent_node = self.open_node self.tree.expand(parent_node) self.tree.collapse(self.closed_node) elif status == "Closed": self.log.debug("Closed") parent_node = self.closed_node self.tree.expand(parent_node) self.tree.collapse(self.open_node) else: # figure out whether project is open or closed self.log.debug("Status unknown...") try: for i in range(self.model.rowCount(self.root_node)): parent_node = self.model.index(i, 0, self.root_node) status = self.model.data(parent_node, Qt.DisplayRole) for row in range(self.model.rowCount(parent_node)): index = self.model.index(row, 0, parent_node) myproj = self.model.data(index, Qt.DisplayRole) if myproj == project: myindex = index myparent_node = parent_node self.tree.expand(myindex) except Exception as E: self.log.error(E) self.log.exception(E) self.select_project(project) self.log.debug("\t=> project found ({}) and successfully expanded".format(status)) return myindex, myparent_node @pyqtSlot(QModelIndex) def onClicked(self, index): """emits signals when a project or sample is selected """ data = self.model.data(index, Qt.DisplayRole) nodetype = self.model.nodeType(index) if nodetype == "Sample": if ("(") in data: data = data.split("(") sample = data[0].strip() nr = int(data[1].strip()[:-1]) else: sample = data nr = 1 project = self.model.data(self.model.parent(index), Qt.DisplayRole) self.changed_allele.emit(sample, nr, project) self.log.debug("Navigation emitted 'Allele changed to {} #{} (project {})'".format(sample, nr, project)) elif nodetype == "Project": status = self.model.data(self.model.parent(index), Qt.DisplayRole) self.changed_projects.emit(data, status) self.log.debug("Navigation emitted 'Project changed to {}'".format(data)) @pyqtSlot(QPoint) def open_menu(self, pos): """provides a context menu """ index = self.tree.indexAt(pos) nodetype = self.model.nodeType(index) self.log.debug("Opening navigation menu...") if nodetype == "Project": menu = QMenu() open_project_act = menu.addAction("Open Project View") action = menu.exec_(self.tree.mapToGlobal(pos)) if action == open_project_act: project = self.model.data(index, Qt.DisplayRole) status = self.model.data(self.model.parent(index), Qt.DisplayRole) self.changed_projects.emit(project, status) self.change_view.emit(3) self.log.debug("Navigation emitted changed_projects & change_view to ProjectView") elif nodetype == "Sample": menu = QMenu() open_sample_act = menu.addAction("Open Sample View") delete_sample_act = menu.addAction("Delete Allele (admin-only!)") action = menu.exec_(self.tree.mapToGlobal(pos)) if action: sample_list = self.model.data(index, Qt.DisplayRole).split() sample = sample_list[0] if len(sample_list) > 1: nr = int(sample_list[1][1:-1]) else: nr = 1 project = self.model.data(self.model.parent(index), Qt.DisplayRole) status = self.model.data(self.model.parent(self.model.parent(index)), Qt.DisplayRole) if action == open_sample_act: try: self.changed_allele.emit(sample, nr, project) self.change_view.emit(4) self.log.debug("Navigation emitted changed_alleles & change_view to AlleleView") except Exception as E: self.log.exception(E) elif action == delete_sample_act: self.log.info("Deleting {} #{} of project {}".format(sample, nr, project)) self.delete_sample(sample, nr, project, status) @pyqtSlot(QModelIndex) def onDoubleClick(self, index): """open SampleView or ProjectView """ nodetype = self.model.nodeType(index) if nodetype == "Project": project = self.model.data(index, Qt.DisplayRole) status = self.model.data(self.model.parent(index), Qt.DisplayRole) self.changed_projects.emit(project, status) self.change_view.emit(3) self.log.debug("Navigation emitted changed_projects & change_view to ProjectView") elif nodetype == "Sample": sample_list = self.model.data(index, Qt.DisplayRole).split() sample = sample_list[0] if len(sample_list) > 1: nr = int(sample_list[1][1:-1]) else: nr = 1 project = self.model.data(self.model.parent(index), Qt.DisplayRole) status = self.model.data(self.model.parent(self.model.parent(index)), Qt.DisplayRole) self.changed_allele.emit(sample, nr, project) self.change_view.emit(4) self.log.debug("Navigation emitted changed_alleles & change_view to AlleleView") @pyqtSlot(str) def select_project(self, project): """looks for <project> in the tree-model and selects it if found; selects it and returns its index """ for top_node in [self.open_node, self.closed_node]: index = self.model.findValue(top_node, project) if index.isValid(): self.tree.setCurrentIndex(index) return index return QModelIndex() @pyqtSlot(str) def select_sample(self, project, sample, nr): """looks for <sample> in <project> in the tree-model and selects it if found; selects it and returns its index """ pindex = self.select_project(project) if int(nr) > 1: sample = "{} ({})".format(sample, nr) index = self.model.findValue(pindex, sample) if index.isValid(): self.tree.setCurrentIndex(index) return index return QModelIndex() def delete_sample(self, sample, nr, project, status): """delete a sample from the database & file system """ self.log.debug("Attempting to delete sample '{}' allele {} of project '{}' from database...".format(sample, nr, project)) if self.settings["login"] == "admin": pass else: pwd, ok = QInputDialog.getText(self, "Enter Password", "Please provide password:"******"ichdarfdas": pass else: return else: return self.log.debug("Asking for confirmation before deleting allele...") reply = QMessageBox.question(self, 'Message', "Are you really sure you want to delete sample {} allele #{} from project {}?".format(sample, nr, project), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # delete from database: delete_q_alleles = "delete from alleles where sample_id_int = '{}' and allele_nr = {} and project_name = '{}'".format(sample, nr, project) success, _ = db_internal.execute_query(delete_q_alleles, 0, self.log, "Deleting sample {} allele #{} from ALLELES table".format(sample, nr), "Sample Deletion Error", self) if success: self.log.debug("\t=> Successfully deleted sample from table ALLELES") more_projects_query = "select project_name from alleles where sample_id_int = '{}'".format(sample) success, data = db_internal.execute_query(more_projects_query, 1, self.log, "Finding more rows with sample {} in ALLELES table".format(sample), "Sample Deletion Error", self) single_allele = False if success: if not data: # sample was only contained in this project and only had one allele single_allele = True delete_q_samples = "delete from SAMPLES where sample_id_int = '{}'".format(sample) success, _ = db_internal.execute_query(delete_q_samples, 0, self.log, "Deleting sample {} from SAMPLES table".format(sample), "Sample Deletion Error", self) if success: self.log.debug("\t=> Successfully deleted sample from table SAMPLES") files_q = "select raw_file, fasta, blast_xml, ena_file, ena_response_file, ipd_submission_file from FILES where sample_id_int = '{}' and allele_nr = {}".format(sample, nr) success, files = db_internal.execute_query(files_q, 6, self.log, "Getting files of sample {} #{} from FILES table".format(sample, nr), "Sample Deletion Error", self) if success: delete_q_files = "delete from FILES where sample_id_int = '{}' and allele_nr = {}".format(sample, nr) success, _ = db_internal.execute_query(delete_q_files, 0, self.log, "Deleting sample {} from FILES table".format(sample), "Sample Deletion Error", self) if success: self.log.debug("\t=> Successfully deleted sample from table FILES") # delete from disk space: self.log.debug("Attempting to delete sample {} allele #{} of project '{}' from file system...".format(sample, nr, project)) sample_dir = os.path.join(self.settings["projects_dir"], project, sample) if files: for myfile in files[0]: if myfile: self.log.debug("\tDeleting {}...".format(myfile)) try: os.remove(os.path.join(sample_dir, myfile)) except Exception: self.log.debug("\t\t=> Could not delete") if single_allele: self.log.debug("\tDeleting sample dir {}...".format(sample_dir)) os.removedirs(sample_dir) self.log.debug("=> Sample {} #{} of project {} successfully deleted from database and file system".format(sample, nr, project)) self.refresh.emit(project) self.changed_projects.emit(project, status)