-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathobjecteditor.py
175 lines (146 loc) · 5.78 KB
/
objecteditor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Generic object editor dialog
"""
# Standard library imports
import datetime
# Third party imports
from qtpy.QtCore import QObject
from spyder_kernels.utils.lazymodules import (
FakeObject, numpy as np, pandas as pd, PIL)
from spyder_kernels.utils.nsview import is_known_type
# Local imports
from spyder.py3compat import is_text_string
from spyder.plugins.variableexplorer.widgets.arrayeditor import ArrayEditor
from spyder.plugins.variableexplorer.widgets.dataframeeditor import (
DataFrameEditor)
from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor
from spyder.widgets.collectionseditor import CollectionsEditor
class DialogKeeper(QObject):
def __init__(self):
QObject.__init__(self)
self.dialogs = {}
self.namespace = None
def set_namespace(self, namespace):
self.namespace = namespace
def create_dialog(self, dialog, refname, func):
self.dialogs[id(dialog)] = dialog, refname, func
dialog.accepted.connect(
lambda eid=id(dialog): self.editor_accepted(eid))
dialog.rejected.connect(
lambda eid=id(dialog): self.editor_rejected(eid))
dialog.show()
dialog.activateWindow()
dialog.raise_()
def editor_accepted(self, dialog_id):
dialog, refname, func = self.dialogs[dialog_id]
self.namespace[refname] = func(dialog)
self.dialogs.pop(dialog_id)
def editor_rejected(self, dialog_id):
self.dialogs.pop(dialog_id)
keeper = DialogKeeper()
def create_dialog(obj, obj_name):
"""Creates the editor dialog and returns a tuple (dialog, func) where func
is the function to be called with the dialog instance as argument, after
quitting the dialog box
The role of this intermediate function is to allow easy monkey-patching.
(uschmitt suggested this indirection here so that he can monkey patch
oedit to show eMZed related data)
"""
# Local import
conv_func = lambda data: data
readonly = not is_known_type(obj)
if isinstance(obj, np.ndarray) and np.ndarray is not FakeObject:
dialog = ArrayEditor()
if not dialog.setup_and_check(obj, title=obj_name,
readonly=readonly):
return
elif (isinstance(obj, PIL.Image.Image) and PIL.Image is not FakeObject
and np.ndarray is not FakeObject):
dialog = ArrayEditor()
data = np.array(obj)
if not dialog.setup_and_check(data, title=obj_name,
readonly=readonly):
return
conv_func = lambda data: PIL.Image.fromarray(data, mode=obj.mode)
elif (isinstance(obj, (pd.DataFrame, pd.Series)) and
pd.DataFrame is not FakeObject):
dialog = DataFrameEditor()
if not dialog.setup_and_check(obj):
return
elif is_text_string(obj):
dialog = TextEditor(obj, title=obj_name, readonly=readonly)
else:
dialog = CollectionsEditor()
dialog.setup(obj, title=obj_name, readonly=readonly)
def end_func(dialog):
return conv_func(dialog.get_value())
return dialog, end_func
def oedit(obj, modal=True, namespace=None):
"""Edit the object 'obj' in a GUI-based editor and return the edited copy
(if Cancel is pressed, return None)
The object 'obj' is a container
Supported container types:
dict, list, set, tuple, str/unicode or numpy.array
(instantiate a new QApplication if necessary,
so it can be called directly from the interpreter)
"""
# Local import
from spyder.utils.qthelpers import qapplication
app = qapplication()
if modal:
obj_name = ''
else:
assert is_text_string(obj)
obj_name = obj
if namespace is None:
namespace = globals()
keeper.set_namespace(namespace)
obj = namespace[obj_name]
# keep QApplication reference alive in the Python interpreter:
namespace['__qapp__'] = app
result = create_dialog(obj, obj_name)
if result is None:
return
dialog, end_func = result
if modal:
if dialog.exec_():
return end_func(dialog)
else:
keeper.create_dialog(dialog, obj_name, end_func)
import os
if os.name == 'nt':
app.exec_()
#==============================================================================
# Tests
#==============================================================================
def test():
"""Run object editor test"""
data = np.random.randint(1, 256, size=(100, 100)).astype('uint8')
image = PIL.Image.fromarray(data)
example = {'str': 'kjkj kj k j j kj k jkj',
'list': [1, 3, 4, 'kjkj', None],
'set': {1, 2, 1, 3, None, 'A', 'B', 'C', True, False},
'dict': {'d': 1, 'a': np.random.rand(10, 10), 'b': [1, 2]},
'float': 1.2233,
'array': np.random.rand(10, 10),
'image': image,
'date': datetime.date(1945, 5, 8),
'datetime': datetime.datetime(1945, 5, 8),
}
image = oedit(image)
class Foobar(object):
def __init__(self):
self.text = "toto"
foobar = Foobar()
print(oedit(foobar)) # spyder: test-skip
print(oedit(example)) # spyder: test-skip
print(oedit(np.random.rand(10, 10))) # spyder: test-skip
print(oedit(oedit.__doc__)) # spyder: test-skip
print(example) # spyder: test-skip
if __name__ == "__main__":
test()