Exemple #1
0
def test_goto_uri_message_box(qtbot, codeeditor, mocker):
    """
    Test that a message box is displayed when the shorthand issue notation is
    used (gh-123) indicating the user that the file is not under a repository
    """
    code_editor = codeeditor
    code_editor.filename = TEMPFILE_PATH
    code_editor._last_hover_pattern_key = 'issue'

    def interact():
        msgbox = code_editor.findChild(QMessageBox)
        assert msgbox
        qtbot.keyClick(msgbox, Qt.Key_Return)

    timer = QTimer()
    timer.setSingleShot(True)
    timer.setInterval(500)
    timer.timeout.connect(interact)
    timer.start()

    code_editor.go_to_uri_from_cursor('gh-123')

    code_editor.filename = None
    code_editor._last_hover_pattern_key = None
    code_editor._last_hover_pattern_text = None
Exemple #2
0
    def _wait_reply(self, call_id, call_name, timeout):
        """Wait for the other side reply."""
        if call_id in self._reply_inbox:
            return

        # Create event loop to wait with
        wait_loop = QEventLoop()
        self._sig_got_reply.connect(wait_loop.quit)
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)
        wait_timeout.timeout.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while len(self._reply_waitlist) > 0:
            if not wait_timeout.isActive():
                self._sig_got_reply.disconnect(wait_loop.quit)
                if call_id in self._reply_waitlist:
                    raise TimeoutError("Timeout while waiting for {}".format(
                        self._reply_waitlist))
                return
            wait_loop.exec_()

        wait_timeout.stop()
        self._sig_got_reply.disconnect(wait_loop.quit)
Exemple #3
0
    def _wait(self, condition, signal, timeout_msg, timeout):
        """
        Wait until condition() is True by running an event loop.

        signal: qt signal that should interrupt the event loop.
        timeout_msg: Message to display in case of a timeout.
        timeout: time in seconds before a timeout
        """
        if condition():
            return

        # Create event loop to wait with
        wait_loop = QEventLoop()
        signal.connect(wait_loop.quit)
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)
        wait_timeout.timeout.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while not condition():
            if not wait_timeout.isActive():
                signal.disconnect(wait_loop.quit)
                if not condition():
                    raise TimeoutError(timeout_msg)
                return
            wait_loop.exec_()

        wait_timeout.stop()
        signal.disconnect(wait_loop.quit)
Exemple #4
0
def test_add_repeated_item(qtbot, pathmanager, tmpdir):
    """
    Check behavior when an unchecked item that is already on the list is added.
    The checkbox should then be checked and if replying 'yes' to the question,
    then the item should be moved to the top.
    """
    pathmanager.show()
    dir1 = str(tmpdir.mkdir("foo"))
    dir2 = str(tmpdir.mkdir("bar"))
    dir3 = str(tmpdir.mkdir("spam"))
    pathmanager.add_path(dir1)
    pathmanager.add_path(dir2)
    pathmanager.add_path(dir3)
    pathmanager.set_row_check_state(1, Qt.Unchecked)
    assert not all(pathmanager.get_path_dict().values())

    def interact_message_box():
        messagebox = pathmanager.findChild(QMessageBox)
        buttons = messagebox.findChildren(QPushButton)
        for button in buttons:
            if 'yes' in button.text().lower():
                qtbot.mouseClick(button, Qt.LeftButton)
                break

    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(interact_message_box)
    timer.start(500)
    pathmanager.add_path(dir2)
    print(pathmanager.get_path_dict())

    # Back to main thread
    assert pathmanager.count() == 3
    assert list(pathmanager.get_path_dict().keys())[0] == dir2
    assert all(pathmanager.get_path_dict().values())
Exemple #5
0
def create_timer(func, interval=500):
    """Helper function to help interact with modal dialogs."""
    timer = QTimer()
    timer.setInterval(interval)
    timer.setSingleShot(True)
    timer.timeout.connect(func)
    timer.start()
    return timer
Exemple #6
0
def main(argv=sys.argv):
    """
    The majority of the code in this function was taken from start_glue() in main.py after a discussion with
    Tom Robataille. We wanted the ability to get command line arguments and use them in here and this seemed
    to be the cleanest way to do it.
    """
    # Make sure the mosviz startup item is registered
    from .startup import mosviz_setup  # noqa

    parser = argparse.ArgumentParser()
    parser.add_argument('data_files', nargs=argparse.REMAINDER)
    args = parser.parse_known_args(argv[1:])

    import glue
    from glue.utils.qt import get_qapp
    app = get_qapp()

    # Splash screen
    splash = get_splash()
    splash.image = QtGui.QPixmap(MOSVIZ_SPLASH_PATH)
    splash.show()

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    datafiles = args[0].data_files

    # # Show the splash screen for 2 seconds
    timer = QTimer()
    timer.setInterval(2000)
    timer.setSingleShot(True)
    timer.timeout.connect(splash.close)
    timer.start()

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    splash.set_progress(100)

    session = glue.core.Session(data_collection=data_collection, hub=hub)
    ga = GlueApplication(session=session)
    qapp = QtWidgets.QApplication.instance()
    ga.setWindowTitle('MOSViz v{0}'.format(__version__))
    qapp.setWindowIcon(QtGui.QIcon(MOSVIZ_ICON_PATH))
    ga.setWindowIcon(QtGui.QIcon(MOSVIZ_ICON_PATH))

    # Load the data files.
    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets, auto_merge=False)

    ga.run_startup_action('mosviz')

    sys.exit(ga.start(maximized=True))
Exemple #7
0
def main():
    logmodule = qrainbowstyle.extras.OutputLogger()
    qInstallMessageHandler(qrainbowstyle.extras.qt_message_handler)

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

    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(qrainbowstyle.load_stylesheet(style="oceanic"))

    # Package options
    # qrainbowstyle.align_buttons_left()
    # qrainbowstyle.use_darwin_buttons()
    qrainbowstyle.setAppIcon(
        os.path.join(os.path.dirname(os.path.realpath(__file__)),
                     "github_logo.png"))

    # Create frameless mainwindow
    win = qrainbowstyle.windows.FramelessWindow()

    menu = QMenu(win)
    menu.setTitle("Some menu")
    menu.addAction(QAction("TEST ACTION", menu))
    win.addMenu(menu)

    # Example for spinner
    spinner = qrainbowstyle.widgets.WaitingSpinner(win,
                                                   centerOnParent=True,
                                                   modality=Qt.WindowModal,
                                                   roundness=70.0,
                                                   fade=70.0,
                                                   radius=9.0,
                                                   lines=24,
                                                   line_length=35.0,
                                                   line_width=2.0)
    spinner.start()
    spinner.fadeIn()
    t = QTimer()
    t.setSingleShot(True)
    t.timeout.connect(spinner.fadeOut)
    t.start(5000)

    win.setMinimumSize(QSize(500, 300))
    # Example of using signals
    win.closeClicked.connect(lambda: print("Close clicked!"))

    # Create content widget and pass reference to main window
    widget = WidgetGallery(win)

    # Add widget to main window and show it
    win.addContentWidget(widget)
    win.show()

    # Fullscreen test
    # win.showFullScreen()
    sys.exit(app.exec())
def embed(aQObject):
    tag = "__eventletEmbededTimer__"

    timer = QTimer()
    timer.setSingleShot(True)
    timer.setInterval(0.1)
    timer.timeout.connect(functools.partial(_timerOnTimeout, timer))
    timer.start()

    aQObject.setProperty(tag, timer)
Exemple #9
0
def open_in_window(widget_name, script):
    """
    Displays a widget in a window.
    :param widget_name:  A qualified name of a widget, ie mantidqt.mywidget.MyWidget
    :param script: A qualified name of a test function that can be run after the
        widget is created. The test function must have the signature:

            def test(widget):
                ...

        where argument widget is an instance of the tested widget.
        The test function can yield from time to time after which the widget can update itself.
        This will make the test non-blocking and changes can be viewed as the script runs.
        If the test yields an integer it is interpreted as the number of seconds to wait
        until the next step.
    """
    raw_input(
        'Please attach the Debugger now if required. Press any key to continue'
    )
    setup_library_paths()
    app = QApplication([""])
    w = create_widget(widget_name)
    w.setWindowTitle(widget_name)
    w.show()

    if script is not None:
        try:
            # If script is a generator script_iter allows non-blocking
            # test execution
            script_iter = iter(run_script(script, w))
            pause_timer = QTimer()
            pause_timer.setSingleShot(True)

            def idle():
                if not pause_timer.isActive():
                    try:
                        # Run test script until the next 'yield'
                        pause_sec = script_iter.next()
                        if pause_sec is not None:
                            # Start non-blocking pause in seconds
                            pause_timer.start(int(pause_sec * 1000))
                    except StopIteration:
                        pass
                    except:
                        traceback.print_exc()

            timer = QTimer()
            # Zero-timeout timer runs idle() between Qt events
            timer.timeout.connect(idle)
            timer.start()
        except:
            pass

    sys.exit(app.exec_())
Exemple #10
0
 def on_mainwindow_visible(self):
     """Actions after the mainwindow in visible."""
     # Show dialog with missing dependencies
     if not running_under_pytest():
         # This avoids computing missing deps before the window is fully up
         timer_report_deps = QTimer(self)
         timer_report_deps.setInterval(2000)
         timer_report_deps.setSingleShot(True)
         timer_report_deps.timeout.connect(
             self.get_container().report_missing_dependencies)
         timer_report_deps.start()
Exemple #11
0
    def compute_dependencies(self):
        """Compute dependencies."""
        self.dependencies_thread.run = self._compute_dependencies
        self.dependencies_thread.finished.connect(
            self.report_missing_dependencies)

        # This avoids computing missing deps before the window is fully up
        dependencies_timer = QTimer(self)
        dependencies_timer.setInterval(10000)
        dependencies_timer.setSingleShot(True)
        dependencies_timer.timeout.connect(self.dependencies_thread.start)
        dependencies_timer.start()
Exemple #12
0
def open_in_window(widget_name, script):
    """
    Displays a widget in a window.
    :param widget_name:  A qualified name of a widget, ie mantidqt.mywidget.MyWidget
    :param script: A qualified name of a test function that can be run after the
        widget is created. The test function must have the signature:

            def test(widget):
                ...

        where argument widget is an instance of the tested widget.
        The test function can yield from time to time after which the widget can update itself.
        This will make the test non-blocking and changes can be viewed as the script runs.
        If the test yields an integer it is interpreted as the number of seconds to wait
        until the next step.
    """
    raw_input('Please attach the Debugger now if required. Press any key to continue')
    setup_library_paths()
    app = QApplication([""])
    w = create_widget(widget_name)
    w.setWindowTitle(widget_name)
    w.show()

    if script is not None:
        try:
            # If script is a generator script_iter allows non-blocking
            # test execution
            script_iter = iter(run_script(script, w))
            pause_timer = QTimer()
            pause_timer.setSingleShot(True)

            def idle():
                if not pause_timer.isActive():
                    try:
                        # Run test script until the next 'yield'
                        pause_sec = script_iter.next()
                        if pause_sec is not None:
                            # Start non-blocking pause in seconds
                            pause_timer.start(int(pause_sec * 1000))
                    except StopIteration:
                        pass
                    except:
                        traceback.print_exc()
            timer = QTimer()
            # Zero-timeout timer runs idle() between Qt events
            timer.timeout.connect(idle)
            timer.start()
        except:
            pass

    sys.exit(app.exec_())
