Exemple #1
0
def qapplication(translate=True, test_time=3):
    """
    Return QApplication instance
    Creates it if it doesn't already exist
    
    test_time: Time to maintain open the application when testing. It's given
    in seconds
    """
    if running_in_mac_app():
        SpyderApplication = MacApplication
    else:
        SpyderApplication = QApplication
    
    app = SpyderApplication.instance()
    if app is None:
        # Set Application name for Gnome 3
        # https://groups.google.com/forum/#!topic/pyside/24qxvwfrRDs
        app = SpyderApplication(['Spyder'])

        # Set application name for KDE (See issue 2207)
        app.setApplicationName('Spyder')
    if translate:
        install_translator(app)

    test_travis = os.environ.get('TEST_CI_WIDGETS', None)
    if test_travis is not None:
        timer_shutdown = QTimer(app)
        timer_shutdown.timeout.connect(app.quit)
        timer_shutdown.start(test_time*1000)
    return app
Exemple #2
0
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        MatplotlibViewerMixin.setup_callbacks(self)

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()
Exemple #3
0
 def start_video(self):
     timer = QTimer()
     self.timer = timer
     timer.timeout.connect(self._wait_for_frame)
     self.cam.start_live_video(**self.settings)
     timer.start(0)  # Run full throttle
     self.is_live = True
     self.needs_resize = True
     self.videoStarted.emit()
Exemple #4
0
class BaseTimerStatus(StatusBarWidget):
    """Status bar widget base for widgets that update based on timers."""

    def __init__(self, parent, statusbar):
        """Status bar widget base for widgets that update based on timers."""
        self.timer = None  # Needs to come before parent call
        super(BaseTimerStatus, self).__init__(parent, statusbar)
        self._interval = 2000

        # Widget setup
        fm = self.label_value.fontMetrics()
        self.label_value.setMinimumWidth(fm.width('000%'))

        # Setup
        if self.is_supported():
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_status)
            self.timer.start(self._interval)
        else:
            self.hide()

    def setVisible(self, value):
        """Override Qt method to stops timers if widget is not visible."""
        if self.timer is not None:
            if value:
                self.timer.start(self._interval)
            else:
                self.timer.stop()
        super(BaseTimerStatus, self).setVisible(value)

    def set_interval(self, interval):
        """Set timer interval (ms)."""
        self._interval = interval
        if self.timer is not None:
            self.timer.setInterval(interval)
    
    def import_test(self):
        """Raise ImportError if feature is not supported."""
        raise NotImplementedError

    def is_supported(self):
        """Return True if feature is supported."""
        try:
            self.import_test()
            return True
        except ImportError:
            return False

    def get_value(self):
        """Return formatted text value."""
        raise NotImplementedError
        
    def update_status(self):
        """Update status label widget, if widget is visible."""
        if self.isVisible():
            self.label_value.setText(self.get_value())
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 #6
0
class DelayJobRunner(object):
    """
    Utility class for running job after a certain delay.
    If a new request is made during this delay, the previous request is dropped
    and the timer is restarted for the new request.

    We use this to implement a cooldown effect that prevents jobs from being
    executed while the IDE is not idle.

    A job is a simple callable.
    """
    def __init__(self, delay=500):
        """
        :param delay: Delay to wait before running the job. This delay applies
        to all requests and cannot be changed afterwards.
        """
        self._timer = QTimer()
        self.delay = delay
        self._timer.timeout.connect(self._exec_requested_job)
        self._args = []
        self._kwargs = {}
        self._job = lambda x: None

    def request_job(self, job, *args, **kwargs):
        """
        Request a job execution.

        The job will be executed after the delay specified in the
        DelayJobRunner contructor elapsed if no other job is requested until
        then.

        :param job: job.
        :type job: callable
        :param args: job's position arguments
        :param kwargs: job's keyworded arguments
        """
        self.cancel_requests()
        self._job = job
        self._args = args
        self._kwargs = kwargs
        self._timer.start(self.delay)

    def cancel_requests(self):
        """Cancels pending requests."""
        self._timer.stop()
        self._job = None
        self._args = None
        self._kwargs = None

    def _exec_requested_job(self):
        """Execute the requested job after the timer has timeout."""
        self._timer.stop()
        self._job(*self._args, **self._kwargs)
Exemple #7
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 #8
0
    def setup(self, icon_painter, painter, rect):

        if self.parent_widget not in self.info:
            timer = QTimer()
            timer.timeout.connect(lambda: self._update(self.parent_widget))
            self.info[self.parent_widget] = [timer, 0, self.step]
            timer.start(self.interval)
        else:
            timer, angle, self.step = self.info[self.parent_widget]
            x_center = rect.width() * 0.5
            y_center = rect.height() * 0.5
            painter.translate(x_center, y_center)
            painter.rotate(angle)
            painter.translate(-x_center, -y_center)
Exemple #9
0
class ConnectionInspector(QWidget):
    def __init__(self, parent=None):
        super(ConnectionInspector, self).__init__(parent, Qt.Window)
        connections = self.fetch_data()
        self.table_view = ConnectionTableView(connections, self)
        self.setLayout(QVBoxLayout(self))
        self.layout().addWidget(self.table_view)
        button_layout = QHBoxLayout()
        self.layout().addItem(button_layout)
        self.save_status_label = QLabel(self)
        button_layout.addWidget(self.save_status_label)
        button_layout.addStretch()
        self.save_button = QPushButton(self)
        self.save_button.setText("Save list to file...")
        self.save_button.clicked.connect(self.save_list_to_file)
        button_layout.addWidget(self.save_button)
        self.update_timer = QTimer(parent=self)
        self.update_timer.setInterval(1500)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.start()

    def update_data(self):
        self.table_view.model().connections = self.fetch_data()

    def fetch_data(self):
        plugins = data_plugins.plugin_modules
        return [connection
                for p in plugins.values()
                for connection in p.connections.values()
                ]

    @Slot()
    def save_list_to_file(self):
        filename, filters = QFileDialog.getSaveFileName(self,
                                                        "Save connection list",
                                                        "",
                                                        "Text Files (*.txt)")
        try:
            with open(filename, "w") as f:
                for conn in self.table_view.model().connections:
                    f.write(
                        "{p}://{a}\n".format(p=conn.protocol, a=conn.address))
            self.save_status_label.setText("File saved to {}".format(filename))
        except Exception as e:
            msgBox = QMessageBox()
            msgBox.setText("Couldn't save connection list to file.")
            msgBox.setInformativeText("Error: {}".format(str(e)))
            msgBox.setStandardButtons(QMessageBox.Ok)
            msgBox.exec_()
Exemple #10
0
def setup():
    qapp = QApplication(sys.argv)
    # qapp.setGraphicsSystem('native')
    qapp.setWindowIcon(QIcon(os.path.join(ICON_PATH, 'application',
                                          'icon.png')))

    #http://stackoverflow.com/questions/4938723/what-is-the-correct-way-to-make-my-pyqt-application-quit-when-killed-from-the-co
    timer = QTimer()
    timer.start(500)  # You may change this if you wish.
    timer.timeout.connect(lambda: None)  # Let the interpreter run each 500 ms.

    app = App(sys.argv)
    app.viewer.main_window.show()

    return qapp, app
def qapplication(translate=True, test_time=3):
    """Return QApplication instance
    Creates it if it doesn't already exist"""
    app = QApplication.instance()
    if app is None:
        app = QApplication(['Conda-Manager'])
        app.setApplicationName('Conda-Manager')
    if translate:
        install_translator(app)

    test_travis = os.environ.get('TEST_CI', None)
    if test_travis is not None:
        timer_shutdown = QTimer(app)
        timer_shutdown.timeout.connect(app.quit)
        timer_shutdown.start(test_time*1000)
    return app
Exemple #12
0
class BaseTimerStatus(StatusBarWidget):
    TITLE = None
    TIP = None
    def __init__(self, parent, statusbar):
        StatusBarWidget.__init__(self, parent, statusbar)
        self.setToolTip(self.TIP)
        layout = self.layout()
        layout.addWidget(QLabel(self.TITLE))
        self.label = QLabel()
        self.label.setFont(self.label_font)
        layout.addWidget(self.label)
        layout.addSpacing(20)
        if self.is_supported():
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_label)
            self.timer.start(2000)
        else:
            self.timer = None
            self.hide()
    
    def set_interval(self, interval):
        """Set timer interval (ms)"""
        if self.timer is not None:
            self.timer.setInterval(interval)
    
    def import_test(self):
        """Raise ImportError if feature is not supported"""
        raise NotImplementedError

    def is_supported(self):
        """Return True if feature is supported"""
        try:
            self.import_test()
            return True
        except ImportError:
            return False
    
    def get_value(self):
        """Return value (e.g. CPU or memory usage)"""
        raise NotImplementedError
        
    def update_label(self):
        """Update status label widget, if widget is visible"""
        if self.isVisible():
            self.label.setText('%d %%' % self.get_value())
Exemple #13
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 #14
0
class FPSLabel(QLabel):
    """This QLabel can show FPS of main canvas in status bar."""
    def __init__(self, parent: QWidget):
        super(FPSLabel, self).__init__(parent)
        self.__t0 = process_time()
        self.__frame_timer = QTimer()
        self.__frame_timer.timeout.connect(self.__update_text)
        self.__frame_timer.start(1000)

    @Slot()
    def __update_text(self) -> None:
        """Update FPS with timer."""
        t1 = process_time() - self.__t0
        fps = 1 / t1 if t1 else 1
        self.setText(f"FPS: {fps:6.02f}")

    @Slot()
    def update_text(self) -> None:
        """Update FPS with timer."""
        self.__update_text()
        self.__t0 = process_time()
Exemple #15
0
def test_remove_item_and_reply_no(qtbot, pathmanager):
    """Check that the item is not removed after answering 'No'."""
    pathmanager.show()
    count = pathmanager.count()

    def interact_message_box():
        messagebox = pathmanager.findChild(QMessageBox)
        buttons = messagebox.findChildren(QPushButton)
        for button in buttons:
            if 'no' 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
Exemple #16
0
class Connection(PyDMConnection):
    def __init__(self, widget, address, protocol=None, parent=None):
        super(Connection, self).__init__(widget, address, protocol, parent)
        self.add_listener(widget)
        self.value = address
        self.rand = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.send_new_value)
        self.timer.start(1000)
        self.connected = True

    def send_new_value(self):
        val_to_send = "{0}-{1}".format(self.value, random.randint(0, 9))
        self.new_value_signal[str].emit(str(val_to_send))

    def send_connection_state(self, conn):
        self.connection_state_signal.emit(conn)

    def add_listener(self, widget):
        super(Connection, self).add_listener(widget)
        self.send_connection_state(True)
Exemple #17
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 #18
0
class BarGraph(PlotWidget):
    """Bar Graph."""

    def __init__(self, channels=list(), xLabels=list(), yLabel='', title=''):
        """Init."""
        super().__init__()
        self._channels = list()
        for chn in channels:
            self._channels.append(_ConnSignal(chn))
        self._xLabels = xLabels
        self._yLabel = yLabel

        self.showGrid(x=True, y=True)
        self.setBackground('w')
        self.setXRange(min=-0.5, max=len(xLabels)-0.5)
        self.setTitle(title)
        self.setLabel('left', text=self._yLabel)
        self.getAxis('left').setStyle(
            autoExpandTextSpace=False, tickTextWidth=30)
        self.getAxis('bottom').setTicks(
            [[(i, l) for i, l in enumerate(self._xLabels)]])

        self._baritems = dict()
        for idx, lbl in enumerate(self._xLabels):
            baritem = BarGraphItem(
                x=[idx, ], width=1, height=0, brush='b')
            self.addItem(baritem)
            self._baritems[idx] = baritem

        self._timer = QTimer()
        self._timer.timeout.connect(self._update_graph)
        self._timer.setInterval(500)  # ms
        self._timer.start()

    def _update_graph(self):
        wave = list()
        for idx, chn in enumerate(self._channels):
            value = chn.value if chn.value is not None else 0
            wave.append(value)
            self._baritems[idx].setOpts(height=value)
Exemple #19
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 #20
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 #21
0
class GEventProcessing:
    """Interoperability class between Qt/gevent that allows processing gevent
    tasks during Qt idle periods."""
    def __init__(self, idle_period=0.010):
        # Limit the IDLE handler's frequency while still allow for gevent
        # to trigger a microthread anytime
        self._idle_period = idle_period
        # IDLE timer: on_idle is called whenever no Qt events left for
        # processing
        self._timer = QTimer()
        self._timer.timeout.connect(self.process_events)
        self._timer.start(0)

    def __enter__(self):
        pass

    def __exit__(self, *exc_info):
        self._timer.stop()

    def process_events(self):
        # Cooperative yield, allow gevent to monitor file handles via libevent
        gevent.sleep(self._idle_period)
Exemple #22
0
    def __init__(self):
        """Initialise the whole program."""
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.positioners.addItems(positioners.keys())
        self.used_positioners = []

        self.connect_views()
        # Make a dummy meristem with random buds in different colours
        self.meristem = BudFronter()

        # set the OpenGL canvas up with the meristem
        self.ui.mainCanvas.objects = self.meristem

        self.ui.flatStem.objects = self.meristem
        self.ui.flatStem.show()
        # Set a timer to refresh the OpenGL screen every 20ms (50fps)
        timer = QTimer(self)
        timer.timeout.connect(self.ui.mainCanvas.update)
        timer.start(20)
Exemple #23
0
class Connection(PyDMConnection):

    def __init__(self, widget, address, protocol=None, parent=None):
        super(Connection, self).__init__(widget, address, protocol, parent)
        self.add_listener(widget)
        self.value = address
        self.rand = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.send_new_value)
        self.timer.start(1000)
        self.connected = True

    def send_new_value(self):
        val_to_send = "{0}-{1}".format(self.value, random.randint(0, 9))
        self.new_value_signal[str].emit(str(val_to_send))

    def send_connection_state(self, conn):
        self.connection_state_signal.emit(conn)

    def add_listener(self, widget):
        super(Connection, self).add_listener(widget)
        self.send_connection_state(True)
Exemple #24
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 #25
0
class ScalarWidget(QLCDNumber):
    def __init__(self, name, topic, addr, parent=None):
        super(__class__, self).__init__(parent)
        self.name = name
        self.topic = topic
        self.timer = QTimer()
        self.setGeometry(QRect(320, 180, 191, 81))
        self.setDigitCount(10)
        self.setObjectName(topic)
        self.comm_handler = AsyncGraphCommHandler(addr.name, addr.comm)
        self.timer.timeout.connect(self.get_scalar)
        self.timer.start(1000)

    @asyncqt.asyncSlot()
    async def get_scalar(self):
        reply = await self.comm_handler.fetch(self.topic)
        if reply is not None:
            self.scalar_updated(reply)
        else:
            logger.warn("failed to fetch %s from manager!", self.topic)

    def scalar_updated(self, data):
        self.display(data)
