Ejemplo n.º 1
0
def test_calltip(main_window, qtbot):
    """Hide the calltip in the editor when a matching ')' is found."""
    # Load test file
    text = 'a = [1,2,3]\n(max'
    main_window.editor.new(fname="test.py", text=text)
    code_editor = main_window.editor.get_focus_widget()

    # Set text to start
    code_editor.set_text(text)
    code_editor.go_to_line(2)
    code_editor.move_cursor(5)
    calltip = code_editor.calltip_widget
    assert not calltip.isVisible()

    qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
    qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
    qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000)

    qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
    qtbot.keyPress(code_editor, Qt.Key_Space)
    assert not calltip.isVisible()
    qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
    qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)

    QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
    main_window.editor.close_file()
Ejemplo n.º 2
0
 def free_memory(self):
     """Free memory signal."""
     self.main.free_memory()
     QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER,
                       lambda: self.main.free_memory())
     QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
                       lambda: self.main.free_memory())
Ejemplo n.º 3
0
 def blink_figure(self):
     """Blink figure once."""
     if self.fig:
         self._blink_flag = not self._blink_flag
         self.repaint()
         if self._blink_flag:
             timer = QTimer()
             timer.singleShot(40, self.blink_figure)
Ejemplo n.º 4
0
    def set_font_size(self, old, new):
        current_font = self.app.font()
        current_font.setPointSizeF(current_font.pointSizeF()/old*new)
        QApplication.instance().setFont(current_font)

        for w in self.app.allWidgets():
            w_c_f = w.font()
            w_c_f.setPointSizeF(w_c_f.pointSizeF()/old*new)
            w.setFont(w_c_f)

        QTimer.singleShot(0, self.resizeForNewDisplayWidget)
Ejemplo n.º 5
0
    def revert(self):
        """
        Takes the data stored in the models and displays them in the widgets.
        """
        # make sure it is called from main thread
        if (not QThread.currentThread() == QCoreApplication.instance(
        ).thread()):
            QTimer.singleShot(0, self.revert)
            return

        for key in self._mappings:
            self._on_model_notification(key)
Ejemplo n.º 6
0
    def focusOutEvent(self, event):
        """Handle focus out event restoring the last valid selected path."""
        # Calling asynchronously the 'add_current_text' to avoid crash
        # https://groups.google.com/group/spyderlib/browse_thread/thread/2257abf530e210bd
        if not self.is_valid():
            lineedit = self.lineEdit()
            QTimer.singleShot(50, lambda: lineedit.setText(self.selected_text))

        hide_status = getattr(self.lineEdit(), 'hide_status_icon', None)
        if hide_status:
            hide_status()
        QComboBox.focusOutEvent(self, event)
Ejemplo n.º 7
0
def test_sort_dataframe_with_category_dtypes(qtbot):  # cf. issue 5361
    df = DataFrame({'A': [1, 2, 3, 4],
                    'B': ['a', 'b', 'c', 'd']})
    df = df.astype(dtype={'B': 'category'})
    df_cols = df.dtypes
    editor = DataFrameEditor(None)
    editor.setup_and_check(df_cols)
    dfm = editor.dataModel
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    editor.dataModel.sort(0)
    assert data(dfm, 0, 0) == 'int64'
    assert data(dfm, 1, 0) == 'category'
Ejemplo n.º 8
0
def environ_dialog(qtbot):
    "Setup the Environment variables Dialog taking into account the os."
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    if os.name == 'nt':
        from spyder.utils.environ import WinUserEnvDialog
        dialog = WinUserEnvDialog()
    else:        
        from spyder.utils.environ import EnvDialog
        dialog = EnvDialog()
    qtbot.addWidget(dialog)
    
    return dialog
Ejemplo n.º 9
0
def test_sort_dataframe_with_duplicate_column(qtbot):
    df = DataFrame({'A': [1, 3, 2], 'B': [4, 6, 5]})
    df = concat((df, df.A), axis=1)
    editor = DataFrameEditor(None)
    editor.setup_and_check(df)
    dfm = editor.dataModel
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    editor.dataModel.sort(0)
    assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '3', '2']
    assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '6', '5']
    editor.dataModel.sort(1)
    assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '2', '3']
    assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '5', '6']
Ejemplo n.º 10
0
 def start(self):
     """
     Start this tester.
     """
     self.timer = QTimer()
     # Connecting self.idle to a QTimer with 0 time delay
     # makes it called when there are no events to process
     self.timer.timeout.connect(self._idle)
     self.timer.start()
     # This calls __call__() method
     QTimer.singleShot(0, self)
     # Start the event loop
     self.app.exec_()
Ejemplo n.º 11
0
 def follow_directories_loaded(self, fname):
     """Follow directories loaded during startup"""
     if self._to_be_loaded is None:
         return
     path = osp.normpath(to_text_string(fname))
     if path in self._to_be_loaded:
         self._to_be_loaded.remove(path)
     if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 \
       and not is_pyqt46:
         self.fsmodel.directoryLoaded.disconnect(
                                     self.follow_directories_loaded)
         if self._scrollbar_positions is not None:
             # The tree view need some time to render branches:
             QTimer.singleShot(50, self.restore_scrollbar_positions)
Ejemplo n.º 12
0
 def set_display_widget(self, new_widget):
     if new_widget == self._display_widget:
         return
     self.clear_display_widget()
     if not new_widget.layout():
         new_widget.setMinimumSize(new_widget.size())
     self._new_widget_size = new_widget.size()
     self._display_widget = new_widget
     self.setCentralWidget(self._display_widget)
     self.update_window_title()
     # Resizing to the new widget's dimensions needs to be
     # done on the event loop for some reason - you can't
     # just do it here.
     QTimer.singleShot(0, self.resizeForNewDisplayWidget)
Ejemplo n.º 13
0
    def submit(self):
        """
        Submits the current values stored in the widgets to the models.
        """
        # make sure it is called from main thread
        if (not QThread.currentThread() == QCoreApplication.instance(
        ).thread()):
            QTimer.singleShot(0, self.submit)
            return

        submit_policy = self._submit_policy
        self.submit_policy = SUBMIT_POLICY_AUTO
        try:
            for key in self._mappings:
                self._on_widget_property_notification(key)
        finally:
            self.submit_policy = submit_policy
Ejemplo n.º 14
0
def test_load_kernel_file_from_id(ipyconsole, qtbot):
    """
    Test that a new client is created using its id
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    connection_file = osp.basename(client.connection_file)
    id_ = connection_file.split('kernel-')[-1].split('.json')[0]

    QTimer.singleShot(2000, lambda: open_client_from_connection_info(
                                        id_, qtbot))
    ipyconsole.create_client_for_kernel()
    qtbot.wait(1000)

    new_client = ipyconsole.get_clients()[1]
    assert new_client.id_ == dict(int_id='1', str_id='B')
Ejemplo n.º 15
0
def test_restart_kernel(ipyconsole, qtbot):
    """
    Test that kernel is restarted correctly
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    # Do an assigment to verify that it's not there after restarting
    with qtbot.waitSignal(shell.executed):
        shell.execute('a = 10')

    # Restart kernel and wait until it's up again
    shell._prompt_html = None
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    client.restart_kernel()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    assert not shell.is_defined('a')
Ejemplo n.º 16
0
def test_close_when_file_is_changed(main_window, qtbot):
    """Test closing spyder when there is a file with modifications open."""
    # Wait until the window is fully up
    shell = main_window.ipyconsole.get_current_shellwidget()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    # Load test file
    test_file = osp.join(LOCATION, 'script.py')
    main_window.editor.load(test_file)
    editorstack = main_window.editor.get_current_editorstack()
    editor = editorstack.get_current_editor()
    editor.document().setModified(True)

    # Close.main-window
    QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
    main_window.close()

    # Wait for the segfault
    qtbot.wait(3000)
Ejemplo n.º 17
0
def test_load_kernel_file_from_location(ipyconsole, qtbot):
    """
    Test that a new client is created using a connection file
    placed in a different location from jupyter_runtime_dir
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    connection_file = osp.join(tempfile.gettempdir(),
                               osp.basename(client.connection_file))
    shutil.copy2(client.connection_file, connection_file)

    QTimer.singleShot(2000, lambda: open_client_from_connection_info(
                                        connection_file,
                                        qtbot))
    ipyconsole.create_client_for_kernel()
    qtbot.wait(1000)

    assert len(ipyconsole.get_clients()) == 2
Ejemplo n.º 18
0
    def event(self, event):
        """Qt Override.

        Filter tab keys and process double tab keys.
        """
        if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Tab):
            self.sig_tab_pressed.emit(True)
            self.numpress += 1
            if self.numpress == 1:
                self.presstimer = QTimer.singleShot(400, self.handle_keypress)
            return True
        return QComboBox.event(self, event)
Ejemplo n.º 19
0
def test_open_files_in_new_editor_window(main_window, qtbot):
    """
    This tests that opening files in a new editor window
    is working as expected.

    Test for issue 4085
    """
    # Set a timer to manipulate the open dialog while it's running
    QTimer.singleShot(2000, lambda: open_file_in_editor(main_window,
                                                        'script.py',
                                                        directory=LOCATION))

    # Create a new editor window
    # Note: editor.load() uses the current editorstack by default
    main_window.editor.create_new_window()
    main_window.editor.load()

    # Perform the test
    # Note: There's always one file open in the Editor
    editorstack = main_window.editor.get_current_editorstack()
    assert editorstack.get_stack_count() == 2
Ejemplo n.º 20
0
def test_load_kernel_file(ipyconsole, qtbot):
    """
    Test that a new client is created using the connection file
    of an existing client
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

    QTimer.singleShot(2000, lambda: open_client_from_connection_info(
                                        client.connection_file,
                                        qtbot))
    ipyconsole.create_client_for_kernel()
    qtbot.wait(1000)

    new_client = ipyconsole.get_clients()[1]
    new_shell = new_client.shellwidget
    with qtbot.waitSignal(new_shell.executed):
        new_shell.execute('a = 10')

    assert new_client.id_ == dict(int_id='1', str_id='B')
    assert shell.get_value('a') == new_shell.get_value('a')