Exemple #13
0
class BufferedItemModel(QStandardItemModel):
    __row_appended = Signal()

    def __init__(self, parent=None, limit=None, refresh=20):
        super().__init__(parent)

        self.limit = limit
        self.buffer = list()
        self.timer = QTimer()

        self.timer.setSingleShot(True)
        self.timer.setInterval(refresh)

        @Slot()
        @helpers.connect_slot(self.__row_appended)
        def __on_row_appended():
            if not self.timer.isActive():
                self.timer.start()

        @Slot()
        @helpers.connect_slot(self.timer.timeout)
        def __on_timer_timeout():
            self.__dump_buffer()

    def __dump_buffer(self):
        self.insertRows(self.rowCount(), len(
            self.buffer))  # Append rows for each item in the buffer

        # Set the items for each new row
        for offset, item in enumerate(self.buffer):
            self.setItem(self.rowCount() - len(self.buffer) + offset, 0, item)

        self.buffer.clear()  # Reset the buffer

    def __apply_limit(self):
        if self.rowCount() > self.limit:
            # Remove rows from the beginning, count being the number of rows over the limit
            self.removeRows(0, self.rowCount() - self.limit)

    def insertRows(self, row, count, _=None):
        super().insertRows(row, count)

        if self.limit:
            self.__apply_limit()

    def appendRow(self, item):
        # Append the QStandardItem to the internal list to be popped into the model on the next timeout
        self.buffer.append(item)
        self.__row_appended.emit()
Exemple #14
0
class ProbeSim(BaseDialog):
    def __init__(self, parent=None):
        super(ProbeSim, self).__init__(parent=parent)

        self.info = Info()
        self.log = Log

        self.close_button = QPushButton("Touch")
        self.pulse_checkbox = QCheckBox("Pulse")

        main_layout = QVBoxLayout()

        main_layout.addWidget(self.close_button)
        main_layout.addWidget(self.pulse_checkbox)

        self.setLayout(main_layout)
        self.setWindowTitle("Simulate touch probe")

        self.close_button.pressed.connect(self.touch_on)
        self.close_button.released.connect(self.touch_off)

        self.timer = QTimer()
        self.timer.timeout.connect(self.pulse_off)
        self.timer.setSingleShot(True)

    def touch_on(self):

        if self.pulse_checkbox.checkState():
            self.timer.start(1000)
            subprocess.Popen(['halcmd', 'setp', 'motion.probe-input', '1'])

        else:
            subprocess.Popen(['halcmd', 'setp', 'motion.probe-input', '1'])

    def touch_off(self):

        if self.pulse_checkbox.checkState():
            return

        subprocess.Popen(['halcmd', 'setp', 'motion.probe-input', '0'])

    def pulse_off(self):
        subprocess.Popen(['halcmd', 'setp', 'motion.probe-input', '0'])

    def close(self):
        self.hide()
Exemple #15
0
class ClickableSvgItem(SvgItem):
    def __init__(self,
                 id,
                 renderer,
                 signal: NodeSignal,
                 node_name: str,
                 parent=None):
        super().__init__(id, renderer, parent)
        self.__signal = signal
        self.__node_name = node_name
        self.__timer = QTimer(self)
        self.__timer.setSingleShot(True)
        self.__timer.timeout.connect(self.__on_single_click)
        self.__double_click_interval = QApplication.doubleClickInterval()
        self.setAcceptHoverEvents(True)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
        event.accept()
        if not self.__timer.isActive():
            self.__timer.start(self.__double_click_interval)
        else:
            self.__timer.stop()
            self.__on_double_click()

    def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()
        self.__timer.stop()
        self.__on_double_click()

    def __on_single_click(self):
        self.__signal.on_click.emit(self.__node_name)

    def __on_double_click(self):
        self.__signal.on_double_click.emit(self.__node_name)

    def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
        self.__signal.on_context.emit(self.__node_name, event.screenPos())

    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        self.setToolTip(self.__node_name)

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        self.setToolTip('')
Exemple #16
0
def generate_images():
    """
    Generate images from `CORE_SETTINGS`. and save them in the developer
    section of the docs.
    """
    app = QApplication([])
    pref = PreferencesDialog()
    pref.setStyleSheet(get_stylesheet("dark"))

    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(pref.close)
    timer.start(2000)

    pref.show()

    available_settings = []
    for setting in CORE_SETTINGS:
        preferences_exclude = getattr(setting.NapariConfig,
                                      "preferences_exclude", [])
        schema = setting.schema()
        if set(preferences_exclude) != set(schema["properties"].keys()):
            available_settings.append(setting)

    for idx, setting in enumerate(available_settings):
        schema = setting.schema()
        title = schema.get("title").lower()
        pref.set_current_index(idx)
        pixmap = pref.grab()
        pixmap.save(str(IMAGES_PATH / f"preferences-{title}.png"))

    def grab():
        pixmap = pref._reset_dialog.grab()
        pixmap.save(str(IMAGES_PATH / "preferences-reset.png"))
        pref._reset_dialog.close()

    timer2 = QTimer()
    timer2.setSingleShot(True)
    timer2.timeout.connect(grab)
    timer2.start(300)

    pref.restore_defaults()

    app.exec_()
Exemple #17
0
    def start_symbol_services(self, language):
        """Show symbols for all `language` files."""
        # Save all languages that can send info to this pane.
        self._languages.append(language)

        # Update all files associated to `language` through a timer
        # that allows to wait a bit between updates. That doesn't block
        # the interface at startup.
        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.setInterval(700)
        timer.timeout.connect(lambda: self.update_editors(language))
        self.update_timers[language] = timer

        # Set editors that need to be updated per language
        self.set_editors_to_update(language)

        # Start timer
        timer.start()
Exemple #18
0
    def _wait(self, condition, signal, timeout_msg, timeout):
        """
        Wait until condition() is True by running an event loop.

        signal: qt signal that should interrupt the event loop.
        timeout_msg: Message to display in case of a timeout.
        timeout: time in seconds before a timeout
        """
        # Exit if condition is fulfilled or the kernel is dead.
        if condition():
            return
        if not self.kernel_client.is_alive():
            raise RuntimeError("Kernel is dead")

        # Create event loop to wait with
        wait_loop = QEventLoop()
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)

        # Connect signals to stop kernel loop
        wait_timeout.timeout.connect(wait_loop.quit)
        self.kernel_client.hb_channel.kernel_died.connect(wait_loop.quit)
        signal.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while not condition():
            if not wait_timeout.isActive():
                signal.disconnect(wait_loop.quit)
                self.kernel_client.hb_channel.kernel_died.disconnect(
                    wait_loop.quit)
                if condition():
                    return
                if not self.kernel_client.is_alive():
                    raise RuntimeError("Kernel is dead")
                raise TimeoutError(timeout_msg)
            wait_loop.exec_()

        wait_timeout.stop()
        signal.disconnect(wait_loop.quit)
        self.kernel_client.hb_channel.kernel_died.disconnect(
            wait_loop.quit)
Exemple #19
0
def test_invalid_directories(qtbot, pathmanager):
    """Check [site/dist]-packages are invalid paths."""
    if os.name == 'nt':
        paths = ['/lib/site-packages/foo',
                 '/lib/dist-packages/foo']
    else:
        paths = ['/lib/python3.6/site-packages/foo',
                 '/lib/python3.6/dist-packages/foo']

    def interact_message_box():
        child = pathmanager.findChild(QMessageBox)
        qtbot.keyPress(child, Qt.Key_Enter)

    for path in paths:
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(interact_message_box)
        timer.start(300)
        assert not pathmanager.check_path(path)
        pathmanager.add_path(path)
Exemple #20
0
class TimerDialog(QDialog):
    """ `QDialog <https://doc.qt.io/qt-5/qdialog.html>`_ that will timeout after a given number of milliseconds. 
    
        Parameters
        ----------
        timeout : int
            Number of milliseconds for the dialog to be shown. Default is 3000.
    """
    
    def __init__(self, timeout=3000):
        super().__init__()
        self.timer = QTimer()
        self.timer.setInterval(timeout)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.accept)
        
    def exec_(self, *args, **kwargs):
        """ Show the dialog and start the timer. """
        self.timer.start()
        super().exec_(*args, **kwargs)
        
Exemple #21
0
class AutoCloseMessageBox(QMessageBox):
    """
    Message box that can be closed automatically after a certain period of time.
    """
    def __init__(self,
                 timeout=5000,
                 title="",
                 text="",
                 icon=QMessageBox.NoIcon,
                 parent=None,
                 *args,
                 **kwargs):
        super().__init__(parent=parent, *args, **kwargs)
        self.setText(text)
        self.setWindowTitle(title)
        self.setIcon(icon)

        self.timer = QTimer(self)
        self.timer.setSingleShot(True)

        self.timer.singleShot(timeout, self.close)  # 默认显示5s
Exemple #22
0
def test_remove_item_and_reply_yes(qtbot, pathmanager):
    """Check that the item is indeed removed after answering 'Yes'."""
    pathmanager.show()
    count = pathmanager.count()

    def interact_message_box():
        messagebox = pathmanager.findChild(QMessageBox)
        buttons = messagebox.findChildren(QPushButton)
        for button in buttons:
            if 'yes' in button.text().lower():
                qtbot.mouseClick(button, Qt.LeftButton)
                break

    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(interact_message_box)
    timer.start(100)
    qtbot.mouseClick(pathmanager.remove_button, Qt.LeftButton)

    # Back to main thread
    assert pathmanager.count() == (count - 1)
Exemple #23
0
def test_add_invalid_path(qtbot, pathmanager):
    """Checks for unicode on python 2."""
    pathmanager.show()
    count = pathmanager.count()

    def interact_message_box():
        qtbot.wait(500)
        messagebox = pathmanager.findChild(QMessageBox)
        button = messagebox.findChild(QPushButton)
        qtbot.mouseClick(button, Qt.LeftButton)

    timer = QTimer()
    timer.setSingleShot(True)
    timer.timeout.connect(interact_message_box)
    timer.start(500)
    pathmanager.add_path('/foo/bar/測試')
    qtbot.wait(500)

    # Back to main thread
    assert len(pathmanager.get_path_dict()) == 2
    assert len(pathmanager.get_path_dict(True)) == 3