Exemple #26
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 #27
0
class TimerThread(QObject):
    signal = Signal(str)

    def __init__(self, callback, milis):
        super().__init__()
        self.milis = milis
        self.signal.connect(callback)
        self.thread = QThread()
        self.timer = QTimer()
        #self.timer.timeout.connect(self.timeout)
        #self.timer.start(milis)
        self.thread.started.connect(self.init)

    def Start(self):
        self.thread.start()

    @Slot()
    def init(self):
        self.timer.timeout.connect(self.timeout)
        self.timer.start(self.milis)

    def timeout(self):
        self.signal.emit("tick")
Exemple #28
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 #29
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 #30
0
def qapplication(translate=True, test_time=3):
    """
    Return QApplication instance
    Creates it if it doesn't already exist

    test_time: Time to maintain open the application when testing. It's given
    in seconds
    """
    if sys.platform == "darwin":
        SpyderApplication = MacApplication
    else:
        SpyderApplication = QApplication

    app = SpyderApplication.instance()
    if app is None:
        # Set Application name for Gnome 3
        # https://groups.google.com/forum/#!topic/pyside/24qxvwfrRDs
        app = SpyderApplication(['Spyder'])

        # Set application name for KDE. See spyder-ide/spyder#2207.
        app.setApplicationName('Spyder')

    if (sys.platform == "darwin"
            and not running_in_mac_app()
            and CONF.get('main', 'mac_open_file', False)):
        # Register app if setting is set
        register_app_launchservices()

    if translate:
        install_translator(app)

    test_ci = os.environ.get('TEST_CI_WIDGETS', None)
    if test_ci is not None:
        timer_shutdown = QTimer(app)
        timer_shutdown.timeout.connect(app.quit)
        timer_shutdown.start(test_time * 1000)
    return app
Exemple #31
0
    def hs_napari(self, dataset):

        with napari.gui_qt() as app:
            viewer = napari.Viewer()
            self.viewer = viewer
            self.app = app

            self.update_viewer(dataset)
            start = time.time()

            # timer for exiting napari
            timer = QTimer()
            timer.timeout.connect(self.quit)
            timer.start(1000*1)

            @viewer.bind_key('x')
            def crop(viewer):
                if 'Shapes' in viewer.layers:
                    bound_box = np.array(viewer.layers['Shapes'].data).squeeze()
                else:
                    bound_box = np.array(False)

                if bound_box.shape[0] == 4:

                    #crop full dataset
                    self.crop_section(bound_box)
                    #save current selection
                    selection = {}
                    for d in self.im.dims:
                        if d not in ['row', 'col']:
                            if d in dataset.dims:
                                selection[d] = dataset[d]
                            else:
                                selection[d] = dataset.coords[d].values
                    # update viewer
                    cropped = self.im.sel(selection)
                    self.update_viewer(cropped)
Exemple #32
0
class StartDialog(QDialog):
    def __init__(self, parent):
        super(StartDialog,
              self).__init__()  # Call the inherited classes __init__ method
        #super().__init__(parent)
        uic.loadUi(_ST_DLG, self)
        self.hideText()
        self.index = 0
        self.labels = [
            self.label01, self.label02, self.label03, self.label04,
            self.label05, self.label06
        ]
        self.timer = QTimer()
        self.timer.timeout.connect(self.serialText)
        self.timer.start(1060)
        self.setWindowModality(Qt.ApplicationModal)
        self.exec_()

    @Slot()
    def on_ok_clicked(self):
        self.timer.stop()
        self.close()

    def hideText(self):
        self.label01.hide()
        self.label02.hide()
        self.label03.hide()
        self.label04.hide()
        self.label05.hide()
        self.label06.hide()

    def serialText(self):
        self.labels[self.index].show()
        if self.index < 5:
            self.index += 1
        else:
            self.timer.stop()
def _main(args, exec=True):
    global mainwindow, splash_proc, show_check_timer
    # import pydm
    # app = QApplication([])
    # app = pydm.PyDMApplication()
    app = QApplication.instance() or QApplication([])
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    from xicam.gui.windows import splash
    from xicam.core import msg

    if getattr(args, 'verbose', False):
        QErrorMessage.qtHandler()

    # start splash in subprocess
    splash_proc = QProcess()
    # splash_proc.started.connect(lambda: print('started splash'))
    # splash_proc.finished.connect(lambda: print('finished splashing'))
    log_file = msg.file_handler.baseFilename
    initial_length = os.path.getsize(log_file)
    splash_proc.start(sys.executable,
                      [splash.__file__, log_file,
                       str(initial_length)])

    show_check_timer = QTimer()
    show_check_timer.timeout.connect(check_show_mainwindow)
    show_check_timer.start(100)

    from xicam.gui.windows.mainwindow import XicamMainWindow

    mainwindow = XicamMainWindow()

    if exec:
        return app.exec_()
    else:
        return mainwindow
    def _highlight(self):
        num = self.component_selector.value
        labels = self.labels_layer.value
        bound_info = self.roi_info.bound_info.get(num, None)
        if bound_info is None:
            self._stop()
            return
        slices = bound_info.get_slices()
        component_mark = self.roi_info.roi[tuple(slices)] == num
        translate_grid = labels.translate + bound_info.lower * labels.scale
        if ".Highlight" in self.napari_viewer.layers:
            self.napari_viewer.layers[".Highlight"].data = component_mark
        else:
            layer = self.napari_viewer.add_labels(
                component_mark,
                name=".Highlight",
                scale=labels.scale,
                blending="translucent",
                color={0: (0, 0, 0, 0), 1: "white"},
                opacity=0.7,
            )

            def flash_fun(layer_=layer):
                opacity = layer_.opacity + 0.1
                if opacity > 1:
                    opacity = 0.1
                layer_.opacity = opacity

            timer = QTimer()
            timer.setInterval(100)
            timer.timeout.connect(flash_fun)
            timer.start()
            layer.metadata["timer"] = timer

        self.napari_viewer.layers[".Highlight"].translate = translate_grid
        self._shift_if_need(labels, bound_info)
Exemple #35
0
    def __init__(self, callback=default_message_callback):
        super(vmpwMidiWidget, self).__init__()

        self.setWindowTitle('vmpw Midi Reader')
        self.setLayout(QVBoxLayout(self))
        self.refreshButton = QPushButton("List Midi In Ports")
        self.refreshButton.clicked.connect(self.listMidiIns)
        self.layout().addWidget(self.refreshButton)

        self.readButton = QPushButton("Read Midi Messages")
        self.layout().addWidget(self.readButton)

        self.ins = midiutil.rtmidi.MidiIn(
            rtmidi.midiutil.get_api_from_environment())
        self.ins.set_error_callback(midi_error)

        self.inport = None
        self.inputButtons = []
        _i = 0
        for port in self.listMidiIns():
            button = QPushButton(str(port))
            button.clicked.connect(lambda: self.set_input(1))
            _i = _i + 1
            self.layout().addWidget(button)
            self.inputButtons.append(button)
        self.i = None

        #Monitor
        self.messageBox = QLabel('')
        self.layout().addWidget(self.messageBox)

        timer = QTimer(self)
        timer.setInterval(1)
        timer.timeout.connect(self.getmessages)
        timerID = timer.start(1000 // 60)
        self.timer = timer

        self.callback = callback
        self.show()
class _CondaAPI(QObject):
    """
    """
    ROOT_PREFIX = None
    ENCODING = 'ascii'
    UTF8 = 'utf-8'
    DEFAULT_CHANNELS = ['https://repo.continuum.io/pkgs/pro',
                        'https://repo.continuum.io/pkgs/free']

    def __init__(self, parent=None):
        super(_CondaAPI, self).__init__()
        self._parent = parent
        self._queue = deque()
        self._timer = QTimer()
        self._current_worker = None
        self._workers = []

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        self.set_root_prefix()

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)
        else:
            self._current_worker = None
            self._timer.stop()

    def _start(self):
        """
        """
        if len(self._queue) == 1:
            self._current_worker = self._queue.popleft()
            self._workers.append(self._current_worker)
            self._current_worker.start()
            self._timer.start()

    def is_active(self):
        """
        Check if a worker is still active.
        """
        return len(self._workers) == 0

    def terminate_all_processes(self):
        """
        Kill all working processes.
        """
        for worker in self._workers:
            worker.close()

    # --- Conda api
    # -------------------------------------------------------------------------
    def _call_conda(self, extra_args, abspath=True, parse=False,
                    callback=None):
        """
        Call conda with the list of extra arguments, and return the worker.
        The result can be force by calling worker.communicate(), which returns
        the tuple (stdout, stderr).
        """
        if abspath:
            if sys.platform == 'win32':
                python = join(self.ROOT_PREFIX, 'python.exe')
                conda = join(self.ROOT_PREFIX, 'Scripts',
                             'conda-script.py')
            else:
                python = join(self.ROOT_PREFIX, 'bin/python')
                conda = join(self.ROOT_PREFIX, 'bin/conda')
            cmd_list = [python, conda]
        else:
            # Just use whatever conda is on the path
            cmd_list = ['conda']

        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list, parse=parse,
                                       callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _call_and_parse(self, extra_args, abspath=True, callback=None):
        """
        """
        return self._call_conda(extra_args, abspath=abspath, parse=True,
                                callback=callback)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        cmd_list = []
        if kwargs.get('override_channels', False) and 'channel' not in kwargs:
            raise TypeError('conda search: override_channels requires channel')

        if 'env' in kwargs:
            cmd_list.extend(['--name', kwargs.pop('env')])
        if 'prefix' in kwargs:
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])
        if 'channel' in kwargs:
            channel = kwargs.pop('channel')
            if isinstance(channel, str):
                cmd_list.extend(['--channel', channel])
            else:
                cmd_list.append('--channel')
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append('--' + key.replace('_', '-'))

        return cmd_list

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        This function should only be called once (right after importing
        conda_api).
        """
        if prefix:
            self.ROOT_PREFIX = prefix
        else:
            # Find some conda instance, and then use info to get 'root_prefix'
            worker = self._call_and_parse(['info', '--json'], abspath=False)
            info = worker.communicate()[0]
            self.ROOT_PREFIX = info['root_prefix']

    def get_conda_version(self):
        """
        Return the version of conda being used (invoked) as a string.
        """
        return self._call_conda(['--version'],
                                callback=self._get_conda_version)

    def _get_conda_version(self, stdout, stderr):
        # argparse outputs version to stderr in Python < 3.4.
        # http://bugs.python.org/issue18920
        pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
        m = pat.match(stderr.decode().strip())
        if m is None:
            m = pat.match(stdout.decode().strip())

        if m is None:
            raise Exception('output did not match: {0}'.format(stderr))

        return m.group(1)

    def get_envs(self):
        """
        Return all of the (named) environment (this does not include the root
        environment), as a list of absolute path to their prefixes.
        """
        logger.debug('')
#        return self._call_and_parse(['info', '--json'],
#                                    callback=lambda o, e: o['envs'])
        envs = os.listdir(os.sep.join([self.ROOT_PREFIX, 'envs']))
        envs = [os.sep.join([self.ROOT_PREFIX, 'envs', i]) for i in envs]

        valid_envs = [e for e in envs if os.path.isdir(e) and
                      self.environment_exists(prefix=e)]

        return valid_envs

    def get_prefix_envname(self, name):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        prefix = None
        if name == 'root':
            prefix = self.ROOT_PREFIX

#        envs, error = self.get_envs().communicate()
        envs = self.get_envs()
        for p in envs:
            if basename(p) == name:
                prefix = p

        return prefix

    def linked(self, prefix):
        """
        Return the (set of canonical names) of linked packages in `prefix`.
        """
        logger.debug(str(prefix))

        if not isdir(prefix):
            raise Exception('no such directory: {0}'.format(prefix))

        meta_dir = join(prefix, 'conda-meta')
        if not isdir(meta_dir):
            # We might have nothing in linked (and no conda-meta directory)
            return set()

        return set(fn[:-5] for fn in os.listdir(meta_dir)
                   if fn.endswith('.json'))

    def split_canonical_name(self, cname):
        """
        Split a canonical package name into (name, version, build) strings.
        """
        return tuple(cname.rsplit('-', 2))

    def info(self, abspath=True):
        """
        Return a dictionary with configuration information.
        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
        logger.debug(str(''))
        return self._call_and_parse(['info', '--json'], abspath=abspath)

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.
        """
        return self._call_and_parse(['info', package, '--json'],
                                    abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ['search', '--json']

        if regex and spec:
            raise TypeError('conda search: only one of regex or spec allowed')

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(['--spec', spec])

        if 'platform' in kwargs:
            cmd_list.extend(['--platform', kwargs.pop('platform')])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('canonical', 'unknown', 'use_index_cache', 'outdated',
                 'override_channels')))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True))

    def create(self, name=None, prefix=None, pkgs=None, channels=None):
        """
        Create an environment either by name or path with a specified set of
        packages.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into new environment')

        cmd_list = ['create', '--yes', '--quiet', '--json', '--mkdir']
        if name:
            ref = name
            search = [os.path.join(d, name) for d in
                      self.info().communicate()[0]['envs_dirs']]
            cmd_list.extend(['--name', name])
        elif prefix:
            ref = prefix
            search = [prefix]
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for new environment')

        if any(os.path.exists(prefix) for prefix in search):
            raise CondaEnvExistsError('Conda environment {0} already '
                                      'exists'.format(ref))

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def parse_token_channel(self, channel, token):
        """
        Adapt a channel to include the authentication token of the logged
        user.

        Ignore default channels
        """
        if token and channel not in self.DEFAULT_CHANNELS:
            url_parts = channel.split('/')
            start = url_parts[:-1]
            middle = 't/{0}'.format(token)
            end = url_parts[-1]
            token_channel = '{0}/{1}/{2}'.format('/'.join(start), middle, end)
            return token_channel
        else:
            return channel

    def install(self, name=None, prefix=None, pkgs=None, dep=True,
                channels=None, token=None):
        """
        Install packages into an environment either by name or path with a
        specified set of packages.

        If token is specified, the channels different from the defaults will
        get the token appended.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--yes', '--json', '--force-pscheck']
        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            # Just install into the current environment, whatever that is
            pass

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                channel = self.parse_token_channel(channel, token)
                cmd_list.extend([channel])

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        if not dep:
            cmd_list.extend(['--no-deps'])

        return self._call_and_parse(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ['update', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to update, or "
                            "all=True.")

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'no_deps', 'override_channels',
                 'no_pin', 'force', 'all', 'use_index_cache', 'use_local',
                 'alt_hint')))

        cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath',
                                                                 True))

    def remove(self, name=None, prefix=None, pkgs=None, all_=False):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        logger.debug(str((prefix, pkgs)))

        cmd_list = ['remove', '--json', '--quiet', '--yes']

        if not pkgs and not all_:
            raise TypeError("Must specify at least one package to remove, or "
                            "all=True.")

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for package removal')

        if all_:
            cmd_list.extend(['--all'])
        else:
            cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list)

    def remove_environment(self, name=None, path=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
        return self.remove(name=name, path=path, all=True, **kwargs)

    def clone_environment(self, clone, name=None, prefix=None, **kwargs):
        """
        Clone the environment `clone` into `name` or `prefix`.
        """
        cmd_list = ['create', '--json', '--quiet']

        if (name and prefix) or not (name or prefix):
            raise TypeError("conda clone_environment: exactly one of `name` "
                            "or `path` required")

        if name:
            cmd_list.extend(['--name', name])

        if prefix:
            cmd_list.extend(['--prefix', prefix])

        cmd_list.extend(['--clone', clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'unknown', 'use_index_cache', 'use_local',
                 'no_pin', 'force', 'all', 'channel', 'override_channels',
                 'no_default_packages')))

        return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath',
                                                                 True))

    # FIXME:
    def process(self, name=None, prefix=None, cmd=None):
        """
        Create a Popen process for cmd using the specified args but in the
        conda environment specified by name or prefix.

        The returned object will need to be invoked with p.communicate() or
        similar.
        """
        if bool(name) == bool(prefix):
            raise TypeError('exactly one of name or prefix must be specified')

        if not cmd:
            raise TypeError('cmd to execute must be specified')

        if not args:
            args = []

        if name:
            prefix = self.get_prefix_envname(name)

        conda_env = dict(os.environ)
        sep = os.pathsep

        if sys.platform == 'win32':
            conda_env['PATH'] = join(prefix,
                                     'Scripts') + sep + conda_env['PATH']
        else:
            # Unix
            conda_env['PATH'] = join(prefix, 'bin') + sep + conda_env['PATH']

        conda_env['PATH'] = prefix + os.pathsep + conda_env['PATH']

        cmd_list = [cmd]
        cmd_list.extend(args)

#         = self.subprocess.process(cmd_list, env=conda_env, stdin=stdin,
#                                        stdout=stdout, stderr=stderr)

    def _setup_config_from_kwargs(self, kwargs):
        cmd_list = ['--json', '--force']

        if 'file' in kwargs:
            cmd_list.extend(['--file', kwargs['file']])

        if 'system' in kwargs:
            cmd_list.append('--system')

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['rc_path'])

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['get'])

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--set', key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--add', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove-key', key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ['run', '--json', command]

        return self._call_and_parse(cmd_list, abspath=abspath)

    # --- Additional methods
    # -----------------------------------------------------------------------------
    def dependencies(self, name=None, prefix=None, pkgs=None, channels=None,
                     dep=True):
        """
        Get dependenciy list for packages to be installed into an environment
        defined either by 'name' or 'prefix'.
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--dry-run', '--json', '--force-pscheck']

        if not dep:
            cmd_list.extend(['--no-deps'])

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            pass

        cmd_list.extend(pkgs)

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def environment_exists(self, name=None, prefix=None, abspath=True):
        """
        Check if an environment exists by 'name' or by 'prefix'. If query is
        by 'name' only the default conda environments directory is searched.
        """
        logger.debug(str((name, prefix)))

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if prefix is None:
            prefix = self.ROOT_PREFIX

        return os.path.isdir(os.path.join(prefix, 'conda-meta'))

    def clear_lock(self, abspath=True):
        """
        Clean any conda lock in the system.
        """
        cmd_list = ['clean', '--lock', '--json']
        return self._call_and_parse(cmd_list, abspath=abspath)

    def package_version(self, prefix=None, name=None, pkg=None):
        """
        """
        package_versions = {}

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if self.environment_exists(prefix=prefix):

            for package in self.linked(prefix):
                if pkg in package:
                    n, v, b = self.split_canonical_name(package)
                    package_versions[n] = v

        return package_versions.get(pkg, None)

    def get_platform(self):
        """
        Get platform of current system (system and bitness).
        """
        _sys_map = {'linux2': 'linux', 'linux': 'linux',
                    'darwin': 'osx', 'win32': 'win', 'openbsd5': 'openbsd'}

        non_x86_linux_machines = {'armv6l', 'armv7l', 'ppc64le'}
        sys_platform = _sys_map.get(sys.platform, 'unknown')
        bits = 8 * tuple.__itemsize__

        if (sys_platform == 'linux' and
                platform.machine() in non_x86_linux_machines):
            arch_name = platform.machine()
            subdir = 'linux-{0}'.format(arch_name)
        else:
            arch_name = {64: 'x86_64', 32: 'x86'}[bits]
            subdir = '{0}-{1}'.format(sys_platform, bits)

        return subdir

    def get_condarc_channels(self):
        """
        Returns all the channel urls defined in .condarc using the defined
        `channel_alias`.

        If no condarc file is found, use the default channels.
        """
        # First get the location of condarc file and parse it to get
        # the channel alias and the channels.
        default_channel_alias = 'https://conda.anaconda.org'
        default_urls = ['https://repo.continuum.io/pkgs/free',
                        'https://repo.continuum.io/pkgs/pro']

        condarc_path = os.path.abspath(os.path.expanduser('~/.condarc'))
        channels = default_urls[:]

        if not os.path.isfile(condarc_path):
            condarc = None
            channel_alias = default_channel_alias
        else:
            with open(condarc_path, 'r') as f:
                data = f.read()
                condarc = yaml.load(data)
                channels += condarc.get('channels', [])
                channel_alias = condarc.get('channel_alias',
                                            default_channel_alias)

        if channel_alias[-1] == '/':
            template = "{0}{1}"
        else:
            template = "{0}/{1}"

        if 'defaults' in channels:
            channels.remove('defaults')

        channel_urls = []
        for channel in channels:
            if not channel.startswith('http'):
                channel_url = template.format(channel_alias, channel)
            else:
                channel_url = channel
            channel_urls.append(channel_url)

        return channel_urls

    # --- Pip commands
    # -------------------------------------------------------------------------
    def _call_pip(self, name=None, prefix=None, extra_args=None,
                  callback=None):
        """ """
        cmd_list = self._pip_cmd(name=name, prefix=prefix)
        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list, pip=True, callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _pip_cmd(self, name=None, prefix=None):
        """
        Get pip location based on environment `name` or `prefix`.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if name and self.environment_exists(name=name):
            prefix = self.get_prefix_envname(name)

        if sys.platform == 'win32':
            python = join(prefix, 'python.exe')  # FIXME:
            pip = join(prefix, 'pip.exe')        # FIXME:
        else:
            python = join(prefix, 'bin/python')
            pip = join(prefix, 'bin/pip')

        cmd_list = [python, pip]

        return cmd_list

    def pip_list(self, name=None, prefix=None, abspath=True):
        """
        Get list of pip installed packages.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if name:
            prefix = self.get_prefix_envname(name)

        pip_command = os.sep.join([prefix, 'bin', 'python'])
        cmd_list = [pip_command, PIP_LIST_SCRIPT]
        process_worker = ProcessWorker(cmd_list, pip=True, parse=True,
                                       callback=self._pip_list,
                                       extra_kwargs={'prefix': prefix})
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

