Esempio n. 1
0
class MVCPlaybackControlGUI(PlaybackControlConsole):
    """
    GUI implementation of MVCPlaybackControlBase
    """
    nameFiltersChanged = Signal("QStringList")

    def __init__(self, config):
        assertMainThread()
        super().__init__(config)

        # state
        self.preventSeek = False
        self.beginTime = None
        self.timeRatio = 1.0

        # gui
        srv = Services.getService("MainWindow")
        config.configLoaded.connect(self.restoreState)
        config.configAboutToSave.connect(self.saveState)
        self.config = config
        playbackMenu = srv.menuBar().addMenu("&Playback")

        style = QApplication.style()
        self.actStart = QAction(QIcon.fromTheme("media-playback-start", style.standardIcon(QStyle.SP_MediaPlay)),
                                "Start Playback", self)
        self.actPause = QAction(QIcon.fromTheme("media-playback-pause", style.standardIcon(QStyle.SP_MediaPause)),
                                "Pause Playback", self)
        self.actPause.setEnabled(False)
        self.actStepFwd = QAction(QIcon.fromTheme("media-seek-forward",
                                                  style.standardIcon(QStyle.SP_MediaSeekForward)),
                                  "Step Forward", self)
        self.actStepBwd = QAction(QIcon.fromTheme("media-seek-backward",
                                                  style.standardIcon(QStyle.SP_MediaSeekBackward)),
                                  "Step Backward", self)
        self.actSeekEnd = QAction(QIcon.fromTheme("media-skip-forward",
                                                  style.standardIcon(QStyle.SP_MediaSkipForward)),
                                  "Seek End", self)
        self.actSeekBegin = QAction(QIcon.fromTheme("media-skip-backward",
                                                    style.standardIcon(QStyle.SP_MediaSkipBackward)),
                                    "Seek Begin", self)
        self.actSetTimeFactor = {r : QAction("x 1/%d" % (1/r), self) if r < 1 else QAction("x %d" % r, self)
                                 for r in (1/8, 1/4, 1/2, 1, 2, 4, 8)}

        # pylint: disable=unnecessary-lambda
        # let's stay on the safe side and do not use emit as a slot...
        self.actStart.triggered.connect(lambda: self._startPlayback.emit())
        self.actPause.triggered.connect(lambda: self._pausePlayback.emit())
        self.actStepFwd.triggered.connect(lambda: self._stepForward.emit(self.selectedStream()))
        self.actStepBwd.triggered.connect(lambda: self._stepBackward.emit(self.selectedStream()))
        self.actSeekEnd.triggered.connect(lambda: self._seekEnd.emit())
        self.actSeekBegin.triggered.connect(lambda: self._seekBeginning.emit())
        # pylint: enable=unnecessary-lambda

        def setTimeFactor(newFactor):
            logger.debug("new time factor %f", newFactor)
            self._setTimeFactor.emit(newFactor)

        for r in self.actSetTimeFactor:
            logger.debug("adding action for time factor %f", r)
            self.actSetTimeFactor[r].triggered.connect(functools.partial(setTimeFactor, r))

        self.dockWidget = srv.newDockWidget("PlaybackControl", None, Qt.LeftDockWidgetArea)
        self.dockWidgetContents = QWidget(self.dockWidget)
        self.dockWidget.setWidget(self.dockWidgetContents)
        toolLayout = QBoxLayout(QBoxLayout.TopToBottom, self.dockWidgetContents)
        toolLayout.setContentsMargins(0, 0, 0, 0)
        toolBar = QToolBar()
        toolLayout.addWidget(toolBar)
        toolBar.addAction(self.actSeekBegin)
        toolBar.addAction(self.actStepBwd)
        toolBar.addAction(self.actStart)
        toolBar.addAction(self.actPause)
        toolBar.addAction(self.actStepFwd)
        toolBar.addAction(self.actSeekEnd)
        playbackMenu.addAction(self.actSeekBegin)
        playbackMenu.addAction(self.actStepBwd)
        playbackMenu.addAction(self.actStart)
        playbackMenu.addAction(self.actPause)
        playbackMenu.addAction(self.actStepFwd)
        playbackMenu.addAction(self.actSeekEnd)
        playbackMenu.addSeparator()
        for r in self.actSetTimeFactor:
            playbackMenu.addAction(self.actSetTimeFactor[r])
        self.timeRatioLabel = QLabel("x 1")
        self.timeRatioLabel.addActions(list(self.actSetTimeFactor.values()))
        self.timeRatioLabel.setContextMenuPolicy(Qt.ActionsContextMenu)
        toolBar.addSeparator()
        toolBar.addWidget(self.timeRatioLabel)
        contentsLayout = QGridLayout()
        toolLayout.addLayout(contentsLayout, 10)
        # now we add a position view
        self.positionSlider = QSlider(Qt.Horizontal, self.dockWidgetContents)
        self.beginLabel = QLabel(parent=self.dockWidgetContents)
        self.beginLabel.setAlignment(Qt.AlignLeft|Qt.AlignCenter)
        self.currentLabel = QLabel(parent=self.dockWidgetContents)
        self.currentLabel.setAlignment(Qt.AlignHCenter|Qt.AlignCenter)
        self.endLabel = QLabel(parent=self.dockWidgetContents)
        self.endLabel.setAlignment(Qt.AlignRight|Qt.AlignCenter)
        contentsLayout.addWidget(self.beginLabel, 0, 0, alignment=Qt.AlignLeft)
        contentsLayout.addWidget(self.currentLabel, 0, 1, alignment=Qt.AlignHCenter)
        contentsLayout.addWidget(self.endLabel, 0, 2, alignment=Qt.AlignRight)
        contentsLayout.addWidget(self.positionSlider, 1, 0, 1, 3)
        self.positionSlider.setTracking(False)
        self.positionSlider.valueChanged.connect(self.onSliderValueChanged, Qt.DirectConnection)
        self.positionSlider.sliderMoved.connect(self.displayPosition)

        # file browser
        self.browser = BrowserWidget(self.dockWidget)
        self.nameFiltersChanged.connect(self._onNameFiltersChanged, Qt.QueuedConnection)
        contentsLayout.addWidget(self.browser, 3, 0, 1, 3)
        contentsLayout.setRowStretch(3, 100)
        self.browser.activated.connect(self.browserActivated)

        self.actShowAllFiles = QAction("Show all files")
        self.actShowAllFiles.setCheckable(True)
        self.actShowAllFiles.setChecked(False)
        self.actShowAllFiles.toggled.connect(self._onShowAllFiles)
        playbackMenu.addSeparator()
        playbackMenu.addAction(self.actShowAllFiles)

        self.actGroupStream = QActionGroup(self)
        self.actGroupStream.setExclusionPolicy(QActionGroup.ExclusionPolicy.ExclusiveOptional)
        playbackMenu.addSeparator()
        self.actGroupStreamMenu = playbackMenu.addMenu("Step Stream")
        self._selectedStream = None

        self.recentSeqs = [QAction() for i in range(10)]
        playbackMenu.addSeparator()
        recentMenu = playbackMenu.addMenu("Recent")
        for a in self.recentSeqs:
            a.setVisible(False)
            a.triggered.connect(self.openRecent)
            recentMenu.addAction(a)

        self._supportedFeaturesChanged(set(), set())

    def __del__(self):
        logger.internal("deleting playback control")

    def _onNameFiltersChanged(self, nameFilt):
        self.browser.setFilter(nameFilt)

    def _onShowAllFiles(self, enabled):
        self.fileSystemModel.setNameFilterDisables(enabled)

    def _supportedFeaturesChanged(self, featureset, nameFilters):
        """
        overwritten from MVCPlaybackControlBase. This function is called
        from multiple threads, but not at the same time.

        :param featureset: the current featureset
        :return:
        """
        assertMainThread()
        self.featureset = featureset
        self.actStepFwd.setEnabled("stepForward" in featureset)
        self.actStepBwd.setEnabled("stepBackward" in featureset)
        self.actSeekBegin.setEnabled("seekBeginning" in featureset)
        self.actSeekEnd.setEnabled("seekEnd" in featureset)
        self.positionSlider.setEnabled("seekTime" in featureset)
        self.browser.setEnabled("setSequence" in featureset)
        self.timeRatioLabel.setEnabled("setTimeFactor" in featureset)
        for f in self.actSetTimeFactor:
            self.actSetTimeFactor[f].setEnabled("setTimeFactor" in featureset)
        self.timeRatioLabel.setEnabled("setTimeFactor" in featureset)
        self.timeRatioLabel.setEnabled("setTimeFactor" in featureset)
        self.timeRatioLabel.setEnabled("setTimeFactor" in featureset)
        self.timeRatioLabel.setEnabled("setTimeFactor" in featureset)
        if "startPlayback" not in featureset:
            self.actStart.setEnabled(False)
        if "pausePlayback" not in featureset:
            self.actPause.setEnabled(False)
        logger.debug("current feature set: %s", featureset)
        logger.debug("Setting name filters of browser: %s", list(nameFilters))
        self.nameFiltersChanged.emit(list(nameFilters))
        super()._supportedFeaturesChanged(featureset, nameFilters)

    def scrollToCurrent(self):
        """
        Scrolls to the current item in the browser

        :return:
        """
        assertMainThread()
        c = self.browser.current()
        if c is not None:
            self.browser.scrollTo(c)

    def _sequenceOpened(self, filename, begin, end, streams):
        """
        Notifies about an opened sequence.

        :param filename: the filename which has been opened
        :param begin: timestamp of sequence's first sample
        :param end: timestamp of sequence's last sample
        :param streams: list of streams in the sequence
        :return: None
        """
        assertMainThread()
        self.beginTime = begin
        self.preventSeek = True
        self.positionSlider.setRange(0, end.toMSecsSinceEpoch() - begin.toMSecsSinceEpoch())
        self.preventSeek = False
        self.beginLabel.setText(begin.toString("hh:mm:ss.zzz"))
        self.endLabel.setText(end.toString("hh:mm:ss.zzz"))
        self._currentTimestampChanged(begin)
        try:
            self.browser.blockSignals(True)
            self.browser.setActive(filename)
            self.browser.scrollTo(filename)
        finally:
            self.browser.blockSignals(False)
        self._selectedStream = None
        for a in self.actGroupStream.actions():
            logger.debug("Remove stream group action: %s", a.data())
            self.actGroupStream.removeAction(a)
        for stream in streams:
            act = QAction(stream, self.actGroupStream)
            act.triggered.connect(lambda cstream=stream: self.setSelectedStream(cstream))
            act.setCheckable(True)
            act.setChecked(False)
            logger.debug("Add stream group action: %s", act.data())
            self.actGroupStreamMenu.addAction(act)
        QTimer.singleShot(250, self.scrollToCurrent)
        super()._sequenceOpened(filename, begin, end, streams)

    def _currentTimestampChanged(self, currentTime):
        """
        Notifies about a changed timestamp

        :param currentTime: the new current timestamp
        :return: None
        """
        assertMainThread()
        if self.beginTime is None:
            self.currentLabel.setText("")
        else:
            sliderVal = currentTime.toMSecsSinceEpoch() - self.beginTime.toMSecsSinceEpoch()
            self.preventSeek = True
            self.positionSlider.setValue(sliderVal)
            self.preventSeek = False
            self.positionSlider.blockSignals(False)
            self.currentLabel.setEnabled(True)
            self.currentLabel.setText(currentTime.toString("hh:mm:ss.zzz"))
        super()._currentTimestampChanged(currentTime)

    def onSliderValueChanged(self, value):
        """
        Slot called whenever the slider value is changed.

        :param value: the new slider value
        :return:
        """
        assertMainThread()
        if self.beginTime is None or self.preventSeek:
            return
        if self.actStart.isEnabled():
            ts = QDateTime.fromMSecsSinceEpoch(self.beginTime.toMSecsSinceEpoch() + value, self.beginTime.timeSpec())
            self._seekTime.emit(ts)
        else:
            logger.warning("Can't seek while playing.")

    def displayPosition(self, value):
        """
        Slot called when the slider is moved. Displays the position without actually seeking to it.

        :param value: the new slider value.
        :return:
        """
        assertMainThread()
        if self.beginTime is None:
            return
        if self.positionSlider.isSliderDown():
            ts = QDateTime.fromMSecsSinceEpoch(self.beginTime.toMSecsSinceEpoch() + value, self.beginTime.timeSpec())
            self.currentLabel.setEnabled(False)
            self.currentLabel.setText(ts.toString("hh:mm:ss.zzz"))

    def _playbackStarted(self):
        """
        Notifies about starting playback

        :return: None
        """
        assertMainThread()
        self.actStart.setEnabled(False)
        if "pausePlayback" in self.featureset:
            self.actPause.setEnabled(True)
        super()._playbackStarted()

    def _playbackPaused(self):
        """
        Notifies about pause playback

        :return: None
        """
        assertMainThread()
        logger.debug("playbackPaused received")
        if "startPlayback" in self.featureset:
            self.actStart.setEnabled(True)
        self.actPause.setEnabled(False)
        super()._playbackPaused()

    def openRecent(self):
        """
        Called when the user clicks on a recent sequence.

        :return:
        """
        assertMainThread()
        action = self.sender()
        self.browser.setActive(action.data())

    def browserActivated(self, filename):
        """
        Called when the user activated a file.

        :param filename: the new filename
        :return:
        """
        assertMainThread()
        if filename is not None and Path(filename).is_file():
            foundIdx = None
            for i, a in enumerate(self.recentSeqs):
                if a.data() == filename:
                    foundIdx = i
            if foundIdx is None:
                foundIdx = len(self.recentSeqs)-1
            for i in range(foundIdx, 0, -1):
                self.recentSeqs[i].setText(self.recentSeqs[i-1].text())
                self.recentSeqs[i].setData(self.recentSeqs[i-1].data())
                logger.debug("%d data: %s", i, self.recentSeqs[i-1].data())
                self.recentSeqs[i].setVisible(self.recentSeqs[i-1].data() is not None)
            self.recentSeqs[0].setText(self.compressFileName(filename))
            self.recentSeqs[0].setData(filename)
            self.recentSeqs[0].setVisible(True)
            self._setSequence.emit(filename)

    def _timeRatioChanged(self, newRatio):
        """
        Notifies about a changed playback time ratio,

        :param newRatio the new playback ratio as a float
        :return: None
        """
        assertMainThread()
        self.timeRatio = newRatio
        logger.debug("new timeRatio: %f", newRatio)
        for r in [1/8, 1/4, 1/2, 1, 2, 4, 8]:
            if abs(newRatio / r - 1) < 0.01:
                self.timeRatioLabel.setText(("x 1/%d"%(1/r)) if r < 1 else ("x %d"%r))
                return
        self.timeRatioLabel.setText("%.2f" % newRatio)
        super()._timeRatioChanged(newRatio)

    def selectedStream(self):
        """
        Returns the user-selected stream (for forward/backward stepping)

        :return:
        """
        return self._selectedStream

    def setSelectedStream(self, stream):
        """
        Sets the user-selected stream (for forward/backward stepping)

        :param stream the stream name.
        :return:
        """
        self._selectedStream = stream

    def saveState(self):
        """
        Saves the state of the playback control

        :return:
        """
        assertMainThread()
        propertyCollection = self.config.guiState()
        showAllFiles = self.actShowAllFiles.isChecked()
        folder = self.browser.folder()
        logger.debug("Storing current folder: %s", folder)
        try:
            propertyCollection.setProperty("PlaybackControl_showAllFiles", int(showAllFiles))
            propertyCollection.setProperty("PlaybackControl_folder", folder)
            recentFiles = [a.data() for a in self.recentSeqs if a.data() is not None]
            propertyCollection.setProperty("PlaybackControl_recent", "|".join(recentFiles))
        except PropertyCollectionPropertyNotFound:
            pass

    def restoreState(self):
        """
        Restores the state of the playback control from the given property collection

        :param propertyCollection: a PropertyCollection instance
        :return:
        """
        assertMainThread()
        propertyCollection = self.config.guiState()
        propertyCollection.defineProperty("PlaybackControl_showAllFiles", 0, "show all files setting")
        showAllFiles = propertyCollection.getProperty("PlaybackControl_showAllFiles")
        self.actShowAllFiles.setChecked(bool(showAllFiles))
        propertyCollection.defineProperty("PlaybackControl_folder", "", "current folder name")
        folder = propertyCollection.getProperty("PlaybackControl_folder")
        if Path(folder).is_dir():
            logger.debug("Setting current file: %s", folder)
            self.browser.setFolder(folder)
        propertyCollection.defineProperty("PlaybackControl_recent", "", "recent opened sequences")
        recentFiles = propertyCollection.getProperty("PlaybackControl_recent")
        idx = 0
        for f in recentFiles.split("|"):
            if f != "" and Path(f).is_file():
                self.recentSeqs[idx].setData(f)
                self.recentSeqs[idx].setText(self.compressFileName(f))
                self.recentSeqs[idx].setVisible(True)
                idx += 1
                if idx >= len(self.recentSeqs):
                    break
        for a in self.recentSeqs[idx:]:
            a.setData(None)
            a.setText("")
            a.setVisible(False)

    @staticmethod
    def compressFileName(filename):
        """
        Compresses long path names with an ellipsis (...)

        :param filename: the original path name as a Path or string instance
        :return: the compressed path name as a string instance
        """
        p = Path(filename)
        parts = tuple(p.parts)
        if len(parts) >= 6:
            p = Path(*parts[:2]) / "..." /  Path(*parts[-2:])
        return str(p)
