示例#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
示例#2
0
def test_calltip(main_window, qtbot):
    """Hide the calltip in the editor when a matching ')' is found."""
    # Load test file
    text = 'a = [1,2,3]\n(max'
    main_window.editor.new(fname="test.py", text=text)
    code_editor = main_window.editor.get_focus_widget()

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

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

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

    QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
    main_window.editor.close_file()
示例#3
0
文件: plugin.py 项目: burrbull/spyder
 def free_memory(self):
     """Free memory signal."""
     self.main.free_memory()
     QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER,
                       lambda: self.main.free_memory())
     QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
                       lambda: self.main.free_memory())
示例#4
0
 def blink_figure(self):
     """Blink figure once."""
     if self.fig:
         self._blink_flag = not self._blink_flag
         self.repaint()
         if self._blink_flag:
             timer = QTimer()
             timer.singleShot(40, self.blink_figure)
示例#5
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()
示例#6
0
    def set_font_size(self, old, new):
        current_font = self.app.font()
        current_font.setPointSizeF(current_font.pointSizeF()/old*new)
        QApplication.instance().setFont(current_font)

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

        QTimer.singleShot(0, self.resizeForNewDisplayWidget)
示例#7
0
def test_sort_dataframe_with_category_dtypes(qtbot):  # cf. issue 5361
    df = DataFrame({'A': [1, 2, 3, 4],
                    'B': ['a', 'b', 'c', 'd']})
    df = df.astype(dtype={'B': 'category'})
    df_cols = df.dtypes
    editor = DataFrameEditor(None)
    editor.setup_and_check(df_cols)
    dfm = editor.dataModel
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    editor.dataModel.sort(0)
    assert data(dfm, 0, 0) == 'int64'
    assert data(dfm, 1, 0) == 'category'
示例#8
0
文件: mapper.py 项目: a-stark/qudi
    def revert(self):
        """
        Takes the data stored in the models and displays them in the widgets.
        """
        # make sure it is called from main thread
        if (not QThread.currentThread() == QCoreApplication.instance(
        ).thread()):
            QTimer.singleShot(0, self.revert)
            return

        for key in self._mappings:
            self._on_model_notification(key)
示例#9
0
def environ_dialog(qtbot):
    "Setup the Environment variables Dialog taking into account the os."
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    if os.name == 'nt':
        from spyder.utils.environ import WinUserEnvDialog
        dialog = WinUserEnvDialog()
    else:        
        from spyder.utils.environ import EnvDialog
        dialog = EnvDialog()
    qtbot.addWidget(dialog)
    
    return dialog
示例#10
0
    def focusOutEvent(self, event):
        """Handle focus out event restoring the last valid selected path."""
        # Calling asynchronously the 'add_current_text' to avoid crash
        # https://groups.google.com/group/spyderlib/browse_thread/thread/2257abf530e210bd
        if not self.is_valid():
            lineedit = self.lineEdit()
            QTimer.singleShot(50, lambda: lineedit.setText(self.selected_text))

        hide_status = getattr(self.lineEdit(), 'hide_status_icon', None)
        if hide_status:
            hide_status()
        QComboBox.focusOutEvent(self, event)
示例#11
0
 def start(self):
     """
     Start this tester.
     """
     self.timer = QTimer()
     # Connecting self.idle to a QTimer with 0 time delay
     # makes it called when there are no events to process
     self.timer.timeout.connect(self._idle)
     self.timer.start()
     # This calls __call__() method
     QTimer.singleShot(0, self)
     # Start the event loop
     self.app.exec_()
示例#12
0
def test_sort_dataframe_with_duplicate_column(qtbot):
    df = DataFrame({'A': [1, 3, 2], 'B': [4, 6, 5]})
    df = concat((df, df.A), axis=1)
    editor = DataFrameEditor(None)
    editor.setup_and_check(df)
    dfm = editor.dataModel
    QTimer.singleShot(1000, lambda: close_message_box(qtbot))
    editor.dataModel.sort(0)
    assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '3', '2']
    assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '6', '5']
    editor.dataModel.sort(1)
    assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '2', '3']
    assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '5', '6']
示例#13
0
 def set_display_widget(self, new_widget):
     if new_widget == self._display_widget:
         return
     self.clear_display_widget()
     if not new_widget.layout():
         new_widget.setMinimumSize(new_widget.size())
     self._new_widget_size = new_widget.size()
     self._display_widget = new_widget
     self.setCentralWidget(self._display_widget)
     self.update_window_title()
     # Resizing to the new widget's dimensions needs to be
     # done on the event loop for some reason - you can't
     # just do it here.
     QTimer.singleShot(0, self.resizeForNewDisplayWidget)
示例#14
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)
示例#15
0
 def follow_directories_loaded(self, fname):
     """Follow directories loaded during startup"""
     if self._to_be_loaded is None:
         return
     path = osp.normpath(to_text_string(fname))
     if path in self._to_be_loaded:
         self._to_be_loaded.remove(path)
     if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 \
       and not is_pyqt46:
         self.fsmodel.directoryLoaded.disconnect(
                                     self.follow_directories_loaded)
         if self._scrollbar_positions is not None:
             # The tree view need some time to render branches:
             QTimer.singleShot(50, self.restore_scrollbar_positions)
示例#16
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_()
示例#17
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
示例#18
0
    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

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

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)
示例#19
0
文件: status.py 项目: 0xBADCA7/spyder
    def __init__(self, parent, statusbar):
        """Status bar widget base for widgets that update based on timers."""
        super(BaseTimerStatus, self).__init__(parent, statusbar)

        # Widgets
        self.label = QLabel(self.TITLE)
        self.value = QLabel()

        # Widget setup
        self.setToolTip(self.TIP)
        self.value.setAlignment(Qt.AlignRight)
        self.value.setFont(self.label_font)
        fm = self.value.fontMetrics()
        self.value.setMinimumWidth(fm.width('000%'))

        # Layout
        layout = self.layout()
        layout.addWidget(self.label)
        layout.addWidget(self.value)
        layout.addSpacing(20)

        # Setup
        if self.is_supported():
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_label)
            self.timer.start(2000)
        else:
            self.timer = None
            self.hide()
示例#20
0
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
示例#21
0
    def __init__(self, channel, pv, protocol=None, parent=None):
        """
        Instantiate Pv object and set up the channel access connections.

        :param channel: :class:`PyDMChannel` object as the first listener.
        :type channel:  :class:`PyDMChannel`
        :param pv: Name of the pv to connect to.
        :type pv:  str
        :param parent: PyQt widget that this widget is inside of.
        :type parent:  QWidget
        """
        super(Connection, self).__init__(channel, pv, protocol, parent)
        self.python_type = None
        self.pv = setup_pv(pv,
                           con_cb=self.connected_cb,
                           mon_cb=self.monitor_cb,
                           rwaccess_cb=self.rwaccess_cb)
        self.enums = None
        self.sevr = None
        self.ctrl_llim = None
        self.ctrl_hlim = None
        self.units = None
        self.prec = None
        self.count = None
        self.epics_type = None

        # Auxilliary info to help with throttling
        self.scan_pv = setup_pv(pv + ".SCAN", mon_cb=self.scan_pv_cb,
                                mon_cb_once=True)
        self.throttle = QTimer(self)
        self.throttle.timeout.connect(self.throttle_cb)

        self.add_listener(channel)
示例#22
0
 def __init__(self, connections=[], parent=None):
     super(ConnectionTableModel, self).__init__(parent=parent)
     self._column_names = ("protocol", "address", "connected")
     self.update_timer = QTimer(self)
     self.update_timer.setInterval(1000)
     self.update_timer.timeout.connect(self.update_values)
     self.connections = connections
示例#23
0
文件: status.py 项目: impact27/spyder
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())
示例#24
0
文件: mapper.py 项目: a-stark/qudi
    def submit(self):
        """
        Submits the current values stored in the widgets to the models.
        """
        # make sure it is called from main thread
        if (not QThread.currentThread() == QCoreApplication.instance(
        ).thread()):
            QTimer.singleShot(0, self.submit)
            return

        submit_policy = self._submit_policy
        self.submit_policy = SUBMIT_POLICY_AUTO
        try:
            for key in self._mappings:
                self._on_widget_property_notification(key)
        finally:
            self.submit_policy = submit_policy
示例#25
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())
示例#26
0
文件: tasks.py 项目: fiath/test
 def start_master(self):
     """Start the master thread, used to retrieve the results."""
     if self.use_master_thread:
         self._thread_master = _start_thread(self._retrieve)
     else:
         self._timer_master = QTimer(self)
         self._timer_master.setInterval(int(TIMER_MASTER_DELAY * 1000))
         self._timer_master.timeout.connect(self._retrieve)
         self._timer_master.start()
示例#27
0
 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
示例#28
0
def test_load_kernel_file_from_id(ipyconsole, qtbot):
    """
    Test that a new client is created using its id
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

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

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

    new_client = ipyconsole.get_clients()[1]
    assert new_client.id_ == dict(int_id='1', str_id='B')
示例#29
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()
示例#30
0
def test_restart_kernel(ipyconsole, qtbot):
    """
    Test that kernel is restarted correctly
    """
    shell = ipyconsole.get_current_shellwidget()
    client = ipyconsole.get_current_client()
    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

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

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

    assert not shell.is_defined('a')
示例#31
0
class ScrollFlagArea(Panel):
    """Source code editor's scroll flag area"""
    WIDTH = 24 if sys.platform == 'darwin' else 12
    FLAGS_DX = 4
    FLAGS_DY = 2

    def __init__(self):
        Panel.__init__(self)
        self.setAttribute(Qt.WA_OpaquePaintEvent)
        self.scrollable = True
        self.setMouseTracking(True)

        # Define some attributes to be used for unit testing.
        self._unit_testing = False
        self._range_indicator_is_visible = False
        self._alt_key_is_down = False

        self._slider_range_color = QColor(Qt.gray)
        self._slider_range_color.setAlphaF(.85)
        self._slider_range_brush = QColor(Qt.gray)
        self._slider_range_brush.setAlphaF(.5)

        self._update_list_timer = QTimer(self)
        self._update_list_timer.setSingleShot(True)
        self._update_list_timer.timeout.connect(self.update_flags)

        # Dictionary with flag lists
        self._dict_flag_list = {}

    def on_install(self, editor):
        """Manages install setup of the pane."""
        super().on_install(editor)
        # Define permanent Qt colors that are needed for painting the flags
        # and the slider range.
        self._facecolors = {
            'warning': QColor(editor.warning_color),
            'error': QColor(editor.error_color),
            'todo': QColor(editor.todo_color),
            'breakpoint': QColor(editor.breakpoint_color),
            'occurrence': QColor(editor.occurrence_color),
            'found_results': QColor(editor.found_results_color)
        }
        self._edgecolors = {
            key: color.darker(120)
            for key, color in self._facecolors.items()
        }

        # Signals
        editor.sig_focus_changed.connect(self.update)
        editor.sig_key_pressed.connect(self.keyPressEvent)
        editor.sig_key_released.connect(self.keyReleaseEvent)
        editor.sig_alt_left_mouse_pressed.connect(self.mousePressEvent)
        editor.sig_alt_mouse_moved.connect(self.mouseMoveEvent)
        editor.sig_leave_out.connect(self.update)
        editor.sig_flags_changed.connect(self.delayed_update_flags)
        editor.sig_theme_colors_changed.connect(self.update_flag_colors)

    @property
    def slider(self):
        """This property holds whether the vertical scrollbar is visible."""
        return self.editor.verticalScrollBar().isVisible()

    def sizeHint(self):
        """Override Qt method"""
        return QSize(self.WIDTH, 0)

    def update_flag_colors(self, color_dict):
        """
        Update the permanent Qt colors that are used for painting the flags
        and the slider range with the new colors defined in the given dict.
        """
        for name, color in color_dict.items():
            self._facecolors[name] = QColor(color)
            self._edgecolors[name] = self._facecolors[name].darker(120)

    def delayed_update_flags(self):
        """
        This function is called every time a flag is changed.
        There is no need of updating the flags thousands of time by second,
        as it is quite resources-heavy. This limits the calls to REFRESH_RATE.
        """
        if self._update_list_timer.isActive():
            return

        self._update_list_timer.start(REFRESH_RATE)

    def update_flags(self):
        """
        Update flags list.

        This parses the entire file, which can take a lot of time for
        large files. Save all the flags in lists for painting during
        paint events.
        """
        self._dict_flag_list = {
            'error': [],
            'warning': [],
            'todo': [],
            'breakpoint': [],
        }

        editor = self.editor
        block = editor.document().firstBlock()
        while block.isValid():
            # Parse all lines in the file looking for something to flag.
            data = block.userData()
            if data:
                if data.code_analysis:
                    # Paint the errors and warnings
                    for _, _, severity, _ in data.code_analysis:
                        if severity == DiagnosticSeverity.ERROR:
                            flag_type = 'error'
                            break
                    else:
                        flag_type = 'warning'
                elif data.todo:
                    flag_type = 'todo'
                elif data.breakpoint:
                    flag_type = 'breakpoint'
                else:
                    flag_type = None

                if flag_type is not None:
                    self._dict_flag_list[flag_type].append(block.blockNumber())

            block = block.next()

        self.update()

    def paintEvent(self, event):
        """
        Override Qt method.
        Painting the scroll flag area

        There is two cases:
            - The scroll bar is moving, in which case paint all flags.
            - The scroll bar is not moving, only paint flags corresponding
              to visible lines.
        """
        # The area in which the slider handle of the scrollbar may move.
        groove_rect = self.get_scrollbar_groove_rect()
        # The scrollbar's scale factor ratio between pixel span height and
        # value span height
        scale_factor = groove_rect.height() / self.get_scrollbar_value_height()
        # The vertical offset of the scroll flag area relative to the
        # top of the text editor.
        offset = groove_rect.y()

        # Note that we calculate the pixel metrics required to draw the flags
        # here instead of using the convenience methods of the ScrollFlagArea
        # for performance reason.
        rect_x = ceil(self.FLAGS_DX / 2)
        rect_w = self.WIDTH - self.FLAGS_DX
        rect_h = self.FLAGS_DY

        # Fill the whole painting area
        painter = QPainter(self)
        painter.fillRect(event.rect(), self.editor.sideareas_color)

        editor = self.editor

        # Define compute_flag_ypos to position the flags:
        # Paint flags for the entire document
        last_line = editor.document().lastBlock().firstLineNumber()
        # The 0.5 offset is used to align the flags with the center of
        # their corresponding text edit block before scaling.
        first_y_pos = self.value_to_position(0.5, scale_factor,
                                             offset) - self.FLAGS_DY / 2
        last_y_pos = self.value_to_position(last_line + 0.5, scale_factor,
                                            offset) - self.FLAGS_DY / 2

        def compute_flag_ypos(block):
            line_number = block.firstLineNumber()
            if editor.verticalScrollBar().maximum() == 0:
                geometry = editor.blockBoundingGeometry(block)
                pos = geometry.y() + geometry.height() / 2 + self.FLAGS_DY / 2
            elif last_line != 0:
                frac = line_number / last_line
                pos = first_y_pos + frac * (last_y_pos - first_y_pos)
            else:
                pos = first_y_pos
            return ceil(pos)

        # All the lists of block numbers for flags
        dict_flag_lists = {
            "occurrence": editor.occurrences,
            "found_results": editor.found_results
        }
        dict_flag_lists.update(self._dict_flag_list)

        for flag_type in dict_flag_lists:
            painter.setBrush(self._facecolors[flag_type])
            painter.setPen(self._edgecolors[flag_type])
            for block_number in dict_flag_lists[flag_type]:
                # Find the block
                block = editor.document().findBlockByNumber(block_number)
                if not block.isValid():
                    continue
                # paint if everything else is fine
                rect_y = compute_flag_ypos(block)
                painter.drawRect(rect_x, rect_y, rect_w, rect_h)

        # Paint the slider range
        if not self._unit_testing:
            alt = QApplication.queryKeyboardModifiers() & Qt.AltModifier
        else:
            alt = self._alt_key_is_down

        if self.slider:
            cursor_pos = self.mapFromGlobal(QCursor().pos())
            is_over_self = self.rect().contains(cursor_pos)
            is_over_editor = editor.rect().contains(
                editor.mapFromGlobal(QCursor().pos()))
            # We use QRect.contains instead of QWidget.underMouse method to
            # determined if the cursor is over the editor or the flag scrollbar
            # because the later gives a wrong result when a mouse button
            # is pressed.
            if is_over_self or (alt and is_over_editor):
                painter.setPen(self._slider_range_color)
                painter.setBrush(self._slider_range_brush)
                x, y, width, height = self.make_slider_range(
                    cursor_pos, scale_factor, offset, groove_rect)
                painter.drawRect(x, y, width, height)
                self._range_indicator_is_visible = True
            else:
                self._range_indicator_is_visible = False

    def enterEvent(self, event):
        """Override Qt method"""
        self.update()

    def leaveEvent(self, event):
        """Override Qt method"""
        self.update()

    def mouseMoveEvent(self, event):
        """Override Qt method"""
        self.update()

    def mousePressEvent(self, event):
        """Override Qt method"""
        if self.slider and event.button() == Qt.LeftButton:
            vsb = self.editor.verticalScrollBar()
            value = self.position_to_value(event.pos().y())
            vsb.setValue(int(value - vsb.pageStep() / 2))

    def keyReleaseEvent(self, event):
        """Override Qt method."""
        if event.key() == Qt.Key_Alt:
            self._alt_key_is_down = False
            self.update()

    def keyPressEvent(self, event):
        """Override Qt method"""
        if event.key() == Qt.Key_Alt:
            self._alt_key_is_down = True
            self.update()

    def get_vertical_offset(self):
        """
        Return the vertical offset of the scroll flag area relative to the
        top of the text editor.
        """
        groove_rect = self.get_scrollbar_groove_rect()
        return groove_rect.y()

    def get_slider_min_height(self):
        """
        Return the minimum height of the slider range based on that set for
        the scroll bar's slider.
        """
        return QApplication.instance().style().pixelMetric(
            QStyle.PM_ScrollBarSliderMin)

    def get_scrollbar_groove_rect(self):
        """Return the area in which the slider handle may move."""
        vsb = self.editor.verticalScrollBar()
        style = QApplication.instance().style()
        opt = QStyleOptionSlider()
        vsb.initStyleOption(opt)

        # Get the area in which the slider handle may move.
        groove_rect = style.subControlRect(QStyle.CC_ScrollBar, opt,
                                           QStyle.SC_ScrollBarGroove, self)

        return groove_rect

    def get_scrollbar_position_height(self):
        """Return the pixel span height of the scrollbar area in which
        the slider handle may move"""
        groove_rect = self.get_scrollbar_groove_rect()
        return float(groove_rect.height())

    def get_scrollbar_value_height(self):
        """Return the value span height of the scrollbar"""
        vsb = self.editor.verticalScrollBar()
        return vsb.maximum() - vsb.minimum() + vsb.pageStep()

    def get_scale_factor(self):
        """Return scrollbar's scale factor:
        ratio between pixel span height and value span height"""
        return (self.get_scrollbar_position_height() /
                self.get_scrollbar_value_height())

    def value_to_position(self, y, scale_factor, offset):
        """Convert value to position in pixels"""
        vsb = self.editor.verticalScrollBar()
        return int((y - vsb.minimum()) * scale_factor + offset)

    def position_to_value(self, y):
        """Convert position in pixels to value"""
        vsb = self.editor.verticalScrollBar()
        offset = self.get_vertical_offset()
        return vsb.minimum() + max([0, (y - offset) / self.get_scale_factor()])

    def make_slider_range(self, cursor_pos, scale_factor, offset, groove_rect):
        """
        Return the slider x and y positions and the slider width and height.
        """
        # The slider range indicator position follows the mouse vertical
        # position while its height corresponds to the part of the file that
        # is currently visible on screen.

        vsb = self.editor.verticalScrollBar()
        slider_height = self.value_to_position(vsb.pageStep(), scale_factor,
                                               offset) - offset
        slider_height = max(slider_height, self.get_slider_min_height())

        # Calculate the minimum and maximum y-value to constraint the slider
        # range indicator position to the height span of the scrollbar area
        # where the slider may move.
        min_ypos = offset
        max_ypos = groove_rect.height() + offset - slider_height

        # Determine the bounded y-position of the slider rect.
        slider_y = max(min_ypos,
                       min(max_ypos, ceil(cursor_pos.y() - slider_height / 2)))

        return 1, slider_y, self.WIDTH - 2, slider_height

    def wheelEvent(self, event):
        """Override Qt method"""
        self.editor.wheelEvent(event)

    def set_enabled(self, state):
        """Toggle scroll flag area visibility"""
        self.enabled = state
        self.setVisible(state)