Ejemplo n.º 21
0
 def update_rotation_xy(self, rotation):
     print('Rotation: ', rotation) # in degrees
     # ToDo: use transform matrix to rotate existing path?
     # probably not worth it since rotation is not used much ...
     # nasty hack so ensure the positions have updated before loading
     QTimer.singleShot(10, self.reload_program)
Ejemplo n.º 22
0
class XicamSplashScreen(QSplashScreen):
    minsplashtime = 3000

    def __init__(self, mainwindow: Callable[[], QMainWindow] = None,
                 f: int = Qt.WindowStaysOnTopHint | Qt.SplashScreen):
        """
        A QSplashScreen customized to display an animated gif. The splash triggers launch when clicked.

        After minsplashtime, this splash waits until the animation finishes before triggering the launch.

        Parameters
        ----------
        mainwindow  :   class
            Subclass of QMainWindow to display after splashing
        f           :   int
            Extra flags (see base class)
        """

        # Get logo movie from relative path
        self.movie = QMovie(str(static.path("images/animated_logo.gif")))

        # Setup drawing
        self.movie.frameChanged.connect(self.paintFrame)
        self.movie.jumpToFrame(1)
        self.pixmap = QPixmap(self.movie.frameRect().size())
        super(XicamSplashScreen, self).__init__(self.pixmap, f)
        self.setMask(self.pixmap.mask())
        self.movie.finished.connect(self.restartmovie)

        self._launching = False
        self._launchready = False
        self.timer = QTimer(self)
        self.mainwindow = mainwindow

        if args.nosplash:
            self.execlaunch()
        else:
            # Start splashing
            self.setAttribute(Qt.WA_DeleteOnClose)
            self.show()
            self.raise_()
            self.activateWindow()
            QApplication.instance().setActiveWindow(self)
            # Setup timed triggers for launching the QMainWindow
            self.timer.singleShot(self.minsplashtime, self.launchwindow)

    def showMessage(self, message: str, color=Qt.white):
        # TODO: Make this work.
        super(XicamSplashScreen, self).showMessage(message, color)

    def mousePressEvent(self, *args, **kwargs):
        # TODO: Apparently this doesn't work?
        self.timer.stop()
        self.execlaunch()

    def show(self):
        """
        Start the animation when shown
        """
        super(XicamSplashScreen, self).show()
        self.movie.start()

    def paintFrame(self):
        """
        Paint the current frame
        """
        self.pixmap = self.movie.currentPixmap()
        self.setMask(self.pixmap.mask())
        self.setPixmap(self.pixmap)
        self.movie.setSpeed(self.movie.speed() + 20)

    def sizeHint(self):
        return self.movie.scaledSize()

    def restartmovie(self):
        """
        Once the animation reaches the end, check if its time to launch, otherwise restart animation
        """
        if self._launchready:
            self.execlaunch()
            return
        self.movie.start()

    def launchwindow(self):
        """
        Save state, defer launch until animation finishes
        """
        self._launchready = True

    def execlaunch(self):
        """
        Launch the mainwindow
        """
        if not self._launching:
            self._launching = True

            app = QApplication.instance()

            from xicam.gui.windows.mainwindow import XicamMainWindow
            self.mainwindow = XicamMainWindow()
            self.timer.stop()

            # Show the QMainWindow
            self.mainwindow.show()
            self.mainwindow.raise_()
            self.mainwindow.activateWindow()
            app.setActiveWindow(self.mainwindow)

            # Stop splashing
            self.hide()
            self.movie.stop()
            self.finish(self.mainwindow)
Ejemplo n.º 23
0
 def reloadBackplot(self):
     QTimer.singleShot(100, lambda: self._reloadBackplot())
Ejemplo n.º 24
0
class PluginManager(QObject):

    introspection_complete = Signal(object)

    def __init__(self, executable):

        super(PluginManager, self).__init__()
        plugins = OrderedDict()
        for name in PLUGINS:
            try:
                plugin = PluginClient(name, executable)
                plugin.run()
            except Exception as e:
                debug_print('Introspection Plugin Failed: %s' % name)
                debug_print(str(e))
                continue
            debug_print('Introspection Plugin Loaded: %s' % name)
            plugins[name] = plugin
            plugin.received.connect(self.handle_response)
        self.plugins = plugins
        self.timer = QTimer()
        self.desired = []
        self.ids = dict()
        self.info = None
        self.request = None
        self.pending = None
        self.pending_request = None
        self.waiting = False

    def send_request(self, info):
        """Handle an incoming request from the user."""
        if self.waiting:
            if info.serialize() != self.info.serialize():
                self.pending_request = info
            else:
                debug_print('skipping duplicate request')
            return
        debug_print('%s request' % info.name)
        desired = None
        self.info = info
        editor = info.editor
        if (info.name == 'completion' and 'jedi' not in self.plugins
                and info.line.lstrip().startswith(('import ', 'from '))):
            desired = 'fallback'

        if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj)
                or (editor.in_comment_or_string() and info.name != 'info')):
            desired = 'fallback'

        plugins = self.plugins.values()
        if desired:
            plugins = [self.plugins[desired]]
            self.desired = [desired]
        elif (info.name == 'definition' and not info.editor.is_python()
              or info.name == 'info'):
            self.desired = list(self.plugins.keys())
        else:
            # Use all but the fallback
            plugins = list(self.plugins.values())[:-1]
            self.desired = list(self.plugins.keys())[:-1]

        self._start_time = time.time()
        self.waiting = True
        method = 'get_%s' % info.name
        value = info.serialize()
        self.ids = dict()
        for plugin in plugins:
            request_id = plugin.request(method, value)
            self.ids[request_id] = plugin.name
        self.timer.stop()
        self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout)

    def validate(self):
        for plugin in self.plugins.values():
            plugin.request('validate')

    def handle_response(self, response):
        name = self.ids.get(response['request_id'], None)
        if not name:
            return
        if response.get('error', None):
            debug_print('Response error:', response['error'])
            return
        if name == self.desired[0] or not self.waiting:
            if response.get('result', None):
                self._finalize(response)
        else:
            self.pending = response

    def close(self):
        for name, plugin in self.plugins.items():
            plugin.close()
            debug_print("Introspection Plugin Closed: {}".format(name))

    def _finalize(self, response):
        self.waiting = False
        self.pending = None
        if self.info:
            delta = time.time() - self._start_time
            debug_print('%s request from %s finished: "%s" in %.1f sec' %
                        (self.info.name, response['name'],
                         str(response['result'])[:100], delta))
            response['info'] = self.info
            self.info = None
            self.introspection_complete.emit(response)
        if self.pending_request:
            info = self.pending_request
            self.pending_request = None
            self.send_request(info)

    def _handle_timeout(self):
        self.waiting = False
        if self.pending:
            self._finalize(self.pending)
        else:
            debug_print('No valid responses acquired')
Ejemplo n.º 25
0
 def focusChangedEvent(self, old_w, new_w):
     if issubclass(new_w.__class__, QLineEdit):
         #print "QLineEdit got focus: ", new_w
         QTimer.singleShot(0, new_w.selectAll)
