Пример #1
0
 def set_intervals(self, intervals):
     self.timers.clear()
     self.intervals = intervals
     for val in intervals:
         timer = QTimer(self)
         timer.setSingleShot(True)
         timer.setInterval(val * 1000)
         timer.setTimerType(Qt.PreciseTimer)
         timer.timeout.connect(self.target)
         if (val == intervals[-1]): timer.timeout.connect(self.callback)
         self.timers.append(timer)
Пример #2
0
class IdleDetection(QObject):
    def __init__(self, parent):
        super(IdleDetection, self).__init__(parent)
        self._parent: QWidget = parent

        # Report user inactivity
        self._idle_timer = QTimer()
        self._idle_timer.setSingleShot(True)
        self._idle_timer.setTimerType(Qt.VeryCoarseTimer)
        self._idle_timer.setInterval(10000)

        # Detect inactivity for automatic session save
        self._idle_timer.timeout.connect(self.set_inactive)
        self.idle = False

        self.parent.installEventFilter(self)

    def is_active(self):
        return self.idle

    def set_active(self):
        self.idle = False
        self._idle_timer.stop()

    def set_inactive(self):
        self.idle = True

    def eventFilter(self, obj, eve):
        if eve is None or obj is None:
            return False

        if eve.type() == QEvent.KeyPress or \
           eve.type() == QEvent.MouseMove or \
           eve.type() == QEvent.MouseButtonPress:
            self.set_active()
            return False

        if not self._idle_timer.isActive():
            self._idle_timer.start()

        return False
Пример #3
0
class ViewerApp(QApplication):
    idle_event = Signal()

    def __init__(self, version: str):
        super(ViewerApp, self).__init__(sys.argv)
        self.setApplicationName(f'{APP_NAME}')
        self.setApplicationVersion(version)
        self.setApplicationDisplayName(f'{APP_NAME} v{version}')
        load_style(self)

        self.idle_timer = QTimer()
        self.idle_timer.setSingleShot(True)
        self.idle_timer.setTimerType(Qt.VeryCoarseTimer)
        self.idle_timer.setInterval(3 * 60 * 1000)  # 3 min until idle
        self.idle_timer.timeout.connect(self.set_idle)
        self.installEventFilter(self)

        self.window = ViewerWindow(self)
        self.window.show()

    def eventFilter(self, obj, eve):
        if eve is None or obj is None:
            return False

        if eve.type() == QEvent.KeyPress or \
           eve.type() == QEvent.MouseMove or \
           eve.type() == QEvent.MouseButtonPress:
            self.set_active()
            return False

        return False

    def set_active(self):
        self.idle_timer.start()

    def set_idle(self):
        LOGGER.debug('Application is idle.')
        self.idle_event.emit()