Exemple #24
0
def create_app(datafiles=[], interactive=True):

    app = get_qapp()

    if interactive:
        # Splash screen
        splash = get_splash()
        splash.image = QtGui.QPixmap(MOSVIZ_SPLASH_PATH)
        splash.show()
    else:
        splash = None

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    # # Show the splash screen for 2 seconds
    if interactive:
        timer = QTimer()
        timer.setInterval(2000)
        timer.setSingleShot(True)
        timer.timeout.connect(splash.close)
        timer.start()

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    if interactive:
        splash.set_progress(100)

    ga = _create_glue_app(data_collection, hub)
    ga.run_startup_action('mosviz')

    # Load the data files.
    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets, auto_merge=False)

    return ga
Exemple #25
0
def create_app(datafiles=[], interactive=True):

    app = get_qapp()

    if interactive:
        # Splash screen
        splash = get_splash()
        splash.image = QtGui.QPixmap(MOSVIZ_SPLASH_PATH)
        splash.show()
    else:
        splash = None

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    # # Show the splash screen for 2 seconds
    if interactive:
        timer = QTimer()
        timer.setInterval(2000)
        timer.setSingleShot(True)
        timer.timeout.connect(splash.close)
        timer.start()

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    if interactive:
        splash.set_progress(100)

    ga = _create_glue_app(data_collection, hub)
    ga.run_startup_action('mosviz')

    # Load the data files.
    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets, auto_merge=False)

    return ga
Exemple #26
0
class ApplicationBackend(BaseApplicationBackend):
    _app: QApplication

    def _mgui_get_backend_name(self):
        return "qt"

    def _mgui_process_events(self):
        app = self._mgui_get_native_app()
        app.flush()
        app.processEvents()

    def _mgui_run(self):
        app = self._mgui_get_native_app()
        # only start the event loop if magicgui created it
        if app.applicationName() == APPLICATION_NAME:
            return app.exec_()

    def _mgui_quit(self):
        return self._mgui_get_native_app().quit()

    def _mgui_get_native_app(self):
        # Get native app
        self._app = QApplication.instance()
        if not self._app:
            QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
            self._app = QApplication(sys.argv)
            self._app.setApplicationName(APPLICATION_NAME)
        return self._app

    def _mgui_start_timer(self, interval=0, on_timeout=None, single=False):
        self._timer = QTimer()
        if on_timeout:
            self._timer.timeout.connect(on_timeout)
        self._timer.setSingleShot(single)
        self._timer.setInterval(interval)
        self._timer.start()

    def _mgui_stop_timer(self):
        if getattr(self, "_timer", None):
            self._timer.stop()
Exemple #27
0
    def handle_lsp_down(self, language):
        """
        Handle automatic restart of client/server on failure.
        """
        if (not self.clients_restarting.get(language, False)
                and not running_under_pytest()):
            try:
                self.clients_hearbeat[language].stop()
            except KeyError:
                pass
            logger.info("Automatic restart for {}...".format(language))

            timer = QTimer(self)
            timer.setSingleShot(False)
            timer.setInterval(self.TIME_BETWEEN_RESTARTS)
            timer.timeout.connect(lambda: self.restart_lsp(language))

            self.set_status(language, _('restarting...'))
            self.clients_restarting[language] = True
            self.clients_restart_count[language] = self.MAX_RESTART_ATTEMPTS
            self.clients_restart_timers[language] = timer
            timer.start()
Exemple #28
0
    def check_updates(self, startup=False):
        """Check for spyder updates on github releases using a QThread."""
        # Disable check_updates_action while the thread is working
        self.check_updates_action.setDisabled(True)

        if self.thread_updates is not None:
            self.thread_updates.terminate()

        self.thread_updates = QThread(self)
        self.worker_updates = WorkerUpdates(self, startup=startup)
        self.worker_updates.sig_ready.connect(self._check_updates_ready)
        self.worker_updates.sig_ready.connect(self.thread_updates.quit)
        self.worker_updates.moveToThread(self.thread_updates)
        self.thread_updates.started.connect(self.worker_updates.start)

        # Delay starting this check to avoid blocking the main window
        # while loading.
        # Fixes spyder-ide/spyder#15839
        updates_timer = QTimer(self)
        updates_timer.setInterval(3000)
        updates_timer.setSingleShot(True)
        updates_timer.timeout.connect(self.thread_updates.start)
        updates_timer.start()
Exemple #29
0
class QZmqNode(QObject, zmqNode):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def listen_all(self):
        for connection in list(self.connections.keys()):
            self.listen(name=connection)
        if self.listening:
            self.timer_listen = QTimer()
            self.timer_listen.setInterval(int(self.heart_beat * 1000))
            self.timer_listen.setSingleShot(True)
            self.timer_listen.timeout.connect(self.listen_all, Qt.QueuedConnection)
            self.timer_listen.start()
        elif self.timer_listen:
            self.timer_listen.stop()
            self.timer_listen = None

    def listen(self, name=''):
        super(QZmqNode, self).listen(name)
        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def disconnect(self, name=''):
        zmqNode.disconnect(self, name)
Exemple #30
0
    def start_symbol_services(self, language):
        """Show symbols for all `language` files."""
        # Save all languages that can send info to this pane.
        self._languages.append(language)

        # Update all files associated to `language` through a timer
        # that allows to wait a bit between updates. That doesn't block
        # the interface at startup.
        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.setInterval(700)
        timer.timeout.connect(lambda: self.update_editors(language))
        self.update_timers[language] = timer

        # Select editors to update per language
        to_update = []
        for editor in self.editor_ids.keys():
            if editor.get_language().lower() == language:
                to_update.append(editor)
        self.editors_to_update[language] = to_update

        # Start timer
        timer.start()