Ejemplo n.º 26
0
class AnimationWorker(QObject):
    """A thread to keep the animation timer independent of the main event loop.

    This prevents mouseovers and other events from causing animation lag. See
    QtDims.play() for public-facing docstring.
    """

    frame_requested = Signal(int, int)  # axis, point
    finished = Signal()
    started = Signal()

    def __init__(self, slider):
        super().__init__()
        self.slider = slider
        self.dims = slider.dims
        self.axis = slider.axis
        self.loop_mode = slider.loop_mode
        slider.fps_changed.connect(self.set_fps)
        slider.mode_changed.connect(self.set_loop_mode)
        slider.range_changed.connect(self.set_frame_range)
        self.set_fps(self.slider.fps)
        self.set_frame_range(slider.frame_range)

        # after dims.set_point is called, it will emit a dims.events.axis()
        # we use this to update this threads current frame (in case it
        # was some other event that updated the axis)
        self.dims.events.axis.connect(self._on_axis_changed)
        self.current = max(self.dims.point[self.axis], self.min_point)
        self.current = min(self.current, self.max_point)
        self.timer = QTimer()

    @Slot()
    def work(self):
        """Play the animation."""
        # if loop_mode is once and we are already on the last frame,
        # return to the first frame... (so the user can keep hitting once)
        if self.loop_mode == LoopMode.ONCE:
            if self.step > 0 and self.current >= self.max_point - 1:
                self.frame_requested.emit(self.axis, self.min_point)
            elif self.step < 0 and self.current <= self.min_point + 1:
                self.frame_requested.emit(self.axis, self.max_point)
            self.timer.singleShot(self.interval, self.advance)
        else:
            # immediately advance one frame
            self.advance()
        self.started.emit()

    @Slot(float)
    def set_fps(self, fps):
        """Set the frames per second value for the animation.

        Parameters
        ----------
        fps : float
            Frames per second for the animation.
        """
        if fps == 0:
            return self.finish()
        self.step = 1 if fps > 0 else -1  # negative fps plays in reverse
        self.interval = 1000 / abs(fps)

    @Slot(tuple)
    def set_frame_range(self, frame_range):
        """Frame range for animation, as (minimum_frame, maximum_frame).

        Parameters
        ----------
        frame_range : tuple(int, int)
            Frame range as tuple/list with range (minimum_frame, maximum_frame)
        """
        self.dimsrange = self.dims.range[self.axis]

        if frame_range is not None:
            if frame_range[0] >= frame_range[1]:
                raise ValueError("frame_range[0] must be <= frame_range[1]")
            if frame_range[0] < self.dimsrange[0]:
                raise IndexError("frame_range[0] out of range")
            if frame_range[1] * self.dimsrange[2] >= self.dimsrange[1]:
                raise IndexError("frame_range[1] out of range")
        self.frame_range = frame_range

        if self.frame_range is not None:
            self.min_point, self.max_point = self.frame_range
        else:
            self.min_point = 0
            self.max_point = int(
                np.floor(self.dimsrange[1] - self.dimsrange[2]))
        self.max_point += 1  # range is inclusive

    @Slot(str)
    def set_loop_mode(self, mode):
        """Set the loop mode for the animation.

        Parameters
        ----------
        mode : str
            Loop mode for animation.
            Available options for the loop mode string enumeration are:
            - LoopMode.ONCE
                Animation will stop once movie reaches the max frame
                (if fps > 0) or the first frame (if fps < 0).
            - LoopMode.LOOP
                Movie will return to the first frame after reaching
                the last frame, looping continuously until stopped.
            - LoopMode.BACK_AND_FORTH
                Movie will loop continuously until stopped,
                reversing direction when the maximum or minimum frame
                has been reached.
        """
        self.loop_mode = LoopMode(mode)

    def advance(self):
        """Advance the current frame in the animation.

        Takes dims scale into account and restricts the animation to the
        requested frame_range, if entered.
        """
        self.current += self.step * self.dimsrange[2]
        if self.current < self.min_point:
            if (self.loop_mode == LoopMode.BACK_AND_FORTH
                ):  # 'loop_back_and_forth'
                self.step *= -1
                self.current = self.min_point + self.step * self.dimsrange[2]
            elif self.loop_mode == LoopMode.LOOP:  # 'loop'
                self.current = self.max_point + self.current - self.min_point
            else:  # loop_mode == 'once'
                return self.finish()
        elif self.current >= self.max_point:
            if (self.loop_mode == LoopMode.BACK_AND_FORTH
                ):  # 'loop_back_and_forth'
                self.step *= -1
                self.current = (self.max_point +
                                2 * self.step * self.dimsrange[2])
            elif self.loop_mode == LoopMode.LOOP:  # 'loop'
                self.current = self.min_point + self.current - self.max_point
            else:  # loop_mode == 'once'
                return self.finish()
        with self.dims.events.axis.blocker(self._on_axis_changed):
            self.frame_requested.emit(self.axis, self.current)
        # using a singleShot timer here instead of timer.start() because
        # it makes it easier to update the interval using signals/slots
        self.timer.singleShot(self.interval, self.advance)

    def finish(self):
        """Emit the finished event signal."""
        self.finished.emit()

    @Slot(Event)
    def _on_axis_changed(self, event):
        """Update the current frame if the axis has changed."""
        # slot for external events to update the current frame
        if event.axis == self.axis and hasattr(event, 'value'):
            self.current = event.value
Ejemplo n.º 27
0
    def leaveEvent(self, a0: QEvent) -> None:
        print('leave menu', self.underMouse(), self.tool_button.underMouse())

        QTimer.singleShot(50, self.tool_button.hide_menu)
class _SearchResultsModel(QAbstractTableModel):
    """
    Qt model connecting our model to Qt's model--view machinery

    This is implementing two layers of "laziness" to ensure that the app
    remains responsive when large tables are loaded.

    1. Rows are added dynamically using Qt's canFetchMore / fetchMore
    machinery.
    2. Data (which Qt assumes is readily available in memory) is immediately
    filled with LOADING_PLACEHOLDER. Work is kicked off on a thread to later
    update this with the actual data.
    """
    def __init__(self, model, *args, **kwargs):
        self.model = model  # our internal model for the components subpackage
        super().__init__(*args, **kwargs)

        # State related to dynamically adding rows
        self._current_num_rows = 0
        self._catalog_length = len(self.model.catalog)

        # Cache for loaded data
        self._data = {}
        # Queue of indexes of data to be loaded
        self._work_queue = collections.deque()
        # Set of active workers
        self._active_workers = set()

        # Start a timer that will periodically load any data queued up to be loaded.
        self._data_loading_timer = QTimer(self)
        # We run this once to initialize it. The _process_work_queue schedules
        # it to be run again when it completes. This is better than a strictly
        # periodic timer because it ensures that requests do not pile up if
        # _process_work_queue takes longer than LOADING_LATENCY to complete.
        self._data_loading_timer.singleShot(LOADING_LATENCY,
                                            self._process_work_queue)

        # Changes to the model update the GUI.
        self.model.events.begin_reset.connect(self.on_begin_reset)
        self.model.events.end_reset.connect(self.on_end_reset)

    def _process_work_queue(self):
        if self._work_queue:
            worker = create_worker(_load_data, self.model.get_data,
                                   tuple(self._work_queue))
            self._work_queue.clear()
            # Track this worker in case we need to ignore it and cancel due to
            # model reset.
            self._active_workers.add(worker)
            worker.finished.connect(
                lambda: self._active_workers.discard(worker))
            worker.yielded.connect(self.on_item_loaded)
            worker.start()
        # Else, no work to do.
        # Schedule the next processing.
        self._data_loading_timer.singleShot(LOADING_LATENCY,
                                            self._process_work_queue)

    def on_item_loaded(self, payload):
        # Update state and trigger Qt to run data() to update its internal model.
        index, item = payload
        self._data[index] = item
        self.dataChanged.emit(index, index, [])

    def on_begin_reset(self, event):
        self.beginResetModel()
        self._current_num_rows = 0
        self._catalog_length = len(self.model.catalog)
        for worker in self._active_workers:
            # Cease allowing this worker to mutate _data so that we do not get
            # any stale updates.
            worker.yielded.disconnect(self.on_item_loaded)
            # To avoid doing useless work, try to cancel the worker. We do not
            # rely on this request being effective.
            worker.quit()
        self._active_workers.clear()
        self._work_queue.clear()
        self._data.clear()

    def on_end_reset(self, event):
        self.endResetModel()

    def canFetchMore(self, parent=None):
        if parent.isValid():
            return False
        return self._current_num_rows < self._catalog_length

    def fetchMore(self, parent=None):
        if parent.isValid():
            return
        remainder = self._catalog_length - self._current_num_rows
        rows_to_add = min(remainder, CHUNK_SIZE)
        if rows_to_add <= 0:
            return
        self.beginInsertRows(parent, self._current_num_rows,
                             self._current_num_rows + rows_to_add - 1)
        self._current_num_rows += rows_to_add
        self.endInsertRows()

    def rowCount(self, parent=None):
        return self._current_num_rows

    def columnCount(self, parent=None):
        return len(self.model.headings)

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return super().headerData(section, orientation, role)
        if orientation == Qt.Horizontal and section < self.columnCount():
            return str(self.model.headings[section])
        elif orientation == Qt.Vertical and section < self.rowCount():
            return section

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():  # does > 0 bounds check
            return QtCore.QVariant()
        if index.column() >= self.columnCount() or index.row(
        ) >= self.rowCount():
            return QtCore.QVariant()
        if role == QtCore.Qt.DisplayRole:
            if index in self._data:
                return self._data[index]
            else:
                self._data[index] = LOADING_PLACEHOLDER
                self._work_queue.append(index)
                return LOADING_PLACEHOLDER
        else:
            return QtCore.QVariant()
Ejemplo n.º 29
0
            return
        legend = self.plotWidget.plotItem.legend
        print('Legend:', legend)
        legend.removeItem('%d' % sweepIndex)
        print('Removing item:', curve)
        self.plotWidget.removeItem(curve)
        print('items in plotWidget.plotItem:', self.plotWidget.plotItem.items)
        del self._sweepMap[sweepIndex]
        print('Removed sweep')


if __name__ == '__main__':
    from qtpy.QtGui import QApplication
    from qtpy.QtCore import QTimer

    path = 'ExampleData/'
    fileName = path + 'TES2_IV_20180117_090049.h5'

    app = QApplication([])
    print('Loading data')
    sweepCollection = IvSweepCollection(fileName)
    print('making widget')
    mw = IvGraphWidget(sweepCollection)
    for i in range(0, len(sweepCollection), 4):
        mw.showSweep(i)

    mw.show()
    QTimer.singleShot(3000, lambda: mw.setSelection('up+/up-'))

    app.exec_()
Ejemplo n.º 30
0
    def _pararchute_heartbeat(self):

        self._queue.put(PARACHUTE_HEARTBEAT)
        QTimer.singleShot(1000 * PARACHUTE_HEARTBEAT_INTERVAL,
                          self._pararchute_heartbeat)
Ejemplo n.º 31
0
 def __alive_loopback(self):
     alive = self.is_alive()
     if not alive:
         self.terminal_closed.emit()
     else:
         QTimer.singleShot(250, self.__alive_loopback)