#        if name:
#            cmd_list = ['list', '--name', name]
#        if prefix:
#            cmd_list = ['list', '--prefix', prefix]

#        return self._call_conda(cmd_list, abspath=abspath,
#                                callback=self._pip_list)

    def _pip_list(self, stdout, stderr, prefix=None):
        """
        """
        result = stdout  # A dict
        linked = self.linked(prefix)

        pip_only = []

        linked_names = [self.split_canonical_name(l)[0] for l in linked]

        for pkg in result:
            name = self.split_canonical_name(pkg)[0]
            if name not in linked_names:
                pip_only.append(pkg)
            # FIXME: NEED A MORE ROBUST WAY!
#            if '<pip>' in line and '#' not in line:
#                temp = line.split()[:-1] + ['pip']
#                temp = '-'.join(temp)
#                if '-(' in temp:
#                    start = temp.find('-(')
#                    end = temp.find(')')
#                    substring = temp[start:end+1]
#                    temp = temp.replace(substring, '')
#                result.append(temp)

        return pip_only

    def pip_remove(self, name=None, prefix=None, pkgs=None):
        """
        Remove a pip package in given environment by `name` or `prefix`.
        """
        logger.debug(str((prefix, pkgs)))

        if isinstance(pkgs, list) or isinstance(pkgs, tuple):
            pkg = ' '.join(pkgs)
        else:
            pkg = pkgs

        extra_args = ['uninstall', '--yes', pkg]

        return self._call_pip(name=name, prefix=prefix, extra_args=extra_args)

    def pip_search(self, search_string=None):
        """
        Search for pip installable python packages in PyPI matching
        `search_string`.
        """
        extra_args = ['search', search_string]
        return self._call_pip(name='root', extra_args=extra_args,
                              callback=self._pip_search)

        # if stderr:
        #     raise PipError(stderr)
        # You are using pip version 7.1.2, however version 8.0.2 is available.
        # You should consider upgrading via the 'pip install --upgrade pip'
        # command.

    def _pip_search(self, stdout, stderr):
        result = {}
        lines = to_text_string(stdout).split('\n')
        while '' in lines:
            lines.remove('')

        for line in lines:
            if ' - ' in line:
                parts = line.split(' - ')
                name = parts[0].strip()
                description = parts[1].strip()
                result[name] = description

        return result
Exemple #37
0
class BasePlot(PlotWidget, PyDMPrimitiveWidget):
    crosshair_position_updated = Signal(float, float)

    def __init__(self, parent=None, background='default', axisItems=None):
        PlotWidget.__init__(self, parent=parent, background=background,
                            axisItems=axisItems)
        PyDMPrimitiveWidget.__init__(self)

        self.plotItem = self.getPlotItem()
        self.plotItem.hideButtons()
        self._auto_range_x = None
        self.setAutoRangeX(True)
        self._auto_range_y = None
        self.setAutoRangeY(True)
        self._min_x = 0.0
        self._max_x = 1.0
        self._min_y = 0.0
        self._max_y = 1.0
        self._show_x_grid = None
        self.setShowXGrid(False)
        self._show_y_grid = None
        self.setShowYGrid(False)

        self._show_right_axis = False

        self.redraw_timer = QTimer(self)
        self.redraw_timer.timeout.connect(self.redrawPlot)

        self._redraw_rate = 30 # Redraw at 30 Hz by default.
        self.maxRedrawRate = self._redraw_rate
        self._curves = []
        self._title = None
        self._show_legend = False
        self._legend = self.addLegend()
        self._legend.hide()

        # Drawing crosshair on the ViewBox
        self.vertical_crosshair_line = None
        self.horizontal_crosshair_line = None
        self.crosshair_movement_proxy = None

    def addCurve(self, plot_item, curve_color=None):
        if curve_color is None:
            curve_color = utilities.colors.default_colors[
                    len(self._curves) % len(utilities.colors.default_colors)]
            plot_item.color_string = curve_color
        self._curves.append(plot_item)
        self.addItem(plot_item)
        self.redraw_timer.start()
        # Connect channels
        for chan in plot_item.channels():
            if chan:
                chan.connect()
        # self._legend.addItem(plot_item, plot_item.curve_name)

    def removeCurve(self, plot_item):
        self.removeItem(plot_item)
        self._curves.remove(plot_item)
        if len(self._curves) < 1:
            self.redraw_timer.stop()
        # Disconnect channels
        for chan in plot_item.channels():
            if chan:
                chan.disconnect()

    def removeCurveWithName(self, name):
        for curve in self._curves:
            if curve.name() == name:
                self.removeCurve(curve)

    def removeCurveAtIndex(self, index):
        curve_to_remove = self._curves[index]
        self.removeCurve(curve_to_remove)

    def setCurveAtIndex(self, index, new_curve):
        old_curve = self._curves[index]
        self._curves[index] = new_curve
        # self._legend.addItem(new_curve, new_curve.name())
        self.removeCurve(old_curve)

    def curveAtIndex(self, index):
        return self._curves[index]

    def curves(self):
        return self._curves

    def clear(self):
        legend_items = [label.text for (sample, label) in self._legend.items]
        for item in legend_items:
            self._legend.removeItem(item)
        self.plotItem.clear()
        self._curves = []

    @Slot()
    def redrawPlot(self):
        pass

    def getShowXGrid(self):
        return self._show_x_grid

    def setShowXGrid(self, value, alpha=None):
        self._show_x_grid = value
        self.showGrid(x=self._show_x_grid, alpha=alpha)

    def resetShowXGrid(self):
        self.setShowXGrid(False)

    showXGrid = Property("bool", getShowXGrid, setShowXGrid, resetShowXGrid)

    def getShowYGrid(self):
        return self._show_y_grid

    def setShowYGrid(self, value, alpha=None):
        self._show_y_grid = value
        self.showGrid(y=self._show_y_grid, alpha=alpha)

    def resetShowYGrid(self):
        self.setShowYGrid(False)

    showYGrid = Property("bool", getShowYGrid, setShowYGrid, resetShowYGrid)

    def getBackgroundColor(self):
        return self.backgroundBrush().color()

    def setBackgroundColor(self, color):
        if self.backgroundBrush().color() != color:
            self.setBackgroundBrush(QBrush(color))

    backgroundColor = Property(QColor, getBackgroundColor, setBackgroundColor)

    def getAxisColor(self):
        return self.getAxis('bottom')._pen.color()

    def setAxisColor(self, color):
        if self.getAxis('bottom')._pen.color() != color:
            self.getAxis('bottom').setPen(color)
            self.getAxis('left').setPen(color)
            self.getAxis('top').setPen(color)
            self.getAxis('right').setPen(color)

    axisColor = Property(QColor, getAxisColor, setAxisColor)

    def getBottomAxisLabel(self):
        return self.getAxis('bottom').labelText

    def getShowRightAxis(self):
        """
        Provide whether the right y-axis is being shown.

        Returns : bool
        -------
        True if the graph shows the right y-axis. False if not.

        """
        return self._show_right_axis

    def setShowRightAxis(self, show):
        """
        Set whether the graph should show the right y-axis.

        Parameters
        ----------
        show : bool
            True for showing the right axis; False is for not showing.
        """
        if show:
            self.showAxis("right")
        else:
            self.hideAxis("right")
        self._show_right_axis = show

    showRightAxis = Property("bool", getShowRightAxis, setShowRightAxis)

    def getPlotTitle(self):
        if self._title is None:
            return ""
        return str(self._title)

    def setPlotTitle(self, value):
        self._title = str(value)
        if len(self._title) < 1:
            self._title = None
        self.setTitle(self._title)

    def resetPlotTitle(self):
        self._title = None
        self.setTitle(self._title)

    title = Property(str, getPlotTitle, setPlotTitle, resetPlotTitle)

    def getShowLegend(self):
        """
        Check if the legend is being shown.

        Returns : bool
        -------
            True if the legend is displayed on the graph; False if not.
        """
        return self._show_legend

    def setShowLegend(self, value):
        """
        Set to display the legend on the graph.

        Parameters
        ----------
        value : bool
            True to display the legend; False is not.
        """
        self._show_legend = value
        if self._show_legend:
            if self._legend is None:
                self._legend = self.addLegend()
            else:
                self._legend.show()
        else:
            if self._legend is not None:
                self._legend.hide()

    def resetShowLegend(self):
        """
        Reset the legend display status to hidden.
        """
        self.setShowLegend(False)

    showLegend = Property(bool, getShowLegend, setShowLegend, resetShowLegend)

    def getAutoRangeX(self):
        return self._auto_range_x

    def setAutoRangeX(self, value):
        self._auto_range_x = value
        if self._auto_range_x:
            self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x)

    def resetAutoRangeX(self):
        self.setAutoRangeX(True)

    def getAutoRangeY(self):
        return self._auto_range_y

    def setAutoRangeY(self, value):
        self._auto_range_y = value
        if self._auto_range_y:
            self.plotItem.enableAutoRange(ViewBox.YAxis, enable=self._auto_range_y)

    def resetAutoRangeY(self):
        self.setAutoRangeY(True)

    def getMinXRange(self):
        """
        Minimum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][0]

    def setMinXRange(self, new_min_x_range):
        """
        Set the minimum X-axis value visible on the plot.

        Parameters
        -------
        new_min_x_range : float
        """
        if self._auto_range_x:
            return
        self._min_x = new_min_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMaxXRange(self):
        """
        Maximum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][1]

    def setMaxXRange(self, new_max_x_range):
        """
        Set the Maximum X-axis value visible on the plot.

        Parameters
        -------
        new_max_x_range : float
        """
        if self._auto_range_x:
            return

        self._max_x = new_max_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMinYRange(self):
        """
        Minimum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][0]

    def setMinYRange(self, new_min_y_range):
        """
        Set the minimum Y-axis value visible on the plot.

        Parameters
        -------
        new_min_y_range : float
        """
        if self._auto_range_y:
            return

        self._min_y = new_min_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)


    def getMaxYRange(self):
        """
        Maximum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][1]

    def setMaxYRange(self, new_max_y_range):
        """
        Set the maximum Y-axis value visible on the plot.

        Parameters
        -------
        new_max_y_range : float
        """
        if self._auto_range_y:
            return

        self._max_y = new_max_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)

    @Property(bool)
    def mouseEnabledX(self):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][0]

    @mouseEnabledX.setter
    def mouseEnabledX(self, x_enabled):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Parameters
        -------
        x_enabled : bool
        """
        self.plotItem.setMouseEnabled(x=x_enabled)

    @Property(bool)
    def mouseEnabledY(self):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][1]

    @mouseEnabledY.setter
    def mouseEnabledY(self, y_enabled):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Parameters
        -------
        y_enabled : bool
        """
        self.plotItem.setMouseEnabled(y=y_enabled)

    @Property(int)
    def maxRedrawRate(self):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Returns
        -------
        int
        """
        return self._redraw_rate

    @maxRedrawRate.setter
    def maxRedrawRate(self, redraw_rate):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Parameters
        -------
        redraw_rate : int
        """
        self._redraw_rate = redraw_rate
        self.redraw_timer.setInterval(int((1.0/self._redraw_rate)*1000))

    def pausePlotting(self):
        self.redraw_timer.stop() if self.redraw_timer.isActive() else self.redraw_timer.start()
        return self.redraw_timer.isActive()

    def mouseMoved(self, evt):
        """
        A handler for the crosshair feature. Every time the mouse move, the mouse coordinates are updated, and the
        horizontal and vertical hairlines will be redrawn at the new coordinate. If a PyDMDisplay object is available,
        that display will also have the x- and y- values to update on the UI.

        Parameters
        -------
        evt: MouseEvent
            The mouse event type, from which the mouse coordinates are obtained.
        """
        pos = evt[0]
        if self.sceneBoundingRect().contains(pos):
            mouse_point = self.getViewBox().mapSceneToView(pos)
            self.vertical_crosshair_line.setPos(mouse_point.x())
            self.horizontal_crosshair_line.setPos(mouse_point.y())

            self.crosshair_position_updated.emit(mouse_point.x(), mouse_point.y())

    def enableCrosshair(self, is_enabled, starting_x_pos, starting_y_pos,  vertical_angle=90, horizontal_angle=0,
                        vertical_movable=False, horizontal_movable=False):
        """
        Enable the crosshair to be drawn on the ViewBox.

        Parameters
        ----------
        is_enabled : bool
            True is to draw the crosshair, False is to not draw.
        starting_x_pos : float
            The x coordinate where to start the vertical crosshair line.
        starting_y_pos : float
            The y coordinate where to start the horizontal crosshair line.
        vertical_angle : float
            The angle to tilt the vertical crosshair line. Default at 90 degrees.
        horizontal_angle
            The angle to tilt the horizontal crosshair line. Default at 0 degrees.
        vertical_movable : bool
            True if the vertical line can be moved by the user; False is not.
        horizontal_movable
            False if the horizontal line can be moved by the user; False is not.
        """
        if is_enabled:
            self.vertical_crosshair_line = InfiniteLine(pos=starting_x_pos, angle=vertical_angle,
                                                        movable=vertical_movable)
            self.horizontal_crosshair_line = InfiniteLine(pos=starting_y_pos, angle=horizontal_angle,
                                                          movable=horizontal_movable)

            self.plotItem.addItem(self.vertical_crosshair_line)
            self.plotItem.addItem(self.horizontal_crosshair_line)
            self.crosshair_movement_proxy = SignalProxy(self.plotItem.scene().sigMouseMoved, rateLimit=60,
                                                        slot=self.mouseMoved)
        else:
            if self.vertical_crosshair_line:
                self.plotItem.removeItem(self.vertical_crosshair_line)
            if self.horizontal_crosshair_line:
                self.plotItem.removeItem(self.horizontal_crosshair_line)
            if self.crosshair_movement_proxy:
                self.crosshair_movement_proxy.disconnect()
