def _updateDatasourceMenu(self) -> None: """Update the "Datasource" menu. """ # self._datasourceMenu is of type PyQt5.QtWidgets.QMenu' if self._toolbox is None: return # FIXME[todo]: erase all Datasources from the menu def slot(id, datasource): def setDatasource(checked): LOG.info("qtgui.MainWindow.setDatasource(%s): %s [%s]", checked, id, type(datasource)) # We first set the Datasource in the toolbox # and then prepare it, in order to get a smooth # reaction in the user interface. self._toolbox.datasource = datasource # FIXME[bug]: Object is currently busy. # datasource.prepare() return protect(setDatasource) # Add all datasource to the menu # FIXME[bug]: only add datasources currently missing! # FIXME[todo]: provide a datasourceSelected signal, # that provides the key for the datasource for datasource in self._toolbox.datasources: key = datasource.key label = str(datasource) if key in self._datasources: continue action = QAction(label, self) if True: # datasource == self._toolbox.datasource: # FIXME[bug]: seems to have no effect action.font().setBold(True) action.triggered.connect(slot(id, datasource)) self._datasourceMenu.addAction(action) self._datasources[key] = action
class SimpleRichText(QTextEdit): # pylint: disable=method-hidden def __init__(self, focusin, focusout): QTextEdit.__init__(self) self.focusin = focusin self.focusout = focusout self.createActions() #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) def focusOutEvent(self, event): self.focusout() def focusInEvent(self, event): self.focusin() def closeEvent(self, event): event.accept() def createActions(self): self.boldAct = QAction(self.tr("&Bold"), self) self.boldAct.setCheckable(True) self.boldAct.setShortcut(self.tr("Ctrl+B")) self.boldAct.setStatusTip(self.tr("Make the text bold")) self.boldAct.triggered.connect(self.setBold) ### self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold) self.addAction(self.boldAct) boldFont = self.boldAct.font() boldFont.setBold(True) self.boldAct.setFont(boldFont) self.italicAct = QAction(self.tr("&Italic"), self) self.italicAct.setCheckable(True) self.italicAct.setShortcut(self.tr("Ctrl+I")) self.italicAct.setStatusTip(self.tr("Make the text italic")) self.italicAct.triggered.connect(self.setItalic) ### self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic) self.addAction(self.italicAct) def setBold(self): format = QTextCharFormat() if self.boldAct.isChecked(): weight = QFont.Bold else: weight = QFont.Normal format.setFontWeight(weight) self.setFormat(format) def setItalic(self): format = QTextCharFormat() #format.setFontItalic(self.__italic.isChecked()) format.setFontItalic(self.italicAct.isChecked()) self.setFormat(format) def setUnderline(self): format = QTextCharFormat() format.setFontUnderline(self.__underline.isChecked()) self.setFormat(format) def setFormat(self, format): self.textCursor().mergeCharFormat(format) self.mergeCurrentCharFormat(format) def bold(self): print("bold") def italic(self): print("italic")
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() widget = QWidget() self.setCentralWidget(widget) topFiller = QWidget() topFiller.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.infoLabel = QLabel( "<i>Choose a menu option, or right-click to invoke a context menu</i>", alignment=Qt.AlignCenter) self.infoLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) bottomFiller = QWidget() bottomFiller.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) vbox = QVBoxLayout() vbox.setContentsMargins(5, 5, 5, 5) vbox.addWidget(topFiller) vbox.addWidget(self.infoLabel) vbox.addWidget(bottomFiller) widget.setLayout(vbox) self.createActions() self.createMenus() message = "A context menu is available by right-clicking" self.statusBar().showMessage(message) self.setWindowTitle("Menus") self.setMinimumSize(160,160) self.resize(480,320) def contextMenuEvent(self, event): menu = QMenu(self) menu.addAction(self.cutAct) menu.addAction(self.copyAct) menu.addAction(self.pasteAct) menu.exec_(event.globalPos()) def newFile(self): self.infoLabel.setText("Invoked <b>File|New</b>") def open(self): self.infoLabel.setText("Invoked <b>File|Open</b>") def save(self): self.infoLabel.setText("Invoked <b>File|Save</b>") def print_(self): self.infoLabel.setText("Invoked <b>File|Print</b>") def undo(self): self.infoLabel.setText("Invoked <b>Edit|Undo</b>") def redo(self): self.infoLabel.setText("Invoked <b>Edit|Redo</b>") def cut(self): self.infoLabel.setText("Invoked <b>Edit|Cut</b>") def copy(self): self.infoLabel.setText("Invoked <b>Edit|Copy</b>") def paste(self): self.infoLabel.setText("Invoked <b>Edit|Paste</b>") def bold(self): self.infoLabel.setText("Invoked <b>Edit|Format|Bold</b>") def italic(self): self.infoLabel.setText("Invoked <b>Edit|Format|Italic</b>") def leftAlign(self): self.infoLabel.setText("Invoked <b>Edit|Format|Left Align</b>") def rightAlign(self): self.infoLabel.setText("Invoked <b>Edit|Format|Right Align</b>") def justify(self): self.infoLabel.setText("Invoked <b>Edit|Format|Justify</b>") def center(self): self.infoLabel.setText("Invoked <b>Edit|Format|Center</b>") def setLineSpacing(self): self.infoLabel.setText("Invoked <b>Edit|Format|Set Line Spacing</b>") def setParagraphSpacing(self): self.infoLabel.setText("Invoked <b>Edit|Format|Set Paragraph Spacing</b>") def about(self): self.infoLabel.setText("Invoked <b>Help|About</b>") QMessageBox.about(self, "About Menu", "The <b>Menu</b> example shows how to create menu-bar menus " "and context menus.") def aboutQt(self): self.infoLabel.setText("Invoked <b>Help|About Qt</b>") def createActions(self): self.newAct = QAction("&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction("&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.printAct = QAction("&Print...", self, shortcut=QKeySequence.Print, statusTip="Print the document", triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close) self.undoAct = QAction("&Undo", self, shortcut=QKeySequence.Undo, statusTip="Undo the last operation", triggered=self.undo) self.redoAct = QAction("&Redo", self, shortcut=QKeySequence.Redo, statusTip="Redo the last operation", triggered=self.redo) self.cutAct = QAction("Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.copyAct = QAction("&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction("&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.paste) self.boldAct = QAction("&Bold", self, checkable=True, shortcut="Ctrl+B", statusTip="Make the text bold", triggered=self.bold) boldFont = self.boldAct.font() boldFont.setBold(True) self.boldAct.setFont(boldFont) self.italicAct = QAction("&Italic", self, checkable=True, shortcut="Ctrl+I", statusTip="Make the text italic", triggered=self.italic) italicFont = self.italicAct.font() italicFont.setItalic(True) self.italicAct.setFont(italicFont) self.setLineSpacingAct = QAction("Set &Line Spacing...", self, statusTip="Change the gap between the lines of a paragraph", triggered=self.setLineSpacing) self.setParagraphSpacingAct = QAction("Set &Paragraph Spacing...", self, statusTip="Change the gap between paragraphs", triggered=self.setParagraphSpacing) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=self.aboutQt) self.aboutQtAct.triggered.connect(QApplication.instance().aboutQt) self.leftAlignAct = QAction("&Left Align", self, checkable=True, shortcut="Ctrl+L", statusTip="Left align the selected text", triggered=self.leftAlign) self.rightAlignAct = QAction("&Right Align", self, checkable=True, shortcut="Ctrl+R", statusTip="Right align the selected text", triggered=self.rightAlign) self.justifyAct = QAction("&Justify", self, checkable=True, shortcut="Ctrl+J", statusTip="Justify the selected text", triggered=self.justify) self.centerAct = QAction("&Center", self, checkable=True, shortcut="Ctrl+C", statusTip="Center the selected text", triggered=self.center) self.alignmentGroup = QActionGroup(self) self.alignmentGroup.addAction(self.leftAlignAct) self.alignmentGroup.addAction(self.rightAlignAct) self.alignmentGroup.addAction(self.justifyAct) self.alignmentGroup.addAction(self.centerAct) self.leftAlignAct.setChecked(True) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.undoAct) self.editMenu.addAction(self.redoAct) self.editMenu.addSeparator() self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.editMenu.addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.formatMenu = self.editMenu.addMenu("&Format") self.formatMenu.addAction(self.boldAct) self.formatMenu.addAction(self.italicAct) self.formatMenu.addSeparator().setText("Alignment") self.formatMenu.addAction(self.leftAlignAct) self.formatMenu.addAction(self.rightAlignAct) self.formatMenu.addAction(self.justifyAct) self.formatMenu.addAction(self.centerAct) self.formatMenu.addSeparator() self.formatMenu.addAction(self.setLineSpacingAct) self.formatMenu.addAction(self.setParagraphSpacingAct)
class SimpleRichText(QTextEdit): # pylint: disable=method-hidden def __init__(self, focusin, focusout): QTextEdit.__init__(self) self.focusin = focusin self.focusout = focusout self.createActions() #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) def focusOutEvent ( self, event ): self.focusout() def focusInEvent ( self, event ): self.focusin() def closeEvent(self, event): event.accept() def createActions(self): self.boldAct = QAction(self.tr("&Bold"), self) self.boldAct.setCheckable(True) self.boldAct.setShortcut(self.tr("Ctrl+B")) self.boldAct.setStatusTip(self.tr("Make the text bold")) self.boldAct.triggered.connect(self.setBold) ### self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold) self.addAction(self.boldAct) boldFont = self.boldAct.font() boldFont.setBold(True) self.boldAct.setFont(boldFont) self.italicAct = QAction(self.tr("&Italic"), self) self.italicAct.setCheckable(True) self.italicAct.setShortcut(self.tr("Ctrl+I")) self.italicAct.setStatusTip(self.tr("Make the text italic")) self.italicAct.triggered.connect(self.setItalic) ### self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic) self.addAction(self.italicAct) def setBold(self): format = QTextCharFormat() if self.boldAct.isChecked(): weight = QFont.Bold else: weight = QFont.Normal format.setFontWeight(weight) self.setFormat(format) def setItalic(self): format = QTextCharFormat() #format.setFontItalic(self.__italic.isChecked()) format.setFontItalic(self.italicAct.isChecked()) self.setFormat(format) def setUnderline(self): format = QTextCharFormat() format.setFontUnderline(self.__underline.isChecked()) self.setFormat(format) def setFormat(self, format): self.textCursor().mergeCharFormat(format) self.mergeCurrentCharFormat(format) def bold(self): print("bold") def italic(self): print("italic")
class EditableGraphicView(ZoomableGraphicView): save_as_clicked = pyqtSignal() create_clicked = pyqtSignal(int, int) set_noise_clicked = pyqtSignal() participant_changed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.participants = [] self.__sample_rate = None # For default sample rate in insert sine dialog self.autoRangeY = True self.save_enabled = False # Signal is can be saved self.create_new_signal_enabled = False self.participants_assign_enabled = False self.cache_qad = False # cache qad demod after edit operations? self.__signal = None # type: Signal self.stored_item = None # For copy/paste self.paste_position = 0 # Where to paste? Set in contextmenuevent self.context_menu_position = None # type: QPoint self._init_undo_stack(QUndoStack()) self.addAction(self.undo_action) self.addAction(self.redo_action) self.copy_action = QAction(self.tr("Copy selection"), self) # type: QAction self.copy_action.setShortcut(QKeySequence.Copy) self.copy_action.triggered.connect(self.on_copy_action_triggered) self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.copy_action.setIcon(QIcon.fromTheme("edit-copy")) self.addAction(self.copy_action) self.paste_action = QAction(self.tr("Paste"), self) # type: QAction self.paste_action.setShortcut(QKeySequence.Paste) self.paste_action.triggered.connect(self.on_paste_action_triggered) self.paste_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.paste_action.setIcon(QIcon.fromTheme("edit-paste")) self.addAction(self.paste_action) self.delete_action = QAction(self.tr("Delete selection"), self) self.delete_action.setShortcut(QKeySequence.Delete) self.delete_action.triggered.connect(self.on_delete_action_triggered) self.delete_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.delete_action.setIcon(QIcon.fromTheme("edit-delete")) self.addAction(self.delete_action) self.save_as_action = QAction(self.tr("Save Signal as..."), self) # type: QAction self.save_as_action.setIcon(QIcon.fromTheme("document-save-as")) self.save_as_action.setShortcut(QKeySequence.SaveAs) self.save_as_action.triggered.connect(self.save_as_clicked.emit) self.save_as_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.addAction(self.save_as_action) self.insert_sine_action = QAction(self.tr("Insert sine wave..."), self) font = self.insert_sine_action.font() font.setBold(True) self.insert_sine_action.setFont(font) self.insert_sine_action.triggered.connect( self.on_insert_sine_action_triggered) self.insert_sine_plugin = InsertSinePlugin() self.insert_sine_plugin.insert_sine_wave_clicked.connect( self.on_insert_sine_wave_clicked) def _init_undo_stack(self, undo_stack): self.undo_stack = undo_stack self.undo_action = self.undo_stack.createUndoAction(self) self.undo_action.setIcon(QIcon.fromTheme("edit-undo")) self.undo_action.setShortcut(QKeySequence.Undo) self.undo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.redo_action = self.undo_stack.createRedoAction(self) self.redo_action.setIcon(QIcon.fromTheme("edit-redo")) self.redo_action.setShortcut(QKeySequence.Redo) self.redo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed) def eliminate(self): self.participants = None self.stored_item = None if self.signal is not None: self.signal.eliminate() self.__signal = None self.insert_sine_plugin = None self.undo_action = None self.redo_action = None self.undo_stack = None super().eliminate() @property def sample_rate(self) -> float: return self.__sample_rate @sample_rate.setter def sample_rate(self, value): self.__sample_rate = value @property def signal(self) -> Signal: return self.__signal @property def protocol(self) -> ProtocolAnalyzer: return None # Gets overwritten in EpicGraphicView @property def selection_area(self) -> ROI: return self.scene().selection_area @selection_area.setter def selection_area(self, value): self.scene().selection_area = value def set_signal(self, signal: Signal): self.__signal = signal def create_context_menu(self): self.paste_position = int( self.mapToScene(self.context_menu_position).x()) menu = QMenu(self) if self.save_enabled: menu.addAction(self.save_action) menu.addAction(self.save_as_action) menu.addSeparator() menu.addAction(self.copy_action) self.copy_action.setEnabled(not self.selection_area.is_empty) menu.addAction(self.paste_action) self.paste_action.setEnabled(self.stored_item is not None) menu.addSeparator() if PluginManager().is_plugin_enabled("InsertSine"): menu.addAction(self.insert_sine_action) if not self.selection_area.is_empty: menu.addSeparator() menu.addAction(self.zoom_in_action) menu.addAction(self.zoom_out_action) if not self.selection_area.is_empty: zoom_action = menu.addAction(self.tr("Zoom selection")) zoom_action.setIcon(QIcon.fromTheme("zoom-fit-best")) zoom_action.triggered.connect(self.on_zoom_action_triggered) menu.addSeparator() menu.addAction(self.delete_action) crop_action = menu.addAction(self.tr("Crop to selection")) crop_action.triggered.connect(self.on_crop_action_triggered) mute_action = menu.addAction(self.tr("Mute selection")) mute_action.triggered.connect(self.on_mute_action_triggered) menu.addSeparator() if self.create_new_signal_enabled: create_action = menu.addAction( self.tr("Create signal from selection")) create_action.setIcon(QIcon.fromTheme("document-new")) create_action.triggered.connect( self.on_create_action_triggered) if hasattr(self, "selected_messages"): selected_messages = self.selected_messages else: selected_messages = [] if len(selected_messages) == 1: selected_msg = selected_messages[0] else: selected_msg = None self.participant_actions = {} if len(selected_messages) > 0 and self.participants_assign_enabled: participant_group = QActionGroup(self) participant_menu = menu.addMenu("Participant") none_participant_action = participant_menu.addAction("None") none_participant_action.setCheckable(True) none_participant_action.setActionGroup(participant_group) none_participant_action.triggered.connect( self.on_none_participant_action_triggered) if selected_msg and selected_msg.participant is None: none_participant_action.setChecked(True) for participant in self.participants: pa = participant_menu.addAction(participant.name + " (" + participant.shortname + ")") pa.setCheckable(True) pa.setActionGroup(participant_group) if selected_msg and selected_msg.participant == participant: pa.setChecked(True) self.participant_actions[pa] = participant pa.triggered.connect(self.on_participant_action_triggered) if hasattr(self, "scene_type") and self.scene_type == 0: if not self.selection_area.is_empty: menu.addSeparator() noise_action = menu.addAction( self.tr("Set noise level from Selection")) noise_action.triggered.connect(self.on_noise_action_triggered) menu.addSeparator() menu.addAction(self.undo_action) menu.addAction(self.redo_action) return menu def contextMenuEvent(self, event: QContextMenuEvent): self.context_menu_position = event.pos() menu = self.create_context_menu() menu.exec_(self.mapToGlobal(event.pos())) self.context_menu_position = None def clear_selection(self): self.set_selection_area(0, 0) @pyqtSlot() def on_insert_sine_action_triggered(self): if not self.selection_area.is_empty: num_samples = self.selection_area.width else: num_samples = None original_data = self.signal.data if self.signal is not None else None dialog = self.insert_sine_plugin.get_insert_sine_dialog( original_data=original_data, position=self.paste_position, sample_rate=self.sample_rate, num_samples=num_samples) dialog.show() @pyqtSlot() def on_insert_sine_wave_clicked(self): if self.insert_sine_plugin.complex_wave is not None: self.clear_selection() insert_action = EditSignalAction( signal=self.signal, protocol=self.protocol, data_to_insert=self.insert_sine_plugin.complex_wave, position=self.paste_position, mode=EditAction.insert, cache_qad=self.cache_qad) self.undo_stack.push(insert_action) @pyqtSlot() def on_copy_action_triggered(self): if not self.selection_area.is_empty: self.stored_item = self.signal._fulldata[ int(self.selection_area.start):int(self.selection_area.end)] @pyqtSlot() def on_paste_action_triggered(self): if self.stored_item is not None: # paste_position is set in ContextMenuEvent self.clear_selection() paste_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, data_to_insert=self.stored_item, position=self.paste_position, mode=EditAction.paste, cache_qad=self.cache_qad) self.undo_stack.push(paste_action) @pyqtSlot() def on_delete_action_triggered(self): if not self.selection_area.is_empty: start, end = self.selection_area.start, self.selection_area.end self.clear_selection() del_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.delete, cache_qad=self.cache_qad) self.undo_stack.push(del_action) @pyqtSlot() def on_crop_action_triggered(self): if not self.selection_area.is_empty: start, end = self.selection_area.start, self.selection_area.end self.clear_selection() crop_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.crop, cache_qad=self.cache_qad) self.undo_stack.push(crop_action) @pyqtSlot() def on_mute_action_triggered(self): mute_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, mode=EditAction.mute, cache_qad=self.cache_qad) self.undo_stack.push(mute_action) @pyqtSlot() def on_zoom_action_triggered(self): self.zoom_to_selection(self.selection_area.start, self.selection_area.end) @pyqtSlot() def on_create_action_triggered(self): self.create_clicked.emit(self.selection_area.start, self.selection_area.end) @pyqtSlot() def on_none_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = None self.participant_changed.emit() @pyqtSlot() def on_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = self.participant_actions[self.sender()] self.participant_changed.emit() @pyqtSlot() def on_noise_action_triggered(self): self.set_noise_clicked.emit() @pyqtSlot(int) def on_undo_stack_index_changed(self, index: int): view_width, scene_width = self.view_rect().width(), self.sceneRect( ).width() if view_width > scene_width: self.show_full_scene(reinitialize=True)
class Viewer(QMainWindow): """High-level API to view multi-dimensional arrays. Properties: title -- window title """ def __init__(self, parent=None): QMainWindow.__init__(self, parent) uiDirectory = os.path.split(volumina.__file__)[0] if uiDirectory == "": uiDirectory = "." loadUi(uiDirectory + "/viewer.ui", self) self._dataShape = None self._viewerInitialized = False self.editor = None self.viewingWidget = None self.actionQuit.triggered.connect(qApp.quit) # when connecting in renderScreenshot to a partial(...) function, # we need to remember the created function to be able to disconnect # to it later self._renderScreenshotDisconnect = None self.initLayerstackModel() self.actionCurrentView = QAction(QIcon(), "Only for selected view", self.menuView) f = self.actionCurrentView.font() f.setBold(True) self.actionCurrentView.setFont(f) self.editor = VolumeEditor(self.layerstack, parent=self) # make sure the layer stack widget, which is the right widget # managed by the splitter self.splitter shows up correctly # TODO: find a proper way of doing this within the designer def adjustSplitter(): s = self.splitter.sizes() s = [int(0.66 * s[0]), s[0] - int(0.66 * s[0])] self.splitter.setSizes(s) QTimer.singleShot(0, adjustSplitter) @property def title(self): return self.windowTitle() @title.setter def title(self, t): self.setWindowTitle(t) def initLayerstackModel(self): self.layerstack = LayerStackModel() self.layerWidget.init(self.layerstack) model = self.layerstack self.UpButton.clicked.connect(model.moveSelectedUp) model.canMoveSelectedUp.connect(self.UpButton.setEnabled) self.DownButton.clicked.connect(model.moveSelectedDown) model.canMoveSelectedDown.connect(self.DownButton.setEnabled) self.DeleteButton.clicked.connect(model.deleteSelected) model.canDeleteSelected.connect(self.DeleteButton.setEnabled) @property def dataShape(self): return self._dataShape @dataShape.setter def dataShape(self, s): if s is None: return assert len(s) == 5 self._dataShape = s if not self._viewerInitialized: self._viewerInitialized = True self.volumeEditorWidget.init(self.editor) # make sure the data shape is correctly set # (some signal/slot connections may be set up in the above init) # FIXME: this code is broken # if its 2D, maximize the corresponding window # if len([i for i in list(self.dataShape)[1:4] if i == 1]) == 1: # viewAxis = [i for i in range(1,4) if self.dataShape[i] == 1][0] - 1 # self.volumeEditorWidget.quadview.switchMinMax(viewAxis) self.editor.dataShape = s def addGrayscaleLayer(self, a, name=None, direct=False): source, self.dataShape = createDataSource(a, True) layer = GrayscaleLayer(source, direct=direct) layer.numberOfChannels = self.dataShape[-1] if name: layer.name = name self.layerstack.append(layer) return layer def addAlphaModulatedLayer(self, a, name=None, **kwargs): source, self.dataShape = createDataSource(a, True) layer = AlphaModulatedLayer(source, **kwargs) if name: layer.name = name self.layerstack.append(layer) return layer def addRGBALayer(self, a, name=None): assert a.shape[2] >= 3 sources = [None, None, None, None] for i in range(3): sources[i], self.dataShape = createDataSource(a[..., i], True) if a.shape[-1] >= 4: sources[3], self.dataShape = createDataSource(a[..., 3], True) layer = RGBALayer(sources[0], sources[1], sources[2], sources[3]) if name: layer.name = name self.layerstack.append(layer) return layer def addRandomColorsLayer(self, a, name=None, direct=False): layer = self.addColorTableLayer(a, name, colortable=None, direct=direct) layer.colortableIsRandom = True layer.zeroIsTransparent = True return layer def addColorTableLayer(self, a, name=None, colortable=None, direct=False, clickFunctor=None): if colortable is None: colortable = self._randomColors(16384) source, self.dataShape = createDataSource(a, True) if clickFunctor is None: layer = ColortableLayer(source, colortable, direct=direct) else: layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct) if name: layer.name = name self.layerstack.append(layer) return layer def addRelabelingColorTableLayer(self, a, name=None, relabeling=None, colortable=None, direct=False, clickFunctor=None, right=True): if colortable is None: colortable = self._randomColors() source = RelabelingArraySource(a) if relabeling is None: source.setRelabeling(numpy.zeros(numpy.max(a) + 1, dtype=a.dtype)) else: source.setRelabeling(relabeling) if colortable is None: colortable = [QColor(0, 0, 0, 0).rgba(), QColor(255, 0, 0).rgba()] if clickFunctor is None: layer = ColortableLayer(source, colortable, direct=direct) else: layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct, right=right) if name: layer.name = name self.layerstack.append(layer) return (layer, source) def addClickableSegmentationLayer(self, a, name=None, direct=False, colortable=None, reuseColors=True): return ClickableSegmentationLayer(a, self, name=name, direct=direct, colortable=colortable, reuseColors=reuseColors) def addSegmentationEdgesLayer(self, a, name=None, **kwargs): source = createDataSource(a, False) layer = SegmentationEdgesLayer(source, **kwargs) layer.numberOfChannels = 1 layer.name = name self.layerstack.append(layer) return layer def _randomColors(self, M=256): """Generates a pleasing color table with M entries.""" colors = [] for i in range(M): if i == 0: colors.append(QColor(0, 0, 0, 0).rgba()) else: h, s, v = random.random(), random.random(), 1.0 color = numpy.asarray(colorsys.hsv_to_rgb(h, s, v)) * 255 qColor = QColor(*color) colors.append(qColor.rgba()) # for the first 16 objects, use some colors that are easily distinguishable colors[1:17] = default16 return colors
class EditableGraphicView(ZoomableGraphicView): ctrl_state_changed = pyqtSignal(bool) save_as_clicked = pyqtSignal() create_clicked = pyqtSignal(int, int) set_noise_clicked = pyqtSignal() participant_changed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.participants = [] self.__sample_rate = None # For default sample rate in insert sine dialog self.autoRangeY = True self.save_enabled = False # Signal is can be saved self.create_new_signal_enabled = False self.participants_assign_enabled = False self.cache_qad = False # cache qad demod after edit operations? self.__signal = None # type: Signal self.stored_item = None # For copy/paste self.paste_position = 0 # Where to paste? Set in contextmenuevent self._init_undo_stack(QUndoStack()) self.addAction(self.undo_action) self.addAction(self.redo_action) self.copy_action = QAction(self.tr("Copy selection"), self) # type: QAction self.copy_action.setShortcut(QKeySequence.Copy) self.copy_action.triggered.connect(self.on_copy_action_triggered) self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.copy_action.setIcon(QIcon.fromTheme("edit-copy")) self.addAction(self.copy_action) self.paste_action = QAction(self.tr("Paste"), self) # type: QAction self.paste_action.setShortcut(QKeySequence.Paste) self.paste_action.triggered.connect(self.on_paste_action_triggered) self.paste_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.paste_action.setIcon(QIcon.fromTheme("edit-paste")) self.addAction(self.paste_action) self.delete_action = QAction(self.tr("Delete selection"), self) self.delete_action.setShortcut(QKeySequence.Delete) self.delete_action.triggered.connect(self.on_delete_action_triggered) self.delete_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.delete_action.setIcon(QIcon.fromTheme("edit-delete")) self.addAction(self.delete_action) self.save_as_action = QAction(self.tr("Save Signal as..."), self) # type: QAction self.save_as_action.setIcon(QIcon.fromTheme("document-save-as")) self.save_as_action.setShortcut(QKeySequence.SaveAs) self.save_as_action.triggered.connect(self.save_as_clicked.emit) self.save_as_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.addAction(self.save_as_action) self.insert_sine_action = QAction(self.tr("Insert sine wave..."), self) font = self.insert_sine_action.font() font.setBold(True) self.insert_sine_action.setFont(font) self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered) self.insert_sine_plugin = InsertSinePlugin() self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked) def _init_undo_stack(self, undo_stack): self.undo_stack = undo_stack self.undo_action = self.undo_stack.createUndoAction(self) self.undo_action.setIcon(QIcon.fromTheme("edit-undo")) self.undo_action.setShortcut(QKeySequence.Undo) self.undo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.redo_action = self.undo_stack.createRedoAction(self) self.redo_action.setIcon(QIcon.fromTheme("edit-redo")) self.redo_action.setShortcut(QKeySequence.Redo) self.redo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) @property def sample_rate(self) -> float: return self.__sample_rate @sample_rate.setter def sample_rate(self, value): self.__sample_rate = value @property def signal(self) -> Signal: return self.__signal @property def protocol(self) -> ProtocolAnalyzer: return None # Gets overwritten in EpicGraphicView @property def selection_area(self) -> ROI: return self.scene().selection_area @selection_area.setter def selection_area(self, value): self.scene().selection_area = value def set_signal(self, signal: Signal): self.__signal = signal def keyPressEvent(self, event: QKeyEvent): key = event.key() super().keyPressEvent(event) if key == Qt.Key_Control and not self.shift_mode: self.set_loupe_cursor() self.ctrl_mode = True self.ctrl_state_changed.emit(True) if self.ctrl_mode and (key == Qt.Key_Plus or key == Qt.Key_Up): self.zoom(1.1) elif self.ctrl_mode and (key == Qt.Key_Minus or key == Qt.Key_Down): self.zoom(0.9) elif self.ctrl_mode and key == Qt.Key_Right: cur_val = self.horizontalScrollBar().value() step = self.horizontalScrollBar().pageStep() self.horizontalScrollBar().setValue(cur_val + step) elif key == Qt.Key_Right: cur_val = self.horizontalScrollBar().value() step = self.horizontalScrollBar().singleStep() self.horizontalScrollBar().setValue(cur_val + step) elif self.ctrl_mode and key == Qt.Key_Left: cur_val = self.horizontalScrollBar().value() step = self.horizontalScrollBar().pageStep() self.horizontalScrollBar().setValue(cur_val - step) elif key == Qt.Key_Left: cur_val = self.horizontalScrollBar().value() step = self.horizontalScrollBar().singleStep() self.horizontalScrollBar().setValue(cur_val - step) def keyReleaseEvent(self, event: QKeyEvent): super().keyReleaseEvent(event) if event.key() == Qt.Key_Control: self.ctrl_mode = False self.ctrl_state_changed.emit(False) self.unsetCursor() def contextMenuEvent(self, event: QContextMenuEvent): if self.ctrl_mode: return self.paste_position = int(self.mapToScene(event.pos()).x()) menu = QMenu(self) if self.save_enabled: menu.addAction(self.save_action) menu.addAction(self.save_as_action) menu.addSeparator() zoom_action = None create_action = None noise_action = None crop_action = None mute_action = None menu.addAction(self.copy_action) self.copy_action.setEnabled(not self.selection_area.is_empty) menu.addAction(self.paste_action) self.paste_action.setEnabled(self.stored_item is not None) menu.addSeparator() if PluginManager().is_plugin_enabled("InsertSine"): menu.addAction(self.insert_sine_action) if not self.selection_area.is_empty: menu.addSeparator() if not self.selection_area.is_empty: menu.addAction(self.delete_action) crop_action = menu.addAction(self.tr("Crop to selection")) mute_action = menu.addAction(self.tr("Mute selection")) menu.addSeparator() zoom_action = menu.addAction(self.tr("Zoom selection")) zoom_action.setIcon(QIcon.fromTheme("zoom-in")) if self.create_new_signal_enabled: create_action = menu.addAction(self.tr("Create signal from selection")) create_action.setIcon(QIcon.fromTheme("document-new")) if hasattr(self, "selected_messages"): selected_messages = self.selected_messages else: selected_messages = [] if len(selected_messages) == 1: selected_msg = selected_messages[0] else: selected_msg = None participant_actions = {} if len(selected_messages) > 0 and self.participants_assign_enabled: participant_group = QActionGroup(self) participant_menu = menu.addMenu("Participant") none_participant_action = participant_menu.addAction("None") none_participant_action.setCheckable(True) none_participant_action.setActionGroup(participant_group) if selected_msg and selected_msg.participant is None: none_participant_action.setChecked(True) for participant in self.participants: pa = participant_menu.addAction(participant.name + " (" + participant.shortname + ")") pa.setCheckable(True) pa.setActionGroup(participant_group) if selected_msg and selected_msg.participant == participant: pa.setChecked(True) participant_actions[pa] = participant else: none_participant_action = 42 if hasattr(self, "scene_type") and self.scene_type == 0: if not self.selection_area.is_empty: menu.addSeparator() noise_action = menu.addAction(self.tr("Set noise level from Selection")) menu.addSeparator() menu.addAction(self.undo_action) menu.addAction(self.redo_action) QApplication.processEvents() # without this the menu flickers on first create action = menu.exec_(self.mapToGlobal(event.pos())) if action is None: return elif action == zoom_action: self.zoom_to_selection(self.selection_area.x, self.selection_area.end) elif action == create_action: self.create_clicked.emit(self.selection_area.x, self.selection_area.end) elif action == crop_action: if not self.selection_area.is_empty: crop_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, mode=EditAction.crop, cache_qad=self.cache_qad) self.undo_stack.push(crop_action) elif action == noise_action: self.set_noise_clicked.emit() elif action == mute_action: mute_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, mode=EditAction.mute, cache_qad=self.cache_qad) self.undo_stack.push(mute_action) elif action == none_participant_action: for msg in selected_messages: msg.participant = None self.participant_changed.emit() elif action in participant_actions: for msg in selected_messages: msg.participant = participant_actions[action] self.participant_changed.emit() def clear_selection(self): self.set_selection_area(0, 0) def set_loupe_cursor(self): pixmap = QPixmap(QSize(20, 20)) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_Source) painter.setPen(QPen(constants.AXISCOLOR, 2)) painter.Antialiasing = True painter.HighQualityAntialiasing = True painter.drawEllipse(0, 0, 10, 10) painter.drawLine(10, 10, 20, 20) del painter self.setCursor(QCursor(pixmap)) def zoom_to_selection(self, start: int, end: int): if start == end: return x_factor = self.view_rect().width() / (end - start) self.zoom(x_factor) self.centerOn(start + (end - start) / 2, self.y_center) @pyqtSlot() def on_insert_sine_action_triggered(self): if not self.selection_area.is_empty: num_samples = self.selection_area.width else: num_samples = None self.insert_sine_plugin.show_insert_sine_dialog(sample_rate=self.sample_rate, num_samples=num_samples) @pyqtSlot() def on_insert_sine_wave_clicked(self): if self.insert_sine_plugin.complex_wave is not None: self.clear_selection() insert_action = EditSignalAction(signal=self.signal, protocol=self.protocol, data_to_insert=self.insert_sine_plugin.complex_wave, position=self.paste_position, mode=EditAction.insert, cache_qad=self.cache_qad) self.undo_stack.push(insert_action) @pyqtSlot() def on_copy_action_triggered(self): if not self.selection_area.is_empty: self.stored_item = self.signal._fulldata[int(self.selection_area.start):int(self.selection_area.end)] @pyqtSlot() def on_paste_action_triggered(self): if self.stored_item is not None: # paste_position is set in ContextMenuEvent self.clear_selection() paste_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, data_to_insert=self.stored_item, position=self.paste_position, mode=EditAction.paste, cache_qad=self.cache_qad) self.undo_stack.push(paste_action) @pyqtSlot() def on_delete_action_triggered(self): if not self.selection_area.is_empty: start, end = self.selection_area.start, self.selection_area.end self.clear_selection() del_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.delete, cache_qad=self.cache_qad) self.undo_stack.push(del_action) self.centerOn(start, self.y_center)
class EditableGraphicView(ZoomableGraphicView): save_as_clicked = pyqtSignal() export_demodulated_clicked = pyqtSignal() create_clicked = pyqtSignal(int, int) set_noise_clicked = pyqtSignal() participant_changed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.participants = [] self.__sample_rate = None # For default sample rate in insert sine dialog self.protocol = None # gets overwritten in epic graphic view self.autoRangeY = True self.save_enabled = False # Signal is can be saved self.create_new_signal_enabled = False self.participants_assign_enabled = False self.cache_qad = False # cache qad demod after edit operations? self.__signal = None # type: Signal self.stored_item = None # For copy/paste self.paste_position = 0 # Where to paste? Set in contextmenuevent self.init_undo_stack(QUndoStack()) self.addAction(self.undo_action) self.addAction(self.redo_action) self.copy_action = QAction(self.tr("Copy selection"), self) # type: QAction self.copy_action.setShortcut(QKeySequence.Copy) self.copy_action.triggered.connect(self.on_copy_action_triggered) self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.copy_action.setIcon(QIcon.fromTheme("edit-copy")) self.addAction(self.copy_action) self.paste_action = QAction(self.tr("Paste"), self) # type: QAction self.paste_action.setShortcut(QKeySequence.Paste) self.paste_action.triggered.connect(self.on_paste_action_triggered) self.paste_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.paste_action.setIcon(QIcon.fromTheme("edit-paste")) self.addAction(self.paste_action) self.delete_action = QAction(self.tr("Delete selection"), self) self.delete_action.setShortcut(QKeySequence.Delete) self.delete_action.triggered.connect(self.on_delete_action_triggered) self.delete_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.delete_action.setIcon(QIcon.fromTheme("edit-delete")) self.addAction(self.delete_action) self.save_as_action = QAction(self.tr("Save Signal as..."), self) # type: QAction self.save_as_action.setIcon(QIcon.fromTheme("document-save-as")) self.save_as_action.setShortcut(QKeySequence.SaveAs) self.save_as_action.triggered.connect(self.save_as_clicked.emit) self.save_as_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.addAction(self.save_as_action) self.insert_sine_action = QAction(self.tr("Insert sine wave..."), self) font = self.insert_sine_action.font() font.setBold(True) self.insert_sine_action.setFont(font) self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered) self.insert_sine_plugin = InsertSinePlugin() self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked) def init_undo_stack(self, undo_stack): self.undo_stack = undo_stack self.undo_action = self.undo_stack.createUndoAction(self) self.undo_action.setIcon(QIcon.fromTheme("edit-undo")) self.undo_action.setShortcut(QKeySequence.Undo) self.undo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.redo_action = self.undo_stack.createRedoAction(self) self.redo_action.setIcon(QIcon.fromTheme("edit-redo")) self.redo_action.setShortcut(QKeySequence.Redo) self.redo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed) def eliminate(self): self.participants = None self.stored_item = None if self.signal is not None: self.signal.eliminate() self.__signal = None self.insert_sine_plugin = None self.undo_action = None self.redo_action = None self.undo_stack = None super().eliminate() @property def sample_rate(self) -> float: return self.__sample_rate @sample_rate.setter def sample_rate(self, value): self.__sample_rate = value @property def signal(self) -> Signal: return self.__signal @property def selection_area(self) -> HorizontalSelection: return self.scene().selection_area @selection_area.setter def selection_area(self, value): self.scene().selection_area = value def set_signal(self, signal: Signal): self.__signal = signal def create_context_menu(self): self.paste_position = int(self.mapToScene(self.context_menu_position).x()) menu = QMenu(self) menu.addAction(self.copy_action) self.copy_action.setEnabled(self.something_is_selected) menu.addAction(self.paste_action) self.paste_action.setEnabled(self.stored_item is not None) menu.addSeparator() if PluginManager().is_plugin_enabled("InsertSine"): menu.addAction(self.insert_sine_action) self._add_zoom_actions_to_menu(menu) if self.something_is_selected: menu.addAction(self.delete_action) crop_action = menu.addAction(self.tr("Crop to selection")) crop_action.triggered.connect(self.on_crop_action_triggered) mute_action = menu.addAction(self.tr("Mute selection")) mute_action.triggered.connect(self.on_mute_action_triggered) if self.create_new_signal_enabled: create_action = menu.addAction(self.tr("Create signal from selection")) create_action.setIcon(QIcon.fromTheme("document-new")) create_action.triggered.connect(self.on_create_action_triggered) menu.addSeparator() if hasattr(self, "selected_messages"): selected_messages = self.selected_messages else: selected_messages = [] if len(selected_messages) == 1: selected_msg = selected_messages[0] else: selected_msg = None self.participant_actions = {} if len(selected_messages) > 0 and self.participants_assign_enabled: participant_group = QActionGroup(self) participant_menu = menu.addMenu("Participant") none_participant_action = participant_menu.addAction("None") none_participant_action.setCheckable(True) none_participant_action.setActionGroup(participant_group) none_participant_action.triggered.connect(self.on_none_participant_action_triggered) if selected_msg and selected_msg.participant is None: none_participant_action.setChecked(True) for participant in self.participants: pa = participant_menu.addAction(participant.name + " (" + participant.shortname + ")") pa.setCheckable(True) pa.setActionGroup(participant_group) if selected_msg and selected_msg.participant == participant: pa.setChecked(True) self.participant_actions[pa] = participant pa.triggered.connect(self.on_participant_action_triggered) if self.scene_type == 0 and self.something_is_selected: menu.addSeparator() noise_action = menu.addAction(self.tr("Set noise level from Selection")) noise_action.triggered.connect(self.on_noise_action_triggered) menu.addSeparator() menu.addAction(self.undo_action) menu.addAction(self.redo_action) if self.scene_type == 0: menu.addSeparator() if self.save_enabled: menu.addAction(self.save_action) menu.addAction(self.save_as_action) elif self.scene_type == 1: menu.addSeparator() export_demod_action = menu.addAction("Export demodulated...") export_demod_action.triggered.connect(self.export_demodulated_clicked.emit) return menu def clear_horizontal_selection(self): self.set_horizontal_selection(0, 0) @pyqtSlot() def on_insert_sine_action_triggered(self): if self.something_is_selected: num_samples = self.selection_area.width else: num_samples = None original_data = self.signal.data if self.signal is not None else None dialog = self.insert_sine_plugin.get_insert_sine_dialog(original_data=original_data, position=self.paste_position, sample_rate=self.sample_rate, num_samples=num_samples) dialog.show() @pyqtSlot() def on_insert_sine_wave_clicked(self): if self.insert_sine_plugin.complex_wave is not None: self.clear_horizontal_selection() insert_action = EditSignalAction(signal=self.signal, protocol=self.protocol, data_to_insert=self.insert_sine_plugin.complex_wave, position=self.paste_position, mode=EditAction.insert, cache_qad=self.cache_qad) self.undo_stack.push(insert_action) @pyqtSlot() def on_copy_action_triggered(self): if self.something_is_selected: self.stored_item = self.signal._fulldata[int(self.selection_area.start):int(self.selection_area.end)] @pyqtSlot() def on_paste_action_triggered(self): if self.stored_item is not None: # paste_position is set in ContextMenuEvent self.clear_horizontal_selection() paste_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, data_to_insert=self.stored_item, position=self.paste_position, mode=EditAction.paste, cache_qad=self.cache_qad) self.undo_stack.push(paste_action) @pyqtSlot() def on_delete_action_triggered(self): if self.something_is_selected: start, end = self.selection_area.start, self.selection_area.end self.clear_horizontal_selection() del_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.delete, cache_qad=self.cache_qad) self.undo_stack.push(del_action) @pyqtSlot() def on_crop_action_triggered(self): if self.something_is_selected: start, end = self.selection_area.start, self.selection_area.end self.clear_horizontal_selection() crop_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.crop, cache_qad=self.cache_qad) self.undo_stack.push(crop_action) @pyqtSlot() def on_mute_action_triggered(self): mute_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, mode=EditAction.mute, cache_qad=self.cache_qad) self.undo_stack.push(mute_action) @pyqtSlot() def on_create_action_triggered(self): self.create_clicked.emit(self.selection_area.start, self.selection_area.end) @pyqtSlot() def on_none_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = None self.participant_changed.emit() @pyqtSlot() def on_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = self.participant_actions[self.sender()] self.participant_changed.emit() @pyqtSlot() def on_noise_action_triggered(self): self.set_noise_clicked.emit() @pyqtSlot(int) def on_undo_stack_index_changed(self, index: int): view_width, scene_width = self.view_rect().width(), self.sceneRect().width() if view_width > scene_width: self.show_full_scene(reinitialize=True)
class EditableGraphicView(ZoomableGraphicView): save_as_clicked = pyqtSignal() export_demodulated_clicked = pyqtSignal() create_clicked = pyqtSignal(int, int) set_noise_clicked = pyqtSignal() participant_changed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.participants = [] self.__sample_rate = None # For default sample rate in insert sine dialog self.protocol = None # gets overwritten in epic graphic view self.autoRangeY = True self.save_enabled = False # Signal is can be saved self.create_new_signal_enabled = False self.participants_assign_enabled = False self.cache_qad = False # cache qad demod after edit operations? self.__signal = None # type: Signal self.stored_item = None # For copy/paste self.paste_position = 0 # Where to paste? Set in contextmenuevent self.init_undo_stack(QUndoStack()) self.addAction(self.undo_action) self.addAction(self.redo_action) self.copy_action = QAction(self.tr("Копіювати виділене"), self) # type: QAction self.copy_action.setShortcut(QKeySequence.Copy) self.copy_action.triggered.connect(self.on_copy_action_triggered) self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.copy_action.setIcon(QIcon.fromTheme("edit-copy")) self.addAction(self.copy_action) self.paste_action = QAction(self.tr("Вставити"), self) # type: QAction self.paste_action.setShortcut(QKeySequence.Paste) self.paste_action.triggered.connect(self.on_paste_action_triggered) self.paste_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.paste_action.setIcon(QIcon.fromTheme("edit-paste")) self.addAction(self.paste_action) self.delete_action = QAction(self.tr("Видалити виділене"), self) self.delete_action.setShortcut(QKeySequence.Delete) self.delete_action.triggered.connect(self.on_delete_action_triggered) self.delete_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.delete_action.setIcon(QIcon.fromTheme("edit-delete")) self.addAction(self.delete_action) self.save_as_action = QAction(self.tr("Зберегти сигнал як..."), self) # type: QAction self.save_as_action.setIcon(QIcon.fromTheme("document-save-as")) self.save_as_action.setShortcut(QKeySequence.SaveAs) self.save_as_action.triggered.connect(self.save_as_clicked.emit) self.save_as_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.addAction(self.save_as_action) self.show_symbol_legend_action = QAction(self.tr("Показати довжину символа"), self) self.show_symbol_legend_action.setShortcut("L") self.show_symbol_legend_action.triggered.connect(self.toggle_symbol_legend) self.show_symbol_legend_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.show_symbol_legend_action.setCheckable(True) self.show_symbol_legend_action.setChecked(False) self.addAction(self.show_symbol_legend_action) self.insert_sine_action = QAction(self.tr("Вставити синусоїду..."), self) font = self.insert_sine_action.font() font.setBold(True) self.insert_sine_action.setFont(font) self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered) self.insert_sine_plugin = InsertSinePlugin() self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked) def init_undo_stack(self, undo_stack): self.undo_stack = undo_stack self.undo_action = self.undo_stack.createUndoAction(self) self.undo_action.setIcon(QIcon.fromTheme("edit-undo")) self.undo_action.setShortcut(QKeySequence.Undo) self.undo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.redo_action = self.undo_stack.createRedoAction(self) self.redo_action.setIcon(QIcon.fromTheme("edit-redo")) self.redo_action.setShortcut(QKeySequence.Redo) self.redo_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed) def eliminate(self): self.participants = None self.stored_item = None if self.signal is not None: self.signal.eliminate() self.__signal = None self.insert_sine_plugin = None self.undo_action = None self.redo_action = None self.undo_stack = None super().eliminate() @property def sample_rate(self) -> float: return self.__sample_rate @sample_rate.setter def sample_rate(self, value): self.__sample_rate = value @property def signal(self) -> Signal: return self.__signal @property def selection_area(self) -> HorizontalSelection: return self.scene().selection_area @selection_area.setter def selection_area(self, value): self.scene().selection_area = value def set_signal(self, signal: Signal): self.__signal = signal def create_context_menu(self): self.paste_position = int(self.mapToScene(self.context_menu_position).x()) menu = QMenu(self) # menu.addAction(self.copy_action) self.copy_action.setEnabled(self.something_is_selected) # menu.addAction(self.paste_action) self.paste_action.setEnabled(self.stored_item is not None) menu.addSeparator() # if PluginManager().is_plugin_enabled("InsertSine"): # # menu.addAction(self.insert_sine_action) # self._add_zoom_actions_to_menu(menu) # if self.something_is_selected: # menu.addAction(self.delete_action) # crop_action = menu.addAction(self.tr("Crop to selection")) # crop_action.triggered.connect(self.on_crop_action_triggered) # mute_action = menu.addAction(self.tr("Заглушити виділене")) # mute_action.triggered.connect(self.on_mute_action_triggered) # if self.create_new_signal_enabled: # create_action = menu.addAction(self.tr("Створити сигнал з виділеного")) # create_action.setIcon(QIcon.fromTheme("document-new")) # create_action.triggered.connect(self.on_create_action_triggered) # menu.addSeparator() if hasattr(self, "selected_messages"): selected_messages = self.selected_messages else: selected_messages = [] if len(selected_messages) == 1: selected_msg = selected_messages[0] else: selected_msg = None self.participant_actions = {} # if len(selected_messages) > 0 and self.participants_assign_enabled: # participant_group = QActionGroup(self) # participant_menu = menu.addMenu("Participant") # none_participant_action = participant_menu.addAction("None") # none_participant_action.setCheckable(True) # none_participant_action.setActionGroup(participant_group) # none_participant_action.triggered.connect(self.on_none_participant_action_triggered) # # if selected_msg and selected_msg.participant is None: # none_participant_action.setChecked(True) # # for participant in self.participants: # pa = participant_menu.addAction(participant.name + " (" + participant.shortname + ")") # pa.setCheckable(True) # pa.setActionGroup(participant_group) # if selected_msg and selected_msg.participant == participant: # pa.setChecked(True) # # self.participant_actions[pa] = participant # pa.triggered.connect(self.on_participant_action_triggered) # if self.scene_type == 0 and self.something_is_selected: # menu.addSeparator() # noise_action = menu.addAction(self.tr("Встановити рівень шуму з виділеного")) # noise_action.triggered.connect(self.on_noise_action_triggered) menu.addSeparator() # menu.addAction(self.undo_action) # menu.addAction(self.redo_action) # if self.scene_type == 0: # menu.addSeparator() # if self.save_enabled: # menu.addAction(self.save_action) # # menu.addAction(self.save_as_action) # elif self.scene_type == 1: # menu.addSeparator() # export_demod_action = menu.addAction("Експорт демодуляції...") # export_demod_action.triggered.connect(self.export_demodulated_clicked.emit) # # menu.addAction(self.show_symbol_legend_action) return menu def clear_horizontal_selection(self): self.set_horizontal_selection(0, 0) def toggle_symbol_legend(self): if self.scene_type == 1: self.scene().always_show_symbols_legend = self.show_symbol_legend_action.isChecked() self.scene().draw_sep_area(self.y_sep) @pyqtSlot() def on_insert_sine_action_triggered(self): if self.something_is_selected: num_samples = self.selection_area.width else: num_samples = None original_data = self.signal.iq_array.data if self.signal is not None else None dialog = self.insert_sine_plugin.get_insert_sine_dialog(original_data=original_data, position=self.paste_position, sample_rate=self.sample_rate, num_samples=num_samples) dialog.show() @pyqtSlot() def on_insert_sine_wave_clicked(self): if self.insert_sine_plugin.complex_wave is not None: self.clear_horizontal_selection() insert_action = EditSignalAction(signal=self.signal, protocol=self.protocol, data_to_insert=self.insert_sine_plugin.complex_wave, position=self.paste_position, mode=EditAction.insert, cache_qad=self.cache_qad) self.undo_stack.push(insert_action) @pyqtSlot() def on_copy_action_triggered(self): if self.something_is_selected: self.stored_item = self.signal.iq_array[int(self.selection_area.start):int(self.selection_area.end)] @pyqtSlot() def on_paste_action_triggered(self): if self.stored_item is not None: # paste_position is set in ContextMenuEvent self.clear_horizontal_selection() paste_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, data_to_insert=self.stored_item, position=self.paste_position, mode=EditAction.paste, cache_qad=self.cache_qad) self.undo_stack.push(paste_action) @pyqtSlot() def on_delete_action_triggered(self): if self.something_is_selected: start, end = self.selection_area.start, self.selection_area.end self.clear_horizontal_selection() del_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.delete, cache_qad=self.cache_qad) self.undo_stack.push(del_action) @pyqtSlot() def on_crop_action_triggered(self): if self.something_is_selected: start, end = self.selection_area.start, self.selection_area.end self.clear_horizontal_selection() crop_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=start, end=end, mode=EditAction.crop, cache_qad=self.cache_qad) self.undo_stack.push(crop_action) @pyqtSlot() def on_mute_action_triggered(self): mute_action = EditSignalAction(signal=self.signal, protocol=self.protocol, start=self.selection_area.start, end=self.selection_area.end, mode=EditAction.mute, cache_qad=self.cache_qad) self.undo_stack.push(mute_action) @pyqtSlot() def on_create_action_triggered(self): self.create_clicked.emit(self.selection_area.start, self.selection_area.end) @pyqtSlot() def on_none_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = None self.participant_changed.emit() @pyqtSlot() def on_participant_action_triggered(self): for msg in self.selected_messages: msg.participant = self.participant_actions[self.sender()] self.participant_changed.emit() @pyqtSlot() def on_noise_action_triggered(self): self.set_noise_clicked.emit() @pyqtSlot(int) def on_undo_stack_index_changed(self, index: int): view_width, scene_width = self.view_rect().width(), self.sceneRect().width() if view_width > scene_width: self.show_full_scene(reinitialize=True)
class ListLayout(QWidget, CueLayout): NAME = 'List Layout' DESCRIPTION = ''' This layout organize the cues in a list: <ul> <li>Unlimited cues; <li>Side panel with playing media-cues; <li>Cues can be moved in the list; <li>Keyboard control; </ul>''' HEADER = ['', 'Cue', 'Duration', 'Progress'] # I need to redefine those from CueLayout key_pressed = CueLayout.key_pressed cue_added = CueLayout.cue_added cue_removed = CueLayout.cue_removed focus_changed = CueLayout.focus_changed def __init__(self, app, **kwds): super().__init__(**kwds) self.mainWindow = app.mainWindow self.menuLayout = self.mainWindow.menuLayout self._cue_items = [] self._playing_widgets = {} self._context_item = None self._show_dbmeter = config['ListLayout']['ShowDbMeters'] == 'True' self._show_seek = config['ListLayout']['ShowSeek'] == 'True' self._accurate_time = config['ListLayout']['ShowAccurate'] == 'True' self._auto_next = config['ListLayout']['AutoNext'] == 'True' # Add layout-specific menus self.showDbMeterAction = QAction(self) self.showDbMeterAction.setCheckable(True) self.showDbMeterAction.setChecked(self._show_dbmeter) self.showDbMeterAction.triggered.connect(self.set_dbmeter_visible) self.showSeekAction = QAction(self) self.showSeekAction.setCheckable(True) self.showSeekAction.setChecked(self._show_seek) self.showSeekAction.triggered.connect(self.set_seek_visible) self.accurateTimingAction = QAction(self) self.accurateTimingAction.setCheckable(True) self.accurateTimingAction.setChecked(self._accurate_time) self.accurateTimingAction.triggered.connect(self.set_accurate_time) self.autoNextAction = QAction(self) self.autoNextAction.setCheckable(True) self.autoNextAction.setChecked(self._auto_next) self.autoNextAction.triggered.connect(self.set_auto_next) self.menuLayout.addAction(self.showDbMeterAction) self.menuLayout.addAction(self.showSeekAction) self.menuLayout.addAction(self.accurateTimingAction) self.menuLayout.addAction(self.autoNextAction) # Add a toolbar to MainWindow self.toolBar = QToolBar(self.mainWindow) self.toolBar.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.playAction = QAction(self) self.playAction.setIcon(QIcon.fromTheme("media-playback-start")) self.playAction.triggered.connect(self.play_current) self.pauseAction = QAction(self) self.pauseAction.setIcon(QIcon.fromTheme("media-playback-pause")) self.pauseAction.triggered.connect(self.pause_current) self.stopAction = QAction(self) self.stopAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.stopAction.triggered.connect(self.stop_current) self.stopAllAction = QAction(self) self.stopAllAction.font().setBold(True) self.stopAllAction.triggered.connect(self.stop_all) self.pauseAllAction = QAction(self) self.pauseAllAction.font().setBold(True) self.pauseAllAction.triggered.connect(self.pause_all) self.restartAllAction = QAction(self) self.restartAllAction.font().setBold(True) self.restartAllAction.triggered.connect(self.restart_all) self.toolBar.addAction(self.playAction) self.toolBar.addAction(self.pauseAction) self.toolBar.addAction(self.stopAction) self.toolBar.addSeparator() self.toolBar.addAction(self.stopAllAction) self.toolBar.addAction(self.pauseAllAction) self.toolBar.addAction(self.restartAllAction) self.mainWindow.addToolBar(self.toolBar) self.hLayout = QHBoxLayout(self) self.hLayout.setContentsMargins(5, 5, 5, 5) # On the left (cue list) self.listView = ListWidget(self) self.listView.context_event.connect(self.context_event) self.listView.key_event.connect(self.onKeyPressEvent) self.listView.drop_move_event.connect(self.move_cue_at) self.listView.drop_copy_event.connect(self._copy_cue_at) self.listView.setHeaderLabels(self.HEADER) self.listView.header().setSectionResizeMode(QHeaderView.Fixed) self.listView.header().setSectionResizeMode(self.HEADER.index('Cue'), QHeaderView.Stretch) self.listView.setColumnWidth(0, 40) self.hLayout.addWidget(self.listView) self.vLayout = QVBoxLayout() self.vLayout.setContentsMargins(0, 0, 0, 0) self.vLayout.setSpacing(2) # On the right (playing media-cues) self.label = QLabel('Playing', self) self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setStyleSheet('font-size: 17pt; font-weight: bold;') self.vLayout.addWidget(self.label) self.playView = QListWidget(self) self.playView.setMinimumWidth(300) self.playView.setMaximumWidth(300) self.playView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.playView.setFocusPolicy(QtCore.Qt.NoFocus) self.playView.setSelectionMode(QAbstractItemView.NoSelection) self.vLayout.addWidget(self.playView) self.hLayout.addLayout(self.vLayout) # Add cue preferences widgets self.add_settings_section(MediaCueGeneral, MediaCue) self.add_settings_section(Appearance) # Context menu actions self.edit_action = QAction(self) self.edit_action.triggered.connect(self.edit_context_cue) self.remove_action = QAction(self) self.remove_action.triggered.connect(self.remove_context_cue) self.select_action = QAction(self) self.select_action.triggered.connect(self.select_context_cue) self.add_context_item(self.edit_action) self.sep1 = self.add_context_separator() self.add_context_item(self.remove_action) self.add_context_item(self.select_action) self.retranslateUi() def retranslateUi(self): self.showDbMeterAction.setText("Show Db-meters") self.showSeekAction.setText("Show seek bars") self.accurateTimingAction.setText('Accurate timing') self.autoNextAction.setText('Auto select next cue') self.playAction.setText("Go") self.pauseAction.setText("Pause") self.stopAction.setText("Stop") self.stopAllAction.setText("Stop All") self.pauseAllAction.setText("Pause All") self.restartAllAction.setText("Restart All") self.edit_action.setText('Edit option') self.remove_action.setText('Remove') self.select_action.setText('Select') def current_cue(self): item = self.current_item() if item is not None: return item.cue def current_item(self): if len(self._cue_items) > 0: return self._cue_items[self.listView.currentIndex().row()] def select_context_cue(self): self._context_item.select() def __add_cue__(self, cue, index): if isinstance(cue, MediaCue): item = MediaItem(cue, self.listView) # Use weak-references for avoid cyclic-references with lambda(s) wself = weakref.ref(self) wcue = weakref.ref(cue) cue.media.on_play.connect(lambda: wself().show_playing(wcue())) cue.media.interrupted.connect(lambda: wself().hide_playing(wcue())) cue.media.stopped.connect(lambda: wself().hide_playing(wcue())) cue.media.error.connect(lambda: wself().hide_playing(wcue())) cue.media.eos.connect(lambda: wself().hide_playing(wcue())) elif isinstance(cue, ActionCue): item = ActionItem(cue) item.cue = cue else: raise Exception('Cue type not supported') item.setFlags(item.flags() & ~QtCore.Qt.ItemIsDropEnabled) if index is None or (index < 0 or index >= len(self._cue_items)): cue['index'] = len(self._cue_items) self.listView.addTopLevelItem(item) self._cue_items.append(item) else: self.listView.insertTopLevelItem(index, item) self._cue_items.insert(index, item) for n in range(index, len(self._cue_items)): self._cue_items[n].cue['index'] = n if isinstance(item, MediaItem): item.init() if len(self._cue_items) == 1: self.listView.setCurrentItem(item) self.listView.setFocus() self.listView.resizeColumnToContents(1) self.cue_added.emit(cue) def set_accurate_time(self, enable): self._accurate_time = enable for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_accurate_time(enable) for item in self._cue_items: if isinstance(item, MediaItem): item.set_accurate_time(enable) def set_auto_next(self, enable): self._auto_next = enable def set_seek_visible(self, visible): self._show_seek = visible for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_seek_visible(visible) def set_dbmeter_visible(self, visible): self._show_dbmeter = visible for i in range(self.playView.count()): widget = self.playView.itemWidget(self.playView.item(i)) widget.set_dbmeter_visible(visible) def show_playing(self, cue): if cue not in self._playing_widgets: media_time = self._cue_items[cue['index']].media_time widget = PlayingMediaWidget(cue, media_time, self.playView) widget.set_dbmeter_visible(self._show_dbmeter) widget.set_seek_visible(self._show_seek) widget.set_accurate_time(self._accurate_time) list_item = QListWidgetItem() list_item.setSizeHint(widget.size()) self.playView.addItem(list_item) self.playView.setItemWidget(list_item, widget) self._playing_widgets[cue] = list_item def hide_playing(self, cue): if cue in self._playing_widgets: list_item = self._playing_widgets.pop(cue) widget = self.playView.itemWidget(list_item) row = self.playView.indexFromItem(list_item).row() self.playView.removeItemWidget(list_item) self.playView.takeItem(row) widget.destroy_widget() def onKeyPressEvent(self, e): if e.key() == QtCore.Qt.Key_Space: if qApp.keyboardModifiers() == Qt.ShiftModifier: cue = self.current_cue() if cue is not None: self.edit_cue(cue) elif qApp.keyboardModifiers() == Qt.ControlModifier: item = self.current_item() if item is not None: item.select() else: cue = self.current_cue() if cue is not None: cue.execute() if self._auto_next: nextitem = self.listView.currentIndex().row() + 1 if nextitem < self.listView.topLevelItemCount(): nextitem = self.listView.topLevelItem(nextitem) self.listView.setCurrentItem(nextitem) else: self.key_pressed.emit(e) e.accept() def play_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.play() else: cue.execute() def pause_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.pause() def stop_current(self): cue = self.current_cue() if isinstance(cue, MediaCue): cue.media.stop() def context_event(self, event): item = self.listView.itemAt(event.pos()) if item is not None: index = self.listView.indexOfTopLevelItem(item) self._context_item = self._cue_items[index] self.show_context_menu(event.globalPos()) event.accept() def remove_context_cue(self): self.remove_cue(self.get_context_cue()) def edit_context_cue(self): self.edit_cue(self.get_context_cue()) def stop_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): item.cue.media.stop() def pause_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): item.cue.media.pause() def restart_all(self): for item in self._cue_items: if isinstance(item.cue, MediaCue): if item.cue.media.state == Media.PAUSED: item.cue.media.play() def __remove_cue__(self, cue): index = cue['index'] self.listView.takeTopLevelItem(index) self._cue_items.pop(index) if isinstance(cue, MediaCue): cue.media.interrupt() if cue in self._playing_widgets: self.hide_playing(self._playing_widgets[cue]) for item in self._cue_items[index:]: item.cue['index'] = item.cue['index'] - 1 cue.finalize() self.cue_removed.emit(cue) def move_cue_at(self, old_index, index): self.move_cue(self._cue_items[old_index].cue, index) def _copy_cue_at(self, old_index, index): newcue = CueFactory.clone_cue(self._cue_items[old_index].cue) self.add_cue(newcue, index) def __move_cue__(self, cue, index): item = self._cue_items.pop(cue['index']) self._cue_items.insert(index, item) self.listView.setCurrentItem(item) if isinstance(item, MediaItem): item.init() for n, item in enumerate(self._cue_items): item.cue['index'] = n def get_cues(self, cue_class=Cue): # i -> item return [i.cue for i in self._cue_items if isinstance(i.cue, cue_class)] def get_cue_at(self, index): if index < len(self._cue_items): return self._cue_items[index].cue def get_cue_by_id(self, cue_id): for item in self._cue_items: if item.cue.cue_id() == cue_id: return item.cue def get_selected_cues(self, cue_class=Cue): cues = [] for item in self._cue_items: if item.selected and isinstance(item.cue, cue_class): cues.append(item.cue) return cues def clear_layout(self): while len(self._cue_items) > 0: self.__remove_cue__(self._cue_items[-1].cue) def destroy_layout(self): self.clear_layout() self.menuLayout.clear() self.mainWindow.removeToolBar(self.toolBar) self.toolBar.deleteLater() # Remove context-items self.remove_context_item(self.edit_action) self.remove_context_item(self.sep1) self.remove_context_item(self.remove_action) self.remove_context_item(self.select_action) # Remove settings-sections self.remove_settings_section(Appearance) self.remove_settings_section(MediaCueGeneral) # !! Delete the layout references !! self.deleteLater() def get_context_cue(self): return self._context_item.cue def select_all(self): for item in self._cue_items: if not item.selected: item.select() def deselect_all(self): for item in self._cue_items: if item.selected: item.select() def invert_selection(self): for item in self._cue_items: item.select()