Esempio n. 2
0
class MainWindow(QMainWindow, Ui_MainWindow):
    """
    The main GUI window
    """
    HELP_URL = "https://www.github.com/jakoma02/pyCovering"

    model_type_changed = Signal()
    view_type_changed = Signal()
    model_changed = Signal(GeneralCoveringModel)
    view_changed = Signal(GeneralView)
    info_updated = Signal(GeneralCoveringModel, GeneralView)
    settings_changed = Signal()

    def __init__(self):
        QMainWindow.__init__(self)

        self.model = None
        self.view = None

        self.setupUi(self)
        self.create_action_groups()

        # A dict Action name -> GeneralView, so that we can set the
        # correct view upon view type action trigger
        self.action_views = dict()

        self.actionAbout_2.triggered.connect(self.show_about_dialog)
        self.actionDocumentation.triggered.connect(self.show_help)
        self.actionChange_dimensions.triggered.connect(
            self.show_dimensions_dialog)
        self.actionChange_tile_size.triggered.connect(
            self.show_block_size_dialog)

        self.actionGenerate.triggered.connect(self.start_covering)

        self.model_type_changed.connect(self.update_model_type)
        self.model_type_changed.connect(self.update_view_type_menu)
        self.model_type_changed.connect(self.update_constraints_menu)
        self.model_type_changed.connect(self.enable_model_menu_buttons)

        self.model_changed.connect(
            lambda _: self.info_updated.emit(self.model, self.view))

        self.view_type_changed.connect(self.update_view_type)

        self.view_changed.connect(
            lambda _: self.info_updated.emit(self.model, self.view))
        self.info_updated.connect(self.infoText.update)
        self.info_updated.connect(self.update_view)

        self.tiles_list_model = BlockListModel()
        self.tilesList.setModel(self.tiles_list_model)

        self.model_changed.connect(self.tiles_list_model.update_data)
        self.tiles_list_model.checkedChanged.connect(self.set_block_visibility)

        self.model_changed.emit(self.model)
        self.update_view_type_menu()

    def set_block_visibility(self, block, visible):
        """
        Update model visibility based on block list checkbox change
        """
        block.visible = visible
        self.model_changed.emit(self.model)

    def show_about_dialog(self):
        """
        Shows the "About" dialog
        """
        dialog = AboutDialog(self)
        dialog.open()

    def start_covering(self):
        """
        Starts covering, shows the corresponding dialog
        """
        if self.model is None:
            QMessageBox.warning(self, "No model", "No model selected!")
            return

        self.model.reset()

        self.thread = GenerateModelThread(self.model)
        dialog = CoveringDialog(self)

        dialog.rejected.connect(self.cancel_covering)

        self.thread.success.connect(dialog.accept)
        self.thread.success.connect(self.covering_success)
        self.thread.failed.connect(dialog.reject)
        self.thread.failed.connect(self.covering_failed)

        self.thread.done.connect(lambda: self.model_changed.emit(self.model))

        self.thread.start()
        dialog.open()

    def show_block_size_dialog(self):
        """
        Shows "Change block size" dialog
        """
        if self.model is None:
            QMessageBox.warning(self, "No model", "No model selected!")
            return

        curr_min = self.model.min_block_size
        curr_max = self.model.max_block_size

        dialog = BlockSizeDialog(self)
        dialog.sizesAccepted.connect(self.block_sizes_accepted)
        dialog.set_values(curr_min, curr_max)
        dialog.open()

    def show_dimensions_dialog(self):
        """
        Shows "Change dimensions" dialog
        """
        if self.model is None:
            QMessageBox.warning(self, "No model", "No model selected!")
            return

        if isinstance(self.model, TwoDCoveringModel):
            curr_width = self.model.width
            curr_height = self.model.height

            dialog = TwoDDimensionsDialog(self)
            dialog.set_values(curr_width, curr_height)

            dialog.dimensionsAccepted.connect(self.two_d_dimensions_accepted)
            dialog.show()

        elif isinstance(self.model, PyramidCoveringModel):
            curr_size = self.model.size

            dialog = PyramidDimensionsDialog(self)
            dialog.set_value(curr_size)

            dialog.dimensionsAccepted.connect(self.pyramid_dimensions_accepted)
            dialog.show()

    def two_d_dimensions_accepted(self, width, height):
        """
        Updates TwoDCoveringModel dimensions (after dialog confirmation)
        """
        assert isinstance(self.model, TwoDCoveringModel)

        self.model.set_size(width, height)
        self.model_changed.emit(self.model)

        self.message("Size updated")

    def pyramid_dimensions_accepted(self, size):
        """
        Updates PyramidCoveringModel dimensions (after dialog confirmation)
        """
        assert isinstance(self.model, PyramidCoveringModel)

        # PyLint doesn't know that this is a `PyramidCoveringModel`
        # and not a `TwoDCoveringModel`
        # pylint: disable=no-value-for-parameter
        self.model.set_size(size)
        self.model_changed.emit(self.model)

        self.message("Size updated")

    def block_sizes_accepted(self, min_val, max_val):
        """
        Updates covering model block size (after dialog confirmation)
        """
        assert self.model is not None

        self.model.set_block_size(min_val, max_val)
        self.model_changed.emit(self.model)

        self.message("Block size updated")

    @staticmethod
    def update_view(model, view):
        """
        Refreshes contents of given view
        """
        if view is None:
            return
        if model is not None and model.is_filled():
            view.show(model)
        else:
            view.close()

    def message(self, msg):
        """
        Shows a log message in the "Messages" window
        """
        self.messagesText.add_message(msg)

    def show_help(self):
        """
        Opens a webpage with help
        """
        webbrowser.open(self.HELP_URL)

    def create_action_groups(self):
        """
        Groups exclusive choice menu buttons in action groups.

        This should ideally be done in UI files, but Qt designer
        doesn't support it.
        """
        self.model_type_group = QActionGroup(self)
        self.model_type_group.addAction(self.action2D_Rectangle_2)
        self.model_type_group.addAction(self.actionPyramid_2)

        self.view_type_group = QActionGroup(self)

        self.model_type_group.triggered.connect(self.model_type_changed)
        self.view_type_group.triggered.connect(self.view_type_changed)

    def update_model_type(self):
        """
        Sets the current model after model type changed in menu
        """
        selected_model = self.model_type_group.checkedAction()

        if selected_model == self.action2D_Rectangle_2:
            model = TwoDCoveringModel(10, 10, 4, 4)
        elif selected_model == self.actionPyramid_2:
            model = PyramidCoveringModel(10, 4, 4)
        else:
            model = None

        self.model = model
        self.model_changed.emit(model)
        self.message("Model type updated")

    def enable_model_menu_buttons(self):
        """
        Enable menu buttons that are disabled at program start
        """
        self.actionChange_dimensions.setEnabled(True)
        self.actionChange_tile_size.setEnabled(True)
        self.actionGenerate.setEnabled(True)

    def update_view_type(self):
        """
        Sets the current view after view type changed in menu
        """
        if self.view is not None:
            self.view.close()

        selected_action = self.view_type_group.checkedAction()

        if selected_action is None:
            # Model was probably changed
            self.view = None
        else:
            action_name = selected_action.objectName()
            selected_view = self.action_views[action_name]

            self.view = selected_view()  # New instance of that view

            self.message("View type updated")

        self.view_changed.emit(self.view)

    def cancel_covering(self):
        """
        Stops ongoing covering
        """
        if self.thread.isRunning():
            # The thread is being terminated
            self.model.stop_covering()
            self.message("Covering terminated")

    def covering_success(self):
        """
        Prints a success log message (for now)
        """
        self.message("Covering successful")

    def covering_failed(self):
        """
        Prints a fail log message and shows an error window (for now)
        """
        self.message("Covering failed")
        QMessageBox.critical(self, "Failed", "Covering failed")

    def model_views(self, model):
        """
        Returns a list of tuples for all views
        for given mode as  (name, class)
        """

        if isinstance(model, TwoDCoveringModel):
            return [
                ("2D Print view", text_view_decorator(TwoDPrintView, self)),
                ("2D Visual view", parented_decorator(TwoDVisualView, self))
            ]

        if isinstance(model, PyramidCoveringModel):
            return [("Pyramid Print view",
                     text_view_decorator(PyramidPrintView, self)),
                    ("Pyramid Visual view", PyramidVisualView)]

        return []

    @staticmethod
    def model_constraints(model):
        """
        Returns a list of tuples for all constraint watchers
        for given mode as  (name, class)
        """

        if isinstance(model, TwoDCoveringModel):
            return [("Path blocks", PathConstraintWatcher)]

        if isinstance(model, PyramidCoveringModel):
            return [("Path blocks", PathConstraintWatcher),
                    ("Planar blocks", PlanarConstraintWatcher)]

        return []

    def update_view_type_menu(self):
        """
        Updates options for view type menu afted model type change
        """
        view_type_menu = self.menuType_2
        view_type_menu.clear()

        for action in self.view_type_group.actions():
            self.view_type_group.removeAction(action)

        all_views = self.model_views(self.model)

        if not all_views:
            # Likely no model selected
            view_type_menu.setEnabled(False)
            return

        view_type_menu.setEnabled(True)

        self.action_views.clear()

        for i, view_tuple in enumerate(all_views):
            name, view = view_tuple

            # As good as any, we just need to distinguish the actions
            action_name = f"Action{i}"

            action = QAction(self)
            action.setText(name)
            action.setCheckable(True)
            action.setObjectName(action_name)

            # So that we can later see which view should be activated
            self.action_views[action_name] = view
            view_type_menu.addAction(action)
            self.view_type_group.addAction(action)

        self.update_view_type()

    def watcher_set_active(self, constraint, value):
        """
        A slot, activate/deactivate constraint depending on value (True/False)
        """

        if value is True:
            self.model.add_constraint(constraint)
        else:
            self.model.remove_constraint(constraint)

        self.model_changed.emit(self.model)
        self.message("Constraint settings changed")

    def update_constraints_menu(self):
        """
        Updates options for model constraints after model type change
        """

        cstr_menu = self.menuConstraints
        cstr_menu.clear()

        all_constraints = self.model_constraints(self.model)

        for name, watcher in all_constraints:
            action = QAction(self)
            action.setText(name)
            action.setCheckable(True)

            action.toggled.connect(lambda val, watcher=watcher: self.
                                   watcher_set_active(watcher, val))

            cstr_menu.addAction(action)

        cstr_menu.setEnabled(True)

    def close(self):
        """
        While closing the window also closes the view
        """
        if self.view is not None:
            self.view.close()

        super().close()