Пример #4
0
class MainWindow(QMainWindow):
    def __init__(self, app, state: State, screenFPS, fixedFPS, parent=None):
        """
        The MainWindow is created by the application, here we handle ui/scenes and send out updates.
        :param state: The current state we want to start with.
        :param parent: The widget this held under, None by default because this is top widget
        """
        QMainWindow.__init__(self, parent)
        self.app = app  # the application created
        self.gameScreen = None  # where all graphics are drawn
        self.uiManager = UiManager(self, customWidgets=[
            GameScreen
        ])  # Loads widgets into window from a file
        self.stateManager = StateManager(
            self, state)  # Manages state updates and transitions

        self.updateTimer = QTimer(self)  # Used to create update calls
        self.updateTimer.setTimerType(Qt.PreciseTimer)
        self.updateTimer.timeout.connect(self.__updateHandler)

        self.updateFPSTimer = QTimer(self)  # Used to manage frame timings
        self.updateFPSTimer.timeout.connect(self.__calculateFPS)

        self.lastScreen = 0  # Last frame time
        self.lastFixed = 0

        self.fixedFps = 0  # Calculated FPS
        self.screenFps = 0

        self.fixedTime = 0  # Accumulated Time
        self.screenTime = 0

        self.fixedFrames = 1  # Accumulated Frames
        self.screenFrames = 1

        self.targetfixedFPS = fixedFPS
        self.targetscreenFPS = screenFPS
        self.fixedTiming = 1 / fixedFPS  # seconds / frames per second
        self.screenTiming = 1 / screenFPS
        self.avgscreenTime = 0  # Average amount of time it takes the screen to update includes state update

    def start(self):
        """
        Called to start the window
        :return: None
        """
        self.stateManager.start()  # start the state

        self.updateTimer.start(0)  # start game loops
        self.updateFPSTimer.start(150)  # start frame timing management

    def __updateHandler(self):
        """
        In here fixed and scene updates are made based on timing
        :return: None
        """
        # TODO pref counter
        start = time.clock()  # start time of the update call
        if time.clock(
        ) - self.lastFixed > self.fixedTiming:  # Has enough time passed for next call
            self.__fixedUpdate()
            self.fixedFrames += 1

        now = time.clock()  # Time after physics has been calculated
        if (now - start) < (self.fixedTiming - self.avgscreenTime
                            ):  # Is there enough time left to call update
            if (now - self.lastScreen
                ) >= self.screenTiming:  # Has enough time passed for next call
                if self.gameScreen is not None:
                    self.gameScreen.update()
                self.__stateUpdate()
                self.screenFrames += 1

        self.avgscreenTime = (
            self.avgscreenTime +
            (time.clock() - now)) / 2  # How long the state update took

    def __stateUpdate(self):
        self.screenTime += 1 / (time.clock() - self.lastScreen)
        self.lastScreen = time.clock()
        self.stateManager.update()

    def __fixedUpdate(self):
        self.fixedTime += 1 / (time.clock() - self.lastFixed)
        self.lastFixed = time.clock()
        self.stateManager.fixedUpdate()

    def closeEvent(self, event: PySide2.QtGui.QCloseEvent):
        """
        Called when the window its closed
        :return: None
        """
        print("Window closed.")
        self.updateTimer.stop()
        self.updateFPSTimer.stop()
        self.stateManager.exit()

    def __calculateFPS(self):
        """
        Averages FPS and adjust frame timings
        :return: None
        """
        self.fixedFps = self.fixedTime / self.fixedFrames
        self.fixedTime = 0
        self.fixedFrames = 1

        self.screenFps = self.screenTime / self.screenFrames
        self.screenTime = 0
        self.screenFrames = 1

        if self.fixedFps < self.targetfixedFPS:
            self.fixedTiming *= 0.99
        elif self.fixedFps > self.targetfixedFPS:
            self.fixedTiming *= 1.01

        if self.screenFps < self.targetscreenFPS:
            self.screenTiming *= 0.99
        elif self.screenFps > self.targetscreenFPS:
            self.screenTiming *= 1.01

    def resizeEvent(self, event: PySide2.QtGui.QResizeEvent):
        self.gameScreen: GameScreen
        if self.gameScreen is not None:
            self.gameScreen.setGeometry(0, 0, self.width(), self.height())