Ejemplo n.º 32
0
class XicamSplashScreen(QSplashScreen):
    minsplashtime = 5000

    def __init__(self, log_path: str, initial_length: int, f: int = Qt.WindowStaysOnTopHint | Qt.SplashScreen):
        """
        A QSplashScreen customized to display an animated gif. The splash triggers launch when clicked.

        After minsplashtime, this splash waits until the animation finishes before triggering the launch.

        Parameters
        ----------
        log_path    :   str
            Path to the Xi-CAM log file to reflect
        initial_length: int
            Length in bytes to seek forward before reading
        f           :   int
            Extra flags (see base class)
        """

        # Get logo movie from relative path
        self.movie = QMovie(str(static.path("images/animated_logo.gif")))

        # Setup drawing
        self.movie.frameChanged.connect(self.paintFrame)
        self.movie.jumpToFrame(1)
        self.pixmap = QPixmap(self.movie.frameRect().size())
        super(XicamSplashScreen, self).__init__(self.pixmap, f)
        self.setMask(self.pixmap.mask())
        self.movie.finished.connect(self.restartmovie)
        self.showMessage("Starting Xi-CAM...")

        self._launching = False
        self._launchready = False
        self.timer = QTimer(self)

        self.log_file = open(log_path, "r")
        self.log_file.seek(initial_length)

        # Start splashing
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.show()
        self.raise_()
        self.activateWindow()
        QApplication.instance().setActiveWindow(self)

        # Setup timed triggers for launching the QMainWindow
        self.timer.singleShot(self.minsplashtime, self.launchwindow)

    def showMessage(self, message: str, color=Qt.darkGray):
        # attempt to parse out everyting besides the message
        try:
            message=message.split(" - ")[-1]
        except Exception:
            pass
        else:
            super(XicamSplashScreen, self).showMessage(elide(message), color=color, alignment=Qt.AlignBottom)

    def mousePressEvent(self, *args, **kwargs):
        # TODO: Apparently this doesn't work?
        self.timer.stop()
        self.execlaunch()

    def show(self):
        """
        Start the animation when shown
        """
        super(XicamSplashScreen, self).show()
        self.movie.start()

    def paintFrame(self):
        """
        Paint the current frame
        """
        self.pixmap = self.movie.currentPixmap()
        self.setMask(self.pixmap.mask())
        self.setPixmap(self.pixmap)
        self.movie.setSpeed(self.movie.speed() + 20)

        line = self.log_file.readline().strip()
        if line:
            self.showMessage(elide(line.split(">")[-1]))

    def sizeHint(self):
        return self.movie.scaledSize()

    def restartmovie(self):
        """
        Once the animation reaches the end, check if its time to launch, otherwise restart animation
        """
        if self._launchready:
            self.execlaunch()
            return
        self.movie.start()

    def launchwindow(self):
        """
        Save state, defer launch until animation finishes
        """
        self._launchready = True

    def execlaunch(self):
        """
        Launch the mainwindow
        """
        if not self._launching:
            self._launching = True

            self.timer.stop()

            # Stop splashing
            self.hide()
            self.movie.stop()
            self.close()
            QApplication.instance().quit()
Ejemplo n.º 33
0
 def delayed_refresh(self):
     """Post an event to the event loop that causes the view to
     update on the next cycle"""
     if not self.refresh_queued:
         self.refresh_queued = True
         QTimer.singleShot(0, self.presenter.refresh_view)
Ejemplo n.º 34
0
 def show_warning_icon(self):
     """ Show the warning icon.  This is called when a shell command fails
     (i.e. exits with nonzero status) """
     self.setIcon(self._warning_icon)
     QTimer.singleShot(5000, self.hide_warning_icon)
Ejemplo n.º 35
0
    def setup_ui(self):
        """
        Initialize the widgets and layouts.
        """
        self.setLayout(self.main_layout)

        self.pv_layout.addWidget(self.pv_protocol_cmb)
        self.pv_layout.addWidget(self.pv_name_line_edt)
        self.pv_layout.addWidget(self.pv_connect_push_btn)
        self.pv_add_panel.setLayout(self.pv_layout)
        QTimer.singleShot(0, self.pv_name_line_edt.setFocus)

        self.curve_settings_tab.setLayout(self.curves_tab_layout)

        self.chart_settings_tab.setLayout(self.chart_settings_layout)
        self.setup_chart_settings_layout()

        self.data_settings_tab.setLayout(self.data_tab_layout)
        self.setup_data_tab_layout()

        self.tab_panel.addTab(self.curve_settings_tab, "Curves")
        self.tab_panel.addTab(self.data_settings_tab, "Data")
        self.tab_panel.addTab(self.chart_settings_tab, "Graph")

        self.crosshair_settings_layout.addWidget(self.enable_crosshair_chk)
        self.crosshair_settings_layout.addWidget(self.crosshair_coord_lbl)

        self.zoom_x_layout.addWidget(self.zoom_in_x_btn)
        self.zoom_x_layout.addWidget(self.zoom_out_x_btn)

        self.zoom_y_layout.addWidget(self.zoom_in_y_btn)
        self.zoom_y_layout.addWidget(self.zoom_out_y_btn)

        self.view_all_reset_chart_layout.addWidget(self.reset_chart_btn)
        self.view_all_reset_chart_layout.addWidget(self.view_all_btn)

        self.pause_chart_layout.addWidget(self.pause_chart_btn)

        self.import_export_data_layout.addWidget(self.import_data_btn)
        self.import_export_data_layout.addWidget(self.export_data_btn)

        self.chart_control_layout.addLayout(self.zoom_x_layout)
        self.chart_control_layout.addLayout(self.zoom_y_layout)
        self.chart_control_layout.addLayout(self.view_all_reset_chart_layout)
        self.chart_control_layout.addLayout(self.pause_chart_layout)
        self.chart_control_layout.addLayout(self.crosshair_settings_layout)
        self.chart_control_layout.addLayout(self.import_export_data_layout)
        self.chart_control_layout.insertSpacing(5, 30)

        self.chart_layout.addWidget(self.chart)
        self.chart_layout.addLayout(self.chart_control_layout)

        self.chart_panel.setLayout(self.chart_layout)

        self.splitter.addWidget(self.chart_panel)
        self.splitter.addWidget(self.tab_panel)
        self.splitter.setSizes([1, 0])

        self.splitter.setHandleWidth(10)
        self.splitter.setStretchFactor(0, 0)
        self.splitter.setStretchFactor(1, 1)

        self.charting_layout.addWidget(self.splitter)

        self.body_layout.addWidget(self.pv_add_panel)
        self.body_layout.addLayout(self.charting_layout)
        self.body_layout.setSpacing(0)
        self.body_layout.setContentsMargins(0, 0, 0, 0)
        self.main_layout.addLayout(self.body_layout)

        self.enable_chart_control_buttons(False)

        handle = self.splitter.handle(1)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        button = QToolButton(handle)
        button.setArrowType(Qt.LeftArrow)
        button.clicked.connect(lambda: self.handle_splitter_button(True))
        layout.addWidget(button)
        button = QToolButton(handle)
        button.setArrowType(Qt.RightArrow)
        button.clicked.connect(lambda: self.handle_splitter_button(False))
        layout.addWidget(button)
        handle.setLayout(layout)
Ejemplo n.º 36
0
    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)
Ejemplo n.º 37
0
 def add_data() -> Future[dtype]:  # type: ignore
     future = Future()
     # simulate something that isn't immediately ready when function returns
     QTimer.singleShot(10, partial(future.set_result, data))
     return future
