from PyQt5.QtCore import Qt, QRect, pyqtSignal, pyqtSlot from PyQt5.QtGui import QDragMoveEvent, QDragEnterEvent, QPainter, QBrush, QColor, QPen, QDropEvent, QDragLeaveEvent, \ QContextMenuEvent, QIcon from PyQt5.QtWidgets import QActionGroup, QInputDialog from PyQt5.QtWidgets import QHeaderView, QAbstractItemView, QStyleOption, QMenu from urh.models.GeneratorTableModel import GeneratorTableModel from urh.ui.views.TableView import TableView class GeneratorTableView(TableView): encodings_updated = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) self.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed) self.drop_indicator_rect = QRect() self.drag_active = False self.show_pause_active = False self.pause_row = -1 def model(self) -> GeneratorTableModel: return super().model() def dragEnterEvent(self, event: QDragEnterEvent): event.acceptProposedAction() self.drag_active = True def dragMoveEvent(self, event: QDragMoveEvent): pos = event.pos() row = self.rowAt(pos.y()) index = self.model().createIndex(row, 0) # this always get the default 0 column index rect = self.visualRect(index) rect_left = self.visualRect(index.sibling(index.row(), 0)) rect_right = self.visualRect(index.sibling(index.row(), self.horizontalHeader().logicalIndex( self.model().columnCount() - 1))) # in case section has been moved self.drop_indicator_position = self.position(event.pos(), rect) if self.drop_indicator_position == self.AboveItem: self.drop_indicator_rect = QRect(rect_left.left(), rect_left.top(), rect_right.right() - rect_left.left(), 0) event.accept() elif self.drop_indicator_position == self.BelowItem: self.drop_indicator_rect = QRect(rect_left.left(), rect_left.bottom(), rect_right.right() - rect_left.left(), 0) event.accept() elif self.drop_indicator_position == self.OnItem: self.drop_indicator_rect = QRect(rect_left.left(), rect_left.bottom(), rect_right.right() - rect_left.left(), 0) event.accept() else: self.drop_indicator_rect = QRect() # This is necessary or else the previously drawn rect won't be erased self.viewport().update() def __rect_for_row(self, row): index = self.model().createIndex(row, 0) # this always get the default 0 column index # rect = self.visualRect(index) rect_left = self.visualRect(index.sibling(index.row(), 0)) rect_right = self.visualRect(index.sibling(index.row(), self.horizontalHeader().logicalIndex( self.model().columnCount() - 1))) # in case section has been moved return QRect(rect_left.left(), rect_left.bottom(), rect_right.right() - rect_left.left(), 0) def dropEvent(self, event: QDropEvent): self.drag_active = False row = self.rowAt(event.pos().y()) index = self.model().createIndex(row, 0) # this always get the default 0 column index rect = self.visualRect(index) drop_indicator_position = self.position(event.pos(), rect) if row == -1: row = self.model().row_count - 1 elif drop_indicator_position == self.BelowItem or drop_indicator_position == self.OnItem: row += 1 self.model().dropped_row = row super().dropEvent(event) def dragLeaveEvent(self, event: QDragLeaveEvent): self.drag_active = False self.viewport().update() super().dragLeaveEvent(event) @staticmethod def position(pos, rect): r = QAbstractItemView.OnViewport # margin*2 must be smaller than row height, or the drop onItem rect won't show margin = 5 if pos.y() - rect.top() < margin: r = QAbstractItemView.AboveItem elif rect.bottom() - pos.y() < margin: r = QAbstractItemView.BelowItem elif pos.y() - rect.top() > margin and rect.bottom() - pos.y() > margin: r = QAbstractItemView.OnItem return r def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self.viewport()) # in original implementation, it calls an inline function paintDropIndicator here self.paint_drop_indicator(painter) self.paint_pause_indicator(painter) def paint_drop_indicator(self, painter): if self.drag_active: opt = QStyleOption() opt.initFrom(self) opt.rect = self.drop_indicator_rect rect = opt.rect brush = QBrush(QColor(Qt.darkRed)) if rect.height() == 0: pen = QPen(brush, 2, Qt.SolidLine) painter.setPen(pen) painter.drawLine(rect.topLeft(), rect.topRight()) else: pen = QPen(brush, 2, Qt.SolidLine) painter.setPen(pen) painter.drawRect(rect) def paint_pause_indicator(self, painter): if self.show_pause_active: rect = self.__rect_for_row(self.pause_row) brush = QBrush(QColor(Qt.darkGreen)) pen = QPen(brush, 2, Qt.SolidLine) painter.setPen(pen) painter.drawLine(rect.topLeft(), rect.topRight()) def create_context_menu(self) -> QMenu: menu = super().create_context_menu() add_message_action = menu.addAction("Add empty message...") add_message_action.setIcon(QIcon.fromTheme("edit-table-insert-row-below")) add_message_action.triggered.connect(self.on_add_message_action_triggered) if not self.selection_is_empty: menu.addAction(self.copy_action) if self.model().row_count > 0: duplicate_action = menu.addAction("Duplicate selected lines") duplicate_action.setIcon(QIcon.fromTheme("edit-table-insert-row-under")) duplicate_action.triggered.connect(self.on_duplicate_action_triggered) self._add_insert_column_menu(menu) menu.addSeparator() clear_action = menu.addAction("Clear table") clear_action.triggered.connect(self.on_clear_action_triggered) clear_action.setIcon(QIcon.fromTheme("edit-clear")) self.encoding_actions = {} if not self.selection_is_empty: selected_encoding = self.model().protocol.messages[self.selected_rows[0]].decoder for i in self.selected_rows: if self.model().protocol.messages[i].decoder != selected_encoding: selected_encoding = None break menu.addSeparator() encoding_group = QActionGroup(self) encoding_menu = menu.addMenu("Enforce encoding") for decoding in self.model().decodings: ea = encoding_menu.addAction(decoding.name) ea.setCheckable(True) ea.setActionGroup(encoding_group) if selected_encoding == decoding: ea.setChecked(True) self.encoding_actions[ea] = decoding ea.triggered.connect(self.on_encoding_action_triggered) menu.addSeparator() de_bruijn_action = menu.addAction("Generate De Bruijn Sequence from Selection") de_bruijn_action.triggered.connect(self.on_de_bruijn_action_triggered) return menu @pyqtSlot() def on_duplicate_action_triggered(self): self.model().duplicate_rows(self.selected_rows) @pyqtSlot() def on_clear_action_triggered(self): self.model().clear() @pyqtSlot() def on_encoding_action_triggered(self): for row in self.selected_rows: self.model().protocol.messages[row].decoder = self.encoding_actions[self.sender()] self.encodings_updated.emit() @pyqtSlot() def on_de_bruijn_action_triggered(self): self.setCursor(Qt.WaitCursor) row = self.rowAt(self.context_menu_pos.y()) _, _, start, end = self.selection_range() self.model().generate_de_bruijn(row, start, end) self.unsetCursor() @pyqtSlot() def on_add_message_action_triggered(self): row = self.rowAt(self.context_menu_pos.y()) num_bits, ok = QInputDialog.getInt(self, self.tr("How many bits shall the new message have?"), self.tr("Number of bits:"), 42, 1) if ok: self.model().add_empty_row_behind(row, num_bits)