Exemple #31
0
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);",
             True: ""}
    visibility_changed = Signal(bool)
    
    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None
        
        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)
        
        self.close_button = create_toolbutton(self, triggered=self.hide,
                                      icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)
        
        # Find layout
        self.search_text = PatternComboBox(self, tip=_("Search string"),
                                           adjust_to_minimum=False)
        self.search_text.valid.connect(
                     lambda state:
                     self.find(changed=False, forward=True, rehighlight=False))
        self.search_text.lineEdit().textEdited.connect(
                                                     self.text_has_been_edited)
        
        self.previous_button = create_toolbutton(self,
                                             triggered=self.find_previous,
                                             icon=ima.icon('ArrowUp'))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self, icon=ima.icon('advanced'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())
        
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())
                     
        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())
                     
        self.highlight_button = create_toolbutton(self,
                                              icon=get_icon("highlight.png"),
                                              tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [self.close_button, self.search_text,
                        self.previous_button, self.next_button,
                        self.re_button, self.case_button, self.words_button,
                        self.highlight_button]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self, adjust_to_minimum=False,
                                            tip=_('Replace string'))
        
        self.replace_button = create_toolbutton(self,
                                     text=_('Replace/find'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find,
                                     text_beside_icon=True)
        self.replace_button.clicked.connect(self.update_replace_combo)
        self.replace_button.clicked.connect(self.update_search_combo)
        
        self.all_check = QCheckBox(_("Replace all"))
        
        self.replace_layout = QHBoxLayout()
        widgets = [replace_with, self.replace_text, self.replace_button,
                   self.all_check]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()
        
        self.search_text.setTabOrder(self.search_text, self.replace_text)
        
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        self.shortcuts = self.create_shortcuts(parent)
        
        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        
    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next, context='_',
                                   name='Find next', parent=parent)
        findprev = config_shortcut(self.find_previous, context='_',
                                   name='Find previous', parent=parent)
        togglefind = config_shortcut(self.show, context='_',
                                     name='Find text', parent=parent)
        togglereplace = config_shortcut(self.toggle_replace_widgets,
                                        context='_', name='Replace text',
                                        parent=parent)
        # Fixed
        fixed_shortcut("Escape", self, self.hide)

        return [findnext, findprev, togglefind, togglereplace]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]
        
    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()
        
    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()
    
    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()
        
    def show(self):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        if self.editor is not None:
            text = self.editor.get_selected_text()

            # If no text is highlighted for search, use whatever word is under the cursor
            if not text:
                cursor = self.editor.textCursor()
                cursor.select(QTextCursor.WordUnderCursor)
                text = to_text_string(cursor.selectedText())

            # Now that text value is sorted out, use it for the search
            if text:
                self.search_text.setEditText(text)
                self.search_text.lineEdit().selectAll()
                self.refresh()
            else:
                self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()
        
    def show_replace(self):
        """Show replace widgets"""
        self.show()
        for widget in self.replace_widgets:
            widget.show()
            
    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()
        
    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()
            
    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyderlib.widgets.sourcecode.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False, forward=True, rehighlight=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False, forward=False, rehighlight=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when 
        setting the search pattern combo box text programmatically"""
        self.find(changed=True, forward=True, start_highlight_timer=True)
        
    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text, words=words,
                                                regexp=regexp)
                                                
    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()
        
    def find(self, changed=True, forward=True,
             rehighlight=True, start_highlight_timer=False):
        """Call the find function"""
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            return None
        else:
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text, changed, forward, case=case,
                                          words=words, regexp=regexp)
            self.search_text.lineEdit().setStyleSheet(self.STYLE[found])
            if self.is_code_editor and found:
                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()
            return found

    @Slot()
    def replace_find(self):
        """Replace and find"""
        if (self.editor is not None):
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            pattern = search_text if self.re_button.isChecked() else None
            case = self.case_button.isChecked()
            first = True
            cursor = None
            while True:
                if first:
                    # First found
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()
                    if self.editor.has_selected_text() and cmptxt1 == cmptxt2:
                        # Text was already found, do nothing
                        pass
                    else:
                        if not self.find(changed=False, forward=True,
                                         rehighlight=False):
                            break
                    first = False
                    wrapped = False
                    position = self.editor.get_position('cursor')
                    position0 = position
                    cursor = self.editor.textCursor()
                    cursor.beginEditBlock()
                else:
                    position1 = self.editor.get_position('cursor')
                    if is_position_inf(position1,
                                       position0 + len(replace_text) -
                                       len(search_text) + 1):
                        # Identify wrapping even when the replace string
                        # includes part of the search string
                        wrapped = True
                    if wrapped:
                        if position1 == position or \
                           is_position_sup(position1, position):
                            # Avoid infinite loop: replace string includes
                            # part of the search string
                            break
                    if position1 == position0:
                        # Avoid infinite loop: single found occurrence
                        break
                    position0 = position1
                if pattern is None:
                    cursor.removeSelectedText()
                    cursor.insertText(replace_text)
                else:
                    seltxt = to_text_string(cursor.selectedText())
                    cursor.removeSelectedText()
                    cursor.insertText(re.sub(pattern, replace_text, seltxt))
                if self.find_next():
                    found_cursor = self.editor.textCursor()
                    cursor.setPosition(found_cursor.selectionStart(),
                                       QTextCursor.MoveAnchor)
                    cursor.setPosition(found_cursor.selectionEnd(),
                                       QTextCursor.KeepAnchor)
                else:
                    break
                if not self.all_check.isChecked():
                    break
            self.all_check.setCheckState(Qt.Unchecked)
            if cursor is not None:
                cursor.endEditBlock()
Exemple #32
0
def start_glue(gluefile=None, config=None, datafiles=None, maximized=True,
               startup_actions=None, auto_merge=False):
    """Run a glue session and exit

    Parameters
    ----------
    gluefile : str
        An optional ``.glu`` file to restore.
    config : str
        An optional configuration file to use.
    datafiles : str
        An optional list of data files to load.
    maximized : bool
        Maximize screen on startup. Otherwise, use default size.
    auto_merge : bool, optional
        Whether to automatically merge data passed in `datafiles` (default is `False`)
    """

    import glue

    # Some Qt modules are picky in terms of being imported before the
    # application is set up, so we import them here. We do it here rather
    # than in get_qapp since in the past, including this code in get_qapp
    # caused severe issues (e.g. segmentation faults) in plugin packages
    # during testing.
    try:
        from qtpy import QtWebEngineWidgets  # noqa
    except ImportError:  # Not all PyQt installations have this module
        pass

    from glue.utils.qt import get_qapp
    app = get_qapp()

    splash = get_splash()
    splash.show()

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    from glue.app.qt import GlueApplication

    datafiles = datafiles or []

    hub = None

    from qtpy.QtCore import QTimer

    timer = QTimer()
    timer.setInterval(1000)
    timer.setSingleShot(True)
    timer.timeout.connect(splash.close)
    timer.start()

    if gluefile is not None:
        app = restore_session(gluefile)
        return app.start()

    if config is not None:
        glue.env = glue.config.load_configuration(search_path=[config])

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    splash.set_progress(100)

    session = glue.core.Session(data_collection=data_collection, hub=hub)
    ga = GlueApplication(session=session)

    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets, auto_merge=auto_merge)

    if startup_actions is not None:
        for name in startup_actions:
            ga.run_startup_action(name)

    return ga.start(maximized=maximized)
Exemple #33
0
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin):
    """
    Shell base widget
    """
    
    redirect_stdio = Signal(bool)
    sig_keyboard_interrupt = Signal()
    execute = Signal(str)
    append_to_history = Signal(str, str)
    
    def __init__(self, parent, history_filename, profile=False):
        """
        parent : specifies the parent widget
        """
        ConsoleBaseWidget.__init__(self, parent)
        SaveHistoryMixin.__init__(self)
                
        # Prompt position: tuple (line, index)
        self.current_prompt_pos = None
        self.new_input_line = True
        
        # History
        self.histidx = None
        self.hist_wholeline = False
        assert is_text_string(history_filename)
        self.history_filename = history_filename
        self.history = self.load_history()
        
        # Session
        self.historylog_filename = CONF.get('main', 'historylog_filename',
                                            get_conf_path('history.log'))
        
        # Context menu
        self.menu = None
        self.setup_context_menu()

        # Simple profiling test
        self.profile = profile
        
        # Buffer to increase performance of write/flush operations
        self.__buffer = []
        self.__timestamp = 0.0
        self.__flushtimer = QTimer(self)
        self.__flushtimer.setSingleShot(True)
        self.__flushtimer.timeout.connect(self.flush)

        # Give focus to widget
        self.setFocus()

        # Cursor width
        self.setCursorWidth( CONF.get('main', 'cursor/width') )

    def toggle_wrap_mode(self, enable):
        """Enable/disable wrap mode"""
        self.set_wrap_mode('character' if enable else None)

    def set_font(self, font):
        """Set shell styles font"""
        self.setFont(font)
        self.set_pythonshell_font(font)
        cursor = self.textCursor()
        cursor.select(QTextCursor.Document)
        charformat = QTextCharFormat()
        charformat.setFontFamily(font.family())
        charformat.setFontPointSize(font.pointSize())
        cursor.mergeCharFormat(charformat)


    #------ Context menu
    def setup_context_menu(self):
        """Setup shell context menu"""
        self.menu = QMenu(self)
        self.cut_action = create_action(self, _("Cut"),
                                        shortcut=keybinding('Cut'),
                                        icon=ima.icon('editcut'),
                                        triggered=self.cut)
        self.copy_action = create_action(self, _("Copy"),
                                         shortcut=keybinding('Copy'),
                                         icon=ima.icon('editcopy'),
                                         triggered=self.copy)
        paste_action = create_action(self, _("Paste"),
                                     shortcut=keybinding('Paste'),
                                     icon=ima.icon('editpaste'),
                                     triggered=self.paste)
        save_action = create_action(self, _("Save history log..."),
                                    icon=ima.icon('filesave'),
                                    tip=_("Save current history log (i.e. all "
                                          "inputs and outputs) in a text file"),
                                    triggered=self.save_historylog)
        self.delete_action = create_action(self, _("Delete"),
                                    shortcut=keybinding('Delete'),
                                    icon=ima.icon('editdelete'),
                                    triggered=self.delete)
        selectall_action = create_action(self, _("Select All"),
                                    shortcut=keybinding('SelectAll'),
                                    icon=ima.icon('selectall'),
                                    triggered=self.selectAll)
        add_actions(self.menu, (self.cut_action, self.copy_action,
                                paste_action, self.delete_action, None,
                                selectall_action, None, save_action) )
          
    def contextMenuEvent(self, event):
        """Reimplement Qt method"""
        state = self.has_selected_text()
        self.copy_action.setEnabled(state)
        self.cut_action.setEnabled(state)
        self.delete_action.setEnabled(state)
        self.menu.popup(event.globalPos())
        event.accept()        
        
        
    #------ Input buffer
    def get_current_line_from_cursor(self):
        return self.get_text('cursor', 'eof')
    
    def _select_input(self):
        """Select current line (without selecting console prompt)"""
        line, index = self.get_position('eof')
        if self.current_prompt_pos is None:
            pline, pindex = line, index
        else:
            pline, pindex = self.current_prompt_pos
        self.setSelection(pline, pindex, line, index)

    @Slot()
    def clear_line(self):
        """Clear current line (without clearing console prompt)"""
        if self.current_prompt_pos is not None:
            self.remove_text(self.current_prompt_pos, 'eof')

    @Slot()
    def clear_terminal(self):
        """
        Clear terminal window
        Child classes reimplement this method to write prompt
        """
        self.clear()

    # The buffer being edited
    def _set_input_buffer(self, text):
        """Set input buffer"""
        if self.current_prompt_pos is not None:
            self.replace_text(self.current_prompt_pos, 'eol', text)
        else:
            self.insert(text)
        self.set_cursor_position('eof')

    def _get_input_buffer(self):
        """Return input buffer"""
        input_buffer = ''
        if self.current_prompt_pos is not None:
            input_buffer = self.get_text(self.current_prompt_pos, 'eol')
            input_buffer = input_buffer.replace(os.linesep, '\n')
        return input_buffer

    input_buffer = Property("QString", _get_input_buffer, _set_input_buffer)
        
        
    #------ Prompt
    def new_prompt(self, prompt):
        """
        Print a new prompt and save its (line, index) position
        """
        if self.get_cursor_line_column()[1] != 0:
            self.write('\n')
        self.write(prompt, prompt=True)
        # now we update our cursor giving end of prompt
        self.current_prompt_pos = self.get_position('cursor')
        self.ensureCursorVisible()
        self.new_input_line = False
        
    def check_selection(self):
        """
        Check if selected text is r/w,
        otherwise remove read-only parts of selection
        """
        if self.current_prompt_pos is None:
            self.set_cursor_position('eof')
        else:
            self.truncate_selection(self.current_prompt_pos)
        
        
    #------ Copy / Keyboard interrupt
    @Slot()
    def copy(self):
        """Copy text to clipboard... or keyboard interrupt"""
        if self.has_selected_text():
            ConsoleBaseWidget.copy(self)
        elif not sys.platform == 'darwin':
            self.interrupt()

    def interrupt(self):
        """Keyboard interrupt"""
        self.sig_keyboard_interrupt.emit()

    @Slot()
    def cut(self):
        """Cut text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.cut(self)

    @Slot()
    def delete(self):
        """Remove selected text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.remove_selected_text(self)

    @Slot()
    def save_historylog(self):
        """Save current history log (all text in console)"""
        title = _("Save history log")
        self.redirect_stdio.emit(False)
        filename, _selfilter = getsavefilename(self, title,
                    self.historylog_filename, "%s (*.log)" % _("History logs"))
        self.redirect_stdio.emit(True)
        if filename:
            filename = osp.normpath(filename)
            try:
                encoding.write(to_text_string(self.get_text_with_eol()),
                               filename)
                self.historylog_filename = filename
                CONF.set('main', 'historylog_filename', filename)
            except EnvironmentError as error:
                QMessageBox.critical(self, title,
                                     _("<b>Unable to save file '%s'</b>"
                                       "<br><br>Error message:<br>%s"
                                       ) % (osp.basename(filename),
                                            to_text_string(error)))
        
        
    #------ Basic keypress event handler
    def on_enter(self, command):
        """on_enter"""
        self.execute_command(command)
        
    def execute_command(self, command):
        self.execute.emit(command)
        self.add_to_history(command)
        self.new_input_line = True
        
    def on_new_line(self):
        """On new input line"""
        self.set_cursor_position('eof')
        self.current_prompt_pos = self.get_position('cursor')
        self.new_input_line = False

    @Slot()
    def paste(self):
        """Reimplemented slot to handle multiline paste action"""
        if self.new_input_line:
            self.on_new_line()
        ConsoleBaseWidget.paste(self)
        
    def keyPressEvent(self, event):
        """
        Reimplement Qt Method
        Basic keypress event handler
        (reimplemented in InternalShell to add more sophisticated features)
        """
        if self.preprocess_keyevent(event):
            # Event was accepted in self.preprocess_keyevent
            return
        self.postprocess_keyevent(event)
        
    def preprocess_keyevent(self, event):
        """Pre-process keypress event:
        return True if event is accepted, false otherwise"""
        # Copy must be done first to be able to copy read-only text parts
        # (otherwise, right below, we would remove selection
        #  if not on current line)
        ctrl = event.modifiers() & Qt.ControlModifier
        meta = event.modifiers() & Qt.MetaModifier    # meta=ctrl in OSX
        if event.key() == Qt.Key_C and \
          ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()):
            if meta and sys.platform == 'darwin':
                self.interrupt()
            elif ctrl:
                self.copy()
            event.accept()
            return True
        
        if self.new_input_line and ( len(event.text()) or event.key() in \
           (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ):
            self.on_new_line()
        
        return False
        
    def postprocess_keyevent(self, event):
        """Post-process keypress event:
        in InternalShell, this is method is called when shell is ready"""
        event, text, key, ctrl, shift = restore_keyevent(event)
        
        # Is cursor on the last line? and after prompt?
        if len(text):
            #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ?
            if self.has_selected_text():
                self.check_selection()
            self.restrict_cursor_position(self.current_prompt_pos, 'eof')
            
        cursor_position = self.get_position('cursor')

        if key in (Qt.Key_Return, Qt.Key_Enter):
            if self.is_cursor_on_last_line():
                self._key_enter()
            # add and run selection
            else:
                self.insert_text(self.get_selected_text(), at_end=True)
            
        elif key == Qt.Key_Insert and not shift and not ctrl:
            self.setOverwriteMode(not self.overwriteMode())
            
        elif key == Qt.Key_Delete:
            if self.has_selected_text():
                self.check_selection()
                self.remove_selected_text()
            elif self.is_cursor_on_last_line():
                self.stdkey_clear()
            
        elif key == Qt.Key_Backspace:
            self._key_backspace(cursor_position)
            
        elif key == Qt.Key_Tab:
            self._key_tab()
            
        elif key == Qt.Key_Space and ctrl:
            self._key_ctrl_space()

        elif key == Qt.Key_Left:
            if self.current_prompt_pos == cursor_position:
                # Avoid moving cursor on prompt
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='left')
                
        elif key == Qt.Key_Right:
            if self.is_cursor_at_end():
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='right')

        elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl):
            self._key_home(shift, ctrl)

        elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl):
            self._key_end(shift, ctrl)

        elif key == Qt.Key_Up:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_prompt = self.get_coordinates(self.current_prompt_pos)[1]
            if y_cursor > y_prompt:
                self.stdkey_up(shift)
            else:
                self.browse_history(backward=True)
                
        elif key == Qt.Key_Down:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_end = self.get_coordinates('eol')[1]
            if y_cursor < y_end:
                self.stdkey_down(shift)
            else:
                self.browse_history(backward=False)
            
        elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
            #XXX: Find a way to do this programmatically instead of calling
            # widget keyhandler (this won't work if the *event* is coming from
            # the event queue - i.e. if the busy buffer is ever implemented)
            ConsoleBaseWidget.keyPressEvent(self, event)

        elif key == Qt.Key_Escape and shift:
            self.clear_line()

        elif key == Qt.Key_Escape:
            self._key_escape()
                
        elif key == Qt.Key_L and ctrl:
            self.clear_terminal()
            
        elif key == Qt.Key_V and ctrl:
            self.paste()
            
        elif key == Qt.Key_X and ctrl:
            self.cut()
            
        elif key == Qt.Key_Z and ctrl:
            self.undo()
            
        elif key == Qt.Key_Y and ctrl:
            self.redo()
            
        elif key == Qt.Key_A and ctrl:
            self.selectAll()
                
        elif key == Qt.Key_Question and not self.has_selected_text():
            self._key_question(text)
            
        elif key == Qt.Key_ParenLeft and not self.has_selected_text():
            self._key_parenleft(text)
            
        elif key == Qt.Key_Period and not self.has_selected_text():
            self._key_period(text)

        elif len(text) and not self.isReadOnly():
            self.hist_wholeline = False
            self.insert_text(text)
            self._key_other(text)
                
        else:
            # Let the parent widget handle the key press event
            ConsoleBaseWidget.keyPressEvent(self, event)
            
                
    #------ Key handlers
    def _key_enter(self):
        command = self.input_buffer
        self.insert_text('\n', at_end=True)
        self.on_enter(command)
        self.flush()
    def _key_other(self, text):
        raise NotImplementedError
    def _key_backspace(self, cursor_position):
        raise NotImplementedError
    def _key_tab(self):
        raise NotImplementedError
    def _key_ctrl_space(self):
        raise NotImplementedError
    def _key_home(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_home(shift, ctrl, self.current_prompt_pos)
    def _key_end(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_end(shift, ctrl)
    def _key_pageup(self):
        raise NotImplementedError
    def _key_pagedown(self):
        raise NotImplementedError
    def _key_escape(self):
        raise NotImplementedError
    def _key_question(self, text):
        raise NotImplementedError
    def _key_parenleft(self, text):
        raise NotImplementedError
    def _key_period(self, text):
        raise NotImplementedError

        
    #------ History Management
    def load_history(self):
        """Load history from a .py file in user home directory"""
        if osp.isfile(self.history_filename):
            rawhistory, _ = encoding.readlines(self.history_filename)
            rawhistory = [line.replace('\n', '') for line in rawhistory]
            if rawhistory[1] != self.INITHISTORY[1]:
                rawhistory[1] = self.INITHISTORY[1]
        else:
            rawhistory = self.INITHISTORY
        history = [line for line in rawhistory \
                   if line and not line.startswith('#')]

        # Truncating history to X entries:
        while len(history) >= CONF.get('historylog', 'max_entries'):
            del history[0]
            while rawhistory[0].startswith('#'):
                del rawhistory[0]
            del rawhistory[0]
        # Saving truncated history:
        encoding.writelines(rawhistory, self.history_filename)
        return history
        
    def browse_history(self, backward):
        """Browse history"""
        if self.is_cursor_before('eol') and self.hist_wholeline:
            self.hist_wholeline = False
        tocursor = self.get_current_line_to_cursor()
        text, self.histidx = self.__find_in_history(tocursor,
                                                    self.histidx, backward)
        if text is not None:
            if self.hist_wholeline:
                self.clear_line()
                self.insert_text(text)
            else:
                cursor_position = self.get_position('cursor')
                # Removing text from cursor to the end of the line
                self.remove_text('cursor', 'eol')
                # Inserting history text
                self.insert_text(text)
                self.set_cursor_position(cursor_position)

    def __find_in_history(self, tocursor, start_idx, backward):
        """Find text 'tocursor' in history, from index 'start_idx'"""
        if start_idx is None:
            start_idx = len(self.history)
        # Finding text in history
        step = -1 if backward else 1
        idx = start_idx
        if len(tocursor) == 0 or self.hist_wholeline:
            idx += step
            if idx >= len(self.history) or len(self.history) == 0:
                return "", len(self.history)
            elif idx < 0:
                idx = 0
            self.hist_wholeline = True
            return self.history[idx], idx
        else:
            for index in range(len(self.history)):
                idx = (start_idx+step*(index+1)) % len(self.history)
                entry = self.history[idx]
                if entry.startswith(tocursor):
                    return entry[len(tocursor):], idx
            else:
                return None, start_idx
    
    
    #------ Simulation standards input/output
    def write_error(self, text):
        """Simulate stderr"""
        self.flush()
        self.write(text, flush=True, error=True)
        if DEBUG:
            STDERR.write(text)

    def write(self, text, flush=False, error=False, prompt=False):
        """Simulate stdout and stderr"""
        if prompt:
            self.flush()
        if not is_string(text):
            # This test is useful to discriminate QStrings from decoded str
            text = to_text_string(text)
        self.__buffer.append(text)
        ts = time.time()
        if flush or prompt:
            self.flush(error=error, prompt=prompt)
        elif ts - self.__timestamp > 0.05:
            self.flush(error=error)
            self.__timestamp = ts
            # Timer to flush strings cached by last write() operation in series
            self.__flushtimer.start(50)

    def flush(self, error=False, prompt=False):
        """Flush buffer, write text to console"""
        # Fix for Issue 2452 
        if PY3:
            try:
                text = "".join(self.__buffer)
            except TypeError:
                text = b"".join(self.__buffer)
                try:
                    text = text.decode( locale.getdefaultlocale()[1] )
                except:
                    pass
        else:
            text = "".join(self.__buffer)

        self.__buffer = []
        self.insert_text(text, at_end=True, error=error, prompt=prompt)
        QCoreApplication.processEvents()
        self.repaint()
        # Clear input buffer:
        self.new_input_line = True


    #------ Text Insertion
    def insert_text(self, text, at_end=False, error=False, prompt=False):
        """
        Insert text at the current cursor position
        or at the end of the command line
        """
        if at_end:
            # Insert text at the end of the command line
            self.append_text_to_shell(text, error, prompt)
        else:
            # Insert text at current cursor position
            ConsoleBaseWidget.insert_text(self, text)

            
    #------ Re-implemented Qt Methods
    def focusNextPrevChild(self, next):
        """
        Reimplemented to stop Tab moving to the next window
        """
        if next:
            return False
        return ConsoleBaseWidget.focusNextPrevChild(self, next)

    
    #------ Drag and drop
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """Drag and Drop - Drop event"""
        if (event.mimeData().hasFormat("text/plain")):
            text = to_text_string(event.mimeData().text())
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text, at_end=True)
            self.setFocus()
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()
            
    def drop_pathlist(self, pathlist):
        """Drop path list"""
        raise NotImplementedError
Exemple #34
0
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);",
             True: "",
             None: "",
             'regexp_error': "background-color:rgb(255, 80, 80);",
             }
    TOOLTIP = {False: _("No matches"),
               True: _("Search string"),
               None: _("Search string"),
               'regexp_error': _("Regular expression error")
               }
    visibility_changed = Signal(bool)
    return_shift_pressed = Signal()
    return_pressed = Signal()

    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None
        
        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)
        
        self.close_button = create_toolbutton(self, triggered=self.hide,
                                      icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)
        
        # Find layout
        self.search_text = PatternComboBox(self, tip=_("Search string"),
                                           adjust_to_minimum=False)

        self.return_shift_pressed.connect(
                lambda:
                self.find(changed=False, forward=False, rehighlight=False, 
                          multiline_replace_check = False))

        self.return_pressed.connect(
                     lambda:
                     self.find(changed=False, forward=True, rehighlight=False,
                               multiline_replace_check = False))

        self.search_text.lineEdit().textEdited.connect(
                                                     self.text_has_been_edited)

        self.number_matches_text = QLabel(self)
        self.previous_button = create_toolbutton(self,
                                             triggered=self.find_previous,
                                             icon=ima.icon('ArrowUp'))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self, icon=get_icon('regexp.svg'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())
        
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())
                     
        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())
                     
        self.highlight_button = create_toolbutton(self,
                                              icon=get_icon("highlight.png"),
                                              tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [self.close_button, self.search_text,
                        self.number_matches_text, self.previous_button,
                        self.next_button, self.re_button, self.case_button,
                        self.words_button, self.highlight_button]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self, adjust_to_minimum=False,
                                            tip=_('Replace string'))
        self.replace_text.valid.connect(
                    lambda _: self.replace_find(focus_replace_text=True))
        self.replace_button = create_toolbutton(self,
                                     text=_('Replace/find next'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find,
                                     text_beside_icon=True)
        self.replace_sel_button = create_toolbutton(self,
                                     text=_('Replace selection'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find_selection,
                                     text_beside_icon=True)
        self.replace_sel_button.clicked.connect(self.update_replace_combo)
        self.replace_sel_button.clicked.connect(self.update_search_combo)

        self.replace_all_button = create_toolbutton(self,
                                     text=_('Replace all'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find_all,
                                     text_beside_icon=True)
        self.replace_all_button.clicked.connect(self.update_replace_combo)
        self.replace_all_button.clicked.connect(self.update_search_combo)
        
        self.replace_layout = QHBoxLayout()
        widgets = [replace_with, self.replace_text, self.replace_button,
                   self.replace_sel_button, self.replace_all_button]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()
        
        self.search_text.setTabOrder(self.search_text, self.replace_text)
        
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        self.shortcuts = self.create_shortcuts(parent)
        
        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        self.search_text.installEventFilter(self)

    def eventFilter(self, widget, event):
        """Event filter for search_text widget.

        Emits signals when presing Enter and Shift+Enter.
        This signals are used for search forward and backward.
        Also, a crude hack to get tab working in the Find/Replace boxes.
        """
        if event.type() == QEvent.KeyPress:
            key = event.key()
            shift = event.modifiers() & Qt.ShiftModifier

            if key == Qt.Key_Return:
                if shift:
                    self.return_shift_pressed.emit()
                else:
                    self.return_pressed.emit()

            if key == Qt.Key_Tab:
                if self.search_text.hasFocus():
                    self.replace_text.set_current_text(
                        self.search_text.currentText())
                self.focusNextChild()

        return super(FindReplace, self).eventFilter(widget, event)

    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next, context='_',
                                   name='Find next', parent=parent)
        findprev = config_shortcut(self.find_previous, context='_',
                                   name='Find previous', parent=parent)
        togglefind = config_shortcut(self.show, context='_',
                                     name='Find text', parent=parent)
        togglereplace = config_shortcut(self.show_replace,
                                        context='_', name='Replace text',
                                        parent=parent)
        hide = config_shortcut(self.hide, context='_', name='hide find and replace',
                               parent=self)

        return [findnext, findprev, togglefind, togglereplace, hide]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]
        
    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()
        
    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()
    
    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                if len(to_text_string(self.search_text.currentText()))>0:
                    self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()
        
    def show(self, hide_replace=True):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        self.change_number_matches()
        if self.editor is not None:
            if hide_replace:
                if self.replace_widgets[0].isVisible():
                    self.hide_replace()
            text = self.editor.get_selected_text()
            # When selecting several lines, and replace box is activated the
            # text won't be replaced for the selection
            if hide_replace or len(text.splitlines())<=1:
                highlighted = True
                # If no text is highlighted for search, use whatever word is
                # under the cursor
                if not text:
                    highlighted = False
                    try:
                        cursor = self.editor.textCursor()
                        cursor.select(QTextCursor.WordUnderCursor)
                        text = to_text_string(cursor.selectedText())
                    except AttributeError:
                        # We can't do this for all widgets, e.g. WebView's
                        pass
    
                # Now that text value is sorted out, use it for the search
                if text and not self.search_text.currentText() or highlighted:
                    self.search_text.setEditText(text)
                    self.search_text.lineEdit().selectAll()
                    self.refresh()
                else:
                    self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()
        
    def show_replace(self):
        """Show replace widgets"""
        self.show(hide_replace=False)
        for widget in self.replace_widgets:
            widget.show()
            
    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()
        
    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()
            
    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyder.plugins.editor.widgets.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False, forward=True, rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False, forward=False, rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when 
        setting the search pattern combo box text programmatically)"""
        self.find(changed=True, forward=True, start_highlight_timer=True)

    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text, words=words,
                                                regexp=regexp)

    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()

    def find(self, changed=True, forward=True,
             rehighlight=True, start_highlight_timer=False, multiline_replace_check=True):
        """Call the find function"""
        # When several lines are selected in the editor and replace box is activated, 
        # dynamic search is deactivated to prevent changing the selection. Otherwise
        # we show matching items.
        if multiline_replace_check and self.replace_widgets[0].isVisible() and \
           len(to_text_string(self.editor.get_selected_text()).splitlines())>1:
            return None
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            self.change_number_matches()
            return None
        else:
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text, changed, forward, case=case,
                                          words=words, regexp=regexp)

            stylesheet = self.STYLE[found]
            tooltip = self.TOOLTIP[found]
            if not found and regexp:
                error_msg = regexp_error_msg(text)
                if error_msg:  # special styling for regexp errors
                    stylesheet = self.STYLE['regexp_error']
                    tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg
            self.search_text.lineEdit().setStyleSheet(stylesheet)
            self.search_text.setToolTip(tooltip)

            if self.is_code_editor and found:
                block = self.editor.textCursor().block()
                TextHelper(self.editor).unfold_if_colapsed(block)

                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()

            number_matches = self.editor.get_number_matches(text, case=case,
                                                            regexp=regexp)
            if hasattr(self.editor, 'get_match_number'):
                match_number = self.editor.get_match_number(text, case=case,
                                                            regexp=regexp)
            else:
                match_number = 0
            self.change_number_matches(current_match=match_number,
                                       total_matches=number_matches)
            return found

    @Slot()
    def replace_find(self, focus_replace_text=False, replace_all=False):
        """Replace and find"""
        if (self.editor is not None):
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            re_pattern = None

            # Check regexp before proceeding
            if self.re_button.isChecked():
                try:
                    re_pattern = re.compile(search_text)
                    # Check if replace_text can be substituted in re_pattern
                    # Fixes issue #7177
                    re_pattern.sub(replace_text, '')
                except re.error:
                    # Do nothing with an invalid regexp
                    return

            case = self.case_button.isChecked()
            first = True
            cursor = None
            while True:
                if first:
                    # First found
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()
                    if re_pattern is None:
                        has_selected = self.editor.has_selected_text()
                        if has_selected and cmptxt1 == cmptxt2:
                            # Text was already found, do nothing
                            pass
                        else:
                            if not self.find(changed=False, forward=True,
                                             rehighlight=False):
                                break
                    else:
                        if len(re_pattern.findall(cmptxt2)) > 0:
                            pass
                        else:
                            if not self.find(changed=False, forward=True,
                                             rehighlight=False):
                                break   
                    first = False
                    wrapped = False
                    position = self.editor.get_position('cursor')
                    position0 = position
                    cursor = self.editor.textCursor()
                    cursor.beginEditBlock()
                else:
                    position1 = self.editor.get_position('cursor')
                    if is_position_inf(position1,
                                       position0 + len(replace_text) -
                                       len(search_text) + 1):
                        # Identify wrapping even when the replace string
                        # includes part of the search string
                        wrapped = True
                    if wrapped:
                        if position1 == position or \
                           is_position_sup(position1, position):
                            # Avoid infinite loop: replace string includes
                            # part of the search string
                            break
                    if position1 == position0:
                        # Avoid infinite loop: single found occurrence
                        break
                    position0 = position1
                if re_pattern is None:
                    cursor.removeSelectedText()
                    cursor.insertText(replace_text)
                else:
                    seltxt = to_text_string(cursor.selectedText())
                    cursor.removeSelectedText()
                    cursor.insertText(re_pattern.sub(replace_text, seltxt))
                if self.find_next():
                    found_cursor = self.editor.textCursor()
                    cursor.setPosition(found_cursor.selectionStart(),
                                       QTextCursor.MoveAnchor)
                    cursor.setPosition(found_cursor.selectionEnd(),
                                       QTextCursor.KeepAnchor)
                else:
                    break
                if not replace_all:
                    break
            if cursor is not None:
                cursor.endEditBlock()
            if focus_replace_text:
                self.replace_text.setFocus()

    @Slot()
    def replace_find_all(self, focus_replace_text=False):
        """Replace and find all matching occurrences"""
        self.replace_find(focus_replace_text, replace_all=True)

                
    @Slot()
    def replace_find_selection(self, focus_replace_text=False):
        """Replace and find in the current selection"""
        if self.editor is not None:
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            re_flags = re.MULTILINE if case else re.IGNORECASE|re.MULTILINE

            re_pattern = None
            if self.re_button.isChecked():
                pattern = search_text
            else:
                pattern = re.escape(search_text)
                replace_text = re.escape(replace_text)
            if words:  # match whole words only
                pattern = r'\b{pattern}\b'.format(pattern=pattern)

            # Check regexp before proceeding
            try:
                re_pattern = re.compile(pattern, flags=re_flags)
                # Check if replace_text can be substituted in re_pattern
                # Fixes issue #7177
                re_pattern.sub(replace_text, '')
            except re.error as e:
                # Do nothing with an invalid regexp
                return

            selected_text = to_text_string(self.editor.get_selected_text())
            replacement = re_pattern.sub(replace_text, selected_text)
            if replacement != selected_text:
                cursor = self.editor.textCursor()
                cursor.beginEditBlock()
                cursor.removeSelectedText()
                if not self.re_button.isChecked():
                    replacement = re.sub(r'\\(?![nrtf])(.)', r'\1', replacement)
                cursor.insertText(replacement)
                cursor.endEditBlock()
            if focus_replace_text:
                self.replace_text.setFocus()
            else:
                self.editor.setFocus()

    def change_number_matches(self, current_match=0, total_matches=0):
        """Change number of match and total matches."""
        if current_match and total_matches:
            matches_string = u"{} {} {}".format(current_match, _(u"of"),
                                               total_matches)
            self.number_matches_text.setText(matches_string)
        elif total_matches:
            matches_string = u"{} {}".format(total_matches, _(u"matches"))
            self.number_matches_text.setText(matches_string)
        else:
            self.number_matches_text.setText(_(u"no matches"))