Ejemplo n.º 38
0
def main():
    """Execute QDarkStyle example."""
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument(
        '--qt_from',
        default='qtpy',
        type=str,
        choices=[
            'pyqt5', 'pyqt', 'pyside2', 'pyside', 'qtpy', 'pyqtgraph', 'qt.py'
        ],
        help=
        "Choose which binding and/or abstraction is to be used to run the example."
    )
    parser.add_argument(
        '--no_dark',
        action='store_true',
        help="Exihibts the original window (without jam_darkstyle).")
    parser.add_argument('--test',
                        action='store_true',
                        help="Auto close window after 2s.")
    parser.add_argument('--reset',
                        action='store_true',
                        help="Reset GUI settings (position, size) then opens.")
    parser.add_argument('--screenshots',
                        action='store_true',
                        help="Generate screenshots on images folder.")

    # Parsing arguments from command line
    args = parser.parse_args()

    # To avoid problems when testing without screen
    if args.test or args.screenshots:
        os.environ['QT_QPA_PLATFORM'] = 'offscreen'

    # Set QT_API variable before importing QtPy
    if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']:
        os.environ['QT_API'] = args.qt_from
    elif args.qt_from == 'pyqtgraph':
        os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB']
    elif args.qt_from in ['qt.py', 'qt']:
        try:
            import Qt
        except ImportError:
            print('Could not import Qt (Qt.Py)')
        else:
            os.environ['QT_API'] = Qt.__binding__

    # QtPy imports
    from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION
    from qtpy import __version__ as QTPY_VERSION
    from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget,
                                QStatusBar, QLabel, QPushButton, QMenu)
    from qtpy.QtCore import QTimer, Qt, QSettings

    # Set API_VERSION variable
    API_VERSION = ''

    if PYQT_VERSION:
        API_VERSION = PYQT_VERSION
    elif PYSIDE_VERSION:
        API_VERSION = PYSIDE_VERSION
    else:
        API_VERSION = 'Not found'

    # Import examples UI
    from mw_menus_ui import Ui_MainWindow as ui_main

    from dw_buttons_ui import Ui_DockWidget as ui_buttons
    from dw_displays_ui import Ui_DockWidget as ui_displays
    from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields
    from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields

    from dw_widgets_ui import Ui_DockWidget as ui_widgets
    from dw_views_ui import Ui_DockWidget as ui_views
    from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs
    from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs

    # create the application
    app = QApplication(sys.argv)
    app.setOrganizationName('QDarkStyle')
    app.setApplicationName('QDarkStyle Example')

    style = ''

    if not args.no_dark:
        style = jam_darkstyle.load_stylesheet()

    app.setStyleSheet(style)

    # create main window
    window = QMainWindow()
    window.setObjectName('mainwindow')

    ui = ui_main()
    ui.setupUi(window)

    title = ("QDarkStyle Example - " + "(QDarkStyle=v" +
             jam_darkstyle.__version__ + ", QtPy=v" + QTPY_VERSION + ", " +
             API_NAME + "=v" + API_VERSION + ", Qt=v" + QT_VERSION +
             ", Python=v" + platform.python_version() + ")")

    _logger.info(title)

    window.setWindowTitle(title)

    # Create docks for buttons
    dw_buttons = QDockWidget()
    dw_buttons.setObjectName('buttons')
    ui_buttons = ui_buttons()
    ui_buttons.setupUi(dw_buttons)
    window.addDockWidget(Qt.RightDockWidgetArea, dw_buttons)

    # Add actions on popup toolbuttons
    menu = QMenu()

    for action in ['Action A', 'Action B', 'Action C']:
        menu.addAction(action)

    ui_buttons.toolButtonDelayedPopup.setMenu(menu)
    ui_buttons.toolButtonInstantPopup.setMenu(menu)
    ui_buttons.toolButtonMenuButtonPopup.setMenu(menu)

    # Create docks for buttons
    dw_displays = QDockWidget()
    dw_displays.setObjectName('displays')
    ui_displays = ui_displays()
    ui_displays.setupUi(dw_displays)
    window.addDockWidget(Qt.RightDockWidgetArea, dw_displays)

    # Create docks for inputs - no fields
    dw_inputs_no_fields = QDockWidget()
    dw_inputs_no_fields.setObjectName('inputs_no_fields')
    ui_inputs_no_fields = ui_inputs_no_fields()
    ui_inputs_no_fields.setupUi(dw_inputs_no_fields)
    window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields)

    # Create docks for inputs - fields
    dw_inputs_fields = QDockWidget()
    dw_inputs_fields.setObjectName('inputs_fields')
    ui_inputs_fields = ui_inputs_fields()
    ui_inputs_fields.setupUi(dw_inputs_fields)
    window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields)

    # Create docks for widgets
    dw_widgets = QDockWidget()
    dw_widgets.setObjectName('widgets')
    ui_widgets = ui_widgets()
    ui_widgets.setupUi(dw_widgets)
    window.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets)

    # Create docks for views
    dw_views = QDockWidget()
    dw_views.setObjectName('views')
    ui_views = ui_views()
    ui_views.setupUi(dw_views)
    window.addDockWidget(Qt.LeftDockWidgetArea, dw_views)

    # Create docks for containers - no tabs
    dw_containers_no_tabs = QDockWidget()
    dw_containers_no_tabs.setObjectName('containers_no_tabs')
    ui_containers_no_tabs = ui_containers_no_tabs()
    ui_containers_no_tabs.setupUi(dw_containers_no_tabs)
    window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs)

    # Create docks for containters - tabs
    dw_containers_tabs = QDockWidget()
    dw_containers_tabs.setObjectName('containers_tabs')
    ui_containers_tabs = ui_containers_tabs()
    ui_containers_tabs.setupUi(dw_containers_tabs)
    window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs)

    # Tabify right docks
    window.tabifyDockWidget(dw_buttons, dw_displays)
    window.tabifyDockWidget(dw_displays, dw_inputs_fields)
    window.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields)

    # Tabify left docks
    window.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs)
    window.tabifyDockWidget(dw_containers_tabs, dw_widgets)
    window.tabifyDockWidget(dw_widgets, dw_views)

    # Issues #9120, #9121 on Spyder
    qstatusbar = QStatusBar()
    qstatusbar.addWidget(
        QLabel('Issue Spyder #9120, #9121 - background not matching.'))
    qstatusbar.addWidget(QPushButton('OK'))

    # Add info also in status bar for screenshots get it
    qstatusbar.addWidget(QLabel('INFO: ' + title))
    window.setStatusBar(qstatusbar)

    # Todo: add report info and other info in HELP graphical

    # Auto quit after 2s when in test mode
    if args.test:
        QTimer.singleShot(2000, app.exit)

    # Save screenshots for different displays and quit
    if args.screenshots:
        window.showFullScreen()
        create_screenshots(app, window, args.no_dark)
    # Do not read settings when taking screenshots - like reset
    else:
        _read_settings(window, args.reset, QSettings)
        window.showMaximized()

    app.exec_()
    _write_settings(window, QSettings)

    return window
Ejemplo n.º 39
0
    def __init__(self,
                 parent=None,
                 opts=None,
                 ui_file=None,
                 stylesheet=None,
                 maximize=False,
                 fullscreen=False,
                 position=None,
                 size=None,
                 confirm_exit=True,
                 title=None,
                 menu='default'):

        super(VCPMainWindow, self).__init__(parent)

        if opts is None:
            opts = qtpyvcp.OPTIONS

        self.setWindowTitle(title)

        self.app = QApplication.instance()

        self.confirm_exit = confirm_exit if opts.confirm_exit is None else opts.confirm_exit

        # Load the UI file AFTER defining variables, otherwise the values
        # set in QtDesigner get overridden by the default values
        if ui_file is not None:
            self.loadUi(ui_file)
            self.initUi()

        if menu is not None:
            try:
                # delete any preexisting menuBar added in QtDesigner
                # because it shadows the QMainWindow.menuBar() method
                del self.menuBar
            except AttributeError:
                pass

            if menu == 'default':
                menu = qtpyvcp.CONFIG.get('default_menubar', [])

            self.setMenuBar(self.buildMenuBar(menu))

        if title is not None:
            self.setWindowTitle(title)

        if stylesheet is not None:
            self.loadStylesheet(stylesheet)

        maximize = opts.maximize if opts.maximize is not None else maximize
        if maximize:
            QTimer.singleShot(0, self.showMaximized)

        fullscreen = opts.fullscreen if opts.fullscreen is not None else fullscreen
        if fullscreen:
            QTimer.singleShot(0, self.showFullScreen)

        if opts.hide_menu_bar:
            self.menuBar().hide()

        size = opts.size or size
        if size:
            try:
                width, height = size.lower().split('x')
                self.resize(int(width), int(height))
            except:
                LOG.exception('Error parsing --size argument: %s', size)

        pos = opts.position or position
        if pos:
            try:
                xpos, ypos = pos.lower().split('x')
                self.move(int(xpos), int(ypos))
            except:
                LOG.exception('Error parsing --position argument: %s', pos)

        QShortcut(QKeySequence("F11"), self, self.toggleFullscreen)
        self.app.focusChanged.connect(self.focusChangedEvent)
Ejemplo n.º 40
0
def test_environ(qtbot):
    """Test the environment variables dialog."""
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    dialog = setup_environ(qtbot)
    dialog.show()
    assert dialog
Ejemplo n.º 41
0
 def onToolTableFileChanged(self, path):
     LOG.debug('Tool Table file changed: {}'.format(path))
     # ToolEdit deletes the file and then rewrites it, so wait
     # a bit to ensure the new data has been writen out.
     QTimer.singleShot(50, self.reloadToolTable)