class ProcessWorker(QObject):
    """
    """
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs={}):
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(50)

        self._timer.timeout.connect(self._communicate)
        self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """
        """
        if not self._communicate_first:
            if self._process.state() == QProcess.NotRunning:
                self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """
        """
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda cloud api site' not in stderr.lower():
            if stderr.strip() and self._conda:
                raise Exception('{0}:\n'
                                'STDERR:\n{1}\nEND'
                                ''.format(' '.join(self._cmd_list),
                                          stderr))
#            elif stderr.strip() and self._pip:
#                raise PipError(self._cmd_list)
        else:
            result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except ValueError as error:
                result = stdout, error

            if 'error' in result[0]:
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """
        """
        self._process.close()

    def is_finished(self):
        """
        """
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """
        """
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
Exemple #39
0
class ModalTester(object):
    """
    Helper class for testing modal widgets (dialogs).
    """
    def __init__(self, creator, tester):
        """
        Initialise ModalTester.
        :param creator: Function without arguments that creates a modal widget. Can be a class name.
            A modal widget must have exec_() method.
        :param tester: A function taking a widget as its argument that does testing.
        """
        self.app = QApplication.instance()
        if self.app is None:
            self.app = QApplication([''])
        self.creator = creator
        self.tester = tester
        self.widget = None
        self.timer = None
        self.passed = False

    def __call__(self):
        """
        Initialise testing.
        """
        try:
            self.widget = self.creator()
        except:
            traceback.print_exc()
            self.app.exit(0)
        if self.widget is not None:
            self.widget.exec_()

    def _idle(self):
        """
        This function runs every time in QApplication's event loop.
        Call the testing function.
        """
        modal_widget = self.app.activeModalWidget()
        if modal_widget is not None:
            if self.widget is modal_widget:
                try:
                    self.tester(self.widget)
                    self.passed = True
                except:
                    traceback.print_exc()
            if modal_widget.isVisible():
                modal_widget.close()

    def start(self):
        """
        Start this tester.
        """
        self.timer = QTimer()
        # Connecting self.idle to a QTimer with 0 time delay
        # makes it called when there are no events to process
        self.timer.timeout.connect(self._idle)
        self.timer.start()
        # This calls __call__() method
        QTimer.singleShot(0, self)
        # Start the event loop
        self.app.exec_()
