コード例 #1
0
 def update_finder(self, nsb, old_nsb):
     """Initialize or update finder widget."""
     if self.finder is None:
         # Initialize finder/search related widgets
         self.finder = QWidget(self)
         self.text_finder = NamespacesBrowserFinder(
             nsb.editor,
             callback=nsb.editor.set_regex,
             main=nsb,
             regex_base=VALID_VARIABLE_CHARS)
         self.finder.text_finder = self.text_finder
         self.finder_close_button = self.create_toolbutton(
             'close_finder',
             triggered=self.hide_finder,
             icon=self.create_icon('DialogCloseButton'),
         )
         finder_layout = QHBoxLayout()
         finder_layout.addWidget(self.finder_close_button)
         finder_layout.addWidget(self.text_finder)
         finder_layout.setContentsMargins(0, 0, 0, 0)
         self.finder.setLayout(finder_layout)
         self.layout().addWidget(self.finder)
     else:
         # Just update references to the same text_finder (Custom QLineEdit)
         # widget to the new current NamespaceBrowser and save current
         # finder state in the previous NamespaceBrowser
         if old_nsb is not None:
             self.save_finder_state(old_nsb)
         self.text_finder.update_parent(
             nsb.editor,
             callback=nsb.editor.set_regex,
             main=nsb,
         )