Exemple #35
0
class AutosaveForPlugin(object):
    """
    Component of editor plugin implementing autosave functionality.

    Attributes:
        name_mapping (dict): map between names of opened and autosave files.
        file_hashes (dict): map between file names and hash of their contents.
            This is used for both files opened in the editor and their
            corresponding autosave files.
    """

    # Interval (in ms) between two autosaves
    DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000

    def __init__(self, editor):
        """
        Constructor.

        Autosave is disabled after construction and needs to be enabled
        explicitly if required.

        Args:
            editor (Editor): editor plugin.
        """
        self.editor = editor
        self.name_mapping = {}
        self.file_hashes = {}
        self.timer = QTimer(self.editor)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.do_autosave)
        self._enabled = False  # Can't use setter here
        self._interval = self.DEFAULT_AUTOSAVE_INTERVAL

    @property
    def enabled(self):
        """
        Get or set whether autosave component is enabled.

        The setter will start or stop the autosave component if appropriate.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_enabled):
        if new_enabled == self.enabled:
            return
        self.stop_autosave_timer()
        self._enabled = new_enabled
        self.start_autosave_timer()

    @property
    def interval(self):
        """
        Interval between two autosaves, in milliseconds.

        The setter will perform an autosave if the interval is changed and
        autosave is enabled.
        """
        return self._interval

    @interval.setter
    def interval(self, new_interval):
        if new_interval == self.interval:
            return
        self.stop_autosave_timer()
        self._interval = new_interval
        if self.enabled:
            self.do_autosave()

    def start_autosave_timer(self):
        """
        Start a timer which calls do_autosave() after `self.interval`.

        The autosave timer is only started if autosave is enabled.
        """
        if self.enabled:
            self.timer.start(self.interval)

    def stop_autosave_timer(self):
        """Stop the autosave timer."""
        self.timer.stop()

    def do_autosave(self):
        """Instruct current editorstack to autosave files where necessary."""
        logger.debug('Autosave triggered')
        stack = self.editor.get_current_editorstack()
        stack.autosave.autosave_all()
        self.start_autosave_timer()

    def get_files_to_recover(self):
        """
        Get list of files to recover from pid files in autosave dir.

        This returns a tuple `(files_to_recover, pid_files)`. In this tuple,
        `files_to_recover` is a list of tuples containing the original file
        names and the corresponding autosave file names, as recorded in the
        pid files in the autosave directory. Any files in the autosave
        directory which are not listed in a pid file, are also included, with
        the original file name set to `None`. The second entry, `pid_files`,
        is a list with the names of the pid files.
        """
        autosave_dir = get_conf_path('autosave')
        if not os.access(autosave_dir, os.R_OK):
            return [], []

        files_to_recover = []
        files_mentioned = []
        pid_files = []
        non_pid_files = []

        # In Python 3, easier to use os.scandir()
        for name in os.listdir(autosave_dir):
            full_name = osp.join(autosave_dir, name)
            match = re.match(r'pid([0-9]*)\.txt\Z', name)
            if match:
                pid_files.append(full_name)
                logger.debug('Reading pid file: {}'.format(full_name))
                with open(full_name) as pidfile:
                    txt = pidfile.read()
                    txt_as_dict = ast.literal_eval(txt)
                    files_mentioned += [
                        autosave for (orig, autosave) in txt_as_dict.items()
                    ]
                pid = int(match.group(1))
                if is_spyder_process(pid):
                    logger.debug('Ignoring files in {}'.format(full_name))
                else:
                    files_to_recover += list(txt_as_dict.items())
            else:
                non_pid_files.append(full_name)

        # Add all files not mentioned in any pid file. This can only happen if
        # the pid file somehow got corrupted.
        for filename in set(non_pid_files) - set(files_mentioned):
            files_to_recover.append((None, filename))
            logger.debug('Added unmentioned file: {}'.format(filename))

        return files_to_recover, pid_files

    def try_recover_from_autosave(self):
        """
        Offer to recover files from autosave.

        Read pid files to get a list of files that can possibly be recovered,
        then ask the user what to do with these files, and finally remove
        the pid files.
        """
        files_to_recover, pidfiles = self.get_files_to_recover()
        parent = self.editor if running_under_pytest() else self.editor.main
        dialog = RecoveryDialog(files_to_recover, parent=parent)
        dialog.exec_if_nonempty()
        self.recover_files_to_open = dialog.files_to_open[:]
        for pidfile in pidfiles:
            try:
                os.remove(pidfile)
            except (IOError, OSError):
                pass

    def register_autosave_for_stack(self, autosave_for_stack):
        """
        Register an AutosaveForStack object.

        This replaces the `name_mapping` and `file_hashes` attributes
        in `autosave_for_stack` with references to the corresponding
        attributes of `self`, so that all AutosaveForStack objects
        share the same data.
        """
        autosave_for_stack.name_mapping = self.name_mapping
        autosave_for_stack.file_hashes = self.file_hashes
Exemple #36
0
class AutosaveForPlugin(object):
    """Component of editor plugin implementing autosave functionality."""

    # Interval (in ms) between two autosaves
    DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000

    def __init__(self, editor):
        """
        Constructor.

        Autosave is disabled after construction and needs to be enabled
        explicitly if required.

        Args:
            editor (Editor): editor plugin.
        """
        self.editor = editor
        self.timer = QTimer(self.editor)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.do_autosave)
        self._enabled = False  # Can't use setter here
        self._interval = self.DEFAULT_AUTOSAVE_INTERVAL

    @property
    def enabled(self):
        """
        Get or set whether autosave component is enabled.

        The setter will start or stop the autosave component if appropriate.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_enabled):
        if new_enabled == self.enabled:
            return
        self.stop_autosave_timer()
        self._enabled = new_enabled
        self.start_autosave_timer()

    @property
    def interval(self):
        """
        Interval between two autosaves, in milliseconds.

        The setter will perform an autosave if the interval is changed and
        autosave is enabled.
        """
        return self._interval

    @interval.setter
    def interval(self, new_interval):
        if new_interval == self.interval:
            return
        self.stop_autosave_timer()
        self._interval = new_interval
        if self.enabled:
            self.do_autosave()

    def start_autosave_timer(self):
        """
        Start a timer which calls do_autosave() after `self.interval`.

        The autosave timer is only started if autosave is enabled.
        """
        if self.enabled:
            self.timer.start(self.interval)

    def stop_autosave_timer(self):
        """Stop the autosave timer."""
        self.timer.stop()

    def do_autosave(self):
        """Instruct current editorstack to autosave files where necessary."""
        logger.debug('Autosave triggered')
        stack = self.editor.get_current_editorstack()
        stack.autosave.autosave_all()
        self.start_autosave_timer()

    def try_recover_from_autosave(self):
        """Offer to recover files from autosave."""
        autosave_dir = get_conf_path('autosave')
        autosave_mapping = CONF.get('editor', 'autosave_mapping', {})
        dialog = RecoveryDialog(autosave_dir, autosave_mapping,
                                parent=self.editor)
        dialog.exec_if_nonempty()
        self.recover_files_to_open = dialog.files_to_open[:]