Exemple #40
0
class BackgroundPlotter(QtInteractor):
    """Qt interactive plotter.

    Background plotter for pyvista that allows you to maintain an
    interactive plotting window without blocking the main python
    thread.

    Parameters
    ----------
    show :
        Show the plotting window.  If ``False``, show this window by
        running ``show()``

    app : optional
        Creates a `QApplication` if left as `None`.

    window_size :
        Window size in pixels.  Defaults to ``[1024, 768]``

    off_screen :
        Renders off screen when True.  Useful for automated
        screenshots or debug testing.

    allow_quit_keypress :
        Allow user to exit by pressing ``"q"``.

    toolbar : bool
        If True, display the default camera toolbar. Defaults to True.

    menu_bar : bool
        If True, display the default main menu. Defaults to True.

    editor: bool
        If True, display the VTK object editor. Defaults to True.

    update_app_icon :
        If True, update_app_icon will be called automatically to update the
        Qt app icon based on the current rendering output. If None, the
        logo of PyVista will be used. If False, no icon will be set.
        Defaults to None.

    title : str, optional
        Title of plotting window.

    multi_samples : int, optional
        The number of multi-samples used to mitigate aliasing. 4 is a
        good default but 8 will have better results with a potential
        impact on performance.

    line_smoothing : bool, optional
        If True, enable line smothing

    point_smoothing : bool, optional
        If True, enable point smothing

    polygon_smoothing : bool, optional
        If True, enable polygon smothing

    auto_update : float, bool, optional
        Automatic update rate in seconds.  Useful for automatically
        updating the render window when actors are change without
        being automatically ``Modified``.  If set to ``True``, update
        rate will be 1 second.

    Examples
    --------
    >>> import pyvista as pv
    >>> from pyvistaqt import BackgroundPlotter
    >>> plotter = BackgroundPlotter()
    >>> _ = plotter.add_mesh(pv.Sphere())
    """

    # pylint: disable=too-many-ancestors
    # pylint: disable=too-many-instance-attributes
    # pylint: disable=too-many-statements

    ICON_TIME_STEP = 5.0

    # pylint: disable=too-many-arguments
    # pylint: disable=too-many-locals
    def __init__(
        self,
        show: bool = True,
        app: Optional[QApplication] = None,
        window_size: Optional[Tuple[int, int]] = None,
        off_screen: Optional[bool] = None,
        allow_quit_keypress: bool = True,
        toolbar: bool = True,
        menu_bar: bool = True,
        editor: bool = True,
        update_app_icon: Optional[bool] = None,
        **kwargs: Any,
    ) -> None:
        # pylint: disable=too-many-branches
        """Initialize the qt plotter."""
        # avoid recursion of the close() function by setting
        # self._closed=True until the BasePlotter.__init__
        # is called
        self._closed = True
        LOG.debug("BackgroundPlotter init start")
        _check_type(show, "show", [bool])
        _check_type(app, "app", [QApplication, type(None)])
        _check_type(window_size, "window_size", [tuple, type(None)])
        _check_type(off_screen, "off_screen", [bool, type(None)])
        _check_type(allow_quit_keypress, "allow_quit_keypress", [bool])
        _check_type(toolbar, "toolbar", [bool])
        _check_type(menu_bar, "menu_bar", [bool])
        _check_type(editor, "editor", [bool])
        _check_type(update_app_icon, "update_app_icon", [bool, type(None)])

        # toolbar
        self._view_action: QAction = None
        self.default_camera_tool_bar: QToolBar = None
        self.saved_camera_positions: Optional[list] = None
        self.saved_cameras_tool_bar: QToolBar = None
        # menu bar
        self.main_menu: QMenuBar = None
        self._edl_action: QAction = None
        self._menu_close_action: QAction = None
        self._parallel_projection_action: QAction = None
        # editor
        self.editor: Optional[Editor] = None
        self._editor_action: QAction = None

        self.active = True
        self.counters: List[Counter] = []
        self.allow_quit_keypress = allow_quit_keypress

        if window_size is None:
            window_size = global_theme.window_size

        # Remove notebook argument in case user passed it
        kwargs.pop("notebook", None)

        self.ipython = _setup_ipython()
        self.app = _setup_application(app)
        self.off_screen = _setup_off_screen(off_screen)

        self.app_window = MainWindow(title=kwargs.get("title", global_theme.title))
        self.frame = QFrame(parent=self.app_window)
        self.frame.setFrameStyle(QFrame.NoFrame)
        vlayout = QVBoxLayout()
        super().__init__(parent=self.frame, off_screen=off_screen, **kwargs)
        assert not self._closed
        vlayout.addWidget(self)
        self.frame.setLayout(vlayout)
        self.app_window.setCentralWidget(self.frame)
        self.app_window.grabGesture(QtCore.Qt.PinchGesture)
        self.app_window.signal_gesture.connect(self.gesture_event)
        self.app_window.signal_close.connect(self._close)

        if menu_bar:
            self.add_menu_bar()
            if editor:
                self.add_editor()
        if toolbar:
            self.add_toolbars()

        if show and not self.off_screen:  # pragma: no cover
            self.app_window.show()

        self.window_size = window_size
        self._last_update_time = -np.inf
        self._last_window_size = self.window_size
        self._last_camera_pos = self.camera_position

        if update_app_icon:
            self.add_callback(self.update_app_icon)
        elif update_app_icon is None:
            self.set_icon(
                os.path.join(
                    os.path.dirname(__file__), "data", "pyvista_logo_square.png"
                )
            )
        else:
            assert update_app_icon is False

        # Keypress events
        if self.iren is not None:
            self.add_key_event("S", self._qt_screenshot)  # shift + s
        LOG.debug("BackgroundPlotter init stop")

    def reset_key_events(self) -> None:
        """Reset all of the key press events to their defaults.

        Handles closing configuration for q-key.
        """
        super().reset_key_events()
        if self.allow_quit_keypress:
            # pylint: disable=unnecessary-lambda
            self.add_key_event("q", lambda: self.close())

    def scale_axes_dialog(self, show: bool = True) -> ScaleAxesDialog:
        """Open scale axes dialog."""
        return ScaleAxesDialog(self.app_window, self, show=show)

    def close(self) -> None:
        """Close the plotter.

        This function closes the window which in turn will
        close the plotter through `signal_close`.

        """
        if not self._closed:
            # Can get:
            #
            #     RuntimeError: wrapped C/C++ object of type MainWindow has
            #     been deleted
            #
            # So let's be safe and try/except this in case of a problem.
            try:
                self.app_window.close()
            except Exception:
                pass

    def _close(self) -> None:
        super().close()

    def update_app_icon(self) -> None:
        """Update the app icon if the user is not trying to resize the window."""
        if os.name == "nt" or not hasattr(
            self, "_last_window_size"
        ):  # pragma: no cover
            # DO NOT EVEN ATTEMPT TO UPDATE ICON ON WINDOWS
            return
        cur_time = time.time()
        if self._last_window_size != self.window_size:  # pragma: no cover
            # Window size hasn't remained constant since last render.
            # This means the user is resizing it so ignore update.
            pass
        elif (
            cur_time - self._last_update_time > BackgroundPlotter.ICON_TIME_STEP
        ) and self._last_camera_pos != self.camera_position:
            # its been a while since last update OR
            # the camera position has changed and its been at least one second

            # Update app icon as preview of the window
            self.set_icon(pad_image(self.image))

            # Update trackers
            self._last_update_time = cur_time
            self._last_camera_pos = self.camera_position
        # Update trackers
        self._last_window_size = self.window_size

    def set_icon(self, img: Union[np.ndarray, str]) -> None:
        """Set the icon image.

        Parameters
        ----------
        img : ndarray, shape (w, h, c) | str
            The image. Should be uint8 and square (w == h).
            Can have 3 or 4 color/alpha channels (``c``).
            Can also be a string path that QIcon can load.

        Notes
        -----
        Currently string paths can silently fail, so make sure your path
        is something that produces a valid ``QIcon(img)``.
        """
        if not (
            isinstance(img, np.ndarray)
            and img.ndim == 3
            and img.shape[0] == img.shape[1]
            and img.dtype == np.uint8
            and img.shape[-1] in (3, 4)
        ) and not isinstance(img, str):
            raise ValueError(
                "img must be 3D uint8 ndarray with shape[1] == shape[2] and "
                "shape[2] == 3 or 4, or str"
            )
        if isinstance(img, np.ndarray):
            fmt_str = "Format_RGB"
            fmt_str += ("A8" if img.shape[2] == 4 else "") + "888"
            fmt = getattr(QtGui.QImage, fmt_str)
            img = QtGui.QPixmap.fromImage(
                QtGui.QImage(img.copy(), img.shape[1], img.shape[0], fmt)
            )
        # Currently no way to check if str/path is actually correct (want to
        # allow resource paths and the like so os.path.isfile is no good)
        # and icon.isNull() returns False even if the path is bogus.
        self.app.setWindowIcon(QtGui.QIcon(img))

    def _qt_screenshot(self, show: bool = True) -> FileDialog:
        return FileDialog(
            self.app_window,
            filefilter=["Image File (*.png)", "JPEG (*.jpeg)"],
            show=show,
            directory=bool(os.getcwd()),
            callback=self.screenshot,
        )

    def _qt_export_vtkjs(self, show: bool = True) -> FileDialog:
        """Spawn an save file dialog to export a vtkjs file."""
        return FileDialog(
            self.app_window,
            filefilter=["VTK JS File(*.vtkjs)"],
            show=show,
            directory=bool(os.getcwd()),
            callback=self.export_vtkjs,
        )

    def _toggle_edl(self) -> None:
        if hasattr(self.renderer, "edl_pass"):
            return self.renderer.disable_eye_dome_lighting()
        return self.renderer.enable_eye_dome_lighting()

    def _toggle_parallel_projection(self) -> None:
        if self.camera.GetParallelProjection():
            return self.disable_parallel_projection()
        return self.enable_parallel_projection()

    @property
    def window_size(self) -> Tuple[int, int]:
        """Return render window size."""
        the_size = self.app_window.baseSize()
        return the_size.width(), the_size.height()

    @window_size.setter
    def window_size(self, window_size: QSize) -> None:
        """Set the render window size."""
        self.app_window.setBaseSize(*window_size)
        self.app_window.resize(*window_size)
        # NOTE: setting BasePlotter is unnecessary and Segfaults CI
        # BasePlotter.window_size.fset(self, window_size)

    def __del__(self) -> None:  # pragma: no cover
        """Delete the qt plotter."""
        if not self._closed:
            self.app_window.close()

    def add_callback(
        self, func: Callable, interval: int = 1000, count: Optional[int] = None
    ) -> None:
        """Add a function that can update the scene in the background.

        Parameters
        ----------
        func :
            Function to be called with no arguments.
        interval :
            Time interval between calls to `func` in milliseconds.
        count :
            Number of times `func` will be called. If None,
            `func` will be called until the main window is closed.

        """
        self._callback_timer = QTimer(parent=self.app_window)
        self._callback_timer.timeout.connect(func)
        self._callback_timer.start(interval)
        self.app_window.signal_close.connect(self._callback_timer.stop)
        if count is not None:
            counter = Counter(count)
            counter.signal_finished.connect(self._callback_timer.stop)
            self._callback_timer.timeout.connect(counter.decrease)
            self.counters.append(counter)

    def save_camera_position(self) -> None:
        """Save camera position to saved camera menu for recall."""
        if self.saved_camera_positions is not None:
            # pylint: disable=attribute-defined-outside-init
            self.camera_position: Any
            self.saved_camera_positions.append(self.camera_position)
            ncam = len(self.saved_camera_positions)
        if self.camera_position is not None:
            camera_position: Any = self.camera_position[:]  # py2.7 copy compatibility

        if hasattr(self, "saved_cameras_tool_bar"):

            def load_camera_position() -> None:
                # pylint: disable=attribute-defined-outside-init
                self.camera_position = camera_position

            self.saved_cameras_tool_bar.addAction(
                "Cam %2d" % ncam, load_camera_position
            )
            if ncam < 10:
                self.add_key_event(str(ncam), load_camera_position)

    def clear_camera_positions(self) -> None:
        """Clear all camera positions."""
        if hasattr(self, "saved_cameras_tool_bar"):
            for action in self.saved_cameras_tool_bar.actions():
                if action.text() not in [SAVE_CAM_BUTTON_TEXT, CLEAR_CAMS_BUTTON_TEXT]:
                    self.saved_cameras_tool_bar.removeAction(action)
        self.saved_camera_positions = []

    def _add_action(self, tool_bar: QToolBar, key: str, method: Any) -> QAction:
        action = QAction(key, self.app_window)
        action.triggered.connect(method)
        tool_bar.addAction(action)
        return action

    def add_toolbars(self) -> None:
        """Add the toolbars."""
        # Camera toolbar
        self.default_camera_tool_bar = self.app_window.addToolBar("Camera Position")

        def _view_vector(*args: Any) -> None:
            return self.view_vector(*args)

        cvec_setters = {
            # Viewing vector then view up vector
            "Top (-Z)": lambda: _view_vector((0, 0, 1), (0, 1, 0)),
            "Bottom (+Z)": lambda: _view_vector((0, 0, -1), (0, 1, 0)),
            "Front (-Y)": lambda: _view_vector((0, 1, 0), (0, 0, 1)),
            "Back (+Y)": lambda: _view_vector((0, -1, 0), (0, 0, 1)),
            "Left (-X)": lambda: _view_vector((1, 0, 0), (0, 0, 1)),
            "Right (+X)": lambda: _view_vector((-1, 0, 0), (0, 0, 1)),
            "Isometric": lambda: _view_vector((1, 1, 1), (0, 0, 1)),
        }
        for key, method in cvec_setters.items():
            self._view_action = self._add_action(
                self.default_camera_tool_bar, key, method
            )
        # pylint: disable=unnecessary-lambda
        self._add_action(
            self.default_camera_tool_bar, "Reset", lambda: self.reset_camera()
        )

        # Saved camera locations toolbar
        self.saved_camera_positions = []
        self.saved_cameras_tool_bar = self.app_window.addToolBar(
            "Saved Camera Positions"
        )

        self._add_action(
            self.saved_cameras_tool_bar, SAVE_CAM_BUTTON_TEXT, self.save_camera_position
        )
        self._add_action(
            self.saved_cameras_tool_bar,
            CLEAR_CAMS_BUTTON_TEXT,
            self.clear_camera_positions,
        )

    def add_menu_bar(self) -> None:
        """Add the main menu bar."""
        self.main_menu = _create_menu_bar(parent=self.app_window)
        self.app_window.signal_close.connect(self.main_menu.clear)

        file_menu = self.main_menu.addMenu("File")
        file_menu.addAction("Take Screenshot", self._qt_screenshot)
        file_menu.addAction("Export as VTKjs", self._qt_export_vtkjs)
        file_menu.addSeparator()
        # member variable for testing only
        self._menu_close_action = file_menu.addAction("Exit", self.app_window.close)

        view_menu = self.main_menu.addMenu("View")
        self._edl_action = view_menu.addAction(
            "Toggle Eye Dome Lighting", self._toggle_edl
        )
        view_menu.addAction("Scale Axes", self.scale_axes_dialog)
        view_menu.addAction("Clear All", self.clear)

        tool_menu = self.main_menu.addMenu("Tools")
        tool_menu.addAction("Enable Cell Picking (through)", self.enable_cell_picking)
        tool_menu.addAction(
            "Enable Cell Picking (visible)",
            lambda: self.enable_cell_picking(through=False),
        )

        cam_menu = view_menu.addMenu("Camera")
        self._parallel_projection_action = cam_menu.addAction(
            "Toggle Parallel Projection", self._toggle_parallel_projection
        )

        view_menu.addSeparator()
        # Orientation marker
        orien_menu = view_menu.addMenu("Orientation Marker")
        orien_menu.addAction("Show All", self.show_axes_all)
        orien_menu.addAction("Hide All", self.hide_axes_all)
        # Bounds axes
        axes_menu = view_menu.addMenu("Bounds Axes")
        axes_menu.addAction("Add Bounds Axes (front)", self.show_bounds)
        axes_menu.addAction("Add Bounds Grid (back)", self.show_grid)
        axes_menu.addAction("Add Bounding Box", self.add_bounding_box)
        axes_menu.addSeparator()
        axes_menu.addAction("Remove Bounding Box", self.remove_bounding_box)
        axes_menu.addAction("Remove Bounds", self.remove_bounds_axes)

        # A final separator to separate OS options
        view_menu.addSeparator()

    def add_editor(self) -> None:
        """Add the editor."""
        self.editor = Editor(parent=self.app_window, renderers=self.renderers)
        self._editor_action = self.main_menu.addAction("Editor", self.editor.toggle)
        self.app_window.signal_close.connect(self.editor.close)
Exemple #41
0
class ProgressDialog(QDialog, Ui_Dialog):
    """Progress dialog.

    + Batch execute function.
    + Interrupt function.
    """

    stop_signal = Signal()

    def __init__(self, type_num: AlgorithmType, mech_params: Dict[str, Any],
                 setting: Dict[str, Any], parent):
        super(ProgressDialog, self).__init__(parent)
        self.setupUi(self)
        flags = self.windowFlags()
        self.setWindowFlags(flags & ~Qt.WindowContextHelpButtonHint)
        self.rejected.connect(self.__close_work)

        self.mechanisms: List[Dict[str, Any]] = []

        # Batch label
        if 'max_gen' in setting:
            self.limit = setting['max_gen']
            if self.limit > 0:
                self.batch_label.setText(f"{self.limit} generation(s)")
            else:
                self.batch_label.setText('∞')
            self.limit_mode = 'max_gen'
        elif 'min_fit' in setting:
            self.limit = setting['min_fit']
            self.batch_label.setText(f"fitness less then {self.limit}")
            self.limit_mode = 'min_fit'
        elif 'max_time' in setting:
            self.limit = setting['max_time']
            self.batch_label.setText(f"{self.limit // 3600:02d}:"
                                     f"{self.limit % 3600 // 60:02d}:"
                                     f"{self.limit % 3600 % 60:02d}")
            self.limit_mode = 'max_time'
        else:
            self.limit = 0
            self.batch_label.setText('∞')
            self.limit_mode = 'max_gen'
        self.loopTime.setEnabled(self.limit > 0)

        # Timer
        self.time = 0
        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.__set_time)
        self.time_spend = 0.

        # Worker thread
        self.work = WorkerThread(type_num, mech_params, setting, self)
        self.stop_signal.connect(self.work.stop)
        if self.work.is_two_kernel():
            self.fast_kernel_label.hide()
        else:
            self.full_kernel_label.hide()
        self.work.progress_update.connect(self.__set_progress)
        self.work.result.connect(self.__get_result)
        self.work.finished.connect(self.__finish)

    @Slot(int, str)
    def __set_progress(self, progress: int, fitness: str) -> None:
        """Progress bar will always full."""
        value = progress + self.limit * self.work.current_loop
        if self.limit_mode in {'min_fit', 'max_time'} or self.limit == 0:
            self.progress_bar.setMaximum(value)
        self.progress_bar.setValue(value)
        self.fitness_label.setText(fitness)

    @Slot()
    def __set_time(self) -> None:
        """Set time label."""
        self.time += 1
        t_min = self.time % 3600
        self.time_label.setText(f"{self.time // 3600:02d}:"
                                f"{t_min // 60:02d}:"
                                f"{t_min % 60:02d}")

    @Slot(name='on_start_button_clicked')
    def __start(self) -> None:
        """Start the process."""
        loop = self.loopTime.value()
        self.progress_bar.setMaximum(self.limit * loop)
        if self.limit_mode in {'min_fit', 'max_time'} or self.limit == 0:
            # Progress bar will show generations instead of percent.
            self.progress_bar.setFormat("%v generations")
        self.work.set_loop(loop)
        self.timer.start()
        self.work.start()
        self.start_button.setEnabled(False)
        self.loopTime.setEnabled(False)
        self.interrupt_button.setEnabled(True)

    @Slot(dict)
    def __get_result(self, mechanism: Dict[str, Any]) -> None:
        """Get the result."""
        self.mechanisms.append(mechanism)
        self.time_spend += mechanism['time']

    @Slot()
    def __finish(self) -> None:
        """Finish the process."""
        self.timer.stop()
        self.accept()

    @Slot(name='on_interrupt_button_clicked')
    def __interrupt(self) -> None:
        """Interrupt the process."""
        if self.work.isRunning():
            self.stop_signal.emit()
            logger.info("The thread has been interrupted.")

    @Slot()
    def __close_work(self) -> None:
        """Close the thread."""
        if not self.work.isRunning():
            return
        self.stop_signal.emit()
        logger.info("The thread has been canceled.")
        self.work.wait()
Exemple #42
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 #43
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 #44
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 _DownloadAPI(QObject):
    """Download API based on QNetworkAccessManager."""

    def __init__(self, chunk_size=1024, load_rc_func=None):
        """Download API based on QNetworkAccessManager."""
        super(_DownloadAPI, self).__init__()
        self._chunk_size = chunk_size
        self._head_requests = {}
        self._get_requests = {}
        self._paths = {}
        self._workers = {}

        self._load_rc_func = load_rc_func
        self._manager = QNetworkAccessManager(self)
        self._proxy_factory = NetworkProxyFactory(load_rc_func=load_rc_func)
        self._timer = QTimer()

        # Setup
        self._manager.setProxyFactory(self._proxy_factory)
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        # Signals
        self._manager.finished.connect(self._request_finished)
        self._manager.sslErrors.connect(self._handle_ssl_errors)
        self._manager.proxyAuthenticationRequired.connect(
            self._handle_proxy_auth)

    @staticmethod
    def _handle_ssl_errors(reply, errors):
        """Callback for ssl_errors."""
        logger.error(str(('SSL Errors', errors, reply)))

    @staticmethod
    def _handle_proxy_auth(proxy, authenticator):
        """Callback for ssl_errors."""
#        authenticator.setUser('1')`
#        authenticator.setPassword('1')
        logger.error(str(('Proxy authentication Error. '
                          'Enter credentials in condarc',
                          proxy,
                          authenticator)))

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for url in self._workers.copy():
                w = self._workers[url]
                if w.is_finished():
                    self._workers.pop(url)
                    self._paths.pop(url)
                    if url in self._get_requests:
                        self._get_requests.pop(url)

        else:
            self._timer.stop()

    def _request_finished(self, reply):
        """Callback for download once the request has finished."""
        url = to_text_string(reply.url().toEncoded(), encoding='utf-8')

        if url in self._paths:
            path = self._paths[url]
        if url in self._workers:
            worker = self._workers[url]

        if url in self._head_requests:
            error = reply.error()