Пример #5
0
class DataView(QWidget):
    def __init__(self, main_window, experiment_data):
        super(DataView, self).__init__()

        self.file_name = None
        self.main_window = main_window
        self.experiment_data = experiment_data
        self.condition_dataframe = None
        self.maximum_time_sec = None
        self.maximum_time_msec = None
        self.timer = QTimer(self)

        self.behavioralView = BehavioralView()
        self.eyetrackingView = EyeTrackingView(main_window)
        self.physioRespiration = PsychoPhysiologicalView('Respiration')
        self.physioHeartRate = PsychoPhysiologicalView('HeartRate')
        self.physioPupilDilation = PsychoPhysiologicalView('PupilDilation')
        self.fMRIRoiView = fMRIRoiView()
        self.fMRIFullView = fMRIFullView()

        self.create_layout()

        # initialize view
        self.stimuli_changed(0)

    def create_layout(self):
        left_layout = self.create_layout_left()
        right_layout = self.create_layout_right()
        bottom_layout = self.create_layout_bottom()

        center_layout = QHBoxLayout()
        center_layout.addLayout(left_layout)
        center_layout.addLayout(right_layout)

        main_layout = QVBoxLayout(self)
        main_layout.addLayout(center_layout)
        main_layout.addLayout(bottom_layout)

    def create_layout_left(self):
        left_layout = QVBoxLayout()
        self.participant_label = QLabel("Participant: " + self.experiment_data['participant'])
        self.participant_label.setFont(QtGui.QFont("Times", 16, QtGui.QFont.Bold))
        self.participant_label.setTextInteractionFlags(Qt.TextBrowserInteraction)

        left_layout.addWidget(self.participant_label)
        left_stimuli_layout = QHBoxLayout()
        stimuli_label = QLabel("Stimuli: ")
        stimuli_label.setFont(QtGui.QFont("Times", 16, QtGui.QFont.Bold))
        left_stimuli_layout.addWidget(stimuli_label)

        self.stimuli_selection_box = QComboBox()
        self.stimuli_selection_box.setFont(QtGui.QFont("Times", 14, QtGui.QFont.Normal))
        for condition in self.experiment_data['conditions']:
            self.stimuli_selection_box.addItem(condition)

        self.stimuli_selection_box.currentIndexChanged[int].connect(self.stimuli_changed)
        left_stimuli_layout.addWidget(self.stimuli_selection_box)
        left_stimuli_layout.addStretch()
        left_layout.addLayout(left_stimuli_layout)
        left_layout.addStretch()

        if config.PLUGIN_EYETRACKING_ACTIVE:
            self.eyetrackingView.create_view(left_layout)
            left_layout.addStretch()

        if config.PLUGIN_BEHAVORIAL_ACTIVE:
            self.behavioralView.create_view(left_layout)

        return left_layout

    def create_layout_bottom(self):
        bottomLayout = QHBoxLayout()
        self.time = 0
        self.timeLabel = QLabel("0:00.00 / 0:00.00")
        self.timeLabel.setFont(QtGui.QFont("Times", 14, QtGui.QFont.Normal))

        self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.slider.setRange(0, 99)
        self.slider.setValue(0)
        self.slider.setTickInterval(100)
        self.slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
        self.slider.setToolTip("Time")
        self.slider.sliderMoved.connect(self.set_time)

        self.playButton = QPushButton("Play")
        self.playButton.clicked.connect(self.play_data)
        self.isPlaying = False

        bottomLayout.addStretch()
        bottomLayout.addWidget(self.playButton)
        playerLayout = QVBoxLayout()
        timeLayout = QHBoxLayout()
        timeLayout.addStretch()
        timeLayout.addWidget(self.timeLabel)
        timeLayout.addStretch()

        playerLayout.addLayout(timeLayout)
        playerLayout.addWidget(self.slider)
        bottomLayout.addLayout(playerLayout)
        bottomLayout.addStretch()

        return bottomLayout

    def create_layout_right(self):
        right_layout = QVBoxLayout()

        if config.PLUGIN_PHYSIO_ACTIVE:
            physio_label = QLabel("Psycho-Physiological Data")
            physio_label.setFont(QtGui.QFont("Times", 16, QtGui.QFont.Bold))
            right_layout.addWidget(physio_label)

            physio_label_layout = QHBoxLayout()
            physio_plot_layout = QHBoxLayout()

            self.physioHeartRate.create_view(physio_label_layout, physio_plot_layout, self.experiment_data['participant'])
            self.physioRespiration.create_view(physio_label_layout, physio_plot_layout, self.experiment_data['participant'])
            self.physioPupilDilation.create_view(physio_label_layout, physio_plot_layout, self.experiment_data['participant'])

            right_layout.addLayout(physio_label_layout)
            right_layout.addLayout(physio_plot_layout)

        if config.PLUGIN_FMRI_ACTIVE:
            self.fMRIRoiView.create_view(right_layout, self.experiment_data)
            self.fMRIFullView.create_view(right_layout, self.experiment_data)

        right_layout.addStretch(1)

        return right_layout

    def set_time(self, time):
        self.time = time
        self.update_data(force_update=True)

    def stimuli_changed(self, index):
        selected_condition = self.experiment_data['conditions'][index]

        self.condition_dataframe = self.experiment_data['dataframe'][self.experiment_data['dataframe']['Condition'] == selected_condition]
        self.condition_start_pos = self.condition_dataframe['Time'].iloc[0]
        self.condition_end_pos = self.condition_dataframe['Time'].iloc[-1]
        self.condition_dataframe.reset_index(inplace=True, drop=True)

        self.maximum_time_sec = math.floor(len(self.condition_dataframe) / 100)
        self.maximum_time_msec = len(self.condition_dataframe) % 100

        self.time = 0
        self.shifted_scan = None
        self.slider.setMaximum(self.maximum_time_sec * 100 + self.maximum_time_msec)
        self.slider.setValue(0)

        self.timeLabel.setText("0:00.00 / 0:" + str(self.maximum_time_sec).zfill(2) + "." + str(self.maximum_time_msec).zfill(2))

        # update plug-in views
        self.behavioralView.update_view(self.experiment_data, selected_condition, self.condition_start_pos)
        self.eyetrackingView.setConditionDataframe(selected_condition, self.condition_dataframe)

        self.update_data()

    def play_data(self):
        if self.isPlaying:
            self.isPlaying = False
            self.timer.stop()
            self.playButton.setText('Play')
        else:
            self.timer.setTimerType(QtCore.Qt.TimerType.PreciseTimer)
            self.timer.timeout.connect(self.update_data)
            self.timer.start(10)
            self.isPlaying = True
            self.playButton.setText('Stop')

    def update_data(self, force_update=False):
        # check if timer should stop
        if self.time >= (self.maximum_time_sec * 100 + self.maximum_time_msec):
            self.timer.stop()
            self.isPlaying = False
            self.playButton.setText('Play')
        else:
            self.slider.setValue(self.time)
            self.timeLabel.setText("0:" + str(math.floor(self.time / 100)).zfill(2) + "." + str(self.time % 100).zfill(2) + " / 0:" + str(self.maximum_time_sec).zfill(2) + "." + str(self.maximum_time_msec).zfill(2))

            # psycho-physiological data
            if force_update or self.time % 10 == 0:
                self.physioRespiration.update_text(self.condition_dataframe['Respiration'][self.time])
                self.physioHeartRate.update_text(self.condition_dataframe['HeartRate'][self.time])
                self.physioPupilDilation.update_text(self.condition_dataframe['PupilDilation'][self.time])

            # only update plots every second instead of every millisecond (for performance)
            # use preprocessed plots and just switch image for faster speeds
            if force_update or self.time % 100 == 0:
                self.physioRespiration.update_plot(self.time)
                self.physioHeartRate.update_plot(self.time)
                self.physioPupilDilation.update_plot(self.time)

            # eye-tracking data
            self.eyetrackingView.setTime(self.time)

            # fMRI data
            current_scan = math.floor(((self.condition_start_pos + self.time) / 100) * config.FMRI_RESOLUTION)
            shifted_scan = current_scan + SHIFT_FMRI_SCAN

            if self.shifted_scan != shifted_scan:
                self.fMRIRoiView.update_data(self.experiment_data, current_scan)
                self.fMRIFullView.update_data(self.experiment_data, shifted_scan)

                self.shifted_scan = shifted_scan

            # todo: it should only add 1ms per tick, for now artificially jump a few ms to fix slow replay time
            self.time += 4  # my Macbook is really slow
            # self.time += 2  # windows PC is faster

    def dialog(self):
        msg_box = QMessageBox()
        msg_box.setText("Not supported yet.")
        msg_box.setStandardButtons(QMessageBox.Ok)
        msg_box.exec_()
