Skip to content

Commit

Permalink
Merge pull request #21312 from jitseniesen/refresh
Browse files Browse the repository at this point in the history
PR: Add Refresh button to editors from Variable Explorer
  • Loading branch information
ccordoba12 authored Nov 25, 2023
2 parents 6e315af + 35165f1 commit 4749cb2
Show file tree
Hide file tree
Showing 11 changed files with 1,289 additions and 349 deletions.
517 changes: 341 additions & 176 deletions spyder/plugins/variableexplorer/widgets/arrayeditor.py

Large diffs are not rendered by default.

143 changes: 131 additions & 12 deletions spyder/plugins/variableexplorer/widgets/collectionsdelegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
import datetime
import functools
import operator
from typing import Any, Callable, Optional

# Third party imports
from qtpy.compat import to_qvariant
from qtpy.QtCore import QDateTime, Qt, Signal
from qtpy.QtWidgets import (QAbstractItemDelegate, QDateEdit, QDateTimeEdit,
QItemDelegate, QLineEdit, QMessageBox, QTableView)
from qtpy.QtCore import QDateTime, QModelIndex, Qt, Signal
from qtpy.QtWidgets import (
QAbstractItemDelegate, QDateEdit, QDateTimeEdit, QItemDelegate, QLineEdit,
QMessageBox, QTableView)
from spyder_kernels.utils.lazymodules import (
FakeObject, numpy as np, pandas as pd, PIL)
from spyder_kernels.utils.nsview import (display_to_value, is_editable_type,
Expand All @@ -43,9 +45,15 @@ class CollectionsDelegate(QItemDelegate, SpyderFontsMixin):
sig_editor_creation_started = Signal()
sig_editor_shown = Signal()

def __init__(self, parent=None, namespacebrowser=None):
def __init__(
self,
parent=None,
namespacebrowser=None,
data_function: Optional[Callable[[], Any]] = None
):
QItemDelegate.__init__(self, parent)
self.namespacebrowser = namespacebrowser
self.data_function = data_function
self._editors = {} # keep references on opened editors

def get_value(self, index):
Expand All @@ -56,6 +64,49 @@ def set_value(self, index, value):
if index.isValid():
index.model().set_value(index, value)

def make_data_function(
self,
index: QModelIndex
) -> Optional[Callable[[], Any]]:
"""
Construct function which returns current value of data.
This is used to refresh editors created from this piece of data.
For instance, if `self` is the delegate for an editor that displays
the dict `xxx` and the user opens another editor for `xxx["aaa"]`,
then to refresh the data of the second editor, the nested function
`datafun` first gets the refreshed data for `xxx` and then gets the
item with key "aaa".
Parameters
----------
index : QModelIndex
Index of item whose current value is to be returned by the
function constructed here.
Returns
-------
Optional[Callable[[], Any]]
Function which returns the current value of the data, or None if
such a function cannot be constructed.
"""
if self.data_function is None:
return None
key = index.model().keys[index.row()]

def datafun():
data = self.data_function()
if isinstance(data, (tuple, list, dict, set)):
return data[key]

try:
return getattr(data, key)
except (NotImplementedError, AttributeError,
TypeError, ValueError):
return None

return datafun

def show_warning(self, index):
"""
Decide if showing a warning when the user is trying to view
Expand Down Expand Up @@ -163,7 +214,10 @@ def createEditor(self, parent, option, index, object_explorer=False):
elif isinstance(value, (list, set, tuple, dict)) and not object_explorer:
from spyder.widgets.collectionseditor import CollectionsEditor
editor = CollectionsEditor(
parent=parent, namespacebrowser=self.namespacebrowser)
parent=parent,
namespacebrowser=self.namespacebrowser,
data_function=self.make_data_function(index)
)
editor.setup(value, key, icon=self.parent().windowIcon(),
readonly=readonly)
self.create_dialog(editor, dict(model=index.model(), editor=editor,
Expand All @@ -174,7 +228,10 @@ def createEditor(self, parent, option, index, object_explorer=False):
np.ndarray is not FakeObject and not object_explorer):
# We need to leave this import here for tests to pass.
from .arrayeditor import ArrayEditor
editor = ArrayEditor(parent=parent)
editor = ArrayEditor(
parent=parent,
data_function=self.make_data_function(index)
)
if not editor.setup_and_check(value, title=key, readonly=readonly):
self.sig_editor_shown.emit()
return
Expand Down Expand Up @@ -205,7 +262,10 @@ def createEditor(self, parent, option, index, object_explorer=False):
and pd.DataFrame is not FakeObject and not object_explorer):
# We need to leave this import here for tests to pass.
from .dataframeeditor import DataFrameEditor
editor = DataFrameEditor(parent=parent)
editor = DataFrameEditor(
parent=parent,
data_function=self.make_data_function(index)
)
if not editor.setup_and_check(value, title=key):
self.sig_editor_shown.emit()
return
Expand Down Expand Up @@ -272,6 +332,7 @@ def createEditor(self, parent, option, index, object_explorer=False):
name=key,
parent=parent,
namespacebrowser=self.namespacebrowser,
data_function=self.make_data_function(index),
readonly=readonly)
self.create_dialog(editor, dict(model=index.model(),
editor=editor,
Expand Down Expand Up @@ -414,8 +475,12 @@ def updateEditorGeometry(self, editor, option, index):

class ToggleColumnDelegate(CollectionsDelegate):
"""ToggleColumn Item Delegate"""
def __init__(self, parent=None, namespacebrowser=None):
CollectionsDelegate.__init__(self, parent, namespacebrowser)

def __init__(self, parent=None, namespacebrowser=None,
data_function: Optional[Callable[[], Any]] = None):
CollectionsDelegate.__init__(
self, parent, namespacebrowser, data_function
)
self.current_index = None
self.old_obj = None

Expand All @@ -435,6 +500,51 @@ def set_value(self, index, value):
if index.isValid():
index.model().set_value(index, value)

def make_data_function(
self,
index: QModelIndex
) -> Optional[Callable[[], Any]]:
"""
Construct function which returns current value of data.
This is used to refresh editors created from this piece of data.
For instance, if `self` is the delegate for an editor displays the
object `obj` and the user opens another editor for `obj.xxx.yyy`,
then to refresh the data of the second editor, the nested function
`datafun` first gets the refreshed data for `obj` and then gets the
`xxx` attribute and then the `yyy` attribute.
Parameters
----------
index : QModelIndex
Index of item whose current value is to be returned by the
function constructed here.
Returns
-------
Optional[Callable[[], Any]]
Function which returns the current value of the data, or None if
such a function cannot be constructed.
"""
if self.data_function is None:
return None

obj_path = index.model().get_key(index).obj_path
path_elements = obj_path.split('.')
del path_elements[0] # first entry is variable name

def datafun():
data = self.data_function()
try:
for attribute_name in path_elements:
data = getattr(data, attribute_name)
return data
except (NotImplementedError, AttributeError,
TypeError, ValueError):
return None

return datafun

def createEditor(self, parent, option, index):
"""Overriding method createEditor"""
if self.show_warning(index):
Expand Down Expand Up @@ -471,7 +581,10 @@ def createEditor(self, parent, option, index):
if isinstance(value, (list, set, tuple, dict)):
from spyder.widgets.collectionseditor import CollectionsEditor
editor = CollectionsEditor(
parent=parent, namespacebrowser=self.namespacebrowser)
parent=parent,
namespacebrowser=self.namespacebrowser,
data_function=self.make_data_function(index)
)
editor.setup(value, key, icon=self.parent().windowIcon(),
readonly=readonly)
self.create_dialog(editor, dict(model=index.model(), editor=editor,
Expand All @@ -480,7 +593,10 @@ def createEditor(self, parent, option, index):
# ArrayEditor for a Numpy array
elif (isinstance(value, (np.ndarray, np.ma.MaskedArray)) and
np.ndarray is not FakeObject):
editor = ArrayEditor(parent=parent)
editor = ArrayEditor(
parent=parent,
data_function=self.make_data_function(index)
)
if not editor.setup_and_check(value, title=key, readonly=readonly):
return
self.create_dialog(editor, dict(model=index.model(), editor=editor,
Expand All @@ -501,7 +617,10 @@ def createEditor(self, parent, option, index):
# DataFrameEditor for a pandas dataframe, series or index
elif (isinstance(value, (pd.DataFrame, pd.Index, pd.Series))
and pd.DataFrame is not FakeObject):
editor = DataFrameEditor(parent=parent)
editor = DataFrameEditor(
parent=parent,
data_function=self.make_data_function(index)
)
if not editor.setup_and_check(value, title=key):
return
self.create_dialog(editor, dict(model=index.model(), editor=editor,
Expand Down
Loading

0 comments on commit 4749cb2

Please sign in to comment.