Exemple #37
0
def start_glue(gluefile=None, config=None, datafiles=None, maximized=True,
               startup_actions=None, auto_merge=False):
    """Run a glue session and exit

    Parameters
    ----------
    gluefile : str
        An optional ``.glu`` file to restore.
    config : str
        An optional configuration file to use.
    datafiles : str
        An optional list of data files to load.
    maximized : bool
        Maximize screen on startup. Otherwise, use default size.
    auto_merge : bool, optional
        Whether to automatically merge data passed in `datafiles` (default is `False`)
    """

    import glue

    # Some Qt modules are picky in terms of being imported before the
    # application is set up, so we import them here. We do it here rather
    # than in get_qapp since in the past, including this code in get_qapp
    # caused severe issues (e.g. segmentation faults) in plugin packages
    # during testing.
    try:
        from qtpy import QtWebEngineWidgets  # noqa
    except ImportError:  # Not all PyQt installations have this module
        pass

    from glue.utils.qt.decorators import die_on_error

    from glue.utils.qt import get_qapp
    app = get_qapp()

    splash = get_splash()
    splash.show()

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash, require_qt_plugins=True)

    from glue.app.qt import GlueApplication

    datafiles = datafiles or []

    hub = None

    from qtpy.QtCore import QTimer

    timer = QTimer()
    timer.setInterval(1000)
    timer.setSingleShot(True)
    timer.timeout.connect(splash.close)
    timer.start()

    if gluefile is not None:
        with die_on_error("Error restoring Glue session"):
            app = GlueApplication.restore_session(gluefile, show=False)
        return app.start(maximized=maximized)

    if config is not None:
        glue.env = glue.config.load_configuration(search_path=[config])

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    splash.set_progress(100)

    session = glue.core.Session(data_collection=data_collection, hub=hub)
    ga = GlueApplication(session=session)

    if datafiles:
        with die_on_error("Error reading data file"):
            datasets = load_data_files(datafiles)
        ga.add_datasets(datasets, auto_merge=auto_merge)

    if startup_actions is not None:
        for name in startup_actions:
            ga.run_startup_action(name)

    return ga.start(maximized=maximized)