Ejemplo n.º 42
0
class MainWindow(QWidget):
    def __init__(self, config: Config) -> None:
        """
        Main window with the GUI and whatever player is being used.
        """

        super().__init__()
        self.setWindowTitle('vidify')

        # Setting the window to stay on top
        if config.stay_on_top:
            self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # Setting the fullscreen and window size
        if config.fullscreen:
            self.showFullScreen()
        else:
            self.resize(config.width or 800, config.height or 600)

        # Loading the used fonts (Inter)
        font_db = QFontDatabase()
        for font in Res.fonts:
            font_db.addApplicationFont(font)

        # Initializing the player and saving the config object in the window.
        self.layout = QHBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.player = initialize_player(config.player, config)
        logging.info("Using %s as the player", config.player)
        self.config = config

        # The API initialization is more complex. For more details, please
        # check the flow diagram in vidify.api. First we have to check if
        # the API is saved in the config:
        try:
            api_data = get_api_data(config.api)
        except KeyError:
            # Otherwise, the user is prompted for an API. After choosing one,
            # it will be initialized from outside this function.
            logging.info("API not found: prompting the user")
            self.API_selection = APISelection()
            self.layout.addWidget(self.API_selection)
            self.API_selection.api_chosen.connect(self.on_api_selection)
        else:
            logging.info("Using %s as the API", config.api)
            self.initialize_api(api_data)

    @Slot(str)
    def on_api_selection(self, api_str: str) -> None:
        """
        Method called when the API is selected with APISelection.
        The provided api string must be an existent entry
        inside the APIData enumeration.
        """

        # Removing the widget used to obtain the API string
        self.layout.removeWidget(self.API_selection)
        self.API_selection.setParent(None)
        self.API_selection.hide()
        del self.API_selection

        # Saving the API in the config
        self.config.api = api_str

        # Starting the API initialization
        self.initialize_api(APIData[api_str])

    def initialize_api(self, api_data: APIData) -> None:
        """
        Initializes an API with the information from APIData.
        """

        # The API may need interaction with the user to obtain credentials
        # or similar data. This function will already take care of the
        # rest of the initialization.
        if api_data.gui_init_fn is not None:
            fn = getattr(self, api_data.gui_init_fn)
            fn()
            return

        # Initializing the API with dependency injection.
        mod = importlib.import_module(api_data.module)
        cls = getattr(mod, api_data.class_name)
        self.api = cls()

        self.wait_for_connection(
            self.api.connect_api, message=api_data.connect_msg,
            event_loop_interval=api_data.event_loop_interval)

    def wait_for_connection(self, conn_fn: Callable[[], None],
                            message: Optional[str] = None,
                            event_loop_interval: int = 1000) -> None:

        """
        Creates an APIConnecter instance and waits for the API to be
        available, or times out otherwise.
        """

        self.event_loop_interval = event_loop_interval
        self.api_connecter = APIConnecter(
            conn_fn, message or "Waiting for connection")
        self.api_connecter.success.connect(self.on_conn_success)
        self.api_connecter.fail.connect(self.on_conn_fail)
        self.layout.addWidget(self.api_connecter)
        self.api_connecter.start()

    @Slot()
    def on_conn_fail(self) -> None:
        """
        If the API failed to connect, the app will be closed.
        """

        print("Timed out waiting for the connection")
        QCoreApplication.exit(1)

    @Slot(float)
    def on_conn_success(self, start_time: float) -> None:
        """
        Once the connection has been established correctly, the API can
        be started properly.
        """

        logging.info("Succesfully connected to the API")
        self.layout.removeWidget(self.api_connecter)
        del self.api_connecter

        # Initializing the optional audio synchronization extension, now
        # that there's access to the API's data. Note that this feature
        # is only available on Linux.
        if self.config.audiosync:
            from vidify.audiosync import AudiosyncWorker
            self.audiosync = AudiosyncWorker(self.api.player_name)
            self.audiosync.success.connect(self.on_audiosync_success)
            self.audiosync.failed.connect(self.on_audiosync_fail)

        # Loading the player
        self.setStyleSheet(f"background-color:{Colors.black};")
        self.layout.addWidget(self.player)
        self.play_video(self.api.artist, self.api.title, start_time)

        # Connecting to the signals generated by the API
        self.api.new_song_signal.connect(self.play_video)
        self.api.position_signal.connect(self.change_video_position)
        self.api.status_signal.connect(self.change_video_status)

        # Starting the event loop if it was initially passed as
        # a parameter.
        if self.event_loop_interval is not None:
            self.start_event_loop(self.api.event_loop,
                                  self.event_loop_interval)

    def start_event_loop(self, event_loop: Callable[[], None],
                         ms: int) -> None:
        """
        Starts a "manual" event loop with a timer every `ms` milliseconds.
        This is used with the SwSpotify API and the Web API to check every
        `ms` seconds if a change has happened, like if the song was paused.
        """

        logging.info("Starting event loop")
        timer = QTimer(self)

        # Qt doesn't accept a method as the parameter so it's converted
        # to a function.
        if isinstance(event_loop, types.MethodType):
            timer.timeout.connect(lambda: event_loop())
        else:
            timer.timeout.connect(event_loop)
        timer.start(ms)

    @Slot(bool)
    def change_video_status(self, is_playing: bool) -> None:
        """
        Slot used for API updates of the video status.
        """

        self.player.pause = not is_playing

        # If there is an audiosync thread running, this will pause the sound
        # recording and youtube downloading.
        if self.config.audiosync and self.audiosync.status != 'idle':
            self.audiosync.is_running = is_playing

    @Slot(int)
    def change_video_position(self, ms: int) -> None:
        """
        Slot used for API updates of the video position.
        """

        if not self.config.audiosync:
            self.player.position = ms

        # Audiosync is aborted if the position of the video changed, since
        # the audio being recorded won't make sense.
        if self.config.audiosync and self.audiosync.status != 'idle':
            self.audiosync.abort()

    @Slot(str, str, float)
    def play_video(self, artist: str, title: str, start_time: float) -> None:
        """
        Slot used to play a video. This is called when the API is first
        initialized from this GUI, and afterwards from the event loop handler
        whenever a new song is detected.

        If an error was detected when downloading the video, the default one
        is shown instead.

        Both audiosync and youtubedl work in separate threads to avoid
        blocking the GUI. This method will start both of them.
        """

        # Checking that the artist and title are valid first of all
        if self.api.artist in (None, '') and self.api.title in (None, ''):
            logging.info("The provided artist and title are empty.")
            self.on_youtubedl_fail()
            if self.config.audiosync:
                self.on_audiosync_fail()
            return

        # This delay is used to know the elapsed time until the video
        # actually starts playing, used in the audiosync feature.
        self.timestamp = start_time
        query = f"ytsearch:{format_name(artist, title)} Official Video"

        if self.config.audiosync:
            self.launch_audiosync(query)

        self.launch_youtubedl(query)

    def launch_audiosync(self, query: str) -> None:
        """
        Starts the audiosync thread, that will call either
        self.on_audiosync_success, or self.on_audiosync_fail once it's
        finished.

        First trying to stop the previous audiosync thread, as only
        one audiosync thread can be running at once.

        Note: QThread.start() is guaranteed to work once QThread.run()
        has returned. Thus, this will wait until it's done and launch
        the new one.
        """

        self.audiosync.abort()
        self.audiosync.wait()
        self.audiosync.youtube_title = query
        self.audiosync.start()
        logging.info("Started a new audiosync job")

    def launch_youtubedl(self, query: str) -> None:
        """
        Starts a YoutubeDL thread that will call either
        self.on_youtubedl_success or self.on_youtubedl_fail once it's done.
        """

        logging.info("Starting the youtube-dl thread")
        self.youtubedl = YouTubeDLWorker(
            query, self.config.debug, self.config.width, self.config.height)
        self.yt_thread = QThread()
        self.youtubedl.moveToThread(self.yt_thread)
        self.yt_thread.started.connect(self.youtubedl.get_url)
        self.youtubedl.success.connect(self.on_yt_success)
        self.youtubedl.fail.connect(self.on_youtubedl_fail)
        self.youtubedl.finish.connect(self.yt_thread.exit)
        self.yt_thread.start()

    @Slot()
    def on_youtubedl_fail(self) -> None:
        """
        If Youtube-dl for whatever reason failed to load the video, a fallback
        error video is shown, along with a message to let the user know what
        happened.
        """

        self.player.start_video(Res.default_video, self.api.is_playing)
        print("The video wasn't found, either because of an issue with your"
              " internet connection or because the provided data was invalid."
              " For more information, enable the debug mode.")

    @Slot(str)
    def on_yt_success(self, url: str) -> None:
        """
        Obtains the video URL from the Youtube-dl thread and starts playing
        the video. Also shows the lyrics if enabled. The position of the video
        isn't set if it's using audiosync, because this is done by the
        AudiosyncWorker thread.
        """

        self.player.start_video(url, self.api.is_playing)

        if not self.config.audiosync:
            try:
                self.player.position = self.api.position
            except NotImplementedError:
                self.player.position = 0

        # Finally, the lyrics are displayed. If the video wasn't found, an
        # error message is shown.
        if self.config.lyrics:
            print(get_lyrics(self.api.artist, self.api.title))

    @Slot()
    def on_audiosync_fail(self) -> None:
        """
        Currently, when audiosync fails, nothing happens.
        """

        logging.info("Audiosync module failed to return the lag")

    @Slot(int)
    def on_audiosync_success(self, lag: int) -> None:
        """
        Slot used after the audiosync function has finished. It sets the
        returned lag in milliseconds on the player.

        This assumes that the song wasn't paused until this issue is fixed:
        https://github.com/vidify/audiosync/issues/12
        """

        logging.info("Audiosync module returned %d ms", lag)

        # The current API position according to what's being recorded.
        playback_delay = round((time.time() - self.timestamp) * 1000) \
            - self.player.position
        lag += playback_delay

        # The user's custom audiosync delay. This is basically the time taken
        # until the module started recording (which may depend on the user
        # hardware and other things). Thus, it will almost always be a
        # negative value.
        lag += self.config.audiosync_calibration

        logging.info("Total delay is %d ms", lag)
        if lag > 0:
            self.player.position += lag
        elif lag < 0:
            # If a negative delay is larger than the current player position,
            # the player position is set to zero after the lag has passed
            # with a timer.
            if self.player.position < -lag:
                self.sync_timer = QTimer(self)
                self.sync_timer.singleShot(
                    -lag, lambda: self.change_video_position(0))
            else:
                self.player.position += lag

    def init_spotify_web_api(self) -> None:
        """
        SPOTIFY WEB API CUSTOM FUNCTION

        Note: the Tekore imports are done inside the functions so that
        Tekore isn't needed for whoever doesn't plan to use the Spotify
        Web API.
        """

        from vidify.api.spotify.web import get_token
        from vidify.gui.api.spotify_web import SpotifyWebPrompt

        token = get_token(self.config.refresh_token, self.config.client_id,
                          self.config.client_secret)

        if token is not None:
            # If the previous token was valid, the API can already start.
            logging.info("Reusing a previously generated token")
            self.start_spotify_web_api(token, save_config=False)
        else:
            # Otherwise, the credentials are obtained with the GUI. When
            # a valid auth token is ready, the GUI will initialize the API
            # automatically exactly like above. The GUI won't ask for a
            # redirect URI for now.
            logging.info("Asking the user for credentials")
            # The SpotifyWebPrompt handles the interaction with the user and
            # emits a `done` signal when it's done.
            self._spotify_web_prompt = SpotifyWebPrompt(
                self.config.client_id, self.config.client_secret,
                self.config.redirect_uri)
            self._spotify_web_prompt.done.connect(self.start_spotify_web_api)
            self.layout.addWidget(self._spotify_web_prompt)

    def start_spotify_web_api(self, token: 'RefreshingToken',
                              save_config: bool = True) -> None:
        """
        SPOTIFY WEB API CUSTOM FUNCTION

        Initializes the Web API, also saving them in the config for future
        usage (if `save_config` is true).
        """
        from vidify.api.spotify.web import SpotifyWebAPI

        logging.info("Initializing the Spotify Web API")

        # Initializing the web API
        self.api = SpotifyWebAPI(token)
        api_data = APIData['SPOTIFY_WEB']
        self.wait_for_connection(
            self.api.connect_api, message=api_data.connect_msg,
            event_loop_interval=api_data.event_loop_interval)

        # The obtained credentials are saved for the future
        if save_config:
            logging.info("Saving the Spotify Web API credentials")
            self.config.client_secret = self._spotify_web_prompt.client_secret
            self.config.client_id = self._spotify_web_prompt.client_id
            self.config.refresh_token = token.refresh_token

        # The credentials prompt widget is removed after saving the data. It
        # may not exist because start_spotify_web_api was called directly,
        # so errors are taken into account.
        try:
            self.layout.removeWidget(self._spotify_web_prompt)
            self._spotify_web_prompt.hide()
            del self._spotify_web_prompt
        except AttributeError:
            pass