Пример #6
0
class KnechtSession(QObject):
    session_zip = CreateZip.settings_dir / 'Session_data.zip'
    files_list_name = 'session_files.json'

    class FileNameStorage:
        def __init__(self):
            self.store = {
                'example_filename.xml': {'file': Path(), 'order': 0, 'clean_state': False}
                }
            self.store = dict()

            self.dupli_count = 0

        def file_names(self) -> List[str]:
            return [file_entry['file'].name for file_entry in self.store.values()]

        def add_file(self, file: Path, tab_order: int, clean: bool) -> Path:
            # Rename duplicate file names
            if file.name in self.store:
                self.dupli_count += 1
                file = file.parent / Path(file.stem + f'_{self.dupli_count:01d}' + file.suffix)

            # Add entry
            self.store[file.name] = {
                'file': file.as_posix(), 'order': tab_order, 'clean_state': clean
                }

            return file

        def restore_file_order(self, load_dir: Path) -> List[Tuple[Path, bool]]:
            """ Restore the order in which the files have been saved """
            file_ls = list()
            files_names_to_restore = [f.name for f in load_dir.glob('*.xml')]

            for file_entry in self.sort_storage_dict_entries(self.store):
                file = Path(file_entry.get('file') or '')
                if file.name in files_names_to_restore:
                    file_ls.append(file)

            return file_ls[::-1]

        @staticmethod
        def sort_storage_dict_entries(d) -> List[dict]:
            entry_list, order_list = list(), list()

            for k, entry in d.items():
                if not isinstance(entry, dict):
                    continue

                order = entry.get('order') or 0
                order_list.append(order)
                insert_idx = bisect(sorted(order_list), order) - 1

                entry_list.insert(insert_idx, d[k])

            return entry_list

    def __init__(self, ui, idle_save: bool=False):
        """ Save and restore user session of opened documents

        :param modules.gui.main_ui.KnechtWindow ui: The main UI window
        :param bool idle_save: auto save session when UI is idle
        """
        super(KnechtSession, self).__init__(ui)
        self.restore_files_storage = self.FileNameStorage()

        self.load_save_mgr = SaveLoadController(self, create_recent_entries=False)
        # Load models into views and associate original save file path
        self.load_save_mgr.model_loaded.connect(self.model_loaded)
        # We will silently ignore load errors on session restore
        self.load_save_mgr.load_aborted.connect(self._load_next)

        self.idle_timer = QTimer()
        self.save_timer = QTimer()
        self.idle = False

        if idle_save:
            self.idle_timer.setSingleShot(True)
            self.save_timer.setSingleShot(True)
            self.idle_timer.setTimerType(Qt.VeryCoarseTimer)
            self.save_timer.setTimerType(Qt.VeryCoarseTimer)
            self.idle_timer.setInterval(10000)
            self.save_timer.setInterval(10000)

            # Detect inactivity for automatic session save
            self.idle_timer.timeout.connect(self.set_inactive)
            self.save_timer.timeout.connect(self.auto_save)
            ui.app.installEventFilter(self)

        self.ui = ui
        self.load_dir = None
        self.load_queue = list()

    def set_active(self):
        self.idle = False
        self.idle_timer.start()

    def set_inactive(self):
        self.idle = True
        self.save_timer.start()
        LOGGER.debug('Idling.')

    def eventFilter(self, obj, eve):
        if eve is None or obj is None:
            return False

        if eve.type() == QEvent.KeyPress or \
           eve.type() == QEvent.MouseMove or \
           eve.type() == QEvent.MouseButtonPress:
            self.set_active()
            return False

        return False

    def _load_next(self):
        if not self.load_queue:
            self.restore_finished()
            return

        file = self.load_queue.pop()
        file = self.load_dir / file.name
        self.load_save_mgr.open(file)

    @Slot(KnechtModel, Path)
    def model_loaded(self, model: KnechtModel, file: Path):
        LOGGER.debug('Restoring: %s', file.name)
        # Update progress
        view = self.ui.view_mgr.current_view()
        view.progress_msg.hide_progress()
        clean_state = True

        # Restore original save path
        if file.name in self.restore_files_storage.store:
            if isinstance(self.restore_files_storage.store[file.name], dict):
                file = Path(self.restore_files_storage.store[file.name].get('file') or '')
                clean_state = self.restore_files_storage.store[file.name].get('clean_state')
            else:
                file = Path(self.restore_files_storage.store[file.name])

        if file.name == 'Variants_Tree.xml':
            # Update Variants Tree
            update_model = UpdateModel(self.ui.variantTree)
            update_model.update(model)
            new_view = self.ui.variantTree
        else:
            # Create a new view inside a new tab or load into current view if view model is empty
            new_view = self.ui.view_mgr.create_view(model, file)

        # Refresh model data
        new_view.model().sourceModel().initial_item_id_connection()
        new_view.model().sourceModel().refreshData()

        # Mark document non-clean
        if isinstance(clean_state, bool):
            if not clean_state:
                new_view.undo_stack.resetClean()

        self._load_next()

    def auto_save(self):
        if self.load_queue or not self.idle:
            return

        result = self.save()

        if result:
            self.ui.statusBar().showMessage(_('Sitzung während Leerlauf erfolgreich gespeichert'))

    def save(self) -> bool:
        tmp_dir = CreateZip.create_tmp_dir()
        storage = self.FileNameStorage()
        result = True

        documents_list = list()
        documents_list.append(
            (self.ui.variantTree, Path('Variants_Tree.xml'))
            )

        for widget, file in zip(self.ui.view_mgr.file_mgr.widgets, self.ui.view_mgr.file_mgr.files):
            if hasattr(widget, 'user_view'):
                documents_list.append(
                    (widget.user_view, file)
                    )

        for view, file in documents_list:
            if not view.model().rowCount() or not file:
                continue

            tab_order_index = self.get_tab_order(file)

            file = storage.add_file(file, tab_order_index, view.undo_stack.isClean())

            # Save document
            tmp_file = tmp_dir / file.name
            r, _ = self.load_save_mgr.save(tmp_file, view)

            if not r:
                result = False
                del storage.store[file.name]
            else:
                LOGGER.debug('Saved session document: %s', tmp_file.name)

        # Save original file paths stored in Files class
        Settings.save(storage, tmp_dir / self.files_list_name)

        if not CreateZip.save_dir_to_zip(tmp_dir, self.session_zip):
            result = False

        CreateZip.remove_dir(tmp_dir)
        return result

    def get_tab_order(self, file: Path):
        """ Find the current file in the TabWidgets TabBar """
        current_tab_text = ''
        for tab_idx in range(0, self.ui.view_mgr.tab.count()):
            tab_file = self.ui.view_mgr.file_mgr.get_file_from_widget(self.ui.view_mgr.tab.widget(tab_idx))
            if tab_file == file:
                current_tab_text = self.ui.view_mgr.tab.tabText(tab_idx)

        for tab_bar_idx in range(0, self.ui.view_mgr.tab.tabBar().count()):
            if self.ui.view_mgr.tab.tabBar().tabText(tab_bar_idx) == current_tab_text:
                break
        else:
            tab_bar_idx = 0

        return tab_bar_idx

    def restore(self) -> bool:
        """ Restore a user session asynchronous """
        if not path_exists(self.session_zip):
            return False

        self.load_dir = CreateZip.create_tmp_dir()

        try:
            with ZipFile(self.session_zip, 'r') as zip_file:
                zip_file.extractall(self.load_dir)
        except Exception as e:
            LOGGER.error(e)
            return False

        # Restore original file save paths
        Settings.load(self.restore_files_storage, self.load_dir / self.files_list_name)

        # Restore in saved order
        for file in self.restore_files_storage.restore_file_order(self.load_dir):
            LOGGER.debug('Starting restore of document: %s @ %s',
                         file.name, self.restore_files_storage.store.get(file.name))
            self.load_queue.append(file)

        self._load_next()
        return True

    def restore_finished(self):
        CreateZip.remove_dir(self.load_dir)
        LOGGER.debug('Session restored.')

        self.ui.statusBar().showMessage(_('Sitzungswiederherstellung abgeschlossen'), 8000)
        self.ui.view_mgr.tab.setCurrentIndex(0)