#            print(url, error)
            if error:
                logger.error(str(('Head Reply Error:', error)))
                worker.sig_download_finished.emit(url, path)
                worker.sig_finished.emit(worker, path, error)
                return

            self._head_requests.pop(url)
            start_download = not bool(error)
            header_pairs = reply.rawHeaderPairs()
            headers = {}

            for hp in header_pairs:
                headers[to_text_string(hp[0]).lower()] = to_text_string(hp[1])

            total_size = int(headers.get('content-length', 0))

            # Check if file exists
            if os.path.isfile(path):
                file_size = os.path.getsize(path)

                # Check if existing file matches size of requested file
                start_download = file_size != total_size

            if start_download:
                # File sizes dont match, hence download file
                qurl = QUrl(url)
                request = QNetworkRequest(qurl)
                self._get_requests[url] = request
                reply = self._manager.get(request)

                error = reply.error()
                if error:
                    logger.error(str(('Reply Error:', error)))

                reply.downloadProgress.connect(
                    lambda r, t, w=worker: self._progress(r, t, w))
            else:
                # File sizes match, dont download file or error?
                worker.finished = True
                worker.sig_download_finished.emit(url, path)
                worker.sig_finished.emit(worker, path, None)
        elif url in self._get_requests:
            data = reply.readAll()
            self._save(url, path, data)

    def _save(self, url, path, data):
        """Save `data` of downloaded `url` in `path`."""
        worker = self._workers[url]
        path = self._paths[url]

        if len(data):
            try:
                with open(path, 'wb') as f:
                    f.write(data)
            except Exception:
                logger.error((url, path))

        # Clean up
        worker.finished = True
        worker.sig_download_finished.emit(url, path)
        worker.sig_finished.emit(worker, path, None)
        self._get_requests.pop(url)
        self._workers.pop(url)
        self._paths.pop(url)

    @staticmethod
    def _progress(bytes_received, bytes_total, worker):
        """Return download progress."""
        worker.sig_download_progress.emit(
            worker.url, worker.path, bytes_received, bytes_total)

    def download(self, url, path):
        """Download url and save data to path."""
        # original_url = url
#        print(url)
        qurl = QUrl(url)
        url = to_text_string(qurl.toEncoded(), encoding='utf-8')

        logger.debug(str((url, path)))
        if url in self._workers:
            while not self._workers[url].finished:
                return self._workers[url]

        worker = DownloadWorker(url, path)

        # Check download folder exists
        folder = os.path.dirname(os.path.abspath(path))
        if not os.path.isdir(folder):
            os.makedirs(folder)

        request = QNetworkRequest(qurl)
        self._head_requests[url] = request
        self._paths[url] = path
        self._workers[url] = worker
        self._manager.head(request)
        self._timer.start()

        return worker

    def terminate(self):
        """Terminate all download workers and threads."""
        pass
