File: C:/Users/fred/anaconda3/Lib/site-packages/spyder/plugins/variableexplorer/widgets/main_widget.py
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Variable Explorer Main Plugin Widget.
"""
# Third party imports
from qtpy.QtCore import QTimer, Slot
from qtpy.QtWidgets import QAction, QHBoxLayout, QWidget
# Local imports
from spyder.api.config.decorators import on_conf_change
from spyder.api.translations import _
from spyder.api.shellconnect.main_widget import ShellConnectMainWidget
from spyder.plugins.variableexplorer.widgets.namespacebrowser import (
NamespaceBrowser, NamespacesBrowserFinder, VALID_VARIABLE_CHARS)
from spyder.utils.programs import is_module_installed
# =============================================================================
# ---- Constants
# =============================================================================
class VariableExplorerWidgetActions:
# Triggers
ImportData = 'import_data_action'
SaveData = 'save_data_action'
SaveDataAs = 'save_data_as_action'
ResetNamespace = 'reset_namespaces_action'
Search = 'search'
Refresh = 'refresh'
# Toggles
ToggleExcludePrivate = 'toggle_exclude_private_action'
ToggleExcludeUpperCase = 'toggle_exclude_uppercase_action'
ToggleExcludeCapitalized = 'toggle_exclude_capitalized_action'
ToggleExcludeUnsupported = 'toggle_exclude_unsupported_action'
ToggleExcludeCallablesAndModules = (
'toggle_exclude_callables_and_modules_action')
ToggleMinMax = 'toggle_minmax_action'
class VariableExplorerWidgetOptionsMenuSections:
Display = 'excludes_section'
Highlight = 'highlight_section'
class VariableExplorerWidgetMainToolBarSections:
Main = 'main_section'
class VariableExplorerWidgetMenus:
EmptyContextMenu = 'empty'
PopulatedContextMenu = 'populated'
class VariableExplorerContextMenuActions:
ResizeRowsAction = 'resize_rows_action'
ResizeColumnsAction = 'resize_columns_action'
PasteAction = 'paste_action'
CopyAction = 'copy'
EditAction = 'edit_action'
PlotAction = 'plot_action'
HistogramAction = 'histogram_action'
ImshowAction = 'imshow_action'
SaveArrayAction = 'save_array_action'
InsertAction = 'insert_action'
RemoveAction = 'remove_action'
RenameAction = 'rename_action'
DuplicateAction = 'duplicate_action'
ViewAction = 'view_action'
class VariableExplorerContextMenuSections:
Edit = 'edit_section'
Insert = 'insert_section'
View = 'view_section'
Resize = 'resize_section'
# =============================================================================
# ---- Widgets
# =============================================================================
class VariableExplorerWidget(ShellConnectMainWidget):
# 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
def __init__(self, name=None, plugin=None, parent=None):
super().__init__(name, plugin, parent)
# Widgets
self.context_menu = None
self.empty_context_menu = None
# --- Finder
self.finder = None
# ---- PluginMainWidget API
# ------------------------------------------------------------------------
def get_title(self):
return _('Variable Explorer')
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"),
icon=self.create_icon('collapse_row'),
triggered=self.resize_rows
)
resize_columns_action = self.create_action(
VariableExplorerContextMenuActions.ResizeColumnsAction,
_("Resize columns to contents"),
icon=self.create_icon('collapse_column'),
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.copy_action, self.paste_action,
self.rename_action, self.remove_action,
self.save_array_action]:
self.add_item_to_menu(
item,
menu=self.context_menu,
section=VariableExplorerContextMenuSections.Edit,
)
for item in [self.insert_action, self.duplicate_action]:
self.add_item_to_menu(
item,
menu=self.context_menu,
section=VariableExplorerContextMenuSections.Insert,
)
for item in [self.view_action, self.plot_action, self.hist_action,
self.imshow_action, self.show_minmax_action]:
self.add_item_to_menu(
item,
menu=self.context_menu,
section=VariableExplorerContextMenuSections.View,
)
for item in [resize_rows_action, resize_columns_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_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 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 switch_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.finder.setVisible(finder_visible)
search_action = self.get_action(VariableExplorerWidgetActions.Search)
search_action.setChecked(finder_visible)
# ---- Public API
# ------------------------------------------------------------------------
def create_new_widget(self, shellwidget):
nsb = NamespaceBrowser(self)
nsb.set_shellwidget(shellwidget)
nsb.setup()
nsb.sig_free_memory_requested.connect(
self.free_memory)
nsb.sig_start_spinner_requested.connect(
self.start_spinner)
nsb.sig_stop_spinner_requested.connect(
self.stop_spinner)
nsb.sig_hide_finder_requested.connect(
self.hide_finder)
self._set_actions_and_menus(nsb)
return nsb
def close_widget(self, nsb):
nsb.close()
nsb.setParent(None)
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)
QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER,
self.sig_free_memory_requested)
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()