class PlumbingBridge(QObject):
    engineLoaded = Signal(top.PlumbingEngine)
    dataUpdated = Signal(dict, np.ndarray)
    pausedChanged = Signal()

    def __init__(self):
        QObject.__init__(self)

        self.engine = None
        self.step_size = 0.05e6  # TODO(jacob): Add a UI field for this.
        self.timer = QTimer()
        self.timer.setTimerType(Qt.PreciseTimer)
        self.timer.timeout.connect(self.step_time)
        self._paused = True

    def load_engine(self, new_engine):
        self.engine = new_engine
        self.engineLoaded.emit(new_engine)
        self.timeStop()

    def load_from_files(self, filepaths):
        parser = top.pdl.Parser(filepaths)
        new_engine = parser.make_engine()

        self.load_engine(new_engine)

    def step_time(self):
        pressures = self.engine.step(self.step_size)
        self.dataUpdated.emit(pressures, np.array([self.engine.time]))

    def set_paused(self, should_pause):
        if should_pause:
            self.timer.stop()
        self._paused = should_pause
        self.pausedChanged.emit()

    def get_paused(self):
        return self._paused

    # `paused` is both a Python property and a Qt property, which means
    # we can directly treat it as a variable. Writing `paused = True`
    # will call set_paused(True).
    paused = Property(bool, get_paused, set_paused, notify=pausedChanged)

    @Slot()
    def loadFromDialog(self):
        # TODO(jacob): Make this menu remember the last file opened
        filepaths, _ = QFileDialog.getOpenFileNames(self.parent(),
                                                    'Load PDL files')
        if len(filepaths) > 0:
            self.load_from_files(filepaths)

    # Time controls

    @Slot()
    def timePlayBackwards(self):
        pass

    @Slot()
    def timeStepBackwards(self):
        pass

    @Slot()
    def timePlay(self):
        self.paused = False
        self.timer.start(50)

    @Slot()
    def timePause(self):
        self.paused = True

    @Slot()
    def timeStop(self):
        self.paused = True
        self.engine.reset()
        self.dataUpdated.emit(self.engine.current_pressures(),
                              np.array([self.engine.time]))

    @Slot()
    def timeStepForward(self):
        self.paused = True
        self.step_time()

    @Slot()
    def timeAdvance(self):
        self.paused = True
        initial_time = self.engine.time
        states = self.engine.solve(return_resolution=self.step_size)

        # TODO(jacob/wendi): Change the data format returned by
        # PlumbingEngine.solve so we don't need to rearrange the data on
        # this side.
        pressures = {node: [] for node in states[0]}
        for state in states:
            for node, pressure in state.items():
                pressures[node].append(pressure)
        pressures_np = {
            node: np.array(pvals)
            for node, pvals in pressures.items()
        }

        times = np.arange(
            len(states)) * self.step_size + initial_time + self.step_size

        self.dataUpdated.emit(pressures_np, times)