class CondaDependenciesModel(QAbstractTableModel):
    """ """
    def __init__(self, parent, dic, packages_sizes=None):
        super(CondaDependenciesModel, self).__init__(parent)
        self._parent = parent
        self._packages = dic
        self._packages_sizes = packages_sizes
        self._rows = []
        self._bold_rows = []
        self._timer = QTimer()
        self._timer.timeout.connect(self._timer_update)
        self._timer_dots = ['.  ', '.. ', '...', '   ']
        self._timer_counter = 0

        if len(dic) == 0:
            self._timer.start(650)
            self._rows = [[_(u'Resolving dependencies     '), u'', u'', u'']]
            self._bold_rows.append(0)
        else:
            if 'actions' in dic:
                dic = dic['actions']

            titles = [_('Name'), _('Unlink'), _('Link'), _('Download')]
            order = ['UNLINK', 'LINK', 'FETCH']
            packages_dic = self._build_packages_table(dic)
            total_size = packages_dic.pop('TOTAL##', '')
            packages_order = sorted(list(packages_dic))

            rows = [titles]
            self._bold_rows.append(0)
            for package in packages_order:
                row = [package]
                item = packages_dic[package]
                for section in order:
                    row.append(item.get(section, u'-'))
                rows.append(row)

            if total_size:
                rows.append([u'', u'', u'', u'TOTAL'])
                rows.append([u'', u'', u'', total_size])
                self._bold_rows.append(len(rows) - 2)
                self._bold_rows.append(len(rows) - 1)

            for row in rows:
                self._rows.append(row)

    def _timer_update(self):
        """Add some moving points to the dependency resolution text."""
        self._timer_counter += 1
        dot = self._timer_dots.pop(0)
        self._timer_dots = self._timer_dots + [dot]
        self._rows = [[_(u'Resolving dependencies') + dot, u'', u'', u'']]
        index = self.createIndex(0, 0)
        self.dataChanged.emit(index, index)

        if self._timer_counter > 150:
            self._timer.stop()
            self._timer_counter = 0

    def _build_packages_table(self, dic):
        """ """
        self._timer.stop()
        sections = {'FETCH': None,
                    'EXTRACT': None,
                    'LINK': None,
                    'UNLINK': None}
        packages = {}

        for section in sections:
            sections[section] = dic.get(section, ())
            if sections[section]:
                for item in sections[section]:
                    i = item.split(' ')[0]
                    name, version, build = split_canonical_name(i)
                    packages[name] = {}

        for section in sections:
            pkgs = sections[section]
            for item in pkgs:
                i = item.split(' ')[0]
                name, version, build = split_canonical_name(i)
                packages[name][section] = version

        total = 0
        for pkg in packages:
            val = packages[pkg]
            if u'FETCH' in val:
                v = val['FETCH']
                if pkg in self._packages_sizes:
                    size = self._packages_sizes[pkg].get(v, '-')
                    if size != '-':
                        total += size
                        size = human_bytes(size)
                    packages[pkg]['FETCH'] = size
        packages['TOTAL##'] = human_bytes(total)

        return packages

    def flags(self, index):
        """Override Qt method"""
        if not index.isValid():
            return Qt.ItemIsEnabled
        column = index.column()
        if column in [0, 1, 2, 3]:
            return Qt.ItemFlags(Qt.ItemIsEnabled)
        else:
            return Qt.ItemFlags(Qt.NoItemFlags)

    def data(self, index, role=Qt.DisplayRole):
        """Override Qt method"""
        if not index.isValid() or not 0 <= index.row() < len(self._rows):
            return to_qvariant()
        row = index.row()
        column = index.column()

        # Carefull here with the order, this has to be adjusted manually
        if self._rows[row] == row:
            name, unlink, link, fetch = [u'', u'', u'', u'']
        else:
            name, unlink, link, fetch = self._rows[row]

        if role == Qt.DisplayRole:
            if column == 0:
                return to_qvariant(name)
            elif column == 1:
                return to_qvariant(unlink)
            elif column == 2:
                return to_qvariant(link)
            elif column == 3:
                return to_qvariant(fetch)
        elif role == Qt.TextAlignmentRole:
            if column in [0]:
                return to_qvariant(int(Qt.AlignLeft | Qt.AlignVCenter))
            elif column in [1, 2, 3]:
                return to_qvariant(int(Qt.AlignHCenter | Qt.AlignVCenter))
        elif role == Qt.ForegroundRole:
            return to_qvariant()
        elif role == Qt.FontRole:
            font = QFont()
            if row in self._bold_rows:
                font.setBold(True)
                return to_qvariant(font)
            else:
                font.setBold(False)
                return to_qvariant(font)
        return to_qvariant()

    def rowCount(self, index=QModelIndex()):
        """Override Qt method."""
        return len(self._rows)

    def columnCount(self, index=QModelIndex()):
        """Override Qt method."""
        return 4

    def row(self, row_index):
        """Get a row by row index."""
        return self._rows[row_index]