コード例 #2
0
def test_filtering_with_large_rows(namespacebrowser, qtbot):
    """
    Test that filtering works when there's a large number of rows.
    """
    browser = namespacebrowser

    text_finder = NamespacesBrowserFinder(
        browser.editor,
        callback=browser .editor.set_regex,
        main=browser,
        regex_base=VALID_VARIABLE_CHARS)
    browser.set_text_finder(text_finder)

    # Create data
    variables = {}
    for i in range(200):
        letter = string.ascii_lowercase[i // 10]
        var = letter + str(i)
        variables[var] = (
            {'type': 'int', 'size': 1, 'view': '1', 'python_type': 'int',
             'numpy_type': 'Unknown'}
        )

    # Set data
    browser.set_data(variables)

    # Assert we loaded the expected amount of data and that we can fetch
    # more data.
    model = browser.editor.model
    assert model.rowCount() == ROWS_TO_LOAD
    assert model.canFetchMore(QModelIndex())
    assert data(model, 49, 0) == 'e49'

    # Assert we can filter variables not loaded yet.
    qtbot.keyClicks(text_finder, "t19")
    assert model.rowCount() == 10

    # Assert all variables effectively start with 't19'.
    for i in range(10):
        assert data(model, i, 0) == 't19{}'.format(i)

    # Reset text_finder widget.
    text_finder.setText('')

    # Create a new variable that starts with a different letter than
    # the rest.
    new_variables = variables.copy()
    new_variables['z'] = (
        {'type': 'int', 'size': 1, 'view': '1', 'python_type': 'int',
         'numpy_type': 'Unknown'}
    )

    # Emulate the process of loading those variables after the
    # namespace view is sent from the kernel.
    browser.process_remote_view(new_variables)

    # Assert that can find 'z' among the declared variables.
    qtbot.keyClicks(text_finder, "z")
    assert model.rowCount() == 1
コード例 #3
0
def test_filter_rows(qtbot):
    """Test rows filtering."""

    df = pandas.DataFrame(['foo', 'bar'])
    editor = CollectionsEditorTableView(None, {'dfa': df, 'dfb': df})
    editor.finder = NamespacesBrowserFinder(editor,
                                            editor.set_regex)
    qtbot.addWidget(editor)

    # Initially two rows
    assert editor.model.rowCount() == 2

    # Match two rows by name
    editor.finder.setText("df")
    assert editor.model.rowCount() == 2

    # Match two rows by type
    editor.finder.setText("DataFrame")
    assert editor.model.rowCount() == 2

    # Only one match
    editor.finder.setText("dfb")
    assert editor.model.rowCount() == 1

    # No match
    editor.finder.setText("dfbc")
    assert editor.model.rowCount() == 0
コード例 #4
0
def test_filter_rows(qtbot):
    """Test rows filtering."""
    data = (
        {'dfa':
            {'type': 'DataFrame', 'size': (2, 1), 'color': '#00ff00',
             'view': 'Column names: 0'},
         'dfb':
            {'type': 'DataFrame', 'size': (2, 1), 'color': '#00ff00',
             'view': 'Column names: 0'}}
    )
    editor = RemoteCollectionsEditorTableView(None, data)
    editor.finder = NamespacesBrowserFinder(editor,
                                            editor.set_regex)
    qtbot.addWidget(editor)

    # Initially two rows
    assert editor.model.rowCount() == 2

    # Match two rows by name
    editor.finder.setText("df")
    assert editor.model.rowCount() == 2

    # Match two rows by type
    editor.finder.setText("DataFrame")
    assert editor.model.rowCount() == 2

    # Only one match
    editor.finder.setText("dfb")
    assert editor.model.rowCount() == 1

    # No match
    editor.finder.setText("dfbc")
    assert editor.model.rowCount() == 0
コード例 #5
0
ファイル: main_widget.py プロジェクト: wkx228/spyder
class VariableExplorerWidget(PluginMainWidget):

    # PluginMainWidget class constants
    ENABLE_SPINNER = True

    # Other class constants
    INITIAL_FREE_MEMORY_TIME_TRIGGER = 60 * 1000  # ms
    SECONDARY_FREE_MEMORY_TIME_TRIGGER = 180 * 1000  # ms

    # Signals
    sig_free_memory_requested = Signal()

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

        # Widgets
        self._stack = NamespaceStackedWidget(self)
        self._shellwidgets = {}
        self.context_menu = None
        self.empty_context_menu = None

        # --- Finder
        self.finder = None

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self._stack)
        # Note: Later with the addition of the first NamespaceBrowser the
        # find/search widget is added. See 'set_current_widget'
        self.setLayout(layout)

        # Signals
        self._stack.sig_free_memory_requested.connect(self.free_memory)
        self._stack.sig_start_spinner_requested.connect(self.start_spinner)
        self._stack.sig_stop_spinner_requested.connect(self.stop_spinner)
        self._stack.sig_hide_finder_requested.connect(self.hide_finder)

    # ---- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _('Variable Explorer')

    def get_focus_widget(self):
        return self.current_widget()

    def setup(self):
        # ---- Options menu actions
        exclude_private_action = self.create_action(
            VariableExplorerWidgetActions.ToggleExcludePrivate,
            text=_("Exclude private variables"),
            tip=_("Exclude variables that start with an underscore"),
            toggled=True,
            option='exclude_private',
        )

        exclude_uppercase_action = self.create_action(
            VariableExplorerWidgetActions.ToggleExcludeUpperCase,
            text=_("Exclude all-uppercase variables"),
            tip=_("Exclude variables whose name is uppercase"),
            toggled=True,
            option='exclude_uppercase',
        )

        exclude_capitalized_action = self.create_action(
            VariableExplorerWidgetActions.ToggleExcludeCapitalized,
            text=_("Exclude capitalized variables"),
            tip=_("Exclude variables whose name starts with a capital "
                  "letter"),
            toggled=True,
            option='exclude_capitalized',
        )

        exclude_unsupported_action = self.create_action(
            VariableExplorerWidgetActions.ToggleExcludeUnsupported,
            text=_("Exclude unsupported data types"),
            tip=_("Exclude references to data types that don't have "
                  "an specialized viewer or can't be edited."),
            toggled=True,
            option='exclude_unsupported',
        )

        exclude_callables_and_modules_action = self.create_action(
            VariableExplorerWidgetActions.ToggleExcludeCallablesAndModules,
            text=_("Exclude callables and modules"),
            tip=_("Exclude references to functions, modules and "
                  "any other callable."),
            toggled=True,
            option='exclude_callables_and_modules')

        self.show_minmax_action = self.create_action(
            VariableExplorerWidgetActions.ToggleMinMax,
            text=_("Show arrays min/max"),
            tip=_("Show minimum and maximum of arrays"),
            toggled=True,
            option='minmax')

        # ---- Toolbar actions
        import_data_action = self.create_action(
            VariableExplorerWidgetActions.ImportData,
            text=_('Import data'),
            icon=self.create_icon('fileimport'),
            triggered=lambda x: self.import_data(),
        )

        save_action = self.create_action(
            VariableExplorerWidgetActions.SaveData,
            text=_("Save data"),
            icon=self.create_icon('filesave'),
            triggered=lambda x: self.save_data(),
        )

        save_as_action = self.create_action(
            VariableExplorerWidgetActions.SaveDataAs,
            text=_("Save data as..."),
            icon=self.create_icon('filesaveas'),
            triggered=lambda x: self.save_data(),
        )

        reset_namespace_action = self.create_action(
            VariableExplorerWidgetActions.ResetNamespace,
            text=_("Remove all variables"),
            icon=self.create_icon('editdelete'),
            triggered=lambda x: self.reset_namespace(),
        )

        search_action = self.create_action(
            VariableExplorerWidgetActions.Search,
            text=_("Search variable names and types"),
            icon=self.create_icon('find'),
            toggled=self.show_finder,
            register_shortcut=True)

        refresh_action = self.create_action(
            VariableExplorerWidgetActions.Refresh,
            text=_("Refresh variables"),
            icon=self.create_icon('refresh'),
            triggered=self.refresh_table,
            register_shortcut=True,
        )

        # ---- Context menu actions
        resize_rows_action = self.create_action(
            VariableExplorerContextMenuActions.ResizeRowsAction,
            text=_("Resize rows to contents"),
            triggered=self.resize_rows)

        resize_columns_action = self.create_action(
            VariableExplorerContextMenuActions.ResizeColumnsAction,
            _("Resize columns to contents"),
            triggered=self.resize_columns)

        self.paste_action = self.create_action(
            VariableExplorerContextMenuActions.PasteAction,
            _("Paste"),
            icon=self.create_icon('editpaste'),
            triggered=self.paste)

        self.copy_action = self.create_action(
            VariableExplorerContextMenuActions.CopyAction,
            _("Copy"),
            icon=self.create_icon('editcopy'),
            triggered=self.copy)

        self.edit_action = self.create_action(
            VariableExplorerContextMenuActions.EditAction,
            _("Edit"),
            icon=self.create_icon('edit'),
            triggered=self.edit_item)

        self.plot_action = self.create_action(
            VariableExplorerContextMenuActions.PlotAction,
            _("Plot"),
            icon=self.create_icon('plot'),
            triggered=self.plot_item)
        self.plot_action.setVisible(False)

        self.hist_action = self.create_action(
            VariableExplorerContextMenuActions.HistogramAction,
            _("Histogram"),
            icon=self.create_icon('hist'),
            triggered=self.histogram_item)
        self.hist_action.setVisible(False)

        self.imshow_action = self.create_action(
            VariableExplorerContextMenuActions.ImshowAction,
            _("Show image"),
            icon=self.create_icon('imshow'),
            triggered=self.imshow_item)
        self.imshow_action.setVisible(False)

        self.save_array_action = self.create_action(
            VariableExplorerContextMenuActions.SaveArrayAction,
            _("Save array"),
            icon=self.create_icon('filesave'),
            triggered=self.save_array)
        self.save_array_action.setVisible(False)

        self.insert_action = self.create_action(
            VariableExplorerContextMenuActions.InsertAction,
            _("Insert"),
            icon=self.create_icon('insert'),
            triggered=self.insert_item)

        self.remove_action = self.create_action(
            VariableExplorerContextMenuActions.RemoveAction,
            _("Remove"),
            icon=self.create_icon('editdelete'),
            triggered=self.remove_item)

        self.rename_action = self.create_action(
            VariableExplorerContextMenuActions.RenameAction,
            _("Rename"),
            icon=self.create_icon('rename'),
            triggered=self.rename_item)

        self.duplicate_action = self.create_action(
            VariableExplorerContextMenuActions.DuplicateAction,
            _("Duplicate"),
            icon=self.create_icon('edit_add'),
            triggered=self.duplicate_item)

        self.view_action = self.create_action(
            VariableExplorerContextMenuActions.ViewAction,
            _("View with the Object Explorer"),
            icon=self.create_icon('outline_explorer'),
            triggered=self.view_item)

        # Options menu
        options_menu = self.get_options_menu()
        for item in [
                exclude_private_action, exclude_uppercase_action,
                exclude_capitalized_action, exclude_unsupported_action,
                exclude_callables_and_modules_action, self.show_minmax_action
        ]:
            self.add_item_to_menu(
                item,
                menu=options_menu,
                section=VariableExplorerWidgetOptionsMenuSections.Display,
            )

        # Main toolbar
        main_toolbar = self.get_main_toolbar()
        for item in [
                import_data_action, save_action, save_as_action,
                reset_namespace_action, search_action, refresh_action
        ]:
            self.add_item_to_toolbar(
                item,
                toolbar=main_toolbar,
                section=VariableExplorerWidgetMainToolBarSections.Main,
            )
        save_action.setEnabled(False)

        # ---- Context menu to show when there are variables present
        self.context_menu = self.create_menu(
            VariableExplorerWidgetMenus.PopulatedContextMenu)
        for item in [
                self.edit_action, self.plot_action, self.hist_action,
                self.imshow_action, self.save_array_action, self.insert_action,
                self.remove_action, self.copy_action, self.paste_action,
                self.view_action
        ]:
            self.add_item_to_menu(
                item,
                menu=self.context_menu,
                section=VariableExplorerContextMenuSections.Edit,
            )

        for item in [self.rename_action, self.duplicate_action]:
            self.add_item_to_menu(
                item,
                menu=self.context_menu,
                section=VariableExplorerContextMenuSections.Rename,
            )

        for item in [
                resize_rows_action, resize_columns_action,
                self.show_minmax_action
        ]:
            self.add_item_to_menu(
                item,
                menu=self.context_menu,
                section=VariableExplorerContextMenuSections.Resize,
            )

        # ---- Context menu when the variable explorer is empty
        self.empty_context_menu = self.create_menu(
            VariableExplorerWidgetMenus.EmptyContextMenu)
        for item in [self.insert_action, self.paste_action]:
            self.add_item_to_menu(
                item,
                menu=self.empty_context_menu,
                section=VariableExplorerContextMenuSections.Edit,
            )

    def update_style(self):
        self._stack.setStyleSheet(
            "NamespaceStackedWidget {padding: 0px; border: 0px}")

    def update_actions(self):
        action = self.get_action(VariableExplorerWidgetActions.ToggleMinMax)
        action.setEnabled(is_module_installed('numpy'))
        nsb = self.current_widget()

        for __, action in self.get_actions().items():
            if action:
                # IMPORTANT: Since we are defining the main actions in here
                # and the context is WidgetWithChildrenShortcut we need to
                # assign the same actions to the children widgets in order
                # for shortcuts to work
                if nsb:
                    save_data_action = self.get_action(
                        VariableExplorerWidgetActions.SaveData)
                    save_data_action.setEnabled(nsb.filename is not None)

                    nsb_actions = nsb.actions()
                    if action not in nsb_actions:
                        nsb.addAction(action)

    @on_conf_change
    def on_section_conf_change(self, section):
        for index in range(self.count()):
            widget = self._stack.widget(index)
            if widget:
                widget.setup()

    # ---- Stack accesors
    # ------------------------------------------------------------------------
    def add_widget(self, nsb):
        self._stack.addWidget(nsb)

    def count(self):
        return self._stack.count()

    def current_widget(self):
        return self._stack.currentWidget()

    def remove_widget(self, nsb):
        self._stack.removeWidget(nsb)

    def update_finder(self, nsb, old_nsb):
        """Initialize or update finder widget."""
        if self.finder is None:
            # Initialize finder/search related widgets
            self.finder = QWidget(self)
            self.text_finder = NamespacesBrowserFinder(
                nsb.editor,
                callback=nsb.editor.set_regex,
                main=nsb,
                regex_base=VALID_VARIABLE_CHARS)
            self.finder.text_finder = self.text_finder
            self.finder_close_button = self.create_toolbutton(
                'close_finder',
                triggered=self.hide_finder,
                icon=self.create_icon('DialogCloseButton'),
            )

            finder_layout = QHBoxLayout()
            finder_layout.addWidget(self.finder_close_button)
            finder_layout.addWidget(self.text_finder)
            finder_layout.setContentsMargins(0, 0, 0, 0)
            self.finder.setLayout(finder_layout)

            layout = self.layout()
            layout.addSpacing(1)
            layout.addWidget(self.finder)
        else:
            # Just update references to the same text_finder (Custom QLineEdit)
            # widget to the new current NamespaceBrowser and save current
            # finder state in the previous NamespaceBrowser
            if old_nsb is not None:
                self.save_finder_state(old_nsb)
            self.text_finder.update_parent(
                nsb.editor,
                callback=nsb.editor.set_regex,
                main=nsb,
            )

    def set_current_widget(self, nsb, old_nsb):
        """
        Set the current NamespaceBrowser.

        This also setup the finder widget to work with the current
        NamespaceBrowser.
        """
        self.update_finder(nsb, old_nsb)
        finder_visible = nsb.set_text_finder(self.text_finder)
        self._stack.setCurrentWidget(nsb)
        self.finder.setVisible(finder_visible)
        search_action = self.get_action(VariableExplorerWidgetActions.Search)
        search_action.setChecked(finder_visible)

    # ---- Public API
    # ------------------------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        """
        Register shell with variable explorer.

        This function creates a new NamespaceBrowser for browsing
        variables in the shell.
        """
        shellwidget_id = id(shellwidget)
        if shellwidget_id not in self._shellwidgets:
            old_nsb = self.current_widget()
            nsb = NamespaceBrowser(self)
            nsb.set_shellwidget(shellwidget)
            nsb.setup()
            self.add_widget(nsb)
            self._set_actions_and_menus(nsb)
            self._shellwidgets[shellwidget_id] = nsb
            self.set_current_widget(nsb, old_nsb)
            self.update_actions()
            return nsb

    def remove_shellwidget(self, shellwidget):
        shellwidget_id = id(shellwidget)
        if shellwidget_id in self._shellwidgets:
            nsb = self._shellwidgets.pop(shellwidget_id)
            self.remove_widget(nsb)
            nsb.close()

    def set_shellwidget(self, shellwidget):
        shellwidget_id = id(shellwidget)
        old_nsb = self.current_widget()
        if shellwidget_id in self._shellwidgets:
            nsb = self._shellwidgets[shellwidget_id]
            self.set_current_widget(nsb, old_nsb)

    def import_data(self, filenames=None):
        """
        Import data in current namespace.
        """
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()
            nsb.import_data(filenames=filenames)

    def save_data(self):
        if self.count():
            nsb = self.current_widget()
            nsb.save_data()
            self.update_actions()

    def reset_namespace(self):
        if self.count():
            nsb = self.current_widget()
            nsb.reset_namespace()

    @Slot(bool)
    def show_finder(self, checked):
        if self.count():
            nsb = self.current_widget()
            if checked:
                self.finder.text_finder.setText(nsb.last_find)
            else:
                self.save_finder_state(nsb)
                self.finder.text_finder.setText('')
            self.finder.setVisible(checked)
            if self.finder.isVisible():
                self.finder.text_finder.setFocus()
            else:
                nsb.editor.setFocus()

    @Slot()
    def hide_finder(self):
        action = self.get_action(VariableExplorerWidgetActions.Search)
        action.setChecked(False)
        nsb = self.current_widget()
        self.save_finder_state(nsb)
        self.finder.text_finder.setText('')

    def save_finder_state(self, nsb):
        """
        Save finder state (last input text and visibility).

        The values are saved in the given NamespaceBrowser.
        """
        last_find = self.text_finder.text()
        finder_visibility = self.finder.isVisible()
        nsb.save_finder_state(last_find, finder_visibility)

    def refresh_table(self):
        if self.count():
            nsb = self.current_widget()
            nsb.refresh_table()

    @Slot()
    def free_memory(self):
        """
        Free memory signal.
        """
        self.sig_free_memory_requested.emit()
        QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER,
                          self.sig_free_memory_requested.emit)
        QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
                          self.sig_free_memory_requested.emit)

    def resize_rows(self):
        self._current_editor.resizeRowsToContents()

    def resize_columns(self):
        self._current_editor.resize_column_contents()

    def paste(self):
        self._current_editor.paste()

    def copy(self):
        self._current_editor.copy()

    def edit_item(self):
        self._current_editor.edit_item()

    def plot_item(self):
        self._current_editor.plot_item('plot')

    def histogram_item(self):
        self._current_editor.plot_item('hist')

    def imshow_item(self):
        self._current_editor.imshow_item()

    def save_array(self):
        self._current_editor.save_array()

    def insert_item(self):
        self._current_editor.insert_item(below=False)

    def remove_item(self):
        self._current_editor.remove_item()

    def rename_item(self):
        self._current_editor.rename_item()

    def duplicate_item(self):
        self._current_editor.duplicate_item()

    def view_item(self):
        self._current_editor.view_item()

    # ---- Private API
    # ------------------------------------------------------------------------
    @property
    def _current_editor(self):
        editor = None
        if self.count():
            nsb = self.current_widget()
            editor = nsb.editor
        return editor

    def _set_actions_and_menus(self, nsb):
        """
        Set actions and menus created here and used by the namespace
        browser editor.

        Although this is not ideal, it's necessary to be able to use
        the CollectionsEditor widget separately from this plugin.
        """
        editor = nsb.editor

        # Actions
        editor.paste_action = self.paste_action
        editor.copy_action = self.copy_action
        editor.edit_action = self.edit_action
        editor.plot_action = self.plot_action
        editor.hist_action = self.hist_action
        editor.imshow_action = self.imshow_action
        editor.save_array_action = self.save_array_action
        editor.insert_action = self.insert_action
        editor.remove_action = self.remove_action
        editor.minmax_action = self.show_minmax_action
        editor.rename_action = self.rename_action
        editor.duplicate_action = self.duplicate_action
        editor.view_action = self.view_action

        # Menus
        editor.menu = self.context_menu
        editor.empty_ws_menu = self.empty_context_menu

        # These actions are not used for dictionaries (so we don't need them
        # for namespaces) but we have to create them so they can be used in
        # several places in CollectionsEditor.
        editor.insert_action_above = QAction()
        editor.insert_action_below = QAction()