Esempio n. 3
0
class ProfileManager(QObject):
    profile_changed = Signal(Profile)

    def __init__(self, menu, parent=None):
        super().__init__(parent)
        self.menu = menu
        actions = self.menu.actions()
        self.sep = actions[-2]
        self.parent = parent
        self.profiles = []
        self.actGroup = None
        self.active_profile = None
        self.load()
        QApplication.instance().aboutToQuit.connect(self.save)

    def load(self):
        settings = QSettings()
        settings.beginGroup('profiles')
        groups = settings.childGroups()
        self.profiles = [
            Profile(p,
                    settings.value(f'{p}/path'), settings.value(f'{p}/mask'),
                    settings.value(f'{p}/pattern')) for p in groups
        ]
        settings.endGroup()
        self.actGroup = QActionGroup(self.parent)
        self.actGroup.triggered.connect(self.set_active_profile)
        active = settings.value('active_profile')
        self.active_profile = self.get_profile(active)
        if len(self.profiles) > 0:
            for name in self.names():
                action = self.do_add_action(name)
                if name == active:
                    action.setChecked(True)

    def save(self):
        settings = QSettings()
        settings.beginGroup('profiles')
        settings.remove('')
        for p in self.profiles:
            settings.setValue(f'{p.name}/path', p.path)
            settings.setValue(f'{p.name}/mask', p.mask)
            settings.setValue(f'{p.name}/pattern', p.pattern)
        settings.endGroup()
        if self.active_profile is not None:
            settings.setValue('active_profile', self.active_profile.name)

    def names(self):
        for p in self.profiles:
            yield p.name

    def add_action(self, name, path, mask, pattern):
        if name in self.names():
            app = QApplication.instance()
            QMessageBox.warning(
                self.parent, app.applicationName(),
                app.translate('profile_manager',
                              '{} already exists').format(name))
        else:
            self.profiles.append(Profile(name, path, mask, pattern))
            self.do_add_action(name)

    def do_add_action(self, name):
        action = QAction(name, self.menu)
        self.menu.insertAction(self.sep, action)
        action.setCheckable(True)
        self.actGroup.addAction(action)
        return action

    def add_from_dialog(self, dialog):
        self.add_action(dialog.get_name(), dialog.get_path(),
                        dialog.get_mask(), dialog.get_pattern())

    def get_profile(self, name):
        for p in self.profiles:
            if name == p.name:
                return p
        return None

    def set_active_profile(self):
        action = self.actGroup.checkedAction()
        self.active_profile = self.get_profile(action.text()) if action \
            is not None else None
        self.profile_changed.emit(self.active_profile)

    def reset_profiles(self, profiles):
        self.clear_menu()
        self.profiles = profiles
        if len(profiles) > 0:
            if self.active_profile.name not in (p.name for p in profiles):
                active = profiles[0].name
            else:
                active = self.active_profile.name
            for name in self.names():
                action = self.do_add_action(name)
                if name == active:
                    action.setChecked(True)
        else:
            active = None
        self.active_profile = self.get_profile(active)
        self.profile_changed.emit(self.active_profile)

    def clear_menu(self):
        while len(self.actGroup.actions()) > 0:
            self.actGroup.removeAction(self.actGroup.actions()[0])