Ejemplo n.º 43
0
class QtRemoteDispatcher(QObject):
    """
    Dispatch documents received over the network from a Kafka broker.

    This is designed to be run in a Qt application.

    Parameters
    ----------
    topics: list
        List of topics as strings such as ["topic-1", "topic-2"]
    bootstrap_servers : str
        Comma-delimited list of Kafka server addresses as a string
        such as ``'127.0.0.1:9092'``
    group_id: str
        Required string identifier for Kafka Consumer group
    consumer_config: dict
        Override default configuration or specify additional configuration
        options to confluent_kafka.Consumer.
    polling_duration: float
        Time in seconds to wait for a message before running function
        work_while_waiting. Default is 0.05.
    deserializer: function, optional
        optional function to deserialize data. Default is msgpack.loads.
    parent_qobject: QObject
        optional parent in the QT sense

    Example
    -------
    Plot data from all documents generated by remote RunEngines.
    >>> d = RemoteDispatcher(
    >>>         topics=["abc.bluesky.documents", "ghi.bluesky.documents"],
    >>>         bootstrap_servers='localhost:9092',
    >>>         group_id="document-printers",
    >>>         consumer_config={
    >>>             "auto.offset.reset": "latest"
    >>>         }
    >>>    )
    >>> d.subscribe(stream_documents_into_runs(model.add_run))
    >>> d.start()  # launches periodic workers on background threads
    >>> d.stop()  # stop launching workers
    """
    def __init__(
        self,
        topics,
        bootstrap_servers,
        group_id,
        consumer_config=None,
        polling_duration=0.05,
        deserializer=msgpack.loads,
        parent_qobject=None,
    ):
        super().__init__(parent_qobject)
        self.closed = False
        self._timer = QTimer(self)
        self._dispatcher = bluesky_kafka.RemoteDispatcher(
            topics=topics,
            bootstrap_servers=bootstrap_servers,
            group_id=group_id,
            consumer_config=consumer_config,
            polling_duration=polling_duration,
            deserializer=deserializer,
        )
        self.subscribe = self._dispatcher.subscribe
        self._waiting_for_start = True
        self.worker = None

    def _receive_data(self, continue_polling=None):
        # TODO Think about back pressure.
        if continue_polling is None:

            def continue_polling_forever():
                return True

            continue_polling = continue_polling_forever

        while continue_polling():
            try:
                # there should maybe be a poll method on the dispatcher
                msg = self._dispatcher._bluesky_consumer.consumer.poll(
                    self._dispatcher._bluesky_consumer.polling_duration)
                if msg is None:
                    logger.debug("no message")
                    break
                elif msg.error():
                    logger.error("Kafka Consumer error: %s", msg.error())
                else:
                    try:
                        # there should be a more direct way to deserialize the message
                        (
                            name,
                            document,
                        ) = self._dispatcher._bluesky_consumer._deserializer(
                            msg.value())
                        if self._waiting_for_start:
                            if name == "start":
                                self._waiting_for_start = False
                            else:
                                # We subscribed midstream and are seeing documents for
                                # which we do not have the full run. Wait for a 'start'
                                # doc.
                                logger.debug(
                                    "keep waiting for a start document")
                                return
                        yield self._dispatcher._bluesky_consumer.consumer, msg.topic(
                        ), name, document
                    except Exception as exc:
                        logger.exception(exc)
            except Exception as exc:
                logger.exception(exc)

        logger.debug("polling loop has ended cleanly")

    def start(self, continue_polling=None):
        logger.debug("QtRemoteDispatcher.start")
        if self.closed:
            raise RuntimeError("This RemoteDispatcher has already been "
                               "started and interrupted. Create a fresh "
                               "instance with {}".format(repr(self)))

        self._work_loop(continue_polling=continue_polling)

    def _work_loop(self, continue_polling=None):
        self.worker = create_worker(
            self._receive_data,
            continue_polling=continue_polling,
        )
        # Schedule this method to be run again after a brief wait.
        self.worker.finished.connect(lambda: self._timer.singleShot(
            int(LOADING_LATENCY),
            functools.partial(self._work_loop, continue_polling),
        ))
        self.worker.yielded.connect(self._process_result)

        self.worker.start()

    def _process_result(self, result):
        if result is None:
            return
        consumer, topic, name, document = result
        self._dispatcher.process_document(consumer=consumer,
                                          topic=topic,
                                          name=name,
                                          document=document)

    def stop(self):
        logger.debug("QtRemoteDispatcher.stop")
        self.closed = True
Ejemplo n.º 44
0
 def event_jupyter_execute_finished(self):
     """If the jupyter kernel is done, execute the next queued cell."""
     if self._cells_to_run_queue:
         QTimer.singleShot(1000, self._run_cell_from_queue)
Ejemplo n.º 45
0
def test_environ(qtbot):
    """Test the environment variables dialog."""
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    dialog = setup_environ(qtbot)
    dialog.show()
    assert dialog
Ejemplo n.º 46
0
 def toggleLeds(self):
     """Toggle leds state."""
     for led in self.leds:
         led.toggleState()
     QTimer.singleShot(1000, self.toggleLeds)
Ejemplo n.º 47
0
class RemoteDispatcher(QObject):
    """
    Dispatch documents received over the network from a 0MQ proxy.

    This is designed to be run in a Qt application.

    Parameters
    ----------
    address : tuple | str
        Address of a running 0MQ proxy, given either as a string like
        ``'127.0.0.1:5567'`` or as a tuple like ``('127.0.0.1', 5567)``
    prefix : bytes, optional
        User-defined bytestring used to distinguish between multiple
        Publishers. If set, messages without this prefix will be ignored.
        If unset, no mesages will be ignored.
    deserializer: function, optional
        optional function to deserialize data. Default is pickle.loads

    Examples
    --------

    Print all documents generated by remote RunEngines.

    >>> d = RemoteDispatcher(('localhost', 5568))
    >>> d.subscribe(stream_documents_into_runs(model.add_run))
    >>> d.start()  # launches periodic workers on background threads
    >>> d.stop()  # stop launching workers
    """
    def __init__(self,
                 address,
                 *,
                 prefix=b"",
                 deserializer=pickle.loads,
                 parent=None):
        super().__init__(parent)
        if isinstance(prefix, str):
            raise ValueError("prefix must be bytes, not string")
        if b" " in prefix:
            raise ValueError("prefix {!r} may not contain b' '".format(prefix))
        self._prefix = prefix
        if isinstance(address, str):
            address = address.split(":", maxsplit=1)
        self._deserializer = deserializer
        self.address = (address[0], int(address[1]))

        self._context = zmq.Context()
        self._socket = self._context.socket(zmq.SUB)
        url = "tcp://%s:%d" % self.address
        self._socket.connect(url)
        self._socket.setsockopt_string(zmq.SUBSCRIBE, "")
        self._task = None
        self.closed = False
        self._timer = QTimer(self)
        self._dispatcher = Dispatcher()
        self.subscribe = self._dispatcher.subscribe
        self._waiting_for_start = True

    def _receive_data(self):
        our_prefix = self._prefix  # local var to save an attribute lookup
        # TODO Pull on the socket more than once here, until it blocks, to
        # ensure we do not get more and more behind over time.
        message = self._socket.recv()
        prefix, name, doc = message.split(b" ", 2)
        name = name.decode()
        if (not our_prefix) or prefix == our_prefix:
            if self._waiting_for_start:
                if name == "start":
                    self._waiting_for_start = False
                else:
                    # We subscribed midstream and are seeing documents for
                    # which we do not have the full run. Wait for a 'start'
                    # doc.
                    return
            doc = self._deserializer(doc)
        return name, doc

    def start(self):
        if self.closed:
            raise RuntimeError("This RemoteDispatcher has already been "
                               "started and interrupted. Create a fresh "
                               "instance with {}".format(repr(self)))
        self._work_loop()

    def _work_loop(self):
        worker = create_worker(self._receive_data, )
        # Schedule this method to be run again after a brief wait.
        worker.finished.connect(
            lambda: self._timer.singleShot(LOADING_LATENCY, self._work_loop))
        worker.returned.connect(self._process_result)
        worker.start()

    def _process_result(self, result):
        if result is None:
            return
        name, doc = result
        self._dispatcher.process(DocumentNames[name], doc)

    def stop(self):
        self.closed = True