Exemple #38
0
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {
        False: "background-color:rgb(255, 175, 90);",
        True: "",
        None: "",
        'regexp_error': "background-color:rgb(255, 80, 80);",
    }
    TOOLTIP = {
        False: _("No matches"),
        True: _("Search string"),
        None: _("Search string"),
        'regexp_error': _("Regular expression error")
    }
    visibility_changed = Signal(bool)
    return_shift_pressed = Signal()
    return_pressed = Signal()

    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None

        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)

        self.close_button = create_toolbutton(
            self, triggered=self.hide, icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)

        # Find layout
        self.search_text = PatternComboBox(self,
                                           tip=_("Search string"),
                                           adjust_to_minimum=False)

        self.return_shift_pressed.connect(
            lambda: self.find(changed=False,
                              forward=False,
                              rehighlight=False,
                              multiline_replace_check=False))

        self.return_pressed.connect(
            lambda: self.find(changed=False,
                              forward=True,
                              rehighlight=False,
                              multiline_replace_check=False))

        self.search_text.lineEdit().textEdited.connect(
            self.text_has_been_edited)

        self.number_matches_text = QLabel(self)
        self.previous_button = create_toolbutton(self,
                                                 triggered=self.find_previous,
                                                 icon=ima.icon('ArrowUp'),
                                                 tip=_("Find previous"))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'),
                                             tip=_("Find next"))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self,
                                           icon=ima.icon('regex'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())

        self.case_button = create_toolbutton(
            self, icon=ima.icon("format_letter_case"), tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())

        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())

        self.highlight_button = create_toolbutton(
            self, icon=get_icon("highlight.png"), tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [
            self.close_button, self.search_text, self.number_matches_text,
            self.previous_button, self.next_button, self.re_button,
            self.case_button, self.words_button, self.highlight_button
        ]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self,
                                            adjust_to_minimum=False,
                                            tip=_('Replace string'))
        self.replace_text.valid.connect(
            lambda _: self.replace_find(focus_replace_text=True))
        self.replace_button = create_toolbutton(
            self,
            text=_('Replace/find next'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find,
            text_beside_icon=True)
        self.replace_sel_button = create_toolbutton(
            self,
            text=_('Replace in selection'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find_selection,
            text_beside_icon=True)
        self.replace_sel_button.clicked.connect(self.update_replace_combo)
        self.replace_sel_button.clicked.connect(self.update_search_combo)

        self.replace_all_button = create_toolbutton(
            self,
            text=_('Replace all'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find_all,
            text_beside_icon=True)
        self.replace_all_button.clicked.connect(self.update_replace_combo)
        self.replace_all_button.clicked.connect(self.update_search_combo)

        self.replace_layout = QHBoxLayout()
        widgets = [
            replace_with, self.replace_text, self.replace_button,
            self.replace_sel_button, self.replace_all_button
        ]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()

        self.search_text.setTabOrder(self.search_text, self.replace_text)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.shortcuts = self.create_shortcuts(parent)

        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        self.search_text.installEventFilter(self)

    def eventFilter(self, widget, event):
        """Event filter for search_text widget.

        Emits signals when presing Enter and Shift+Enter.
        This signals are used for search forward and backward.
        Also, a crude hack to get tab working in the Find/Replace boxes.
        """
        if event.type() == QEvent.KeyPress:
            key = event.key()
            shift = event.modifiers() & Qt.ShiftModifier

            if key == Qt.Key_Return:
                if shift:
                    self.return_shift_pressed.emit()
                else:
                    self.return_pressed.emit()

            if key == Qt.Key_Tab:
                if self.search_text.hasFocus():
                    self.replace_text.set_current_text(
                        self.search_text.currentText())
                self.focusNextChild()

        return super(FindReplace, self).eventFilter(widget, event)

    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = CONF.config_shortcut(self.find_next,
                                        context='find_replace',
                                        name='Find next',
                                        parent=parent)

        findprev = CONF.config_shortcut(self.find_previous,
                                        context='find_replace',
                                        name='Find previous',
                                        parent=parent)

        togglefind = CONF.config_shortcut(self.show,
                                          context='find_replace',
                                          name='Find text',
                                          parent=parent)

        togglereplace = CONF.config_shortcut(self.show_replace,
                                             context='find_replace',
                                             name='Replace text',
                                             parent=parent)

        hide = CONF.config_shortcut(self.hide,
                                    context='find_replace',
                                    name='hide find and replace',
                                    parent=self)

        return [findnext, findprev, togglefind, togglereplace, hide]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]

    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()

    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()

    def show(self, hide_replace=True):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        self.change_number_matches()
        if self.editor is not None:
            if hide_replace:
                if self.replace_widgets[0].isVisible():
                    self.hide_replace()
            text = self.editor.get_selected_text()
            # When selecting several lines, and replace box is activated the
            # text won't be replaced for the selection
            if hide_replace or len(text.splitlines()) <= 1:
                highlighted = True
                # If no text is highlighted for search, use whatever word is
                # under the cursor
                if not text:
                    highlighted = False
                    try:
                        cursor = self.editor.textCursor()
                        cursor.select(QTextCursor.WordUnderCursor)
                        text = to_text_string(cursor.selectedText())
                    except AttributeError:
                        # We can't do this for all widgets, e.g. WebView's
                        pass

                # Now that text value is sorted out, use it for the search
                if text and not self.search_text.currentText() or highlighted:
                    self.search_text.setEditText(text)
                    self.search_text.lineEdit().selectAll()
                    self.refresh()
                else:
                    self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()

    def show_replace(self):
        """Show replace widgets"""
        if self.enable_replace:
            self.show(hide_replace=False)
            for widget in self.replace_widgets:
                widget.show()

    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()

    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()

    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyder.plugins.editor.widgets.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self, set_focus=True):
        """Find next occurrence"""
        state = self.find(changed=False,
                          forward=True,
                          rehighlight=False,
                          multiline_replace_check=False)
        if set_focus:
            self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self, set_focus=True):
        """Find previous occurrence"""
        state = self.find(changed=False,
                          forward=False,
                          rehighlight=False,
                          multiline_replace_check=False)
        if set_focus:
            self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when
        setting the search pattern combo box text programmatically)"""
        self.find(changed=True, forward=True, start_highlight_timer=True)

    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            case = self.case_button.isChecked()
            word = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text,
                                                word=word,
                                                regexp=regexp,
                                                case=case)

    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()

    def find(self,
             changed=True,
             forward=True,
             rehighlight=True,
             start_highlight_timer=False,
             multiline_replace_check=True):
        """Call the find function"""
        # When several lines are selected in the editor and replace box is
        # activated, dynamic search is deactivated to prevent changing the
        # selection. Otherwise we show matching items.
        if multiline_replace_check and self.replace_widgets[0].isVisible():
            sel_text = self.editor.get_selected_text()
            if len(to_text_string(sel_text).splitlines()) > 1:
                return None
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            self.change_number_matches()
            return None
        else:
            case = self.case_button.isChecked()
            word = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text,
                                          changed,
                                          forward,
                                          case=case,
                                          word=word,
                                          regexp=regexp)

            stylesheet = self.STYLE[found]
            tooltip = self.TOOLTIP[found]
            if not found and regexp:
                error_msg = regexp_error_msg(text)
                if error_msg:  # special styling for regexp errors
                    stylesheet = self.STYLE['regexp_error']
                    tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg
            self.search_text.lineEdit().setStyleSheet(stylesheet)
            self.search_text.setToolTip(tooltip)

            if self.is_code_editor and found:
                block = self.editor.textCursor().block()
                TextHelper(self.editor).unfold_if_colapsed(block)

                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()

            number_matches = self.editor.get_number_matches(text,
                                                            case=case,
                                                            regexp=regexp,
                                                            word=word)
            if hasattr(self.editor, 'get_match_number'):
                match_number = self.editor.get_match_number(text,
                                                            case=case,
                                                            regexp=regexp,
                                                            word=word)
            else:
                match_number = 0
            self.change_number_matches(current_match=match_number,
                                       total_matches=number_matches)
            return found
Exemple #39
0
def create_app(datafiles=[], data_configs=[], data_configs_show=False,
               interactive=True):
    """
    Create and initialize a cubeviz application instance

    Parameters
    ----------
    datafiles : `list`
        A list of filenames representing data files to be loaded
    data_configs : `list`
        A list of filenames representing data configuration files to be used
    data_configs_show : `bool`
        Display matching info about data configuration files
    interactive : `bool`
        Flag to indicate whether session is interactive or not (i.e. for testing)
    """
    app = get_qapp()

    # Splash screen
    if interactive:
        splash = get_splash()
        splash.image = QtGui.QPixmap(CUBEVIZ_LOGO_PATH)
        splash.show()
    else:
        splash = None

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    dfc_kwargs = dict(remove_defaults=True, check_ifu_valid=interactive)
    DataFactoryConfiguration(data_configs, data_configs_show, **dfc_kwargs)

    # Check to make sure each file exists and raise an Exception
    # that will show in the popup if it does not exist.
    _check_datafiles_exist(datafiles)

    # Show the splash screen for 1 second
    if interactive:
        timer = QTimer()
        timer.setInterval(1000)
        timer.setSingleShot(True)
        timer.timeout.connect(splash.close)
        timer.start()

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    ga = _create_glue_app(data_collection, hub)
    ga.run_startup_action('cubeviz')

    # Load the data files.
    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets, auto_merge=False)

    if interactive:
        splash.set_progress(100)

    return ga
Exemple #40
0
def start_glue(gluefile=None, config=None, datafiles=None, maximized=True):
    """Run a glue session and exit

    Parameters
    ----------
    gluefile : str
        An optional ``.glu`` file to restore.

    config : str
        An optional configuration file to use.

    datafiles : str
        An optional list of data files to load.

    maximized : bool
        Maximize screen on startup. Otherwise, use default size.

    """

    import glue

    from glue.utils.qt import get_qapp

    app = get_qapp()

    splash = get_splash()
    splash.show()

    # Start off by loading plugins. We need to do this before restoring
    # the session or loading the configuration since these may use existing
    # plugins.
    load_plugins(splash=splash)

    from glue.app.qt import GlueApplication

    datafiles = datafiles or []

    hub = None

    if gluefile is not None:
        app = restore_session(gluefile)
        return app.start()

    if config is not None:
        glue.env = glue.config.load_configuration(search_path=[config])

    data_collection = glue.core.DataCollection()
    hub = data_collection.hub

    splash.set_progress(100)

    session = glue.core.Session(data_collection=data_collection, hub=hub)
    ga = GlueApplication(session=session)

    from qtpy.QtCore import QTimer

    timer = QTimer()
    timer.setInterval(1000)
    timer.setSingleShot(True)
    timer.timeout.connect(splash.close)
    timer.start()

    if datafiles:
        datasets = load_data_files(datafiles)
        ga.add_datasets(data_collection, datasets)

    return ga.start(maximized=maximized)
class ScriptRunner(object):
    """
    Runs a script that interacts with a widget (tests it).
    If the script is a python generator then after each iteration controls returns
    to the QApplication's event loop.
    Generator scripts can yield a positive number. It is treated as the number of seconds
    before the next iteration is called. During the wait time the event loop is running.
    """
    def __init__(self, script, widget=None, close_on_finish=True, pause=0, is_cli=False):
        """
        Initialise a runner.
        :param script: The script to run.
        :param widget: The widget to test.
        :param close_on_finish: If true close the widget after the script has finished.
        :param is_cli: If true the script is to be run from a command line tool. Exceptions are
            treated slightly differently in this case.
        """
        app = get_application()
        self.script = script
        self.widget = widget
        self.close_on_finish = close_on_finish
        self.pause = pause
        self.is_cli = is_cli
        self.error = None
        self.script_iter = [None]
        self.pause_timer = QTimer(app)
        self.pause_timer.setSingleShot(True)
        self.script_timer = QTimer(app)

    def run(self):
        ret = run_script(self.script, self.widget)
        if isinstance(ret, Exception):
            raise ret
        self.script_iter = [iter(ret) if inspect.isgenerator(ret) else None]
        if self.pause != 0:
            self.script_timer.setInterval(self.pause * 1000)
        # Zero-timeout timer runs script_runner() between Qt events
        self.script_timer.timeout.connect(self, Qt.QueuedConnection)
        QMetaObject.invokeMethod(self.script_timer, 'start', Qt.QueuedConnection)

    def __call__(self):
        app = get_application()
        if not self.pause_timer.isActive():
            try:
                script_iter = self.script_iter[-1]
                if script_iter is None:
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit()
                    return
                # Run test script until the next 'yield'
                try:
                    ret = next(script_iter)
                except ValueError:
                    return
                while ret is not None:
                    if inspect.isgenerator(ret):
                        self.script_iter.append(ret)
                        ret = None
                    elif isinstance(ret, six.integer_types) or isinstance(ret, float):
                        # Start non-blocking pause in seconds
                        self.pause_timer.start(int(ret * 1000))
                        ret = None
                    else:
                        ret = ret()
            except StopIteration:
                if len(self.script_iter) > 1:
                    self.script_iter.pop()
                else:
                    self.script_iter = [None]
                    self.script_timer.stop()
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit(0)
            except Exception as e:
                self.script_iter = [None]
                traceback.print_exc()
                if self.close_on_finish:
                    app.exit(1)
                self.error = e