class _RequestsDownloadAPI(QObject):
    """Download API based on requests."""

    _sig_download_finished = Signal(str, str)
    _sig_download_progress = Signal(str, str, int, int)

    def __init__(self, load_rc_func=None):
        """Download API based on requests."""
        super(QObject, self).__init__()
        self._conda_api = CondaAPI()
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()

        self._load_rc_func = load_rc_func
        self._chunk_size = 1024
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

    @property
    def proxy_servers(self):
        """Return the proxy servers available from the conda rc config file."""
        if self._load_rc_func is None:
            return {}
        else:
            return self._load_rc_func().get('proxy_servers', {})

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """Start the next threaded worker in the queue."""
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a new worker instance."""
        thread = QThread()
        worker = RequestsDownloadWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        self._sig_download_finished.connect(worker.sig_download_finished)
        self._sig_download_progress.connect(worker.sig_download_progress)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    def _download(self, url, path=None, force=False):
        """Callback for download."""
        if path is None:
            path = url.split('/')[-1]

        # Make dir if non existent
        folder = os.path.dirname(os.path.abspath(path))

        if not os.path.isdir(folder):
            os.makedirs(folder)

        # Start actual download
        try:
            r = requests.get(url, stream=True, proxies=self.proxy_servers)
        except Exception as error:
            print('ERROR', 'here', error)
            logger.error(str(error))
            # Break if error found!
#            self._sig_download_finished.emit(url, path)
#            return path

        total_size = int(r.headers.get('Content-Length', 0))

        # Check if file exists
        if os.path.isfile(path) and not force:
            file_size = os.path.getsize(path)

            # Check if existing file matches size of requested file
            if file_size == total_size:
                self._sig_download_finished.emit(url, path)
                return path

        # File not found or file size did not match. Download file.
        progress_size = 0
        with open(path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=self._chunk_size):
                if chunk:
                    f.write(chunk)
                    progress_size += len(chunk)
                    self._sig_download_progress.emit(url, path,
                                                     progress_size,
                                                     total_size)
            self._sig_download_finished.emit(url, path)

        return path

    def _is_valid_url(self, url):
        """Callback for is_valid_url."""
        try:
            r = requests.head(url, proxies=self.proxy_servers)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_channel(self, channel,
                          conda_url='https://conda.anaconda.org'):
        """Callback for is_valid_channel."""
        if channel.startswith('https://') or channel.startswith('http://'):
            url = channel
        else:
            url = "{0}/{1}".format(conda_url, channel)

        if url[-1] == '/':
            url = url[:-1]

        plat = self._conda_api.get_platform()
        repodata_url = "{0}/{1}/{2}".format(url, plat, 'repodata.json')

        try:
            r = requests.head(repodata_url, proxies=self.proxy_servers)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_api_url(self, url):
        """Callback for is_valid_api_url."""
        # Check response is a JSON with ok: 1
        data = {}
        try:
            r = requests.get(url, proxies=self.proxy_servers)
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
        except Exception as error:
            logger.error(str(error))

        return data.get('ok', 0) == 1

    # --- Public API
    # -------------------------------------------------------------------------
    def download(self, url, path=None, force=False):
        """Download file given by url and save it to path."""
        logger.debug(str((url, path, force)))
        method = self._download
        return self._create_worker(method, url, path=path, force=force)

    def terminate(self):
        """Terminate all workers and threads."""
        for t in self._threads:
            t.quit()
        self._thread = []
        self._workers = []

    def is_valid_url(self, url, non_blocking=True):
        """Check if url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_url(url)

    def is_valid_api_url(self, url, non_blocking=True):
        """Check if anaconda api url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_api_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_api_url(url=url)

    def is_valid_channel(self,
                         channel,
                         conda_url='https://conda.anaconda.org',
                         non_blocking=True):
        """Check if a conda channel is valid."""
        logger.debug(str((channel, conda_url)))
        if non_blocking:
            method = self._is_valid_channel
            return self._create_worker(method, channel, conda_url)
        else:
            return self._is_valid_channel(channel, conda_url=conda_url)

    def get_api_info(self, url):
        """Query anaconda api info."""
        data = {}
        try:
            r = requests.get(url, proxies=self.proxy_servers)
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
            if not data:
                data['api_url'] = url
            if 'conda_url' not in data:
                data['conda_url'] = 'https://conda.anaconda.org'
        except Exception as error:
            logger.error(str(error))

        return data
Exemple #47
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
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
Exemple #49
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 #50
0
class GarbageCollector(QObject):
    '''
    Disable automatic garbage collection and instead collect manually
    on a timer.

    This is done to ensure that garbage collection only happens in the GUI
    thread, as otherwise Qt can crash.

    Parameters
    ==========
    @param interval float: timeout interval in seconds. Default: 1s
    @param debug bool: debug output. Default: False

    Version history:
    - Original:
        Credit:  Erik Janssens
        Source:  http://pydev.blogspot.com/2014/03/should-python-garbage-collector-be.html
    - Modified: pyqtgraph
    - Modified: qudi
    '''

    def __init__(self, interval=1.0, debug=False):
        """
        Initializes garbage collector

        @param interval float: timeout interval in seconds. Default: 1s
        @param debug bool: debug output. Default: False
        """
        super().__init__()
        self.debug = debug
        if debug:
            gc.set_debug(gc.DEBUG_LEAK)

        self.timer = QTimer()
        self.timer.timeout.connect(self.check)

        self.threshold = gc.get_threshold()
        gc.disable()
        self.timer.start(interval * 1000)

    @Slot()
    def check(self):
        """
        Method called by the garbage collector timer to check if there is
        something to collect.
        """
        # return self.debug_cycles() # uncomment to just debug cycles
        l0, l1, l2 = gc.get_count()
        if self.debug:
            logger.debug('gc_check called: {0} {1} {2}'.format(l0, l1, l2))
        if l0 > self.threshold[0]:
            num = gc.collect(0)
            if self.debug:
                logger.debug('collecting gen 0, found: {0:d} unreachable'
                             ''.format(num))
            if l1 > self.threshold[1]:
                num = gc.collect(1)
                if self.debug:
                    logger.debug('collecting gen 1, found: {0:d} unreachable'
                                 ''.format(num))
                if l2 > self.threshold[2]:
                    num = gc.collect(2)
                    if self.debug:
                        logger.debug('collecting gen 2, found: {0:d} '
                                     'unreachable'.format(num))

    def debug_cycles(self):
        """
        Method called in check() to debug cycles. Uncomment the corresponding
        line in the implementation of check().
        """
        gc.collect()
        for obj in gc.garbage:
            print(obj, repr(obj), type(obj))
Exemple #51
0
class QtInteractor(QVTKRenderWindowInteractor, BasePlotter):
    """Extend QVTKRenderWindowInteractor class.

    This adds the methods available to pyvista.Plotter.

    Parameters
    ----------
    parent :
        Qt parent.

    title :
        Title of plotting window.

    multi_samples :
        The number of multi-samples used to mitigate aliasing. 4 is a
        good default but 8 will have better results with a potential
        impact on performance.

    line_smoothing :
        If True, enable line smothing

    point_smoothing :
        If True, enable point smothing

    polygon_smoothing :
        If True, enable polygon smothing

    auto_update :
        Automatic update rate in seconds.  Useful for automatically
        updating the render window when actors are change without
        being automatically ``Modified``.
    """

    # pylint: disable=too-many-instance-attributes
    # pylint: disable=too-many-statements

    # Signals must be class attributes
    render_signal = Signal()
    key_press_event_signal = Signal(vtkGenericRenderWindowInteractor, str)

    # pylint: disable=too-many-arguments
    def __init__(
        self,
        parent: MainWindow = None,
        title: str = None,
        off_screen: bool = None,
        multi_samples: int = None,
        line_smoothing: bool = False,
        point_smoothing: bool = False,
        polygon_smoothing: bool = False,
        auto_update: Union[float, bool] = 5.0,
        **kwargs: Any,
    ) -> None:
        # pylint: disable=too-many-branches
        """Initialize Qt interactor."""
        LOG.debug("QtInteractor init start")
        self.url: QtCore.QUrl = None

        # Cannot use super() here because
        # QVTKRenderWindowInteractor silently swallows all kwargs
        # because they use **kwargs in their constructor...
        qvtk_kwargs = dict(parent=parent)
        for key in ("stereo", "iren", "rw", "wflags"):
            if key in kwargs:
                qvtk_kwargs[key] = kwargs.pop(key)
        with _no_base_plotter_init():
            QVTKRenderWindowInteractor.__init__(self, **qvtk_kwargs)
        BasePlotter.__init__(self, **kwargs)
        # backward compat for when we had this as a separate class
        self.interactor = self

        if multi_samples is None:
            multi_samples = global_theme.multi_samples

        self.setAcceptDrops(True)

        # Create and start the interactive renderer
        self.ren_win = self.GetRenderWindow()
        self.ren_win.SetMultiSamples(multi_samples)
        if line_smoothing:
            self.ren_win.LineSmoothingOn()
        if point_smoothing:
            self.ren_win.PointSmoothingOn()
        if polygon_smoothing:
            self.ren_win.PolygonSmoothingOn()

        for renderer in self.renderers:
            renderer.view_isometric()
            self.ren_win.AddRenderer(renderer)

        self.render_signal.connect(self._render)
        self.key_press_event_signal.connect(super().key_press_event)

        self.background_color = global_theme.background
        if self.title:
            self.setWindowTitle(title)

        if off_screen is None:
            off_screen = pyvista.OFF_SCREEN

        self._setup_interactor(off_screen)

        if off_screen:
            self.ren_win.SetOffScreenRendering(1)
        else:
            self._setup_key_press()

        # Make the render timer but only activate if using auto update
        self.render_timer = QTimer(parent=parent)
        if float(auto_update) > 0.0:  # Can be False as well
            # Spawn a thread that updates the render window.
            # Sometimes directly modifiying object data doesn't trigger
            # Modified() and upstream objects won't be updated.  This
            # ensures the render window stays updated without consuming too
            # many resources.
            twait = int((auto_update ** -1) * 1000.0)
            self.render_timer.timeout.connect(self.render)
            self.render_timer.start(twait)

        if global_theme.depth_peeling["enabled"]:
            if self.enable_depth_peeling():
                for renderer in self.renderers:
                    renderer.enable_depth_peeling()

        self._first_time = False  # Crucial!
        LOG.debug("QtInteractor init stop")

    def _setup_interactor(self, off_screen: bool) -> None:
        if off_screen:
            self.iren: Any = None
        else:
            try:
                # pylint: disable=import-outside-toplevel
                from pyvista.plotting.render_window_interactor import (
                    RenderWindowInteractor,
                )

                self.iren = RenderWindowInteractor(
                    self, interactor=self.ren_win.GetInteractor()
                )
                self.iren.interactor.RemoveObservers(
                    "MouseMoveEvent"
                )  # slows window update?
                self.iren.initialize()
            except ImportError:
                self.iren = self.ren_win.GetInteractor()
                self.iren.RemoveObservers("MouseMoveEvent")  # slows window update?
                self.iren.Initialize()
            self.enable_trackball_style()

    def _setup_key_press(self) -> None:
        try:
            self._observers: Dict[
                None, None
            ] = {}  # Map of events to observers of self.iren
            self.iren.add_observer("KeyPressEvent", self.key_press_event)
        except AttributeError:
            self._add_observer("KeyPressEvent", self.key_press_event)
        self.reset_key_events()

    def gesture_event(self, event: QGestureEvent) -> bool:
        """Handle gesture events."""
        pinch = event.gesture(QtCore.Qt.PinchGesture)
        if pinch:
            self.camera.Zoom(pinch.scaleFactor())
            event.accept()
        return True

    def key_press_event(self, obj: Any, event: Any) -> None:
        """Call `key_press_event` using a signal."""
        self.key_press_event_signal.emit(obj, event)

    @wraps(BasePlotter.render)
    def _render(self, *args: Any, **kwargs: Any) -> BasePlotter.render:
        """Wrap ``BasePlotter.render``."""
        return BasePlotter.render(self, *args, **kwargs)

    @conditional_decorator(threaded, platform.system() == "Darwin")
    def render(self) -> None:
        """Override the ``render`` method to handle threading issues."""
        return self.render_signal.emit()

    @wraps(BasePlotter.enable)
    def enable(self) -> None:
        """Wrap ``BasePlotter.enable``."""
        self.setEnabled(True)
        return BasePlotter.enable(self)

    @wraps(BasePlotter.disable)
    def disable(self) -> None:
        """Wrap ``BasePlotter.disable``."""
        self.setDisabled(True)
        return BasePlotter.disable(self)

    # pylint: disable=invalid-name,no-self-use
    def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None:
        """Event is called when something is dropped onto the vtk window.

        Only triggers event when event contains file paths that
        exist.  User can drop anything in this window and we only want
        to allow files.
        """
        # pragma: no cover
        try:
            for url in event.mimeData().urls():
                if os.path.isfile(url.path()):
                    # only call accept on files
                    event.accept()
        except IOError as exception:  # pragma: no cover
            warnings.warn("Exception when dropping files: %s" % str(exception))

    # pylint: disable=invalid-name,useless-return
    def dropEvent(self, event: QtCore.QEvent) -> None:
        """Event is called after dragEnterEvent."""
        for url in event.mimeData().urls():  # pragma: no cover
            self.url = url
            filename = self.url.path()
            if os.path.isfile(filename):
                try:
                    self.add_mesh(pyvista.read(filename))
                except IOError as exception:
                    print(str(exception))

    def close(self) -> None:
        """Quit application."""
        if self._closed:
            return
        if hasattr(self, "render_timer"):
            self.render_timer.stop()
        BasePlotter.close(self)
        QVTKRenderWindowInteractor.close(self)
Exemple #52
0
class ExternalShellBase(QWidget):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = None
    redirect_stdio = Signal(bool)
    sig_finished = Signal()
    
    def __init__(self, parent=None, fname=None, wdir=None,
                 history_filename=None, show_icontext=True,
                 light_background=True, menu_actions=None,
                 show_buttons_inside=True, show_elapsed_time=True):
        QWidget.__init__(self, parent)
        
        self.menu_actions = menu_actions
        
        self.run_button = None
        self.kill_button = None
        self.options_button = None
        self.icontext_action = None

        self.show_elapsed_time = show_elapsed_time
        
        self.fname = fname
        if wdir is None:
            wdir = osp.dirname(osp.abspath(fname))
        self.wdir = wdir if osp.isdir(wdir) else None
        self.arguments = ""
        
        self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename))
        self.shell.set_light_background(light_background)
        self.shell.execute.connect(self.send_to_process)
        self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt)
        # Redirecting some SIGNALs:
        self.shell.redirect_stdio.connect(
                     lambda state: self.redirect_stdio.emit(state))
        
        self.state_label = None
        self.time_label = None
                
        vlayout = QVBoxLayout()
        toolbar_buttons = self.get_toolbar_buttons()
        if show_buttons_inside:
            self.state_label = QLabel()
            hlayout = QHBoxLayout()
            hlayout.addWidget(self.state_label)
            hlayout.addStretch(0)
            hlayout.addWidget(self.create_time_label())
            hlayout.addStretch(0)
            for button in toolbar_buttons:
                hlayout.addWidget(button)
            vlayout.addLayout(hlayout)
        else:
            vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addWidget(self.get_shell_widget())
        self.setLayout(vlayout)
        self.resize(640, 480)
        if parent is None:
            self.setWindowIcon(self.get_icon())
            self.setWindowTitle(_("Console"))

        self.t0 = None
        self.timer = QTimer(self)

        self.process = None
        
        self.is_closing = False

        if show_buttons_inside:
            self.update_time_label_visibility()

    @Slot(bool)
    def set_elapsed_time_visible(self, state):
        self.show_elapsed_time = state
        if self.time_label is not None:
            self.time_label.setVisible(state)
            
    def create_time_label(self):
        """Create elapsed time label widget (if necessary) and return it"""
        if self.time_label is None:
            self.time_label = QLabel()
        return self.time_label
    
    def update_time_label_visibility(self):
        self.time_label.setVisible(self.show_elapsed_time)
        
    def is_running(self):
        if self.process is not None:
            return self.process.state() == QProcess.Running
        
    def get_toolbar_buttons(self):
        if self.run_button is None:
            self.run_button = create_toolbutton(self, text=_("Run"),
                                             icon=ima.icon('run'),
                                             tip=_("Run again this program"),
                                             triggered=self.start_shell)
        if self.kill_button is None:
            self.kill_button = create_toolbutton(self, text=_("Kill"),
                                     icon=ima.icon('kill'),
                                     tip=_("Kills the current process, "
                                           "causing it to exit immediately"))
        buttons = [self.run_button]
        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(self, text=_('Options'),
                                            icon=ima.icon('tooloptions'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)
        buttons.append(self.kill_button)
        return buttons
            
    def set_icontext_visible(self, state):
        """Set icon text visibility"""
        for widget in self.get_toolbar_buttons():
            if state:
                widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            else:
                widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
    
    def get_options_menu(self):
        self.show_time_action = create_action(self, _("Show elapsed time"),
                                          toggled=self.set_elapsed_time_visible)
        self.show_time_action.setChecked(self.show_elapsed_time)
        actions = [self.show_time_action]
        if self.menu_actions is not None:
            actions += [None]+self.menu_actions
        return actions
    
    def get_shell_widget(self):
        return self.shell
    
    def get_icon(self):
        raise NotImplementedError
        
    def show_time(self, end=False):
        if self.time_label is None:
            return
        elapsed_time = time()-self.t0
        if elapsed_time > 24*3600: # More than a day...!
            format = "%d %H:%M:%S"
        else:
            format = "%H:%M:%S"
        if end:
            color = "#AAAAAA"
        else:
            color = "#AA6655"
        text = "<span style=\'color: %s\'><b>%s" \
               "</b></span>" % (color, strftime(format, gmtime(elapsed_time)))
        self.time_label.setText(text)
        
    def closeEvent(self, event):
        if self.process is not None:
            self.is_closing = True
            self.process.kill()
            self.process.waitForFinished(100)

        try:
            self.timer.timeout.disconnect(self.show_time)
        except (RuntimeError, TypeError):
            pass
    
    def set_running_state(self, state=True):
        self.set_buttons_runnning_state(state)
        self.shell.setReadOnly(not state)
        if state:
            if self.state_label is not None:
                self.state_label.setText(_(
                   "<span style=\'color: #44AA44\'><b>Running...</b></span>"))
            self.t0 = time()
            self.timer.timeout.connect(self.show_time)
            self.timer.start(1000)        
        else:
            if self.state_label is not None:
                self.state_label.setText(_('Terminated.'))
            try:
                self.timer.timeout.disconnect(self.show_time)
            except (RuntimeError, TypeError):
                pass

    def set_buttons_runnning_state(self, state):
        self.run_button.setVisible(not state and not self.is_ipykernel)
        self.kill_button.setVisible(state)

    @Slot(bool)
    def start_shell(self, ask_for_arguments=False):
        """Start shell"""
        if ask_for_arguments and not self.get_arguments():
            self.set_running_state(False)
            return
        try:
            self.terminate_button.clicked.disconnect(self.process.terminate)
            self.kill_button.clicked.disconnect(self.process.terminate)
        except (AttributeError, RuntimeError, TypeError):
            pass
        self.create_process()

    @Slot()
    def get_arguments(self):
        arguments, valid = QInputDialog.getText(self, _('Arguments'),
                                                _('Command line arguments:'),
                                                QLineEdit.Normal,
                                                self.arguments)
        if valid:
            self.arguments = to_text_string(arguments)
        return valid
    
    def create_process(self):
        raise NotImplementedError
    
    def finished(self, exit_code, exit_status):
        self.shell.flush()
        self.sig_finished.emit()
        if self.is_closing:
            return
        self.set_running_state(False)
        self.show_time(end=True)
    
#===============================================================================
#    Input/Output
#===============================================================================
    def transcode(self, qba):
        try:
            return to_text_string(qba.data(), 'utf8')
        except UnicodeDecodeError:
            return qba.data()
    
    def get_stdout(self):
        self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            qba += self.process.readAllStandardOutput()
        return self.transcode(qba)
    
    def get_stderr(self):
        self.process.setReadChannel(QProcess.StandardError)
        qba = QByteArray()
        while self.process.bytesAvailable():
            qba += self.process.readAllStandardError()
        return self.transcode(qba)

    def write_output(self):
        self.shell.write(self.get_stdout(), flush=True)
        # Commenting the line below improves crashes on long
        # output. See Issue 2251
        # QApplication.processEvents()

    def send_to_process(self, qstr):
        raise NotImplementedError

    def send_ctrl_to_process(self, letter):
        char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1)
        byte_array = QByteArray()
        byte_array.append(char)
        self.process.write(byte_array)
        self.process.waitForBytesWritten(-1)
        self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True)

    def keyboard_interrupt(self):
        raise NotImplementedError
Exemple #53
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 #54
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 #55
0
class Restarter(QWidget):
    """Widget in charge of displaying the splash information screen and the
       error messages.
    """
    def __init__(self):
        super(Restarter, self).__init__()
        self.ellipsis = ['', '.', '..', '...', '..', '.']

        # Widgets
        self.timer_ellipsis = QTimer(self)
        self.splash = QSplashScreen(
            QPixmap(get_image_path('splash.svg'), 'svg'))

        # Widget setup
        self.setVisible(False)

        font = self.splash.font()
        font.setPixelSize(10)
        self.splash.setFont(font)
        self.splash.show()

        self.timer_ellipsis.timeout.connect(self.animate_ellipsis)

    def _show_message(self, text):
        """Show message on splash screen."""
        self.splash.showMessage(
            text, int(Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute),
            QColor(Qt.white))

    def animate_ellipsis(self):
        """Animate dots at the end of the splash screen message."""
        ellipsis = self.ellipsis.pop(0)
        text = ' ' * len(ellipsis) + self.splash_text + ellipsis
        self.ellipsis.append(ellipsis)
        self._show_message(text)

    def set_splash_message(self, text):
        """Sets the text in the bottom of the Splash screen."""
        self.splash_text = text
        self._show_message(text)
        self.timer_ellipsis.start(500)

    def launch_error_message(self, error_type, error=None):
        """Launch a message box with a predefined error message.

        Parameters
        ----------
        error_type : int [CLOSE_ERROR, RESET_ERROR, RESTART_ERROR]
            Possible error codes when restarting/reseting spyder.
        error : Exception
            Actual Python exception error caught.
        """
        messages = {
            CLOSE_ERROR:
            _("It was not possible to close the previous "
              "Spyder instance.\nRestart aborted."),
            RESET_ERROR:
            _("Spyder could not reset to factory "
              "defaults.\nRestart aborted."),
            RESTART_ERROR:
            _("It was not possible to restart Spyder.\n"
              "Operation aborted.")
        }
        titles = {
            CLOSE_ERROR: _("Spyder exit error"),
            RESET_ERROR: _("Spyder reset error"),
            RESTART_ERROR: _("Spyder restart error")
        }

        if error:
            e = error.__repr__()
            message = messages[error_type] + "\n\n{0}".format(e)
        else:
            message = messages[error_type]

        title = titles[error_type]
        self.splash.hide()
        QMessageBox.warning(self, title, message, QMessageBox.Ok)
        raise RuntimeError(message)
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                logger.error('{0}:\nSTDERR:\n{1}\nEND'.format(
                        ' '.join(self._cmd_list), stderr))
            elif stderr.strip() and self._pip:
                logger.error("pip error: {}".format(self._cmd_list))
        result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except Exception as error:
                result = stdout, str(error)

            if 'error' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
class _ProjectAPIWrapper(QObject):
    """Project API based on conda-kapsel."""

    MAX_THREADS = 1

    def __init__(self):
        """Project API based on anaconda-project."""
        super(QObject, self).__init__()
        self._project_api = AnacondaProject()
        self._queue = deque()
        self._queue_workers = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._timer_worker_delete = QTimer()
        self._running_threads = 0
        self._bag_collector = deque()  # Keeps references to old workers

        self._chunk_size = 1024
        self._timer.setInterval(333)
        self._timer.timeout.connect(self._start)
        self._timer_worker_delete.setInterval(5000)
        self._timer_worker_delete.timeout.connect(self._clean_workers)

    def _clean_workers(self):
        """Delete periodically workers in workers bag."""
        while self._bag_collector:
            self._bag_collector.popleft()
        self._timer_worker_delete.stop()

    def _start(self):
        """Start threads and check for inactive workers."""
        if self._queue_workers and self._running_threads < self.MAX_THREADS:
            # print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            thread = QThread()
            worker = self._queue_workers.popleft()
            worker.moveToThread(thread)
            worker.sig_finished.connect(thread.quit)
            thread.started.connect(worker.start)
            thread.start()
            self._threads.append(thread)

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a new worker instance."""
        worker = ProjectWorker(method, args, kwargs)
        self._workers.append(worker)
        self._queue_workers.append(worker)
        self._timer.start()
        return worker

    def create_project(self,
                       path,
                       make_directory=False,
                       name=None,
                       icon=None,
                       description=None):
        """Initialize project given by path."""
        logger.debug(str((path)))
        method = self._project_api.create_project
        return self._create_worker(
            method,
            path,
            make_directory=make_directory,
            name=name,
            icon=icon,
            description=description,
        )

    def load_project(self, path):
        """Load project given by path."""
        logger.debug(str((path)))
        return self._project_api.load_project(path)

    def upload(self,
               project,
               site=None,
               username=None,
               token=None,
               log_level=None):
        """Upload project to repo."""
        logger.debug(str((project, site, username)))
        method = self._project_api.upload
        return self._create_worker(method,
                                   project,
                                   site=None,
                                   username=None,
                                   token=None,
                                   log_level=None)
