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
Example #2
0
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")
Example #3
0
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)
Example #4
0
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")
Example #5
0
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)
Example #6
0
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
Example #7
0
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)
Example #8
0
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)
Example #9
0
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)
Example #10
0
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()