示例#33
0
class PyDMTimePlot(BasePlot):
    """
    PyDMWaveformPlot is a widget to plot one or more waveforms.

    Each curve can plot either a Y-axis waveform vs. its indices,
    or a Y-axis waveform against an X-axis waveform.

    Parameters
    ----------
    parent : optional
        The parent of this widget.
    init_y_channels : list
        A list of scalar channels to plot vs time.
    plot_by_timestamps : bool
        If True, the x-axis shows timestamps as ticks, and those timestamps
        scroll to the left as time progresses.  If False, the x-axis tick marks
        show time relative to the current time.
    background: optional
        The background color for the plot.  Accepts any arguments that
        pyqtgraph.mkColor will accept.
    """
    SynchronousMode = 1
    AsynchronousMode = 2

    plot_redrawn_signal = Signal(TimePlotCurveItem)

    def __init__(self,
                 parent=None,
                 init_y_channels=[],
                 plot_by_timestamps=True,
                 background='default'):
        """
        Parameters
        ----------

        parent : Widget
            The parent widget of the chart.
        init_y_channels : list
            A list of scalar channels to plot vs time.
        plot_by_timestamps : bool
            If True, the x-axis shows timestamps as ticks, and those timestamps
            scroll to the left as time progresses.  If False, the x-axis tick
            marks show time relative to the current time.
        background : str, optional
            The background color for the plot.  Accepts any arguments that
            pyqtgraph.mkColor will accept.
        """
        self._plot_by_timestamps = plot_by_timestamps

        self._left_axis = AxisItem("left")
        if plot_by_timestamps:
            self._bottom_axis = TimeAxisItem('bottom')
        else:
            self.starting_epoch_time = time.time()
            self._bottom_axis = AxisItem('bottom')

        super(PyDMTimePlot, self).__init__(parent=parent,
                                           background=background,
                                           axisItems={
                                               "bottom": self._bottom_axis,
                                               "left": self._left_axis
                                           })

        # Removing the downsampling while PR 763 is not merged at pyqtgraph
        # Reference: https://github.com/pyqtgraph/pyqtgraph/pull/763
        # self.setDownsampling(ds=True, auto=True, mode="mean")

        if self._plot_by_timestamps:
            self.plotItem.disableAutoRange(ViewBox.XAxis)
            self.getViewBox().setMouseEnabled(x=False)
        else:
            self.plotItem.setRange(xRange=[DEFAULT_X_MIN, 0], padding=0)
            self.plotItem.setLimits(xMax=0)

        self._bufferSize = DEFAULT_BUFFER_SIZE

        self._time_span = DEFAULT_TIME_SPAN  # This is in seconds
        self._update_interval = DEFAULT_UPDATE_INTERVAL

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(self._update_interval)
        self._update_mode = PyDMTimePlot.SynchronousMode
        self._needs_redraw = True

        self.labels = {"left": None, "right": None, "bottom": None}

        self.units = {"left": None, "right": None, "bottom": None}

        for channel in init_y_channels:
            self.addYChannel(channel)

    def initialize_for_designer(self):
        # If we are in Qt Designer, don't update the plot continuously.
        # This function gets called by PyDMTimePlot's designer plugin.
        self.redraw_timer.setSingleShot(True)

    def addYChannel(self,
                    y_channel=None,
                    name=None,
                    color=None,
                    lineStyle=None,
                    lineWidth=None,
                    symbol=None,
                    symbolSize=None):
        """
        Adds a new curve to the current plot

        Parameters
        ----------
        y_channel : str
            The PV address
        name : str
            The name of the curve (usually made the same as the PV address)
        color : QColor
            The color for the curve
        lineStyle : str
            The line style of the curve, i.e. solid, dash, dot, etc.
        lineWidth : int
            How thick the curve line should be
        symbol : str
            The symbols as markers along the curve, i.e. circle, square,
            triangle, star, etc.
        symbolSize : int
            How big the symbols should be

        Returns
        -------
        new_curve : TimePlotCurveItem
            The newly created curve.
        """
        plot_opts = dict()
        plot_opts['symbol'] = symbol
        if symbolSize is not None:
            plot_opts['symbolSize'] = symbolSize
        if lineStyle is not None:
            plot_opts['lineStyle'] = lineStyle
        if lineWidth is not None:
            plot_opts['lineWidth'] = lineWidth

        # Add curve
        new_curve = TimePlotCurveItem(
            y_channel,
            plot_by_timestamps=self._plot_by_timestamps,
            name=name,
            color=color,
            **plot_opts)
        new_curve.setUpdatesAsynchronously(self.updatesAsynchronously)
        new_curve.setBufferSize(self._bufferSize)

        self.update_timer.timeout.connect(new_curve.asyncUpdate)
        self.addCurve(new_curve, curve_color=color)

        new_curve.data_changed.connect(self.set_needs_redraw)
        self.redraw_timer.start()

        return new_curve

    def removeYChannel(self, curve):
        """
        Remove a curve from the graph. This also stops update the timer
        associated with the curve.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be removed.
        """
        self.update_timer.timeout.disconnect(curve.asyncUpdate)
        self.removeCurve(curve)
        if len(self._curves) < 1:
            self.redraw_timer.stop()

    def removeYChannelAtIndex(self, index):
        """
        Remove a curve from the graph, given its index in the graph's curve
        list.

        Parameters
        ----------
        index : int
            The curve's index from the graph's curve list.
        """
        curve = self._curves[index]
        self.removeYChannel(curve)

    @Slot()
    def set_needs_redraw(self):
        self._needs_redraw = True

    @Slot()
    def redrawPlot(self):
        """
        Redraw the graph
        """
        if not self._needs_redraw:
            return

        self.updateXAxis()

        for curve in self._curves:
            curve.redrawCurve()
            self.plot_redrawn_signal.emit(curve)
        self._needs_redraw = False

    def updateXAxis(self, update_immediately=False):
        """
        Update the x-axis for every graph redraw.

        Parameters
        ----------
        update_immediately : bool
            Update the axis range(s) immediately if True, or defer until the
            next rendering.
        """
        if len(self._curves) == 0:
            return

        if self._plot_by_timestamps:
            if self._update_mode == PyDMTimePlot.SynchronousMode:
                maxrange = max([curve.max_x() for curve in self._curves])
            else:
                maxrange = time.time()
            minrange = maxrange - self._time_span
            self.plotItem.setXRange(minrange,
                                    maxrange,
                                    padding=0.0,
                                    update=update_immediately)
        else:
            diff_time = self.starting_epoch_time - max(
                [curve.max_x() for curve in self._curves])
            if diff_time > DEFAULT_X_MIN:
                diff_time = DEFAULT_X_MIN
            self.getViewBox().setLimits(minXRange=diff_time)

    def clearCurves(self):
        """
        Remove all curves from the graph.
        """
        super(PyDMTimePlot, self).clear()

    def getCurves(self):
        """
        Dump the current list of curves and each curve's settings into a list
        of JSON-formatted strings.

        Returns
        -------
        settings : list
            A list of JSON-formatted strings, each containing a curve's
            settings
        """
        return [json.dumps(curve.to_dict()) for curve in self._curves]

    def setCurves(self, new_list):
        """
        Add a list of curves into the graph.

        Parameters
        ----------
        new_list : list
            A list of JSON-formatted strings, each contains a curve and its
            settings
        """
        try:
            new_list = [json.loads(str(i)) for i in new_list]
        except ValueError as e:
            logger.exception("Error parsing curve json data: {}".format(e))
            return
        self.clearCurves()
        for d in new_list:
            color = d.get('color')
            if color:
                color = QColor(color)
            self.addYChannel(d['channel'],
                             name=d.get('name'),
                             color=color,
                             lineStyle=d.get('lineStyle'),
                             lineWidth=d.get('lineWidth'),
                             symbol=d.get('symbol'),
                             symbolSize=d.get('symbolSize'))

    curves = Property("QStringList", getCurves, setCurves, designable=False)

    def findCurve(self, pv_name):
        """
        Find a curve from a graph's curve list.

        Parameters
        ----------
        pv_name : str
            The curve's PV address.

        Returns
        -------
        curve : TimePlotCurveItem
            The found curve, or None.
        """
        for curve in self._curves:
            if curve.address == pv_name:
                return curve

    def refreshCurve(self, curve):
        """
        Remove a curve currently being plotted on the timeplot, then redraw
        that curve, which could have been updated with a new symbol, line
        style, line width, etc.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be re-added.
        """
        curve = self.findCurve(curve.channel)
        if curve:
            self.removeYChannel(curve)
            self.addYChannel(y_channel=curve.address,
                             color=curve.color,
                             name=curve.address,
                             lineStyle=curve.lineStyle,
                             lineWidth=curve.lineWidth,
                             symbol=curve.symbol,
                             symbolSize=curve.symbolSize)

    def addLegendItem(self, item, pv_name, force_show_legend=False):
        """
        Add an item into the graph's legend.

        Parameters
        ----------
        item : TimePlotCurveItem
            A curve being plotted in the graph
        pv_name : str
            The PV channel
        force_show_legend : bool
            True to make the legend to be displayed; False to just add the
            item, but do not display the legend.
        """
        self._legend.addItem(item, pv_name)
        self.setShowLegend(force_show_legend)

    def removeLegendItem(self, pv_name):
        """
        Remove an item from the legend.

        Parameters
        ----------
        pv_name : str
            The PV channel, used to search for the legend item to remove.
        """
        self._legend.removeItem(pv_name)
        if len(self._legend.items) == 0:
            self.setShowLegend(False)

    def getBufferSize(self):
        """
        Get the size of the data buffer for the entire chart.

        Returns
        -------
        size : int
            The chart's data buffer size.
        """
        return int(self._bufferSize)

    def setBufferSize(self, value):
        """
        Set the size of the data buffer of the entire chart. This will also
        update the same value for each of the data buffer of each chart's
        curve.

        Parameters
        ----------
        value : int
            The new buffer size for the chart.
        """
        if self._bufferSize != int(value):
            # Originally, the bufferSize is the max between the user's input and 1, and 1 doesn't make sense.
            # So, I'm comparing the user's input with the minimum buffer size, and pick the max between the two
            self._bufferSize = max(int(value), MINIMUM_BUFFER_SIZE)
            for curve in self._curves:
                curve.setBufferSize(value)

    def resetBufferSize(self):
        """
        Reset the data buffer size of the chart, and each of the chart's
        curve's data buffer, to the minimum
        """
        if self._bufferSize != DEFAULT_BUFFER_SIZE:
            self._bufferSize = DEFAULT_BUFFER_SIZE
            for curve in self._curves:
                curve.resetBufferSize()

    bufferSize = Property("int", getBufferSize, setBufferSize, resetBufferSize)

    def getUpdatesAsynchronously(self):
        return self._update_mode == PyDMTimePlot.AsynchronousMode

    def setUpdatesAsynchronously(self, value):
        for curve in self._curves:
            curve.setUpdatesAsynchronously(value)
        if value is True:
            self._update_mode = PyDMTimePlot.AsynchronousMode
            self.update_timer.start()
        else:
            self._update_mode = PyDMTimePlot.SynchronousMode
            self.update_timer.stop()

    def resetUpdatesAsynchronously(self):
        self._update_mode = PyDMTimePlot.SynchronousMode
        self.update_timer.stop()
        for curve in self._curves:
            curve.resetUpdatesAsynchronously()

    updatesAsynchronously = Property("bool", getUpdatesAsynchronously,
                                     setUpdatesAsynchronously,
                                     resetUpdatesAsynchronously)

    def getTimeSpan(self):
        """
        The extent of the x-axis of the chart, in seconds.  In other words,
        how long a data point stays on the plot before falling off the left
        edge.

        Returns
        -------
        time_span : float
            The extent of the x-axis of the chart, in seconds.
        """
        return float(self._time_span)

    def setTimeSpan(self, value):
        """
        Set the extent of the x-axis of the chart, in seconds.
        In aynchronous mode, the chart will allocate enough buffer for the new time span duration.
        Data arriving after each duration will be recorded into the buffer
        having been rotated.

        Parameters
        ----------
        value : float
            The time span duration, in seconds, to allocate enough buffer to
            collect data for, before rotating the buffer.
        """
        value = float(value)
        if self._time_span != value:
            self._time_span = value

            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

            self.updateXAxis(update_immediately=True)

    def resetTimeSpan(self):
        """
        Reset the timespan to the default value.
        """
        if self._time_span != DEFAULT_TIME_SPAN:
            self._time_span = DEFAULT_TIME_SPAN
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))
            self.updateXAxis(update_immediately=True)

    timeSpan = Property(float, getTimeSpan, setTimeSpan, resetTimeSpan)

    def getUpdateInterval(self):
        """
        Get the update interval for the chart.

        Returns
        -------
        interval : float
            The update interval of the chart.
        """
        return float(self._update_interval) / 1000.0

    def setUpdateInterval(self, value):
        """
        Set a new update interval for the chart and update its data buffer size.

        Parameters
        ----------
        value : float
            The new update interval value.
        """
        value = abs(int(1000.0 * value))
        if self._update_interval != value:
            self._update_interval = value
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

    def resetUpdateInterval(self):
        """
        Reset the chart's update interval to the default.
        """
        if self._update_interval != DEFAULT_UPDATE_INTERVAL:
            self._update_interval = DEFAULT_UPDATE_INTERVAL
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

    updateInterval = Property(float, getUpdateInterval, setUpdateInterval,
                              resetUpdateInterval)

    def getAutoRangeX(self):
        if self._plot_by_timestamps:
            return False
        else:
            super(PyDMTimePlot, self).getAutoRangeX()

    def setAutoRangeX(self, value):
        if self._plot_by_timestamps:
            self._auto_range_x = False
            self.plotItem.enableAutoRange(ViewBox.XAxis,
                                          enable=self._auto_range_x)
        else:
            super(PyDMTimePlot, self).setAutoRangeX(value)

    def channels(self):
        return [curve.channel for curve in self._curves]

    # The methods for autoRangeY, minYRange, and maxYRange are
    # all defined in BasePlot, but we don't expose them as properties there, because not all plot
    # subclasses necessarily want them to be user-configurable in Designer.
    autoRangeY = Property(bool,
                          BasePlot.getAutoRangeY,
                          BasePlot.setAutoRangeY,
                          BasePlot.resetAutoRangeY,
                          doc="""
    Whether or not the Y-axis automatically rescales to fit the data.
    If true, the values in minYRange and maxYRange are ignored.
    """)

    minYRange = Property(float,
                         BasePlot.getMinYRange,
                         BasePlot.setMinYRange,
                         doc="""
    Minimum Y-axis value visible on the plot.""")

    maxYRange = Property(float,
                         BasePlot.getMaxYRange,
                         BasePlot.setMaxYRange,
                         doc="""
    Maximum Y-axis value visible on the plot.""")

    def enableCrosshair(self,
                        is_enabled,
                        starting_x_pos=DEFAULT_X_MIN,
                        starting_y_pos=DEFAULT_Y_MIN,
                        vertical_angle=90,
                        horizontal_angle=0,
                        vertical_movable=False,
                        horizontal_movable=False):
        """
        Display a crosshair on the graph.

        Parameters
        ----------
        is_enabled : bool
            True is to display the crosshair; False is to hide it.
        starting_x_pos : float
            The x position where the vertical line will cross
        starting_y_pos : float
            The y position where the horizontal line will cross
        vertical_angle : int
            The angle of the vertical line
        horizontal_angle : int
            The angle of the horizontal line
        vertical_movable : bool
            True if the user can move the vertical line; False if not
        horizontal_movable : bool
            True if the user can move the horizontal line; False if not
        """
        super(PyDMTimePlot,
              self).enableCrosshair(is_enabled, starting_x_pos, starting_y_pos,
                                    vertical_angle, horizontal_angle,
                                    vertical_movable, horizontal_movable)
示例#34
0
 def __alive_loopback(self):
     alive = self.is_alive()
     if not alive:
         self.terminal_closed.emit()
     else:
         QTimer.singleShot(250, self.__alive_loopback)
示例#35
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)
示例#36
0
    def __init__(self,
                 parent=None,
                 opts=None,
                 ui_file=None,
                 stylesheet=None,
                 maximize=False,
                 fullscreen=False,
                 position=None,
                 size=None,
                 confirm_exit=True,
                 title=None,
                 menu='default'):

        super(VCPMainWindow, self).__init__(parent)

        if opts is None:
            opts = qtpyvcp.OPTIONS

        self.setWindowTitle(title)

        self.app = QApplication.instance()
        self.status = getPlugin('status')

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

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

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

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

            self.setMenuBar(self.buildMenuBar(menu))

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

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

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

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

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

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

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

        QShortcut(QKeySequence("F11"), self, self.toggleFullscreen)
        self.app.focusChanged.connect(self.focusChangedEvent)
示例#37
0
class FilterWidget(filterBase):
    __clsName = "LocFilter"
    filterChangeDelay = 200

    varNameRex = re.compile(r"\{(\w*)\}")

    def tr(self, string):
        return QCoreApplication.translate(self.__clsName, string)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._ui = filterClass()
        self._ui.setupUi(self)

        self._delayTimer = QTimer(self)
        self._delayTimer.setInterval(self.filterChangeDelay)
        self._delayTimer.setSingleShot(True)
        if not (qtpy.PYQT4 or qtpy.PYSIDE):
            self._delayTimer.setTimerType(Qt.PreciseTimer)
        self._delayTimer.timeout.connect(self.filterChanged)

        self._ui.filterEdit.textChanged.connect(self._delayTimer.start)

        self._menu = QMenu()
        self._menu.triggered.connect(self._addVariable)

    filterChanged = Signal()

    @Slot(list)
    def setVariables(self, var):
        self._menu.clear()
        for v in var:
            self._menu.addAction(v)

    def setFilterString(self, filt):
        self._ui.filterEdit.setPlainText(filt)

    @Property(str, fset=setFilterString, doc="String describing the filter")
    def filterString(self):
        s = self._ui.filterEdit.toPlainText()
        return self.varNameRex.subn("\\1", s)[0]

    def getFilter(self):
        filterStr = self.filterString
        filterStrList = filterStr.split("\n")

        def filterFunc(data):
            filter = np.ones(len(data), dtype=bool)
            for f in filterStrList:
                with suppress(Exception):
                    filter &= data.eval(f, local_dict={}, global_dict={})
            return filter

        return filterFunc

    @Slot(QAction)
    def _addVariable(self, act):
        self._ui.filterEdit.textCursor().insertText(act.text())

    @Slot(str)
    def on_showVarLabel_linkActivated(self, link):
        if not self._menu.isEmpty():
            self._menu.exec(QCursor.pos())
示例#38
0
class PluginManager(QObject):

    introspection_complete = Signal(object)

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

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

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

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

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

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

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

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

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

    def _handle_timeout(self):
        self.waiting = False
        if self.pending:
            self._finalize(self.pending)
        else:
            debug_print('No valid responses acquired')
示例#39
0
class AddDataTableMixin(object):
    """ Mixin providing validation and type casting for a table for adding cycle data.
    
        Should be used as part of a widget that has a `table` attribute. If it
        also has a `okButton`, this will be enabled/diabled when validation 
        is performed.
    """
    
    invalid = Signal(int, int)
    """ **signal** invalid(int `row`, int `col`)
    
        Emitted if the data in cell `row`,`col` is invalid.
    """
    
    def __init__(self, *args, emptyDateValid=True, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.headerLabels = ['Date', 'Time', 'Distance (km)', 'Calories', 'Gear']
        self.headerLabelColumnOffset = 0
        self.table = QTableWidget(0, len(self.headerLabels))
        self.table.setHorizontalHeaderLabels(self.headerLabels)
        self.table.verticalHeader().setVisible(False)
        
        # dict of methods to validate and cast types for input data in each column
        isDatePartial = partial(isDate, allowEmpty=emptyDateValid)
        validateMethods = [isDatePartial, isDuration, isFloat, 
                           isFloat, isInt]
        parseDatePartial = partial(parseDate, pd_timestamp=True)
        castMethods = [parseDatePartial, parseDuration, float, float, int]
        self.mthds = {name:{'validate':validateMethods[i], 'cast':castMethods[i]}
                      for i, name in enumerate(self.headerLabels)}
        
        self.validateTimer = QTimer()
        self.validateTimer.setInterval(100)
        self.validateTimer.setSingleShot(True)
        self.validateTimer.timeout.connect(self._validate)
        self.table.cellChanged.connect(self.validateTimer.start)
        
        self.invalid.connect(self._invalid)
        
    @property
    def defaultBrush(self):
        # made this a property rather than setting in constructor as the mixin
        # doesn't have a `table` attribute when __init__ is called
        return self.table.item(0,0).background()
    
    @property
    def invalidBrush(self):
        return QBrush(QColor("#910404"))
    
    @Slot(int, int)
    def _invalid(self, row, col):
        """ Set the background of cell `row`,`col` to the `invalidBrush` and 
            disable the 'Ok' button.
        """
        self.table.item(row, col).setBackground(self.invalidBrush)
        if hasattr(self, "okButton"):
            self.okButton.setEnabled(False)
        
    @Slot()
    def _validate(self):
        """ Validate all data in the table. 
        
            If data is valid and the widget has an `okButton`, it will be enabled.
        """
        # it would be more efficient to only validate a single cell, after its
        # text has been changed, but this table is unlikely to ever be more 
        # than a few rows long, so this isn't too inefficient
        allValid = True
        for row in range(self.table.rowCount()):
            for col, name in enumerate(self.headerLabels):
                col += self.headerLabelColumnOffset
                item = self.table.item(row, col)
                value = item.text()
                mthd = self.mthds[name]['validate']
                valid = mthd(value)
                if not valid:
                    if hasattr(self, "_clicked"):
                        if (row, col) not in self._clicked:
                            continue
                    self.invalid.emit(row, col)
                    allValid = False
                elif valid and self.table.item(row, col).background() == self.invalidBrush:
                    self.table.item(row, col).setBackground(self.defaultBrush) 
                    
        if allValid and hasattr(self, "okButton"):
            self.okButton.setEnabled(True)
            
        return allValid
    
    
    def _getValues(self):
        
        values = {name:[] for name in self.headerLabels}
        
        self.table.sortItems(0, Qt.AscendingOrder)

        for row in range(self.table.rowCount()):
            for col, name in enumerate(self.headerLabels):
                item = self.table.item(row, col)
                value = item.text()
                mthd = self.mthds[name]['cast']
                value = mthd(value)
                values[name].append(value)
                
        return values
示例#40
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,
                 extra_path=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.extra_path = extra_path
        self.is_initialized = False
        self.closing = False
        self.notifier = None
        self.process = None
        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 self.env is None:
            python_path = osp.dirname(get_module_path('trex'))
            # 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
            if self.extra_path:
                try:
                    python_path = osp.pathsep.join([python_path] +
                                                   self.extra_path)
                except Exception as e:
                    debug_print("Error when adding extra_path to plugin env")
                    debug_print(e)
            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()

        if self.notifier is not None:
            self.notifier.activated.disconnect(self._on_msg_received)
            self.notifier.setEnabled(False)
            self.notifier = None

        self.request('server_quit')

        if self.process is not None:
            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, zmq.NOBLOCK)
        except Exception as e:
            debug_print(e)
            self.is_initialized = False
            self._on_finished()