Exemple #58
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 #59
0
class HalPlot(QWidget, HALWidget):
    """HAL Plot

    Plot for displaying HAL pin values.

    Input pin type is HAL type ( float).

    Up to four HAL pin values can be plotted

    .. table:: Generated HAL Pins

        ========================= =========== =========
        HAL Pin Name              Type        Direction
        ========================= =========== =========
        qtpyvcp.seriesXname.in     float          in
        ========================= =========== =========
    """

    def __init__(self, parent=None):
        super(HalPlot, self).__init__(parent)

        # HAL sampling frequency parameters
        self._frequency = 1       # Hz
        self._timeWindow = 600      # seconds

        # Internal timestamp for x-axis - data values are ms from when "timestamp" was started
        self.timestamp = QTime()
        self.timestamp.start()

        self._legend = False

        self._yAxisLabel = 'y label'
        self._yAxisUnits = 'y units'
        self._minY = 0
        self._maxY = 1

        self._s1enable = True
        self._s1name = "Series 1"
        self._s1colour = QColor('red')
        self._s1width = 1
        self._s1style = Qt.SolidLine
        self._s1_pin = None

        self._s2enable = False
        self._s2name = "Series 2"
        self._s2colour = QColor('blue')
        self._s2width = 1
        self._s2style = Qt.SolidLine
        self._s2_pin = None

        self._s3enable = False
        self._s3name = "Series 3"
        self._s3colour = QColor('green')
        self._s3width = 1
        self._s3style = Qt.SolidLine
        self._s3_pin = None

        self._s4enable = False
        self._s4name = "Series 4"
        self._s4colour = QColor('yellow')
        self._s4width = 1
        self._s4style = Qt.SolidLine
        self._s4_pin = None

        # PyQtGraph stuff
        self.graph = pg.GraphicsLayoutWidget()

        self.yAxis = pg.AxisItem(orientation='left')
        self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits)
        self.yAxis.setGrid(125)

        self.plot = self.graph.addPlot(axisItems={'bottom': TimeAxisItem(orientation='bottom'), 'left': self.yAxis})
        self.plot.setYRange(self._minY, self._maxY, padding=0.0)

        self.legend = self.plot.addLegend()

        self.p1 = pg.PlotCurveItem(name=self._s1name)
        self.p2 = pg.PlotCurveItem(name=self._s2name)
        self.p3 = pg.PlotCurveItem(name=self._s3name)
        self.p4 = pg.PlotCurveItem(name=self._s4name)

        self.setSeries()
        self.setData()

        self.Vlayout = QVBoxLayout(self)
        self.Vlayout.addWidget(self.graph)

        # HAL stuff
        self._typ = "float"
        self._fmt = "%s"

        if IN_DESIGNER:
            return

        # QTimer
        self.updatetimer = QTimer(self)
        self.updatetimer.timeout.connect(self.updateplot)
        self.updatetimer.start(self._refreshRate)

    def setSeries(self):
        # first remove the legend as it does not update correnctly
        try:
            self.legend.scene().removeItem(self.legend)
        except:
            pass

        # remove all plot items
        self.plot.clear()

        # add the legend and plot itmes
        if self._legend:
            self.legend = self.plot.addLegend()

        if self._s1enable:
            self.p1 = pg.PlotCurveItem(name=self._s1name)
            self.plot.addItem(self.p1)
            self.p1.setPen(QColor(self._s1colour), width=self._s1width, style=self._s1style)

        if self._s2enable:
            self.p2 = pg.PlotCurveItem(name=self._s2name)
            self.plot.addItem(self.p2)
            self.p2.setPen(QColor(self._s2colour), width=self._s2width, style=self._s2style)

        if self._s3enable:
            self.p3 = pg.PlotCurveItem(name=self._s3name)
            self.plot.addItem(self.p3)
            self.p3.setPen(QColor(self._s3colour), width=self._s3width, style=self._s3style)

        if self._s4enable:
            self.p4 = pg.PlotCurveItem(name=self._s4name)
            self.plot.addItem(self.p4)
            self.p4.setPen(QColor(self._s4colour), width=self._s4width, style=self._s4style)

    def setData(self):
        # Data stuff
        self._period = 1.0/self._frequency
        self._refreshRate = int(self._period * 1000)  # sample period in milliseconds
        self._timeWindowMS = self._timeWindow * 1000      # time window in milliseconds
        self._bufsize = int(self._timeWindowMS / self._refreshRate)

        # Data containers: collections.deque is list-like container with fast appends and pops on either end
        self.x = np.linspace(-self.timeWindow, 0.0, self._bufsize)
        self.now = self.timestamp.elapsed()
        self.x_data = deque(np.linspace(self.now-self._timeWindowMS, self.now, self._bufsize),self._bufsize)

        self.s1 = np.zeros(self._bufsize, dtype=np.float)
        self.s1_data = deque([0.0] * self._bufsize, self._bufsize)

        self.s2 = np.zeros(self._bufsize, dtype=np.float)
        self.s2_data = deque([0.0] * self._bufsize, self._bufsize)

        self.s3 = np.zeros(self._bufsize, dtype=np.float)
        self.s3_data = deque([0.0] * self._bufsize, self._bufsize)

        self.s4 = np.zeros(self._bufsize, dtype=np.float)
        self.s4_data = deque([0.0] * self._bufsize, self._bufsize)

    def updateplot(self):
        self.x_data.append(self.timestamp.elapsed())
        self.x[:] = self.x_data

        if self._s1enable:
            self.s1_data.append(self._s1_pin.value)
            self.s1[:] = self.s1_data
            self.p1.setData(self.x, self.s1)

        if self._s2enable:
            self.s2_data.append(self._s2_pin.value)
            self.s2[:] = self.s2_data
            self.p2.setData(self.x, self.s2)

        if self._s3enable:
            self.s3_data.append(self._s3_pin.value)
            self.s3[:] = self.s3_data
            self.p3.setData(self.x, self.s3)

        if self._s4enable:
            self.s4_data.append(self._s4_pin.value)
            self.s4[:] = self.s4_data
            self.p4.setData(self.x, self.s4)

    def setyAxis(self):
        self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits)

    def setYRange(self):
        self.plot.setYRange(self._minY, self._maxY, padding = 0.0)



    @Property(int)
    def frequency(self):
        return self._frequency

    @frequency.setter
    def frequency(self, frequency):
        self._frequency = frequency
        return self.setData()

    @Property(int)
    def timeWindow(self):
        return self._timeWindow

    @timeWindow.setter
    def timeWindow(self, timeWindow):
        self._timeWindow = timeWindow
        return self.setData()

    @Property(str)
    def yAxisLabel(self):
        return self._yAxisLabel

    @yAxisLabel.setter
    def yAxisLabel(self, yAxisLabel):
        self._yAxisLabel = yAxisLabel
        return self.setyAxis()

    @Property(str)
    def yAxisUnits(self):
        return self._yAxisUnits

    @yAxisUnits.setter
    def yAxisUnits(self, yAxisUnits):
        self._yAxisUnits = yAxisUnits
        return self.setyAxis()

    @Property(float)
    def minYRange(self):
        return self._minY

    @minYRange.setter
    def minYRange(self, minY):
        self._minY = minY
        return self.setYRange()

    @Property(float)
    def maxYRange(self):
        return self._maxY

    @maxYRange.setter
    def maxYRange(self, maxY):
        self._maxY = maxY
        return self.setYRange()


    # Legend propterties
    @Property(bool)
    def legendenable(self):
        return self._legend

    @legendenable.setter
    def legendenable(self, legendenable):
        self._legend = legendenable
        self.setSeries()


    # Series 1 properties
    @Property(bool)
    def series1enable(self):
        return self._s1enable

    @series1enable.setter
    def series1enable(self, series1enable):
        self._s1enable = series1enable
        self.setSeries()

    @Property(str)
    def series1name(self):
        return self._s1name

    @series1name.setter
    def series1name(self, series1name):
        self._s1name = series1name
        self.setSeries()

    @Property(QColor)
    def series1colour(self):
        return self._s1colour

    @series1colour.setter
    def series1colour(self, series1colour):
        self._s1colour = series1colour
        self.setSeries()

    @Property(int)
    def series1width(self):
        return self._s1width

    @series1width.setter
    def series1width(self, series1width):
        self._s1width = series1width
        self.setSeries()

    @Property(Qt.PenStyle)
    def series1style(self):
        return self._s1style

    @series1style.setter
    def series1style(self, series1style):
        self._s1style = series1style
        self.setSeries()


    # Series 2 properties
    @Property(bool)
    def series2enable(self):
        return self._s2enable

    @series2enable.setter
    def series2enable(self, series2enable):
        self._s2enable = series2enable
        self.setSeries()

    @Property(str)
    def series2name(self):
        return self._s2name

    @series2name.setter
    def series2name(self, series2name):
        self._s2name = series2name
        self.setSeries()

    @Property(QColor)
    def series2colour(self):
        return self._s2colour

    @series2colour.setter
    def series2colour(self, series2colour):
        self._s2colour = series2colour
        self.setSeries()

    @Property(int)
    def series2width(self):
        return self._s2width

    @series2width.setter
    def series2width(self, series2width):
        self._s2width = series2width
        self.setSeries()

    @Property(Qt.PenStyle)
    def series2style(self):
        return self._s2style

    @series2style.setter
    def series2style(self, series2style):
        self._s2style = series2style
        self.setSeries()


    # Series 3 properties
    @Property(bool)
    def series3enable(self):
        return self._s3enable

    @series3enable.setter
    def series3enable(self, series3enable):
        self._s3enable = series3enable
        self.setSeries()

    @Property(str)
    def series3name(self):
        return self._s3name

    @series3name.setter
    def series3name(self, series3name):
        self._s3name = series3name
        self.setSeries()

    @Property(QColor)
    def series3colour(self):
        return self._s3colour

    @series3colour.setter
    def series3colour(self, series3colour):
        self._s3colour = series3colour
        self.setSeries()

    @Property(int)
    def series3width(self):
        return self._s3width

    @series3width.setter
    def series3width(self, series3width):
        self._s3width = series3width
        self.setSeries()

    @Property(Qt.PenStyle)
    def series3style(self):
        return self._s3style

    @series3style.setter
    def series3style(self, series3style):
        self._s3style = series3style
        self.setSeries()


    # Series 4 properties
    @Property(bool)
    def series4enable(self):
        return self._s4enable

    @series4enable.setter
    def series4enable(self, series4enable):
        self._s4enable = series4enable
        self.setSeries()

    @Property(str)
    def series4name(self):
        return self._s4name

    @series4name.setter
    def series4name(self, series4name):
        self._s4name = series4name
        self.setSeries()

    @Property(QColor)
    def series4colour(self):
        return self._s4colour

    @series4colour.setter
    def series4colour(self, series4colour):
        self._s4colour = series4colour
        self.setSeries()

    @Property(int)
    def series4width(self):
        return self._s4width

    @series4width.setter
    def series4width(self, series4width):
        self._s4width = series4width
        self.setSeries()

    @Property(Qt.PenStyle)
    def series4style(self):
        return self._s4style

    @series4style.setter
    def series4style(self, series4style):
        self._s4style = series4style
        self.setSeries()

    def initialize(self):
        comp = hal.getComponent()
        obj_name = self.getPinBaseName()

        # add HAL pins
        if self._s1enable:
            self._s1_pin = comp.addPin(obj_name + "." + self._s1name.replace(' ', ''), self._typ, "in")

        if self._s2enable:
            self._s2_pin = comp.addPin(obj_name + "." + self._s2name.replace(' ', ''), self._typ, "in")

        if self._s3enable:
            self._s3_pin = comp.addPin(obj_name + "." + self._s3name.replace(' ', ''), self._typ, "in")

        if self._s4enable:
            self._s4_pin = comp.addPin(obj_name + "." + self._s4name.replace(' ', ''), self._typ, "in")
Exemple #60
0
class AsyncClient(QObject):

    """
    A class which handles a connection to a client through a QProcess.
    """

    # Emitted when the client has initialized.
    initialized = Signal()

    # Emitted when the client errors.
    errored = Signal()

    # Emitted when a request response is received.
    received = Signal(object)

    def __init__(self, target, executable=None, name=None,
                 extra_args=None, libs=None, cwd=None, env=None):
        super(AsyncClient, self).__init__()
        self.executable = executable or sys.executable
        self.extra_args = extra_args
        self.target = target
        self.name = name or self
        self.libs = libs
        self.cwd = cwd
        self.env = env
        self.is_initialized = False
        self.closing = False
        self.context = zmq.Context()
        QApplication.instance().aboutToQuit.connect(self.close)

        # Set up the heartbeat timer.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._heartbeat)

    def run(self):
        """Handle the connection with the server.
        """
        # Set up the zmq port.
        self.socket = self.context.socket(zmq.PAIR)
        self.port = self.socket.bind_to_random_port('tcp://*')

        # Set up the process.
        self.process = QProcess(self)
        if self.cwd:
            self.process.setWorkingDirectory(self.cwd)
        p_args = ['-u', self.target, str(self.port)]
        if self.extra_args is not None:
            p_args += self.extra_args

        # Set up environment variables.
        processEnvironment = QProcessEnvironment()
        env = self.process.systemEnvironment()
        if (self.env and 'PYTHONPATH' not in self.env) or DEV:
            python_path = osp.dirname(get_module_path('spyderlib'))
            # Add the libs to the python path.
            for lib in self.libs:
                try:
                    path = osp.dirname(imp.find_module(lib)[1])
                    python_path = osp.pathsep.join([python_path, path])
                except ImportError:
                    pass
            env.append("PYTHONPATH=%s" % python_path)
        if self.env:
            env.update(self.env)
        for envItem in env:
            envName, separator, envValue = envItem.partition('=')
            processEnvironment.insert(envName, envValue)
        self.process.setProcessEnvironment(processEnvironment)

        # Start the process and wait for started.
        self.process.start(self.executable, p_args)
        self.process.finished.connect(self._on_finished)
        running = self.process.waitForStarted()
        if not running:
            raise IOError('Could not start %s' % self)

        # Set up the socket notifer.
        fid = self.socket.getsockopt(zmq.FD)
        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
        self.notifier.activated.connect(self._on_msg_received)

    def request(self, func_name, *args, **kwargs):
        """Send a request to the server.

        The response will be a dictionary the 'request_id' and the
        'func_name' as well as a 'result' field with the object returned by
        the function call or or an 'error' field with a traceback.
        """
        if not self.is_initialized:
            return
        request_id = uuid.uuid4().hex
        request = dict(func_name=func_name,
                       args=args,
                       kwargs=kwargs,
                       request_id=request_id)
        self._send(request)
        return request_id

    def close(self):
        """Cleanly close the connection to the server.
        """
        self.closing = True
        self.is_initialized = False
        self.timer.stop()
        self.notifier.activated.disconnect(self._on_msg_received)
        self.notifier.setEnabled(False)
        del self.notifier
        self.request('server_quit')
        self.process.waitForFinished(1000)
        self.process.close()
        self.context.destroy()

    def _on_finished(self):
        """Handle a finished signal from the process.
        """
        if self.closing:
            return
        if self.is_initialized:
            debug_print('Restarting %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.is_initialized = False
            self.notifier.setEnabled(False)
            self.run()
        else:
            debug_print('Errored %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.errored.emit()

    def _on_msg_received(self):
        """Handle a message trigger from the socket.
        """
        self.notifier.setEnabled(False)
        while 1:
            try:
                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)
            except zmq.ZMQError:
                self.notifier.setEnabled(True)
                return
            if not self.is_initialized:
                self.is_initialized = True
                debug_print('Initialized %s' % self.name)
                self.initialized.emit()
                self.timer.start(HEARTBEAT)
                continue
            resp['name'] = self.name
            self.received.emit(resp)

    def _heartbeat(self):
        """Send a heartbeat to keep the server alive.
        """
        self._send(dict(func_name='server_heartbeat'))

    def _send(self, obj):
        """Send an object to the server.
        """
        try:
            self.socket.send_pyobj(obj)
        except Exception as e:
            debug_print(e)
            self.is_initialized = False
            self._on_finished()