Ejemplo n.º 48
0
 def start_delayed(self):
     QTimer.singleShot(100, self._start_when_locked)
Ejemplo n.º 49
0
class PluginManager(QObject):

    introspection_complete = Signal(object)

    def __init__(self, executable):
        super(PluginManager, self).__init__()
        plugins = OrderedDict()
        for name in PLUGINS:
            try:
                plugin = PluginClient(name, executable)
                plugin.run()
            except Exception as e:
                debug_print('Introspection Plugin Failed: %s' % name)
                debug_print(str(e))
                continue
            debug_print('Introspection Plugin Loaded: %s' % name)
            plugins[name] = plugin
            plugin.received.connect(self.handle_response)
        self.plugins = plugins
        self.timer = QTimer()
        self.desired = []
        self.ids = dict()
        self.info = None
        self.request = None
        self.pending = None
        self.pending_request = None
        self.waiting = False

    def send_request(self, info):
        """Handle an incoming request from the user."""
        if self.waiting:
            if info.serialize() != self.info.serialize():
                self.pending_request = info
            else:
                debug_print('skipping duplicate request')
            return
        debug_print('%s request' % info.name)
        desired = None
        self.info = info
        editor = info.editor
        if (info.name == 'completion' and 'jedi' not in self.plugins and
                info.line.lstrip().startswith(('import ', 'from '))):
            desired = 'fallback'

        if ((not editor.is_python_like()) or
                sourcecode.is_keyword(info.obj) or
                (editor.in_comment_or_string() and info.name != 'info')):
            desired = 'fallback'

        plugins = self.plugins.values()
        if desired:
            plugins = [self.plugins[desired]]
            self.desired = [desired]
        elif (info.name == 'definition' and not info.editor.is_python() or
              info.name == 'info'):
            self.desired = list(self.plugins.keys())
        else:
            # Use all but the fallback
            plugins = list(self.plugins.values())[:-1]
            self.desired = list(self.plugins.keys())[:-1]

        self._start_time = time.time()
        self.waiting = True
        method = 'get_%s' % info.name
        value = info.serialize()
        self.ids = dict()
        for plugin in plugins:
            request_id = plugin.request(method, value)
            self.ids[request_id] = plugin.name
        self.timer.stop()
        self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout)

    def validate(self):
        for plugin in self.plugins.values():
            plugin.request('validate')

    def handle_response(self, response):
        name = self.ids.get(response['request_id'], None)
        if not name:
            return
        if response.get('error', None):
            debug_print('Response error:', response['error'])
            return
        if name == self.desired[0] or not self.waiting:
            if response.get('result', None):
                self._finalize(response)
        else:
            self.pending = response

    def close(self):
        [plugin.close() for plugin in self.plugins]

    def _finalize(self, response):
        self.waiting = False
        self.pending = None
        if self.info:
            delta = time.time() - self._start_time
            debug_print('%s request from %s finished: "%s" in %.1f sec'
                % (self.info.name, response['name'],
                   str(response['result'])[:100], delta))
            response['info'] = self.info
            self.introspection_complete.emit(response)
            self.info = None
        if self.pending_request:
            info = self.pending_request
            self.pending_request = None
            self.send_request(info)

    def _handle_timeout(self):
        self.waiting = False
        if self.pending:
            self._finalize(self.pending)
        else:
            debug_print('No valid responses acquired')
Ejemplo n.º 50
0
def _main(args):
    # To avoid problems when testing without screen
    if args.test or args.screenshots:
        os.environ['QT_QPA_PLATFORM'] = 'offscreen'

    # Set QT_API variable before importing QtPy
    if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']:
        os.environ['QT_API'] = args.qt_from
    elif args.qt_from == 'pyqtgraph':
        os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB']
    elif args.qt_from in ['qt.py', 'qt']:
        try:
            import Qt
        except ImportError:
            print('Could not import Qt (Qt.Py)')
        else:
            os.environ['QT_API'] = Qt.__binding__

    # QtPy imports
    from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION
    from qtpy import __version__ as QTPY_VERSION
    from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget,
                                QStatusBar, QLabel, QMenu)
    from qtpy.QtCore import QTimer, Qt, QSettings

    # Set API_VERSION variable
    API_VERSION = ''

    if PYQT_VERSION:
        API_VERSION = PYQT_VERSION
    elif PYSIDE_VERSION:
        API_VERSION = PYSIDE_VERSION
    else:
        API_VERSION = 'Not found'

    # Import examples UI
    from mw_menus_ui import Ui_MainWindow as ui_main

    from dw_buttons_ui import Ui_DockWidget as ui_buttons
    from dw_displays_ui import Ui_DockWidget as ui_displays
    from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields
    from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields

    from dw_widgets_ui import Ui_DockWidget as ui_widgets
    from dw_views_ui import Ui_DockWidget as ui_views
    from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs
    from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs

    # qrainbowstyle.useDarwinButtons()

    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)

    # create the application
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        app = QApplication.instance()
    app.setOrganizationName('QRainbowStyle')
    app.setApplicationName('QRainbowStyle Example')

    styles = qrainbowstyle.getAvailableStyles()

    style = args.style
    if not args.style:
        style = styles[random.randint(0, len(styles)) - 1]

    app.setStyleSheet(qrainbowstyle.load_stylesheet(style=str(style)))

    # create main window
    window = qrainbowstyle.windows.FramelessWindow()
    window.setTitlebarHeight(30)

    widget = QMainWindow(window)
    widget.setWindowFlags(Qt.Widget)
    widget.setObjectName('mainwindow')
    ui = ui_main()
    ui.setupUi(widget)

    window.addContentWidget(widget)

    title = ("QRainbowStyle Example - "
             + "(QRainbowStyle=v" + qrainbowstyle.__version__
             + ", QtPy=v" + QTPY_VERSION
             + ", " + API_NAME + "=v" + API_VERSION
             + ", Qt=v" + QT_VERSION
             + ", Python=v" + platform.python_version() + ")")

    _logger.info(title)

    window.setWindowTitle(title)

    # Create docks for buttons
    dw_buttons = QDockWidget()
    dw_buttons.setObjectName('buttons')
    ui_buttons = ui_buttons()
    ui_buttons.setupUi(dw_buttons)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_buttons)

    # Add actions on popup toolbuttons
    menu = QMenu()

    for action in ['Action A', 'Action B', 'Action C']:
        menu.addAction(action)

    ui_buttons.toolButtonDelayedPopup.setMenu(menu)
    ui_buttons.toolButtonInstantPopup.setMenu(menu)
    ui_buttons.toolButtonMenuButtonPopup.setMenu(menu)

    # Create docks for buttons
    dw_displays = QDockWidget()
    dw_displays.setObjectName('displays')
    ui_displays = ui_displays()
    ui_displays.setupUi(dw_displays)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_displays)

    # Create docks for inputs - no fields
    dw_inputs_no_fields = QDockWidget()
    dw_inputs_no_fields.setObjectName('inputs_no_fields')
    ui_inputs_no_fields = ui_inputs_no_fields()
    ui_inputs_no_fields.setupUi(dw_inputs_no_fields)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields)

    # Create docks for inputs - fields
    dw_inputs_fields = QDockWidget()
    dw_inputs_fields.setObjectName('inputs_fields')
    ui_inputs_fields = ui_inputs_fields()
    ui_inputs_fields.setupUi(dw_inputs_fields)
    widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields)

    # Create docks for widgets
    dw_widgets = QDockWidget()
    dw_widgets.setObjectName('widgets')
    ui_widgets = ui_widgets()
    ui_widgets.setupUi(dw_widgets)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets)

    # Create docks for views
    dw_views = QDockWidget()
    dw_views.setObjectName('views')
    ui_views = ui_views()
    ui_views.setupUi(dw_views)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_views)

    # Create docks for containers - no tabs
    dw_containers_no_tabs = QDockWidget()
    dw_containers_no_tabs.setObjectName('containers_no_tabs')
    ui_containers_no_tabs = ui_containers_no_tabs()
    ui_containers_no_tabs.setupUi(dw_containers_no_tabs)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs)

    # Create docks for containters - tabs
    dw_containers_tabs = QDockWidget()
    dw_containers_tabs.setObjectName('containers_tabs')
    ui_containers_tabs = ui_containers_tabs()
    ui_containers_tabs.setupUi(dw_containers_tabs)
    widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs)

    # Tabify right docks
    widget.tabifyDockWidget(dw_buttons, dw_displays)
    widget.tabifyDockWidget(dw_displays, dw_inputs_fields)
    widget.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields)

    # Tabify left docks
    widget.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs)
    widget.tabifyDockWidget(dw_containers_tabs, dw_widgets)
    widget.tabifyDockWidget(dw_widgets, dw_views)

    # Issues #9120, #9121 on Spyder
    qstatusbar = QStatusBar()
    qstatusbar.addWidget(QLabel('Style'))
    qstatusbarbutton = qrainbowstyle.widgets.StylePickerHorizontal()
    qstatusbar.addWidget(qstatusbarbutton)
    qstatusbar.setSizeGripEnabled(False)

    # Add info also in status bar for screenshots get it
    qstatusbar.addWidget(QLabel('INFO: ' + title))
    widget.setStatusBar(qstatusbar)

    # Todo: add report info and other info in HELP graphical

    # Auto quit after 2s when in test mode
    if args.test:
        QTimer.singleShot(2000, app.exit)

    _read_settings(widget, args.reset, QSettings)
    window.show()
    # window.showMaximized()

    app.exec_()
    _write_settings(widget, QSettings)