Пример #8
0
class OverlayWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('pointout canvas')
        self.setWindowFlags(self.windowFlags()
                            | Qt.Window
                            | Qt.WindowTransparentForInput
                            | Qt.WindowDoesNotAcceptFocus
                            | Qt.FramelessWindowHint
                            | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.setAttribute(Qt.WA_NoSystemBackground)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setAttribute(Qt.WA_TabletTracking)

        self.scribbles = []
        self.current_wet = Overlay()
        self.undo_stack = []

        cursor_bitmap = QBitmap.fromData(
            QSize(5, 5), bytes((
                0b00000,
                0b00000,
                0b00100,
                0b00000,
                0b00000,
            )))
        mask_bitmap = QBitmap.fromData(
            QSize(5, 5), bytes((
                0b00100,
                0b00000,
                0b10101,
                0b00000,
                0b00100,
            )))
        self.setCursor(QCursor(cursor_bitmap, mask_bitmap))

        self.anim_timer = QTimer()
        self.anim_timer.timeout.connect(self.anim_update)
        self.anim_timer.start(1000 // 30)
        self.anim_timer.setTimerType(Qt.CoarseTimer)
        self.wet_end = time.monotonic()

        self.tools = {
            'marker': Marker(),
            'highlighter': Highlighter(),
            'eraser': Eraser(),
            'red': ColorMarker(1, 0, 0),
            'green': ColorMarker(0, 1, 0),
            'blue': ColorMarker(0, 0, 1),
            'yellow': ColorMarker(1, 1, 0),
            'purple': ColorMarker(1, 0, 1),
            'cyan': ColorMarker(0, 1, 1),
        }
        self.tool = self.tools['marker']

    def _handle_timeout(self):
        self.anim_update()
        if self.scribble_parts:
            del self.scribble_parts[0]
        self.scribble_parts.append(Overlay())
        if self.current_part:
            for part in self.scribble_parts:
                part.add(self.current_part)
            if not self.scribbles:
                self.scribbles.append(Overlay())
            self.scribbles[-1].add(self.current_part)
            self.current_part = None
        self.anim_update()

    def anim_update(self):
        if self.current_wet and self.current_wet.rect is not None:
            with self.current_wet.painter_context() as painter:
                painter.setBrush(QColor(0, 0, 0, 255))
                painter.setPen(QPen(0))
                painter.setOpacity(0.1)
                painter.setCompositionMode(
                    QPainter.CompositionMode_DestinationOut)
                painter.drawRect(self.current_wet.rect)
            self.update(self.current_wet.rect)
            if self.wet_end < time.monotonic():
                self.current_wet = Overlay()

    def paintEvent(self, e):
        painter = QPainter(self)
        painter.setOpacity(0.5)
        canvas = None
        for scribble in self.scribbles:
            if scribble.rect:
                if scribble.blend_with_next:
                    if canvas is None:
                        canvas = Overlay()
                    canvas.add(scribble)
                else:
                    if canvas:
                        canvas.add(scribble)
                        canvas.paint(painter)
                        canvas = None
                    else:
                        scribble.paint(painter)
        if canvas:
            canvas.paint(painter)
        painter.setOpacity(1)
        if self.current_wet:
            self.current_wet.paint(painter)
        painter.end()

    def tabletEvent(self, e):
        #print(e.posF(), e.device(), hex(e.buttons()), e.pointerType(), e.pressure(), e.rotation(), e.xTilt(), e.yTilt())
        e.accept()
        if e.type() == QEvent.TabletPress:
            self.last_point = e.posF()
            if self.current_wet.rect and self.scribbles:
                self.scribbles[-1].blend_with_next = True
            self.scribbles.append(Overlay())
        if e.type() in (QEvent.TabletMove, QEvent.TabletRelease):
            tool = self.tool
            if self.tool is None:
                return
            if e.pointerType() == QTabletEvent.Eraser:
                tool = self.tools['eraser']
            if not self.scribbles:
                self.scribbles.append(Overlay())
            if self.last_point:
                tool.set_size(e.pressure())
                update_rect = QRect(self.last_point.toPoint(), e.pos())
                update_rect = update_rect.normalized().adjusted(
                    -tool.size - 1,
                    -tool.size - 1,
                    tool.size + 1,
                    tool.size + 1,
                )
                tool.draw(self.last_point, e.posF(), update_rect,
                          self.scribbles, self.current_wet)
                self.update(update_rect)
            self.last_point = e.posF()
            self.update_wet()
        if e.type() == QEvent.TabletRelease:
            pass
        e.accept()

    def paint(self, painter, e):
        if not self.last_point:
            return
        last_pos, last_pressure = self.last_point
        painter.drawLine(last_pos, e.posF())
        self.update(
            QRect(last_pos.toPoint(),
                  e.pos()).normalized().adjusted(-MAX_RADIUS, -MAX_RADIUS,
                                                 MAX_RADIUS, MAX_RADIUS))

    def set_tool(self, tool_name):
        self.tool = self.tools[tool_name]

    def unset_tool(self):
        self.tool = None

    def clear(self):
        while self.scribbles:
            self.undo()

    def undo(self):
        if self.scribbles:
            undone = self.scribbles.pop()
            if not undone.rect:
                self.undo()
            else:
                self.undo_stack.append(undone)
                self.update(undone.rect)

    def redo(self):
        if self.undo_stack:
            redone = self.undo_stack.pop()
            self.scribbles.append(redone)
            self.update(redone.rect)

    def update_wet(self, seconds=1):
        self.wet_end = time.monotonic() + seconds
Пример #9
0
class KnechtUpdate(QObject):
    update_available = Signal(str)

    remote_version = '0.00'
    remote_version_age = datetime.datetime.now()

    installer_file = Path('Dummy.exe')
    first_run = True

    def __init__(self, ui):
        """ Run an updater thread to check for new versions on remote path.

        :param modules.gui.main_ui.KnechtWindow ui:
        """
        super(KnechtUpdate, self).__init__(ui)
        self.ui = ui

        # Update check thread
        self.ut: _KnechtUpdateThread = None

        self.timeout = QTimer()
        self.timeout.setSingleShot(True)
        self.timeout.setInterval(3000)

        self.schedule_timer = QTimer()
        self.schedule_timer.setInterval(8000000)
        self.schedule_timer.setTimerType(Qt.VeryCoarseTimer)
        self.schedule_timer.timeout.connect(self.schedule_update)
        self.schedule_timer.start()

        self.last_update_check = datetime.datetime.now()

    def schedule_update(self):
        """ Daily update check """
        delta = datetime.datetime.now() - self.last_update_check

        if delta > datetime.timedelta(days=1):
            LOGGER.info('Running scheduled update check after: %s', delta)
            self.run_update()

    def _init_thread(self):
        # Update check thread
        self.ut = _KnechtUpdateThread(self)

        # Update thread signals
        self.ut.already_up_to_date.connect(self._already_latest_version)
        self.ut.update_failed.connect(self._update_error)
        self.ut.update_ready.connect(self._set_update_available)
        self.ut.new_version.connect(self._set_remote_version)

    def run_update(self):
        """
            If an updated installer is available: ask user to execute it.
            Otherwise run update thread to check for newer versions
        """
        # Make sure this is not called within timeout
        if self.timeout.isActive():
            return
        self.timeout.start()
        self.first_run = False
        self.last_update_check = datetime.datetime.now()

        # Exit on running thread
        if self.is_running():
            LOGGER.info(
                'Can not check for updates while update thread is running!')
            return

        # Run update if already available
        if self._is_update_ready():
            if not self._ask_to_run():
                return
            self._execute_update()
            return

        # Check if already up to date
        if self.is_up_to_date():
            self._already_latest_version()
            return

        self._init_thread()
        self.ut.start()
        self.ui.msg(_('Suche nach Anwendungs Updates gestartet.'))

    def is_up_to_date(self) -> bool:
        """ Return True if the remote version equals current version """
        delta = datetime.datetime.now() - self.remote_version_age

        if delta > datetime.timedelta(hours=1):
            # Remote version info is older than 1 hour
            return False

        if self.remote_version == KnechtSettings.app['version']:
            return True
        return False

    def is_running(self):
        """ Determine wherever a Update thread is running. """
        if self.ut is not None:
            if self.ut.is_alive():
                return True
        return False

    def _is_update_ready(self) -> bool:
        if path_exists(self.installer_file):
            if self.remote_version > KnechtSettings.app['version']:
                return True
        return False

    def _execute_update(self):
        args = [
            self.installer_file.as_posix(), '/SILENT', '/CLOSEAPPLICATIONS',
            '/RESTARTAPPLICATIONS'
        ]
        try:
            Popen(args)
            LOGGER.info('Running Update Installer: %s', args)
        except Exception as e:
            LOGGER.error('Could not run update installer. %s', e)
            return

        # Close application
        self.ui.close()

    @Slot(Path)
    def _set_update_available(self, installer_file: Path):
        self.installer_file = installer_file

        if self._is_update_ready():
            self.update_available.emit(self.remote_version)
            self._show_notification()

    @Slot(str)
    def _set_remote_version(self, version: str):
        self.remote_version = version
        self.remote_version_age = datetime.datetime.now()

    @Slot()
    def _already_latest_version(self):
        self.ui.msg(_('Die Anwendung ist auf dem neusten Stand.'), 5000)

    @Slot()
    def _update_error(self):
        self.ui.msg(_('Aktualisierung konnte nicht durchgeführt werden.'),
                    4000)

    def _ask_to_run(self) -> bool:
        msg_box = AskToContinue(self.ui)
        self.ui.play_hint_sound()

        if not msg_box.ask(
                title=_('App Update'),
                txt=_(
                    'Eine aktualisierte Version {} ist verfügbar.<br><br>'
                    'Möchten Sie die Aktualisierung jetzt durchführen?<br><br>'
                    'Die Anwendung muss für das Update beendet werden.').
                format(self.remote_version),
                ok_btn_txt=_('Jetzt aktualisieren'),
                abort_btn_txt=_('Vielleicht später...'),
        ):
            # User wants to update later
            return False
        return True

    def _show_notification(self):
        def msg_callback():
            if self._ask_to_run():
                self._execute_update()

        self.ui.show_tray_notification(
            title=_('Aktualisierung'),
            message=_('Version {} ist bereit zur Installation.').format(
                self.remote_version),
            clicked_callback=msg_callback)
Пример #10
0
rows = Counter()
numberClasses.valueChanged[int].connect(edit_number_rows)

file_list = load_from_file("classes.csv", rows)

numberClasses.setValue(rows)


submitButton = QPushButton(text="Save")
submitButton.clicked.connect(save_form)
layout.addWidget(submitButton)

timer = QTimer()
timer.setInterval(5 * 1000)
timer.setTimerType(Qt.CoarseTimer)
timer.timeout.connect(open_class_wrapper)

runButton = QPushButton(text="Run")
runButton.clicked.connect(run_app)
layout.addWidget(runButton)

trayIcon = QSystemTrayIcon(QIcon(app_icon))
trayIcon.activated.connect(foreground)


minButton = QPushButton(text="Minimize")
minButton.clicked.connect(minimize)
layout.addWidget(minButton)
editPage = QWidget()
editPage.setLayout(layout)