示例#41
0
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin, BrowseHistoryMixin):
    """
    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,
                 initial_message=None,
                 default_foreground_color=None,
                 error_foreground_color=None,
                 traceback_foreground_color=None,
                 prompt_foreground_color=None,
                 background_color=None):
        """
        parent : specifies the parent widget
        """
        ConsoleBaseWidget.__init__(self, parent)
        SaveHistoryMixin.__init__(self, history_filename)
        BrowseHistoryMixin.__init__(self)

        # Prompt position: tuple (line, index)
        self.current_prompt_pos = None
        self.new_input_line = True

        # History
        assert is_text_string(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 = []
        if initial_message:
            self.__buffer.append(initial_message)

        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_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:
                pass

    #------ 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:
        try:
            encoding.writelines(rawhistory, self.history_filename)
        except EnvironmentError:
            pass

        return history

    #------ Simulation standards input/output
    def write_error(self, text):
        """Simulate stderr"""
        self.flush()
        self.write(text, flush=True, error=True)
        if get_debug_level():
            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
示例#42
0
class ToolTipWidget(QLabel):
    """
    Shows tooltips that can be styled with the different themes.
    """

    sig_completion_help_requested = Signal(str, str)
    sig_help_requested = Signal(str)

    def __init__(self, parent=None, as_tooltip=False):
        """
        Shows tooltips that can be styled with the different themes.
        """
        super(ToolTipWidget, self).__init__(parent, Qt.ToolTip)

        # Variables
        self.completion_doc = None
        self._url = ''
        self.app = QCoreApplication.instance()
        self.as_tooltip = as_tooltip
        self.tip = None
        self._timer_hide = QTimer()
        self._text_edit = parent

        # Setup
        # This keeps the hints below other applications
        if sys.platform == 'darwin':
            self.setWindowFlags(Qt.SplashScreen)
        else:
            self.setWindowFlags(Qt.ToolTip | Qt.FramelessWindowHint)

        self._timer_hide.setInterval(500)
        self.setTextInteractionFlags(Qt.TextSelectableByMouse)
        self.setTextInteractionFlags(Qt.TextBrowserInteraction)
        self.setOpenExternalLinks(False)
        self.setForegroundRole(QPalette.ToolTipText)
        self.setBackgroundRole(QPalette.ToolTipBase)
        self.setPalette(QToolTip.palette())
        self.setAlignment(Qt.AlignLeft)
        self.setIndent(1)
        self.setFrameStyle(QFrame.NoFrame)
        style = self.style()
        delta_margin = style.pixelMetric(QStyle.PM_ToolTipLabelFrameWidth,
                                         None, self)
        self.setMargin(1 + delta_margin)

        # Signals
        self.linkHovered.connect(self._update_hover_html_link_style)
        self._timer_hide.timeout.connect(self.hide)

    def paintEvent(self, event):
        """Reimplemented to paint the background panel."""
        painter = QStylePainter(self)
        option = QStyleOptionFrame()
        option.initFrom(self)
        painter.drawPrimitive(QStyle.PE_PanelTipLabel, option)
        painter.end()

        super(ToolTipWidget, self).paintEvent(event)

    def _update_hover_html_link_style(self, url):
        """Update style of labels that include rich text and html links."""
        link = 'text-decoration:none;'
        link_hovered = 'text-decoration:underline;'
        self._url = url

        if url:
            QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
            new_text, old_text = link_hovered, link
        else:
            new_text, old_text = link, link_hovered
            QApplication.restoreOverrideCursor()

        text = self.text()
        new_text = text.replace(old_text, new_text)

        self.setText(new_text)

    # ------------------------------------------------------------------------
    # --- 'ToolTipWidget' interface
    # ------------------------------------------------------------------------
    def show_basic_tip(self, point, tip):
        """Show basic tip."""
        self.tip = tip
        self.setText(tip)
        self.resize(self.sizeHint())
        y = point.y() - self.height()
        self.move(point.x(), y)
        self.show()
        return True

    def show_tip(self, point, tip, cursor=None, completion_doc=None):
        """
        Attempts to show the specified tip at the current cursor location.
        """
        # Don't attempt to show it if it's already visible and the text
        # to be displayed is the same as the one displayed before.
        if self.tip == tip:
            if not self.isVisible():
                self.show()
            return

        # Set the text and resize the widget accordingly.
        self.tip = tip
        self.setText(tip)
        self.resize(self.sizeHint())

        self.completion_doc = completion_doc

        padding = 0
        text_edit = self._text_edit
        if cursor is None:
            cursor_rect = text_edit.cursorRect()
        else:
            cursor_rect = text_edit.cursorRect(cursor)

        screen_rect = self.app.desktop().screenGeometry(text_edit)
        point.setY(point.y() + padding)
        tip_height = self.size().height()
        tip_width = self.size().width()

        vertical = 'bottom'
        horizontal = 'Right'

        if point.y() + tip_height > screen_rect.height() + screen_rect.y():
            point_ = text_edit.mapToGlobal(cursor_rect.topRight())
            # If tip is still off screen, check if point is in top or bottom
            # half of screen.
            if point_.y() - tip_height < padding:
                # If point is in upper half of screen, show tip below it.
                # otherwise above it.
                if 2 * point.y() < screen_rect.height():
                    vertical = 'bottom'
                else:
                    vertical = 'top'
            else:
                vertical = 'top'

        if point.x() + tip_width > screen_rect.width() + screen_rect.x():
            point_ = text_edit.mapToGlobal(cursor_rect.topRight())
            # If tip is still off-screen, check if point is in the right or
            # left half of the screen.
            if point_.x() - tip_width < padding:
                if 2 * point.x() < screen_rect.width():
                    horizontal = 'Right'
                else:
                    horizontal = 'Left'
            else:
                horizontal = 'Left'
        pos = getattr(cursor_rect, '%s%s' % (vertical, horizontal))
        adjusted_point = text_edit.mapToGlobal(pos())

        if vertical == 'top':
            if os.name == 'nt':
                padding = -7
            else:
                padding = -4.5

            point.setY(adjusted_point.y() - tip_height - padding)

        if horizontal == 'Left':
            point.setX(adjusted_point.x() - tip_width - padding)

        self.move(point)
        if not self.isVisible():
            self.show()
        return True

    def mousePressEvent(self, event):
        """
        Reimplemented to hide it when focus goes out of the main window.
        """
        QApplication.restoreOverrideCursor()
        if self.completion_doc:
            name = self.completion_doc.get('name', '')
            signature = self.completion_doc.get('signature', '')
            self.sig_completion_help_requested.emit(name, signature)
        else:
            self.sig_help_requested.emit(self._url)
        super(ToolTipWidget, self).mousePressEvent(event)
        self._hide()

    def focusOutEvent(self, event):
        """
        Reimplemented to hide it when focus goes out of the main window.
        """
        self.hide()

    def leaveEvent(self, event):
        """Override Qt method to hide the tooltip on leave."""
        super(ToolTipWidget, self).leaveEvent(event)
        self.hide()

    def _hide(self):
        """Hide method helper."""
        QApplication.restoreOverrideCursor()
        self._timer_hide.start()

    def hide(self):
        """Override Qt method to add timer and restore cursor."""
        super(ToolTipWidget, self).hide()
        self._timer_hide.stop()
示例#43
0
        # Update upper section of plot (old)
        if np.any(move_bool) or (new_t0 != rs['latest_t0']):
            # Update old
            x_vals = rs['old_timestamps'] % self.x_lim
            y_vals = 1.1 * np.ones_like(x_vals)
            for row_ix in range(1, self.plot_config['y_range']):
                row_cutoff = new_t0 - (row_ix * self.x_lim)
                y_vals[rs['old_timestamps'] < row_cutoff] += 1
            x_vals = np.repeat(x_vals, 2)
            y_vals = np.repeat(y_vals, 2)
            y_vals[1::2] += 0.8
            rs['old'].setData(x=x_vals, y=y_vals)
        # Save some variables
        rs['latest_t0'] = new_t0
        rs['start_time'] = max(rs['start_time'], rs['last_spike_time'] - (self.x_lim * self.plot_config['y_range']))
        # Update frate annotation.
        samples_elapsed = rs['last_spike_time'] - rs['start_time']
        if samples_elapsed > 0:
            frate = self.rasters[line_label]['count'] * self.samplingRate / samples_elapsed
            self.modify_frate(line_label, frate)


if __name__ == '__main__':
    qapp = QApplication(sys.argv)
    aw = RasterGUI()
    timer = QTimer()
    timer.timeout.connect(aw.update)
    timer.start(1)

    if (sys.flags.interactive != 1) or not hasattr(qtpy.QtCore, 'PYQT_VERSION'):
        QApplication.instance().exec_()
示例#44
0
class RunDialog(QDialog):
    simulation_done = Signal(bool, str)
    simulation_termination_request = Signal()

    def __init__(self,
                 config_file,
                 run_model,
                 simulation_arguments,
                 parent=None):
        QDialog.__init__(self, parent)
        self.setWindowFlags(Qt.Window)
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setModal(True)
        self.setWindowModality(Qt.WindowModal)
        self.setWindowTitle("Simulations - {}".format(config_file))

        self._snapshot_model = SnapshotModel(self)
        self._run_model = run_model

        self._isDetailedDialog = False
        self._minimum_width = 1200

        ert = None
        if isinstance(run_model, BaseRunModel):
            ert = run_model.ert()

        self._simulations_argments = simulation_arguments

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._on_ticker)

        progress_proxy_model = ProgressProxyModel(self._snapshot_model,
                                                  parent=self)

        self._total_progress_label = QLabel(_TOTAL_PROGRESS_TEMPLATE.format(0),
                                            self)

        self._total_progress_bar = QProgressBar(self)
        self._total_progress_bar.setRange(0, 100)
        self._total_progress_bar.setTextVisible(False)

        self._iteration_progress_label = QLabel(self)

        self._progress_view = ProgressView(self)
        self._progress_view.setModel(progress_proxy_model)
        self._progress_view.setIndeterminate(True)

        legend_view = LegendView(self)
        legend_view.setModel(progress_proxy_model)

        self._tab_widget = QTabWidget(self)
        self._snapshot_model.rowsInserted.connect(self.on_new_iteration)

        self._job_label = QLabel(self)

        self._job_model = JobListProxyModel(self, 0, 0, 0, 0)
        self._job_model.setSourceModel(self._snapshot_model)

        self._job_view = QTableView(self)
        self._job_view.clicked.connect(self._job_clicked)
        self._open_files = {}
        self._job_view.setModel(self._job_model)

        self.running_time = QLabel("")

        self.plot_tool = PlotTool(config_file)
        self.plot_tool.setParent(self)
        self.plot_button = QPushButton(self.plot_tool.getName())
        self.plot_button.clicked.connect(self.plot_tool.trigger)
        self.plot_button.setEnabled(ert is not None)

        self.kill_button = QPushButton("Kill Simulations")
        self.done_button = QPushButton("Done")
        self.done_button.setHidden(True)
        self.restart_button = QPushButton("Restart")
        self.restart_button.setHidden(True)
        self.show_details_button = QPushButton("Show Details")
        self.show_details_button.setCheckable(True)

        size = 20
        spin_movie = resourceMovie("ide/loading.gif")
        spin_movie.setSpeed(60)
        spin_movie.setScaledSize(QSize(size, size))
        spin_movie.start()

        self.processing_animation = QLabel()
        self.processing_animation.setMaximumSize(QSize(size, size))
        self.processing_animation.setMinimumSize(QSize(size, size))
        self.processing_animation.setMovie(spin_movie)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.processing_animation)
        button_layout.addWidget(self.running_time)
        button_layout.addStretch()
        button_layout.addWidget(self.show_details_button)
        button_layout.addWidget(self.plot_button)
        button_layout.addWidget(self.kill_button)
        button_layout.addWidget(self.done_button)
        button_layout.addWidget(self.restart_button)

        button_widget_container = QWidget()
        button_widget_container.setLayout(button_layout)

        layout = QVBoxLayout()
        layout.addWidget(self._total_progress_label)
        layout.addWidget(self._total_progress_bar)
        layout.addWidget(self._iteration_progress_label)
        layout.addWidget(self._progress_view)
        layout.addWidget(legend_view)
        layout.addWidget(self._tab_widget)
        layout.addWidget(self._job_label)
        layout.addWidget(self._job_view)
        layout.addWidget(button_widget_container)

        self.setLayout(layout)

        self.kill_button.clicked.connect(self.killJobs)
        self.done_button.clicked.connect(self.accept)
        self.restart_button.clicked.connect(self.restart_failed_realizations)
        self.show_details_button.clicked.connect(self.toggle_detailed_progress)
        self.simulation_done.connect(self._on_simulation_done)

        self.setMinimumWidth(self._minimum_width)
        self._setSimpleDialog()

    def _setSimpleDialog(self) -> None:
        self._isDetailedDialog = False
        self._tab_widget.setVisible(False)
        self._job_label.setVisible(False)
        self._job_view.setVisible(False)
        self.show_details_button.setText("Show Details")

    def _setDetailedDialog(self) -> None:
        self._isDetailedDialog = True
        self._tab_widget.setVisible(True)
        self._job_label.setVisible(True)
        self._job_view.setVisible(True)
        self.show_details_button.setText("Hide Details")

    @Slot(QModelIndex, int, int)
    def on_new_iteration(self, parent: QModelIndex, start: int,
                         end: int) -> None:
        if not parent.isValid():
            iter = start
            self._iteration_progress_label.setText(
                f"Progress for iteration {iter}")

            widget = RealizationWidget(iter)
            widget.setSnapshotModel(self._snapshot_model)
            widget.currentChanged.connect(self._select_real)

            self._tab_widget.addTab(widget,
                                    f"Realizations for iteration {iter}")

    @Slot(QModelIndex)
    def _job_clicked(self, index):
        if not index.isValid():
            return
        selected_file = index.data(FileRole)

        if selected_file and selected_file not in self._open_files:
            job_name = index.siblingAtColumn(0).data()
            viewer = FileDialog(
                selected_file,
                job_name,
                index.row(),
                index.model().get_real(),
                index.model().get_iter(),
                self,
            )
            self._open_files[selected_file] = viewer
            viewer.finished.connect(
                lambda _, f=selected_file: self._open_files.pop(f))

        elif selected_file in self._open_files:
            self._open_files[selected_file].raise_()

    @Slot(QModelIndex)
    def _select_real(self, index):
        step = 0
        stage = 0
        real = index.row()
        iter_ = index.model().get_iter()
        self._job_model.set_step(iter_, real, stage, step)
        self._job_label.setText(
            f"Realization id {index.data(RealIens)} in iteration {iter_}")

        self._job_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        # Clear the selection in the other tabs
        for i in range(0, self._tab_widget.count()):
            if i != self._tab_widget.currentIndex():
                self._tab_widget.widget(i).clearSelection()

    def reject(self):
        return

    def closeEvent(self, QCloseEvent):
        if self._run_model.isFinished():
            self.simulation_done.emit(self._run_model.hasRunFailed(),
                                      self._run_model.getFailMessage())
        else:
            # Kill jobs if dialog is closed
            if self.killJobs() != QMessageBox.Yes:
                QCloseEvent.ignore()

    def startSimulation(self):
        self._run_model.reset()
        self._snapshot_model.reset()
        self._tab_widget.clear()

        def run():
            self._run_model.startSimulations(self._simulations_argments)

        simulation_thread = Thread(name="ert_gui_simulation_thread")
        simulation_thread.setDaemon(True)
        simulation_thread.run = run
        simulation_thread.start()

        self._ticker.start(1000)

        tracker = create_tracker(
            self._run_model,
            num_realizations=self._simulations_argments["active_realizations"].
            count(),
            ee_config=self._simulations_argments.get("ee_config", None),
        )

        worker = TrackerWorker(tracker)
        worker_thread = QThread()
        worker.done.connect(worker_thread.quit)
        worker.consumed_event.connect(self._on_tracker_event)
        worker.moveToThread(worker_thread)
        self.simulation_done.connect(worker.stop)
        self._worker = worker
        self._worker_thread = worker_thread
        worker_thread.started.connect(worker.consume_and_emit)
        self._worker_thread.start()

    def killJobs(self):

        msg = "Are you sure you want to kill the currently running simulations?"
        if self._run_model.getQueueStatus().get(
                JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0:
            msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!"
        kill_job = QMessageBox.question(self, "Kill simulations?", msg,
                                        QMessageBox.Yes | QMessageBox.No)

        if kill_job == QMessageBox.Yes:
            # Normally this slot would be invoked by the signal/slot system,
            # but the worker is busy tracking the evaluation.
            self._worker.request_termination()
            self.reject()
        return kill_job

    @Slot(bool, str)
    def _on_simulation_done(self, failed, failed_msg):
        self.processing_animation.hide()
        self.kill_button.setHidden(True)
        self.done_button.setHidden(False)
        self.restart_button.setVisible(self.has_failed_realizations())
        self.restart_button.setEnabled(self._run_model.support_restart)
        self._total_progress_bar.setValue(100)
        self._total_progress_label.setText(
            _TOTAL_PROGRESS_TEMPLATE.format(100))

        if failed:
            QMessageBox.critical(
                self,
                "Simulations failed!",
                f"The simulation failed with the following error:\n\n{failed_msg}",
            )

    @Slot()
    def _on_ticker(self):
        runtime = self._run_model.get_runtime()
        self.running_time.setText(format_running_time(runtime))

    @Slot(object)
    def _on_tracker_event(self, event):
        if isinstance(event, EndEvent):
            self.simulation_done.emit(event.failed, event.failed_msg)
            self._worker.stop()
            self._ticker.stop()

        elif isinstance(event, FullSnapshotEvent):
            if event.snapshot is not None:
                self._snapshot_model._add_snapshot(event.snapshot,
                                                   event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(progress))

        elif isinstance(event, SnapshotUpdateEvent):
            if event.partial_snapshot is not None:
                self._snapshot_model._add_partial_snapshot(
                    event.partial_snapshot, event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(progress))

    def has_failed_realizations(self):
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        for (index, successful) in enumerate(completed):
            if initial[index] and not successful:
                return True
        return False

    def count_successful_realizations(self):
        """
        Counts the realizations completed in the prevoius ensemble run
        :return:
        """
        completed = self._run_model.completed_realizations_mask
        return completed.count(True)

    def create_mask_from_failed_realizations(self):
        """
        Creates a BoolVector mask representing the failed realizations
        :return: Type BoolVector
        """
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        inverted_mask = BoolVector(default_value=False)
        for (index, successful) in enumerate(completed):
            inverted_mask[index] = initial[index] and not successful
        return inverted_mask

    def restart_failed_realizations(self):

        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setText(
            "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences."
        )
        msg.setWindowTitle("Restart Failed Realizations")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        result = msg.exec_()

        if result == QMessageBox.Ok:
            self.restart_button.setVisible(False)
            self.kill_button.setVisible(True)
            self.done_button.setVisible(False)
            active_realizations = self.create_mask_from_failed_realizations()
            self._simulations_argments[
                "active_realizations"] = active_realizations
            self._simulations_argments[
                "prev_successful_realizations"] = self._simulations_argments.get(
                    "prev_successful_realizations", 0)
            self._simulations_argments[
                "prev_successful_realizations"] += self.count_successful_realizations(
                )
            self.startSimulation()

    @Slot()
    def toggle_detailed_progress(self):
        if self._isDetailedDialog:
            self._setSimpleDialog()
        else:
            self._setDetailedDialog()

        self.adjustSize()
示例#45
0
 def focusChangedEvent(self, old_w, new_w):
     if issubclass(new_w.__class__, QLineEdit):
         #print("QLineEdit got focus: ", new_w)
         QTimer.singleShot(0, new_w.selectAll)
示例#46
0
class PyDMApplication(QApplication):
    """
    PyDMApplication handles loading PyDM display files, opening
    new windows, and most importantly, establishing and managing
    connections to channels via data plugins.

    Parameters
    ----------
    ui_file : str, optional
        The file path to a PyDM display file (.ui or .py).
    command_line_args : list, optional
        A list of strings representing arguments supplied at the command
        line.  All arguments in this list are handled by QApplication,
        in addition to PyDMApplication.
    display_args : list, optional
        A list of command line arguments that should be forwarded to the
        Display class.  This is only useful if a Related Display Button
        is opening up a .py file with extra arguments specified, and
        probably isn't something you will ever need to use when writing
        code that instantiates PyDMApplication.
    perfmon : bool, optional
        Whether or not to enable performance monitoring using 'psutil'.
        When enabled, CPU load information on a per-thread basis is
        periodically printed to the terminal.
    hide_nav_bar : bool, optional
        Whether or not to display the navigation bar (forward/back/home buttons)
        when the main window is first displayed.
    hide_menu_bar: bool, optional
        Whether or not to display the menu bar (File, View)
        when the main window is first displayed.
    hide_status_bar: bool, optional
        Whether or not to display the status bar (general messages and errors)
        when the main window is first displayed.
    read_only: bool, optional
        Whether or not to launch PyDM in a read-only state.
    macros : dict, optional
        A dictionary of macro variables to be forwarded to the display class
        being loaded.
    use_main_window : bool, optional
        If ui_file is note given, this parameter controls whether or not to
        create a PyDMMainWindow in the initialization (Default is True).
    fullscreen : bool, optional
        Whether or not to launch PyDM in a full screen mode.
    """
    # Instantiate our plugins.
    plugins = data_plugins.plugin_modules

    def __init__(self,
                 ui_file=None,
                 command_line_args=[],
                 display_args=[],
                 perfmon=False,
                 hide_nav_bar=False,
                 hide_menu_bar=False,
                 hide_status_bar=False,
                 read_only=False,
                 macros=None,
                 use_main_window=True,
                 stylesheet_path=None,
                 fullscreen=False):
        super(PyDMApplication, self).__init__(command_line_args)
        # Enable High DPI display, if available.
        if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
            self.setAttribute(Qt.AA_UseHighDpiPixmaps)
        # The macro and directory stacks are needed for nested displays (usually PyDMEmbeddedDisplays).
        # During the process of loading a display (whether from a .ui file, or a .py file), the application's
        # 'open_file' method will be called recursively.    Inside open_file, the last item on the stack represents
        # the parent widget's file path and macro variables.    Any file paths are joined to the end of the parent's
        # file path, and any macros are merged with the parent's macros.    This system depends on open_file always
        # being called hierarchially (i.e., parent calls it first, then on down the ancestor tree, with no unrelated
        # calls in between).    If something crazy happens and PyDM somehow gains the ability to open files in a
        # multi-threaded way, for example, this system will fail.
        data_plugins.set_read_only(read_only)
        self.main_window = None
        self.directory_stack = ['']
        self.macro_stack = [{}]
        self.windows = {}
        self.display_args = display_args
        self.hide_nav_bar = hide_nav_bar
        self.hide_menu_bar = hide_menu_bar
        self.hide_status_bar = hide_status_bar
        self.fullscreen = fullscreen

        # Open a window if required.
        if ui_file is not None:
            self.make_main_window(stylesheet_path=stylesheet_path)
            self.make_window(ui_file, macros, command_line_args)
        elif use_main_window:
            self.make_main_window(stylesheet_path=stylesheet_path)

        self.had_file = ui_file is not None
        # Re-enable sigint (usually blocked by pyqt)
        signal.signal(signal.SIGINT, signal.SIG_DFL)

        # Performance monitoring
        if perfmon:
            import psutil
            self.perf = psutil.Process()
            self.perf_timer = QTimer()
            self.perf_timer.setInterval(2000)
            self.perf_timer.timeout.connect(self.get_CPU_usage)
            self.perf_timer.start()

    def get_string_encoding(self):
        return os.getenv("PYDM_STRING_ENCODING", "utf_8")

    def exec_(self):
        """
        Execute the QApplication.
        """
        return super(PyDMApplication, self).exec_()

    def is_read_only(self):
        warnings.warn("'PyDMApplication.is_read_only' is deprecated, "
                      "use 'pydm.data_plugins.is_read_only' instead.")
        return data_plugins.is_read_only()

    @Slot()
    def get_CPU_usage(self):
        """
        Prints total CPU usage (in percent), as well as per-thread usage, to the terminal.
        """
        with self.perf.oneshot():
            total_percent = self.perf.cpu_percent(interval=None)
            total_time = sum(self.perf.cpu_times())
            usage = [
                total_percent * ((t.system_time + t.user_time) / total_time)
                for t in self.perf.threads()
            ]
        print("Total: {tot}, Per Thread: {percpu}".format(tot=total_percent,
                                                          percpu=usage))

    def new_pydm_process(self, ui_file, macros=None, command_line_args=None):
        """
        Spawn a new PyDM process and open the supplied file.  Commands to open
        new windows in PyDM typically actually spawn an entirely new PyDM process.
        This keeps each window isolated, so that one window cannot slow
        down or crash another.

        Parameters
        ----------
        ui_file : str
            The path to a .ui or .py file to open in the new process.
        macros : dict, optional
            A dictionary of macro variables to supply to the display file
            to be opened.
        command_line_args : list, optional
            A list of command line arguments to pass to the new process.
            Typically, this argument is used by related display buttons
            to pass in extra arguments.  It is probably rare that code you
            write needs to use this argument.
        """
        # Expand user (~ or ~user) and environment variables.
        ui_file = os.path.expanduser(os.path.expandvars(ui_file))
        base_dir, fname, args = path_info(str(ui_file))
        filepath = os.path.join(base_dir, fname)
        filepath_args = args
        pydm_display_app_path = which("pydm")

        if pydm_display_app_path is None:
            if os.environ.get("PYDM_PATH") is not None:
                pydm_display_app_path = os.path.join(os.environ["PYDM_PATH"],
                                                     "pydm")
            else:
                # Not in the PATH and no ENV VAR pointing to it...
                # Let's try the script folder...
                pydm_display_app_path = os.path.join(
                    os.path.split(os.path.realpath(__file__))[0], "..",
                    "scripts", "pydm")

        args = [pydm_display_app_path]
        if self.hide_nav_bar:
            args.extend(["--hide-nav-bar"])
        if self.hide_menu_bar:
            args.extend(["--hide-menu-bar"])
        if self.hide_status_bar:
            args.extend(["--hide-status-bar"])
        if self.fullscreen:
            args.extend(["--fullscreen"])
        if macros is not None:
            args.extend(["-m", json.dumps(macros)])
        args.append(filepath)
        args.extend(self.display_args)
        args.extend(filepath_args)
        if command_line_args is not None:
            args.extend(command_line_args)
        subprocess.Popen(args, shell=False)

    def new_window(self, ui_file, macros=None, command_line_args=None):
        """
        Make a new window and open the supplied file.
        Currently, this method just calls `new_pydm_process`.

        This is an internal method that typically will not be needed by users.

        Parameters
        ----------
        ui_file : str
            The path to a .ui or .py file to open in the new process.
        macros : dict, optional
            A dictionary of macro variables to supply to the display file
            to be opened.
        command_line_args : list, optional
            A list of command line arguments to pass to the new process.
            Typically, this argument is used by related display buttons
            to pass in extra arguments.  It is probably rare that code you
            write needs to use this argument.
        """
        # All new windows are spawned as new processes.
        self.new_pydm_process(ui_file, macros, command_line_args)

    def make_main_window(self, stylesheet_path=None):
        """
        Instantiate a new PyDMMainWindow, add it to the application's
        list of windows. Typically, this function is only called as part
        of starting up a new process, because PyDMApplications only have
        one window per process.
        """
        main_window = PyDMMainWindow(hide_nav_bar=self.hide_nav_bar,
                                     hide_menu_bar=self.hide_menu_bar,
                                     hide_status_bar=self.hide_status_bar)

        self.main_window = main_window
        apply_stylesheet(stylesheet_path, widget=self.main_window)
        self.main_window.update_tools_menu()

        if self.fullscreen:
            main_window.enter_fullscreen()
        else:
            main_window.show()

        # If we are launching a new window, we don't want it to sit right on top of an existing window.
        if len(self.windows) > 1:
            main_window.move(main_window.x() + 10, main_window.y() + 10)

    def make_window(self, ui_file, macros=None, command_line_args=None):
        """
        Open the ui_file in the window.

        Parameters
        ----------
        ui_file : str
            The path to a .ui or .py file to open in the new process.
        macros : dict, optional
            A dictionary of macro variables to supply to the display file
            to be opened.
        command_line_args : list, optional
            A list of command line arguments to pass to the new process.
            Typically, this argument is used by related display buttons
            to pass in extra arguments.  It is probably rare that code you
            write needs to use this argument.
        """
        if ui_file is not None:
            self.main_window.open_file(ui_file, macros, command_line_args)
            self.windows[self.main_window] = path_info(ui_file)[0]

    def close_window(self, window):
        try:
            del self.windows[window]
        except KeyError:
            # If window is no longer at self.windows
            # it means that we already closed it.
            pass

    def open_file(self,
                  ui_file,
                  macros=None,
                  command_line_args=None,
                  defer_connections=False,
                  **kwargs):
        """
        Open a .ui or .py file, and return a widget from the loaded file.
        This method is the entry point for all opening of new displays,
        and manages handling macros and relative file paths when opening
        nested displays.

        Parameters
        ----------
        ui_file : str
            The path to a .ui or .py file to open in the new process.
        macros : dict, optional
            A dictionary of macro variables to supply to the display file
            to be opened.
        command_line_args : list, optional
            A list of command line arguments to pass to the new process.
            Typically, this argument is used by related display buttons
            to pass in extra arguments.  It is probably rare that code you
            write needs to use this argument.

        Returns
        -------
        QWidget
        """
        if 'establish_connection' in kwargs:
            logger.warning(
                "Ignoring 'establish_connection' parameter at "
                "open_relative. The connection is now handled by the"
                " widgets.")

        # First split the ui_file string into a filepath and arguments
        args = command_line_args if command_line_args is not None else []
        dir_name, file_name, extra_args = path_info(ui_file)
        args.extend(extra_args)
        filepath = os.path.join(dir_name, file_name)
        self.directory_stack.append(dir_name)
        (filename, extension) = os.path.splitext(file_name)
        if macros is None:
            macros = {}
        merged_macros = self.macro_stack[-1].copy()
        merged_macros.update(macros)
        self.macro_stack.append(merged_macros)
        with data_plugins.connection_queue(
                defer_connections=defer_connections):
            if extension == '.ui':
                widget = load_ui_file(filepath, merged_macros)
            elif extension == '.py':
                widget = load_py_file(filepath, args, merged_macros)
            else:
                self.directory_stack.pop()
                self.macro_stack.pop()
                raise ValueError("Invalid file type: {}".format(extension))
        # Add on the macros to the widget after initialization. This is
        # done for both ui files and python files.
        widget.base_macros = merged_macros
        self.directory_stack.pop()
        self.macro_stack.pop()
        return widget

    # get_path gives you the path to ui_file relative to where you are running pydm from.
    # Many widgets handle file paths (related display, embedded display, and drawing image come to mind)
    # and the standard is that they expect paths to be given relative to the .ui or .py file in which the
    # widget lives.  But, python and Qt want the file path relative to the directory you are running
    # pydm from.  This function does that translation.
    def get_path(self, ui_file):
        """
        Gives you the path to ui_file relative to where you are running pydm from.

        Many widgets handle file paths (related display, embedded display,
        and drawing image come to mind) and the standard is that they expect
        paths to be given relative to the .ui or .py file in which the widget
        lives.  But, python and Qt want the file path relative to the directory
        you are running pydm from.  This function does that translation.

        Parameters
        ----------
        ui_file : str

        Returns
        -------
        str
        """
        dirname = self.directory_stack[-1]
        full_path = os.path.join(dirname, str(ui_file))
        return full_path

    def open_relative(self,
                      ui_file,
                      widget,
                      macros=None,
                      command_line_args=[],
                      **kwargs):
        """
        open_relative opens a ui file with a relative path.  This is
        really only used by embedded displays.
        """
        if 'establish_connection' in kwargs:
            logger.warning(
                "Ignoring 'establish_connection' parameter at "
                "open_relative. The connection is now handled by the"
                " widgets.")
        full_path = self.get_path(ui_file)

        if not os.path.exists(full_path):
            new_fname = find_display_in_path(ui_file)
            if new_fname is not None and new_fname != "":
                full_path = new_fname
        return self.open_file(full_path,
                              macros=macros,
                              command_line_args=command_line_args)

    def plugin_for_channel(self, channel):
        """
        Given a PyDMChannel object, determine the appropriate plugin to use.

        Parameters
        ----------
        channel : PyDMChannel

        Returns
        -------
        PyDMPlugin
        """
        warnings.warn("'PyDMApplication.plugin_for_channel' is deprecated, "
                      "use 'pydm.data_plugins.plugin_for_address' instead.")
        if channel.address is None or channel.address == "":
            return None
        return data_plugins.plugin_for_address(channel.address)

    def add_connection(self, channel):
        """
        Add a new connection to a channel.

        Parameters
        ----------
        channel : PyDMChannel
        """
        warnings.warn("'PyDMApplication.add_connection' is deprecated, "
                      "use PyDMConnection.connect()")
        channel.connect()

    def remove_connection(self, channel):
        """
        Remove a connection to a channel.

        Parameters
        ----------
        channel : PyDMChannel
        """
        warnings.warn("'PyDMApplication.remove_connection' is deprecated, "
                      "use PyDMConnection.disconnect()")
        channel.disconnect()

    def eventFilter(self, obj, event):
        warnings.warn("'PyDMApplication.eventFilter' is deprecated, "
                      " this function is now found on PyDMWidget")
        obj.eventFilter(obj, event)

    def show_address_tooltip(self, obj, event):
        warnings.warn("'PyDMApplication.show_address_tooltip' is deprecated, "
                      " this function is now found on PyDMWidget")
        obj.show_address_tooltip(event)

    def establish_widget_connections(self, widget):
        """
        Given a widget to start from, traverse the tree of child widgets,
        and try to establish connections to any widgets with channels.

        Display subclasses which dynamically create widgets may need to
        use this method.

        Parameters
        ----------
        widget : QWidget
        """
        warnings.warn(
            "'PyDMApplication.establish_widget_connections' is deprecated, "
            "this function is now found on `utilities.establish_widget_connections`."
        )
        connection.establish_widget_connections(widget)

    def close_widget_connections(self, widget):
        """
        Given a widget to start from, traverse the tree of child widgets,
        and try to close connections to any widgets with channels.

        Parameters
        ----------
        widget : QWidget
        """
        warnings.warn(
            "'PyDMApplication.close_widget_connections' is deprecated, "
            "this function is now found on `utilities.close_widget_connections`."
        )
        connection.close_widget_connections(widget)

    def open_template(self, template_filename):
        fname = os.path.expanduser(os.path.expandvars(template_filename))
        if os.path.isabs(fname):
            full_path = fname
        else:
            full_path = self.get_path(template_filename)
            if not os.path.exists(full_path):
                new_fname = find_display_in_path(template_filename)
                if new_fname is not None and new_fname != "":
                    full_path = new_fname
        template = macro.template_for_file(full_path)
        return template

    def widget_from_template(self, template, macros):
        if macros is None:
            macros = {}
        merged_macros = self.macro_stack[-1].copy()
        merged_macros.update(macros)
        f = macro.replace_macros_in_template(template, merged_macros)
        w = load_ui_file(f)
        w.base_macros = merged_macros
        return w
示例#47
0
            return
        legend = self.plotWidget.plotItem.legend
        print('Legend:', legend)
        legend.removeItem('%d' % sweepIndex)
        print('Removing item:', curve)
        self.plotWidget.removeItem(curve)
        print('items in plotWidget.plotItem:', self.plotWidget.plotItem.items)
        del self._sweepMap[sweepIndex]
        print('Removed sweep')


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

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

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

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

    app.exec_()
示例#48
0
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(150)

        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.')
class StageUI(QMainWindow):
    col_connected = 0
    col_port = 1
    col_position = 2
    col_moving = 3
    col_engaged = 4
    col_limit = 5

    def __init__(self):
        """
        Stage Controller PyQt UI Object
        """
        super(QMainWindow, self).__init__()
        self.module_path = visual_behavior.__path__
        self.config = visual_behavior.source_project_configuration('visual_behavior_v1.yml')
        self.admin_digest = b'!#/)zW\xa5\xa7C\x89J\x0eJ\x80\x1f\xc3'  # obviously get this from db
        self.admin_enabled = False

        self.hw_proxy = hardware_proxy.RemoteStageController()
        self.stage = None
        self.daq = None

        self.ui = uic.loadUi(self.module_path[0] + '/resources/remote_stage_controller.ui')
        self.setCentralWidget(self.ui)

        self.setup_ui()
        self.log('Stage Controller Ready')
        self.table_timer = QTimer()
        self.table_timer.timeout.connect(self.update_table)

        self.coordinates = {'home': None,
                            'working': None,
                            'load': None,
                            'mouse': None}
        self._ignore_limits = False
        self.db = self.setup_db()
        self._spout_state = 0

        self.signal_connect_to_rig('localhost')

    def log(self, message):
        logging.info(message)
        self.ui.statusBar.showMessage(message)

    def setup_db(self):
        db = {}
        return db

    def load_stage_coordinates(self):
        """

        :return:
        """

        key = f'{self.stage.serial}'

        safe_coords = f'{key}_working'
        if safe_coords in self.db:
            self.coordinates['working'] = yaml.load(self.db[safe_coords])
            self.ui.lbl_coords_safe.setText(f'({coords[0]}, {coords[1]}, {coords[2]})')

        origin_coords = f'{key}_load'
        if origin_coords in self.db:
            self.coordinates['load'] = yaml.load(self.db[origin_coords])
            self.ui.lbl_coords_origin.setText(f'({coords[0]}, {coords[1]},{coords[2]})')

    def load_mouse_coordinates(self):
        """

        :return:
        """

        key = f'{self.stage.serial}_{self.ui.le_mouse_id}'
        if key in self.db:
            self.coordinates['mouse'] = yaml.load(self.db[key])
            self.log(f'loaded mouse offset: ({coords[0]}, {coords[1]}, {coords[2]})')
        else:
            self.log(f'Could not find mouse offset: {key}')

    def setup_ui(self):
        """

        :return:
        """


        self.ui.btn_moveto.clicked.connect(self.signal_move_to)
        self.ui.btn_air_1.clicked.connect(self.signal_extend_lickspout)
        self.ui.btn_air_2.clicked.connect(self.signal_retract_lickspout)
        self.ui.btn_water_1.clicked.connect(self.signal_water_sol_1)
        self.ui.btn_water_2.clicked.connect(self.signal_water_sol_2)
        self.ui.btn_home.clicked.connect(self.signal_home_stage)

        self.ui.btn_z_plus.clicked.connect(partial(self.axis_step, 2, 1))
        self.ui.btn_z_minus.clicked.connect(partial(self.axis_step, 2, -1))
        self.ui.btn_y_plus.clicked.connect(partial(self.axis_step, 1, 1))
        self.ui.btn_y_minus.clicked.connect(partial(self.axis_step, 1, -1))
        self.ui.btn_x_plus.clicked.connect(partial(self.axis_step, 0, 1))
        self.ui.btn_x_minus.clicked.connect(partial(self.axis_step, 0, -1))
        self.ui.btn_stop.clicked.connect(self.signal_stop)
        self.ui.btn_emergency.clicked.connect(self.signal_emergency)
        self.ui.btn_register_working.clicked.connect(partial(self.register_coordinates, 'working'))
        self.ui.btn_register_load.clicked.connect(partial(self.register_coordinates, 'load'))
        self.ui.btn_register_mouse.clicked.connect(partial(self.register_coordinates, 'mouse'))
        self.ui.le_mouse_id.editingFinished.connect(self.load_mouse_coordinates)

        self.ui.btn_working.clicked.connect(partial(self.move_to_coordinates, 'working'))
        self.ui.btn_load.clicked.connect(partial(self.move_to_coordinates, 'load'))
        self.ui.btn_mouse.clicked.connect(partial(self.move_to_coordinates, 'mouse'))


        self.icon_clear = QIcon(self.module_path[0] + '/resources/led_clear.png')
        self.icon_red = QIcon(self.module_path[0] + '/resources/led_red.png')
        self.icon_blue = QIcon(self.module_path[0] + '/resources/led_blue.png')
        self.icon_green = QIcon(self.module_path[0] + '/resources/led_green.png')

        self.ui.btn_x_release.clicked.connect(partial(self.set_clamp, 0))
        self.ui.btn_x_release.clicked.connect(partial(self.set_clamp, 1))
        self.ui.btn_x_release.clicked.connect(partial(self.set_clamp, 2))
        self.ui.btn_reset.clicked.connect(partial(self.set_clamp, 'reset'))
        self.ui.cb_limits.toggled.connect(self.signal_ignore_limits)

        for c, r in product([self.col_port, self.col_position], range(3)):
            item = QTableWidgetItem()
            item.setTextAlignment(QtCore.Qt.AlignCenter)
            self.ui.tbl_stage.setItem(r, c, item)

        for c, r in product([self.col_connected, self.col_moving, self.col_engaged, self.col_limit], range(3)):
            button = QPushButton()
            button.setFlat(True)
            button.setIcon(self.icon_clear)
            self.ui.tbl_stage.setCellWidget(r, c, button)

        channels = self.config.phidget.channels
        self.ui.tbl_stage.item(0, self.col_port).setText(str(channels.x))
        self.ui.tbl_stage.item(1, self.col_port).setText(str(channels.y))
        self.ui.tbl_stage.item(2, self.col_port).setText(str(channels.z))

        self.disable_user_controls()

    def set_clamp(self, axis):
        self.hw_proxy.clamp_signal(axis)

    def signal_emergency(self):
        self.signal_retract_lickspout()
        self.hw_proxy.stop_motion()

    def signal_ignore_limits(self, state):
        if self.ui.cb_limits.isChecked():
            self._ignore_limits = True
            self.hw_proxy.ignore_limits(True)
        else:
            self._ignore_limits = False
            self.hw_proxy.ignore_limits(False)

    def update_table(self):
        moving = self.stage.is_moving
        engaged = self.stage.is_engaged
        limits = self.stage.limits
        position = self.stage.position
        #self.ui.lbl_temperature.setText('Temp: ', signal_temp_value) 'need a refresh frequency'

        # if any(motion):
            # self.ui.lbl_encoder_position.setText('Position: ', signal_encoder_velocity)
        # else:
            # self.ui.lbl_encoder_position.setText('Position: ')

        #if any(motion):
            #self.ui.lbl_encoder_velocity.setText('Velocity: ', signal_encoder_velocity)
         #else:
            #self.ui.lbl_encoder_velocity.setText('Velocity: ')

        # if any(motion):
            # self.ui.lbl_encoder_acceleration.setText('Velocity: ', signal_encoder_velocity)
        # else:
            # self.ui.lbl_encoder_acceleration.setText('Velocity: ')

        if any(moving):
            self.ui.lbl_status.setText('Status: Stage Moving')
        else:
            self.ui.lbl_status.setText('Status:')

        for i in range(3):
            if moving[i]:
                self.ui.tbl_stage.cellWidget(i, self.col_moving).setIcon(self.icon_blue)
            else:
                self.ui.tbl_stage.cellWidget(i, self.col_moving).setIcon(self.icon_clear)
            if engaged[i]:
                self.ui.tbl_stage.cellWidget(i, self.col_engaged).setIcon(self.icon_blue)
            else:
                self.ui.tbl_stage.cellWidget(i, self.col_engaged).setIcon(self.icon_clear)
            if limits[i]:
                self.ui.tbl_stage.cellWidget(i, self.col_limit).setIcon(self.icon_red)
            else:
                self.ui.tbl_stage.cellWidget(i, self.col_limit).setIcon(self.icon_blue)
            self.ui.tbl_stage.item(i, self.col_position).setText(str(position[i]))

    def axis_step(self, axis, sign=1):
        """
        Step the axis by a value defined by sb_step.
        If the axis has hit it's limit switch, send the analog signal to release the break before sending a move_stage
        :param axis:
        :param sign:
        :param sb_step:
        :return:
        """
        if not self.stage:
            return

        if self.ui.le_step_size.text() != '':
            step = float(self.ui.le_step_size.text())
        else:
            step = self.config.phidget.step_size
        position = self.stage.position
        position[axis] += step * sign
        self.stage.append_move(position)

    def register_coordinates(self, name):
        """

        :param name:
        :param label:
        :return:
        """
        try:
            key = f'{self.stage.serial}_{name}'
            coords = list(self.stage.position)
            self.coordinates[name] = coords
            self.log('setting {name} to {coords}')
            self.db[key] = yaml.dump(coords)

            '''There is a hardware mixup preventing proper registration o Load/Unload.  For now it is set as an 
            x-translation o working'''

            if name == 'working':
                coords[0] += self.config.load_x_translation
                self.log(f'setting load to {coords}')
                self.coordinates['load'] = coords
                self.db[f'{self.hw_proxy.serial}_load'] = yaml.dump(coords)

                ''' b/c we can get stuck on the monument, we will retract the lickspout, move about, and extend again'''
                self.signal_retract_lickspout()
                coords = list(self.stage.position)
                coords[2] -= 10
                self.move_to_coordinates(coords)
                QTimer.singleShot(200, self.signal_extend_lickspout)

        except Exception as err:
            self.log(f'Error recording {name} coordinates to the db')
            self.log(f'{err}')

    def move_to_coordinates(self, name):

        coords = self.coordinates[name]
        try:
            self.log(f'Driving to {name}: {coords}')
            if coords:
                self.hw_proxy.move_to(coords)
        except KeyError as err:
            self.log(f'Error: {key} has not been registered.')

    def signal_stop(self):
        self.how_proxy.stop_motion()

    def signal_home_stage(self):
        self.hw_proxy.home_stage()

    def signal_retract_lickspout(self):
        self.hw_proxy.retract_lickspout()

    def signal_extend_lickspout(self):
        self.hw_proxy.extend_lickspout()

    def signal_water_sol_1(self):
        self.hw_proxy.signal_water_sol_1()

    def signal_water_sol_2(self):
        self.hw_proxy.signal_water_sol_2()

    def signal_temp_value(self):
        self.hw_proxy.signal_temp()

    def signal_encoder_movement(self):
        self.hw_proxy.signal_encoder_movement()

    def signal_move_to(self):
        try:
            x = float(self.ui.le_x.text())
            y = float(self.ui.le_y.text())
            z = float(self.ui.le_z.text())
            self.stage.append_move([x, y, z])
        except Exception as err:
            print('error:', err)
            pass

    def signal_connect_to_rig(self, host):
        host = 'localhost'

        self.stage = self.hw_proxy
        # self.daq = self.hw_proxy.daq
        # self.load_stage_coordinates()
        self.log(f'Connected to stage: {self.hw_proxy.stage_serial}')
        self.enable_user_controls()
        for i in range(3):
            self.ui.tbl_stage.cellWidget(i, self.col_connected).setIcon(self.icon_blue)
        self.table_timer.start(500)

    def disable_user_controls(self):
        user_controls = [self.ui.btn_x_minus, self.ui.btn_x_plus, self.ui.btn_y_minus, self.ui.btn_y_plus,
                         self.ui.btn_z_minus, self.ui.btn_z_plus, self.ui.btn_home, self.ui.btn_load,
                         self.ui.btn_working, self.ui.btn_mouse, self.ui.btn_stop ]

        for control in user_controls:
            control.setEnabled(False)

    def enable_user_controls(self):
        user_controls = [self.ui.btn_x_minus, self.ui.btn_x_plus, self.ui.btn_y_minus, self.ui.btn_y_plus,
                         self.ui.btn_z_minus, self.ui.btn_z_plus, self.ui.btn_home, self.ui.btn_load,
                         self.ui.btn_working, self.ui.btn_mouse, self.ui.btn_stop]

        for control in user_controls:
            control.setEnabled(True)

    def keyPressEvent(self, event):
        """

        :param event:
        :return:
        """
        modifiers = QApplication.keyboardModifiers()
        if event.key() == QtCore.Qt.Key_A:
            self.axis_step(0, -1)
        if event.key() == QtCore.Qt.Key_Space:
            if not self.admin_enabled:
                return

            if self._spout_state:
                self.signal_retract_lickspout()
                self._spout_state = 0
            else:
                self.signal_extend_lickspout()
                self._spout_state = 1

        if event.key() == QtCore.Qt.Key_D:
            if modifiers == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier):
                if self.admin_enabled:
                    self.admin_enabled = False
                else:
                    if self.authenticate_admin():
                        logging.info('Admin mode enabled.')
                        self.admin_enabled = True
            else:
                self.axis_step(0, 1)
        elif event.key() == QtCore.Qt.Key_W:
            self.axis_step(1, 1)
        elif event.key() == QtCore.Qt.Key_S:
            self.axis_step(1, -1)
        elif event.key() == QtCore.Qt.Key_Q:
            self.axis_step(2, -1)
        elif event.key() == QtCore.Qt.Key_E:
            self.axis_step(2, 1)

    def authenticate_admin(self):
        """


        :return:
        """
        text, ok = QInputDialog.getText(self, 'Administration Panel', 'Password:'******'Invalid Password')
                dialog.setText('Unable to enable administration mode.  Invalid Password.')

                dialog.exec_()
        return False
示例#50
0
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
示例#51
0
def main():
    """Execute QDarkStyle example."""
    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--qt_from', default='qtpy', type=str,
                        choices=['pyqt5', 'pyqt', 'pyside2', 'pyside', 'qtpy', 'pyqtgraph', 'qt.py'],
                        help="Choose which binding and/or abstraction is to be used to run the example.")
    parser.add_argument('--no_dark', action='store_true',
                        help="Exihibts the original window (without qdarkstyle).")
    parser.add_argument('--test', action='store_true',
                        help="Auto close window after 2s.")
    parser.add_argument('--reset', action='store_true',
                        help="Reset GUI settings (position, size) then opens.")
    parser.add_argument('--screenshots', action='store_true',
                        help="Generate screenshots on images folder.")

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

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

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

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

    # Set API_VERSION variable
    API_VERSION = ''

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

    # Import examples UI
    from mw_menus_ui import Ui_MainWindow as ui_main

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

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

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

    style = ''

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

    app.setStyleSheet(style)

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

    ui = ui_main()
    ui.setupUi(window)

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

    _logger.info(title)

    window.setWindowTitle(title)

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

    # Add actions on popup toolbuttons
    menu = QMenu()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    app.exec_()
    _write_settings(window, QSettings)
示例#52
0
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);", True: "", None: ""}
    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)
        hide = config_shortcut(self.hide,
                               context='_',
                               name='hide find and replace',
                               parent=self)

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

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

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

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

    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                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()
            highlighted = True
            # If no text is highlighted for search, use whatever word is under
            # the cursor
            if not text:
                highlighted = False
                try:
                    cursor = self.editor.textCursor()
                    cursor.select(QTextCursor.WordUnderCursor)
                    text = to_text_string(cursor.selectedText())
                except AttributeError:
                    # We can't do this for all widgets, e.g. WebView's
                    pass

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

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

    def show_replace(self):
        """Show replace widgets"""
        self.show()
        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.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("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            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
示例#53
0
文件: client.py 项目: wtheis/spyder
    def __init__(self,
                 plugin,
                 id_,
                 history_filename,
                 config_options,
                 additional_options,
                 interpreter_versions,
                 connection_file=None,
                 hostname=None,
                 menu_actions=None,
                 slave=False,
                 external_kernel=False,
                 given_name=None,
                 options_button=None,
                 show_elapsed_time=False,
                 reset_warning=True,
                 ask_before_restart=True,
                 css_path=None):
        super(ClientWidget, self).__init__(plugin)
        SaveHistoryMixin.__init__(self, history_filename)

        # --- Init attrs
        self.plugin = plugin
        self.id_ = id_
        self.connection_file = connection_file
        self.hostname = hostname
        self.menu_actions = menu_actions
        self.slave = slave
        self.external_kernel = external_kernel
        self.given_name = given_name
        self.show_elapsed_time = show_elapsed_time
        self.reset_warning = reset_warning
        self.ask_before_restart = ask_before_restart

        # --- Other attrs
        self.options_button = options_button
        self.stop_button = None
        self.reset_button = None
        self.stop_icon = ima.icon('stop')
        self.history = []
        self.allow_rename = True
        self.stderr_dir = None
        self.is_error_shown = False

        if css_path is None:
            self.css_path = CSS_PATH
        else:
            self.css_path = css_path

        # --- Widgets
        self.shellwidget = ShellWidget(
            config=config_options,
            ipyclient=self,
            additional_options=additional_options,
            interpreter_versions=interpreter_versions,
            external_kernel=external_kernel,
            local_kernel=True)

        self.infowidget = plugin.infowidget
        self.blank_page = self._create_blank_page()
        self.loading_page = self._create_loading_page()
        # To keep a reference to the page to be displayed
        # in infowidget
        self.info_page = None
        self._show_loading_page()

        # Elapsed time
        self.time_label = None
        self.t0 = time.monotonic()
        self.timer = QTimer(self)
        self.show_time_action = create_action(
            self,
            _("Show elapsed time"),
            toggled=self.set_elapsed_time_visible)

        # --- Layout
        self.layout = QVBoxLayout()
        toolbar_buttons = self.get_toolbar_buttons()

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.create_time_label())
        hlayout.addStretch(0)
        for button in toolbar_buttons:
            hlayout.addWidget(button)

        self.layout.addLayout(hlayout)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.shellwidget)
        self.layout.addWidget(self.infowidget)
        self.setLayout(self.layout)

        # --- Exit function
        self.exit_callback = lambda: plugin.close_client(client=self)

        # --- Dialog manager
        self.dialog_manager = DialogManager()

        # Show timer
        self.update_time_label_visibility()
示例#54
0
class QtFrameRate(QLabel):
    """A frame rate label with green/yellow/red LEDs.

    The LED values are logarithmic, so a lot of detail between 60Hz and
    10Hz but then the highest LED is many seconds long.
    """
    def __init__(self):
        super().__init__()

        self.leds = LedState()  # The per-LED config and state.

        # The last time we were updated, either from mouse move or our
        # timer. We update _last_time in both cases.
        self._last_time: Optional[float] = None

        # The bitmap image we draw into.
        self._image = np.zeros(BITMAP_SHAPE, dtype=np.uint8)

        # We animate on camera movements, but then we use a timer to
        # animate the display. When all the LEDs go off, we stop the timer
        # so that we use zero CPU until another camera movement.
        self._timer = QTimer()
        self._timer.setSingleShot(False)
        self._timer.setInterval(33)
        self._timer.timeout.connect(self._on_timer)

        # _print_calibration()  # Debugging.

    def _on_timer(self) -> None:
        """Animate the LEDs."""
        now = time.time()
        self._draw(now)  # Just animate and draw, no new peak.

        # Stop timer if nothing more to animation, save CPU.
        if self.leds.all_off():
            self._timer.stop()

    def _draw(self, now: float) -> None:
        """Animate the LEDs.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self.leds.update(now)  # Animates the LEDs.
        self._update_image(now)  # Draws our internal self._image
        self._update_bitmap()  # Writes self._image into the QLabel bitmap.

        # We always update _last_time whether this was from a camera move
        # or the timer. This is the right thing to do since in either case
        # it means a frame was drawn.
        self._last_time = now

    def on_camera_move(self) -> None:
        """Update our display to show the new framerate."""

        # Only count this frame if the timer is active. This avoids
        # displaying a potentially super long frame since it might have
        # been many seconds or minutes since the last camera movement.
        #
        # Ideally we should display the draw time of even that first frame,
        # but there's no easy/obvious way to do that today. And this will
        # show everthing except one frame.
        first_time = self._last_time is None
        use_delta = self._timer.isActive() and not first_time
        now = time.time()

        if use_delta:
            delta_seconds = now - self._last_time

            if monitor:
                monitor.add({"frame_time": delta_seconds})

            self.leds.set_peak(now, delta_seconds)

        self._draw(now)  # Draw the whole meter.

        # Since there was activity, we need to start the timer so we can
        # animate the decay of the LEDs. The timer will be shut off when
        # all the LEDs go idle.
        self._timer.start()

    def _update_image(self, now: float) -> None:
        """Update our self._image with the latest meter display.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self._image.fill(0)  # Start fresh each time.

        # Get colors with latest alpha values accord to decay.
        colors = self.leds.get_colors(now)

        # Draw each segment with the right color and alpha (due to decay).
        for index in range(NUM_SEGMENTS):
            x0 = int(index * SEGMENT_SPACING)
            x1 = int(x0 + SEGMENT_WIDTH)
            y0, y1 = 0, BITMAP_SHAPE[0]  # The whole height of the bitmap.
            self._image[y0:y1, x0:x1] = colors[index]

    def _update_bitmap(self) -> None:
        """Update the bitmap with latest image data."""
        height, width = BITMAP_SHAPE[:2]
        image = QImage(self._image, width, height, QImage.Format_RGBA8888)
        self.setPixmap(QPixmap.fromImage(image))
示例#55
0
    def __init__(self,
                 parent=None,
                 init_y_channels=[],
                 plot_by_timestamps=True,
                 background='default'):
        """
        Parameters
        ----------

        parent : Widget
            The parent widget of the chart.
        init_y_channels : list
            A list of scalar channels to plot vs time.
        plot_by_timestamps : bool
            If True, the x-axis shows timestamps as ticks, and those timestamps
            scroll to the left as time progresses.  If False, the x-axis tick
            marks show time relative to the current time.
        background : str, optional
            The background color for the plot.  Accepts any arguments that
            pyqtgraph.mkColor will accept.
        """
        self._plot_by_timestamps = plot_by_timestamps

        self._left_axis = AxisItem("left")
        if plot_by_timestamps:
            self._bottom_axis = TimeAxisItem('bottom')
        else:
            self.starting_epoch_time = time.time()
            self._bottom_axis = AxisItem('bottom')

        super(PyDMTimePlot, self).__init__(parent=parent,
                                           background=background,
                                           axisItems={
                                               "bottom": self._bottom_axis,
                                               "left": self._left_axis
                                           })

        # Removing the downsampling while PR 763 is not merged at pyqtgraph
        # Reference: https://github.com/pyqtgraph/pyqtgraph/pull/763
        # self.setDownsampling(ds=True, auto=True, mode="mean")

        if self._plot_by_timestamps:
            self.plotItem.disableAutoRange(ViewBox.XAxis)
            self.getViewBox().setMouseEnabled(x=False)
        else:
            self.plotItem.setRange(xRange=[DEFAULT_X_MIN, 0], padding=0)
            self.plotItem.setLimits(xMax=0)

        self._bufferSize = DEFAULT_BUFFER_SIZE

        self._time_span = DEFAULT_TIME_SPAN  # This is in seconds
        self._update_interval = DEFAULT_UPDATE_INTERVAL

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(self._update_interval)
        self._update_mode = PyDMTimePlot.SynchronousMode
        self._needs_redraw = True

        self.labels = {"left": None, "right": None, "bottom": None}

        self.units = {"left": None, "right": None, "bottom": None}

        for channel in init_y_channels:
            self.addYChannel(channel)
示例#56
0
class QtPerformance(QWidget):
    """Dockable widget to show performance info.

    Notes
    -----

    1) The progress bar doesn't show "progress", we use it as a bar graph to
       show the average duration of recent "UpdateRequest" events. This
       is actually not the total draw time, but it's generally the biggest
       part of each frame.

    2) We log any event whose duration is longer than the threshold.

    3) We show uptime so you can tell if this window is being updated at all.

    Attributes
    ----------
    start_time : float
        Time is seconds when widget was created.
    bar : QProgressBar
        The progress bar we use as your draw time indicator.
    thresh_ms : float
        Log events whose duration is longer then this.
    timer_label : QLabel
        We write the current "uptime" into this label.
    timer : QTimer
        To update our window every UPDATE_MS.
    """

    # We log events slower than some threshold (in milliseconds).
    THRESH_DEFAULT = 100
    THRESH_OPTIONS = [
        "1",
        "5",
        "10",
        "15",
        "20",
        "30",
        "40",
        "50",
        "100",
        "200",
    ]

    # Update at 250ms / 4Hz for now. The more we update more alive our
    # display will look, but the more we will slow things down.
    UPDATE_MS = 250

    def __init__(self):
        """Create our windgets."""
        super().__init__()
        layout = QVBoxLayout()

        # For our "uptime" timer.
        self.start_time = time.time()

        # Label for our progress bar.
        bar_label = QLabel(trans._("Draw Time:"))
        layout.addWidget(bar_label)

        # Progress bar is not used for "progress", it's just a bar graph to show
        # the "draw time", the duration of the "UpdateRequest" event.
        bar = QProgressBar()
        bar.setRange(0, 100)
        bar.setValue(50)
        bar.setFormat("%vms")
        layout.addWidget(bar)
        self.bar = bar

        # We let the user set the "slow event" threshold.
        self.thresh_ms = self.THRESH_DEFAULT
        self.thresh_combo = QComboBox()
        self.thresh_combo.addItems(self.THRESH_OPTIONS)
        self.thresh_combo.activated[str].connect(self._change_thresh)
        self.thresh_combo.setCurrentText(str(self.thresh_ms))

        combo_layout = QHBoxLayout()
        combo_layout.addWidget(QLabel(trans._("Show Events Slower Than:")))
        combo_layout.addWidget(self.thresh_combo)
        combo_layout.addWidget(QLabel(trans._("milliseconds")))
        combo_layout.addItem(
            QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        )
        layout.addLayout(combo_layout)

        # We log slow events to this window.
        self.log = TextLog()
        layout.addWidget(self.log)

        # Uptime label. To indicate if the widget is getting updated.
        label = QLabel('')
        layout.addWidget(label)
        self.timer_label = label

        self.setLayout(layout)

        # Update us with a timer.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.setInterval(self.UPDATE_MS)
        self.timer.start()

    def _change_thresh(self, text):
        """Threshold combo box change."""
        self.thresh_ms = float(text)
        self.log.clear()  # start fresh with this new threshold

    def _get_timer_info(self):
        """Get the information from the timers that we want to display."""
        average = None
        long_events = []

        # We don't update any GUI/widgets while iterating over the timers.
        # Updating widgets can create immediate Qt Events which would modify the
        # timers out from under us!
        for name, timer in perf.timers.timers.items():

            # The Qt Event "UpdateRequest" is the main "draw" event, so
            # that's what we use for our progress bar.
            if name == "UpdateRequest":
                average = timer.average

            # Log any "long" events to the text window.
            if timer.max >= self.thresh_ms:
                long_events.append((name, timer.max))

        return average, long_events

    def update(self):
        """Update our label and progress bar and log any new slow events."""
        # Update our timer label.
        elapsed = time.time() - self.start_time
        self.timer_label.setText(
            trans._("Uptime: {elapsed:.2f}", elapsed=elapsed)
        )

        average, long_events = self._get_timer_info()

        # Now safe to update the GUI: progress bar first.
        if average is not None:
            self.bar.setValue(int(average))

        # And log any new slow events.
        for name, time_ms in long_events:
            self.log.append(name, time_ms)

        # Clear all the timers since we've displayed them. They will immediately
        # start accumulating numbers for the next update.
        perf.timers.clear()
示例#57
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.replace_on = False
        self.replace_text_button = create_toolbutton(
            self,
            toggled=self.change_replace_state,
            icon=ima.icon('replace'),
            tip=_("Replace text"))
        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.replace_text_button, 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=_('Find next'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find,
            text_beside_icon=True)
        self.replace_sel_button = create_toolbutton(
            self,
            text=_('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=_('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 replace_widget(self, replace_on):
        """Show and hide replace widget"""
        if replace_on:
            self.show_replace()
        else:
            self.hide_replace()

    def change_replace_state(self):
        """Handle the change of the replace state widget."""
        self.replace_on = not self.replace_on
        self.replace_text_button.setChecked(self.replace_on)
        self.replace_widget(self.replace_on)

    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:
                cursor = QTextCursor(self.editor.textCursor())
                TextHelper(self.editor).unfold_if_colapsed(cursor)

                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
示例#58
0
    def __init__(self,
                 config_file,
                 run_model,
                 simulation_arguments,
                 parent=None):
        QDialog.__init__(self, parent)
        self.setWindowFlags(Qt.Window)
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setModal(True)
        self.setWindowModality(Qt.WindowModal)
        self.setWindowTitle("Simulations - {}".format(config_file))

        self._snapshot_model = SnapshotModel(self)
        self._run_model = run_model

        self._isDetailedDialog = False
        self._minimum_width = 1200

        ert = None
        if isinstance(run_model, BaseRunModel):
            ert = run_model.ert()

        self._simulations_argments = simulation_arguments

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._on_ticker)

        progress_proxy_model = ProgressProxyModel(self._snapshot_model,
                                                  parent=self)

        self._total_progress_label = QLabel(_TOTAL_PROGRESS_TEMPLATE.format(0),
                                            self)

        self._total_progress_bar = QProgressBar(self)
        self._total_progress_bar.setRange(0, 100)
        self._total_progress_bar.setTextVisible(False)

        self._iteration_progress_label = QLabel(self)

        self._progress_view = ProgressView(self)
        self._progress_view.setModel(progress_proxy_model)
        self._progress_view.setIndeterminate(True)

        legend_view = LegendView(self)
        legend_view.setModel(progress_proxy_model)

        self._tab_widget = QTabWidget(self)
        self._snapshot_model.rowsInserted.connect(self.on_new_iteration)

        self._job_label = QLabel(self)

        self._job_model = JobListProxyModel(self, 0, 0, 0, 0)
        self._job_model.setSourceModel(self._snapshot_model)

        self._job_view = QTableView(self)
        self._job_view.clicked.connect(self._job_clicked)
        self._open_files = {}
        self._job_view.setModel(self._job_model)

        self.running_time = QLabel("")

        self.plot_tool = PlotTool(config_file)
        self.plot_tool.setParent(self)
        self.plot_button = QPushButton(self.plot_tool.getName())
        self.plot_button.clicked.connect(self.plot_tool.trigger)
        self.plot_button.setEnabled(ert is not None)

        self.kill_button = QPushButton("Kill Simulations")
        self.done_button = QPushButton("Done")
        self.done_button.setHidden(True)
        self.restart_button = QPushButton("Restart")
        self.restart_button.setHidden(True)
        self.show_details_button = QPushButton("Show Details")
        self.show_details_button.setCheckable(True)

        size = 20
        spin_movie = resourceMovie("ide/loading.gif")
        spin_movie.setSpeed(60)
        spin_movie.setScaledSize(QSize(size, size))
        spin_movie.start()

        self.processing_animation = QLabel()
        self.processing_animation.setMaximumSize(QSize(size, size))
        self.processing_animation.setMinimumSize(QSize(size, size))
        self.processing_animation.setMovie(spin_movie)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.processing_animation)
        button_layout.addWidget(self.running_time)
        button_layout.addStretch()
        button_layout.addWidget(self.show_details_button)
        button_layout.addWidget(self.plot_button)
        button_layout.addWidget(self.kill_button)
        button_layout.addWidget(self.done_button)
        button_layout.addWidget(self.restart_button)

        button_widget_container = QWidget()
        button_widget_container.setLayout(button_layout)

        layout = QVBoxLayout()
        layout.addWidget(self._total_progress_label)
        layout.addWidget(self._total_progress_bar)
        layout.addWidget(self._iteration_progress_label)
        layout.addWidget(self._progress_view)
        layout.addWidget(legend_view)
        layout.addWidget(self._tab_widget)
        layout.addWidget(self._job_label)
        layout.addWidget(self._job_view)
        layout.addWidget(button_widget_container)

        self.setLayout(layout)

        self.kill_button.clicked.connect(self.killJobs)
        self.done_button.clicked.connect(self.accept)
        self.restart_button.clicked.connect(self.restart_failed_realizations)
        self.show_details_button.clicked.connect(self.toggle_detailed_progress)
        self.simulation_done.connect(self._on_simulation_done)

        self.setMinimumWidth(self._minimum_width)
        self._setSimpleDialog()
示例#59
0
    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)
示例#60
0
class NapariQtNotification(QDialog):
    """Notification dialog frame, appears at the bottom right of the canvas.

    By default, only the first line of the notification is shown, and the text
    is elided.  Double-clicking on the text (or clicking the chevron icon) will
    expand to show the full notification.  The dialog will autmatically
    disappear in ``DISMISS_AFTER`` milliseconds, unless hovered or clicked.

    Parameters
    ----------
    message : str
        The message that will appear in the notification
    severity : str or NotificationSeverity, optional
        Severity level {'error', 'warning', 'info', 'none'}.  Will determine
        the icon associated with the message.
        by default NotificationSeverity.WARNING.
    source : str, optional
        A source string for the notifcation (intended to show the module and
        or package responsible for the notification), by default None
    actions : list of tuple, optional
        A sequence of 2-tuples, where each tuple is a string and a callable.
        Each tuple will be used to create button in the dialog, where the text
        on the button is determine by the first item in the tuple, and a
        callback function to call when the button is pressed is the second item
        in the tuple. by default ()
    """

    MAX_OPACITY = 0.9
    FADE_IN_RATE = 220
    FADE_OUT_RATE = 120
    DISMISS_AFTER = 4000
    MIN_WIDTH = 400

    message: MultilineElidedLabel
    source_label: QLabel
    severity_icon: QLabel

    def __init__(
            self,
            message: str,
            severity: Union[str, NotificationSeverity] = 'WARNING',
            source: Optional[str] = None,
            actions: ActionSequence = (),
    ):
        """[summary]"""
        super().__init__(None)

        # FIXME: this does not work with multiple viewers.
        # we need a way to detect the viewer in which the error occured.
        for wdg in QApplication.topLevelWidgets():
            if isinstance(wdg, QMainWindow):
                try:
                    # TODO: making the canvas the parent makes it easier to
                    # move/resize, but also means that the notification can get
                    # clipped on the left if the canvas is too small.
                    canvas = wdg.centralWidget().children()[1].canvas.native
                    self.setParent(canvas)
                    canvas.resized.connect(self.move_to_bottom_right)
                    break
                except Exception:
                    pass
        self.setupUi()
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setup_buttons(actions)
        self.setMouseTracking(True)

        self.severity_icon.setText(NotificationSeverity(severity).as_icon())
        self.message.setText(message)
        if source:
            self.source_label.setText(
                trans._('Source: {source}', source=source))

        self.close_button.clicked.connect(self.close)
        self.expand_button.clicked.connect(self.toggle_expansion)

        self.timer = QTimer()
        self.opacity = QGraphicsOpacityEffect()
        self.setGraphicsEffect(self.opacity)
        self.opacity_anim = QPropertyAnimation(self.opacity, b"opacity", self)
        self.geom_anim = QPropertyAnimation(self, b"geometry", self)
        self.move_to_bottom_right()

    def move_to_bottom_right(self, offset=(8, 8)):
        """Position widget at the bottom right edge of the parent."""
        if not self.parent():
            return
        sz = self.parent().size() - self.size() - QSize(*offset)
        self.move(QPoint(sz.width(), sz.height()))

    def slide_in(self):
        """Run animation that fades in the dialog with a slight slide up."""
        geom = self.geometry()
        self.geom_anim.setDuration(self.FADE_IN_RATE)
        self.geom_anim.setStartValue(geom.translated(0, 20))
        self.geom_anim.setEndValue(geom)
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        # fade in
        self.opacity_anim.setDuration(self.FADE_IN_RATE)
        self.opacity_anim.setStartValue(0)
        self.opacity_anim.setEndValue(self.MAX_OPACITY)
        self.geom_anim.start()
        self.opacity_anim.start()

    def show(self):
        """Show the message with a fade and slight slide in from the bottom."""
        super().show()
        self.slide_in()
        if self.DISMISS_AFTER > 0:
            self.timer.setInterval(self.DISMISS_AFTER)
            self.timer.setSingleShot(True)
            self.timer.timeout.connect(self.close)
        self.timer.start()

    def mouseMoveEvent(self, event):
        """On hover, stop the self-destruct timer"""
        self.timer.stop()

    def mouseDoubleClickEvent(self, event):
        """Expand the notification on double click."""
        self.toggle_expansion()

    def close(self):
        """Fade out then close."""
        self.opacity_anim.setDuration(self.FADE_OUT_RATE)
        self.opacity_anim.setStartValue(self.MAX_OPACITY)
        self.opacity_anim.setEndValue(0)
        self.opacity_anim.start()
        self.opacity_anim.finished.connect(super().close)

    def toggle_expansion(self):
        """Toggle the expanded state of the notification frame."""
        self.contract() if self.property('expanded') else self.expand()
        self.timer.stop()

    def expand(self):
        """Expanded the notification so that the full message is visible."""
        curr = self.geometry()
        self.geom_anim.setDuration(100)
        self.geom_anim.setStartValue(curr)
        new_height = self.sizeHint().height()
        delta = new_height - curr.height()
        self.geom_anim.setEndValue(
            QRect(curr.x(),
                  curr.y() - delta, curr.width(), new_height))
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        self.geom_anim.start()
        self.setProperty('expanded', True)
        self.style().unpolish(self.expand_button)
        self.style().polish(self.expand_button)

    def contract(self):
        """Contract notification to a single elided line of the message."""
        geom = self.geometry()
        self.geom_anim.setDuration(100)
        self.geom_anim.setStartValue(geom)
        dlt = geom.height() - self.minimumHeight()
        self.geom_anim.setEndValue(
            QRect(geom.x(),
                  geom.y() + dlt, geom.width(),
                  geom.height() - dlt))
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        self.geom_anim.start()
        self.setProperty('expanded', False)
        self.style().unpolish(self.expand_button)
        self.style().polish(self.expand_button)

    def setupUi(self):
        """Set up the UI during initialization."""
        self.setWindowFlags(Qt.SubWindow)
        self.setMinimumWidth(self.MIN_WIDTH)
        self.setMaximumWidth(self.MIN_WIDTH)
        self.setMinimumHeight(40)
        self.setSizeGripEnabled(False)
        self.setModal(False)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setContentsMargins(2, 2, 2, 2)
        self.verticalLayout.setSpacing(0)

        self.row1_widget = QWidget(self)
        self.row1 = QHBoxLayout(self.row1_widget)
        self.row1.setContentsMargins(12, 12, 12, 8)
        self.row1.setSpacing(4)
        self.severity_icon = QLabel(self.row1_widget)
        self.severity_icon.setObjectName("severity_icon")
        self.severity_icon.setMinimumWidth(30)
        self.severity_icon.setMaximumWidth(30)
        self.row1.addWidget(self.severity_icon, alignment=Qt.AlignTop)
        self.message = MultilineElidedLabel(self.row1_widget)
        self.message.setMinimumWidth(self.MIN_WIDTH - 200)
        self.message.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.row1.addWidget(self.message, alignment=Qt.AlignTop)
        self.expand_button = QPushButton(self.row1_widget)
        self.expand_button.setObjectName("expand_button")
        self.expand_button.setCursor(Qt.PointingHandCursor)
        self.expand_button.setMaximumWidth(20)
        self.expand_button.setFlat(True)

        self.row1.addWidget(self.expand_button, alignment=Qt.AlignTop)
        self.close_button = QPushButton(self.row1_widget)
        self.close_button.setObjectName("close_button")
        self.close_button.setCursor(Qt.PointingHandCursor)
        self.close_button.setMaximumWidth(20)
        self.close_button.setFlat(True)

        self.row1.addWidget(self.close_button, alignment=Qt.AlignTop)
        self.verticalLayout.addWidget(self.row1_widget, 1)
        self.row2_widget = QWidget(self)
        self.row2_widget.hide()
        self.row2 = QHBoxLayout(self.row2_widget)
        self.source_label = QLabel(self.row2_widget)
        self.source_label.setObjectName("source_label")
        self.row2.addWidget(self.source_label, alignment=Qt.AlignBottom)
        self.row2.addStretch()
        self.row2.setContentsMargins(12, 2, 16, 12)
        self.row2_widget.setMaximumHeight(34)
        self.row2_widget.setStyleSheet('QPushButton{'
                                       'padding: 4px 12px 4px 12px; '
                                       'font-size: 11px;'
                                       'min-height: 18px; border-radius: 0;}')
        self.verticalLayout.addWidget(self.row2_widget, 0)
        self.setProperty('expanded', False)
        self.resize(self.MIN_WIDTH, 40)

    def setup_buttons(self, actions: ActionSequence = ()):
        """Add buttons to the dialog.

        Parameters
        ----------
        actions : tuple, optional
            A sequence of 2-tuples, where each tuple is a string and a
            callable. Each tuple will be used to create button in the dialog,
            where the text on the button is determine by the first item in the
            tuple, and a callback function to call when the button is pressed
            is the second item in the tuple. by default ()
        """
        if isinstance(actions, dict):
            actions = list(actions.items())

        for text, callback in actions:
            btn = QPushButton(text)

            def call_back_with_self(callback, self):
                """
                we need a higher order function this to capture the reference to self.
                """
                def _inner():
                    return callback(self)

                return _inner

            btn.clicked.connect(call_back_with_self(callback, self))
            btn.clicked.connect(self.close)
            self.row2.addWidget(btn)
        if actions:
            self.row2_widget.show()
            self.setMinimumHeight(self.row2_widget.maximumHeight() +
                                  self.minimumHeight())

    def sizeHint(self):
        """Return the size required to show the entire message."""
        return QSize(
            super().sizeHint().width(),
            self.row2_widget.height() + self.message.sizeHint().height(),
        )

    @classmethod
    def from_notification(cls,
                          notification: Notification) -> NapariQtNotification:

        from ...utils.notifications import ErrorNotification

        actions = notification.actions

        if isinstance(notification, ErrorNotification):

            def show_tb(parent):
                tbdialog = QDialog(parent=parent.parent())
                tbdialog.setModal(True)
                # this is about the minimum width to not get rewrap
                # and the minimum height to not have scrollbar
                tbdialog.resize(650, 270)
                tbdialog.setLayout(QVBoxLayout())

                text = QTextEdit()
                text.setHtml(notification.as_html())
                text.setReadOnly(True)
                tbdialog.layout().addWidget(text)
                tbdialog.show()

            actions = tuple(notification.actions) + (
                (trans._('View Traceback'), show_tb), )
        else:
            actions = notification.actions

        return cls(
            message=notification.message,
            severity=notification.severity,
            source=notification.source,
            actions=actions,
        )

    @classmethod
    def show_notification(cls, notification: Notification):
        from ...utils.settings import SETTINGS

        # after https://github.com/napari/napari/issues/2370,
        # the os.getenv can be removed (and NAPARI_CATCH_ERRORS retired)
        if (os.getenv("NAPARI_CATCH_ERRORS") not in ('0', 'False')
                and notification.severity >=
                SETTINGS.application.gui_notification_level):
            cls.from_notification(notification).show()