scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyworkflow/apps/__init__.py +29 -0
- pyworkflow/apps/pw_manager.py +37 -0
- pyworkflow/apps/pw_plot.py +51 -0
- pyworkflow/apps/pw_project.py +113 -0
- pyworkflow/apps/pw_protocol_list.py +143 -0
- pyworkflow/apps/pw_protocol_run.py +51 -0
- pyworkflow/apps/pw_run_tests.py +267 -0
- pyworkflow/apps/pw_schedule_run.py +322 -0
- pyworkflow/apps/pw_sleep.py +37 -0
- pyworkflow/apps/pw_sync_data.py +439 -0
- pyworkflow/apps/pw_viewer.py +78 -0
- pyworkflow/constants.py +1 -1
- pyworkflow/gui/__init__.py +36 -0
- pyworkflow/gui/browser.py +760 -0
- pyworkflow/gui/canvas.py +1190 -0
- pyworkflow/gui/dialog.py +979 -0
- pyworkflow/gui/form.py +2726 -0
- pyworkflow/gui/graph.py +247 -0
- pyworkflow/gui/graph_layout.py +271 -0
- pyworkflow/gui/gui.py +566 -0
- pyworkflow/gui/matplotlib_image.py +233 -0
- pyworkflow/gui/plotter.py +247 -0
- pyworkflow/gui/project/__init__.py +25 -0
- pyworkflow/gui/project/base.py +192 -0
- pyworkflow/gui/project/constants.py +139 -0
- pyworkflow/gui/project/labels.py +205 -0
- pyworkflow/gui/project/project.py +491 -0
- pyworkflow/gui/project/searchprotocol.py +238 -0
- pyworkflow/gui/project/searchrun.py +181 -0
- pyworkflow/gui/project/steps.py +171 -0
- pyworkflow/gui/project/utils.py +332 -0
- pyworkflow/gui/project/variables.py +179 -0
- pyworkflow/gui/project/viewdata.py +472 -0
- pyworkflow/gui/project/viewprojects.py +510 -0
- pyworkflow/gui/project/viewprotocols.py +2116 -0
- pyworkflow/gui/project/viewprotocols_extra.py +562 -0
- pyworkflow/gui/text.py +771 -0
- pyworkflow/gui/tooltip.py +185 -0
- pyworkflow/gui/tree.py +684 -0
- pyworkflow/gui/widgets.py +307 -0
- pyworkflow/mapper/__init__.py +26 -0
- pyworkflow/mapper/mapper.py +222 -0
- pyworkflow/mapper/sqlite.py +1581 -0
- pyworkflow/mapper/sqlite_db.py +145 -0
- pyworkflow/project/__init__.py +31 -0
- pyworkflow/project/config.py +454 -0
- pyworkflow/project/manager.py +180 -0
- pyworkflow/project/project.py +2095 -0
- pyworkflow/project/usage.py +165 -0
- pyworkflow/protocol/__init__.py +38 -0
- pyworkflow/protocol/bibtex.py +48 -0
- pyworkflow/protocol/constants.py +87 -0
- pyworkflow/protocol/executor.py +483 -0
- pyworkflow/protocol/hosts.py +317 -0
- pyworkflow/protocol/launch.py +277 -0
- pyworkflow/protocol/package.py +42 -0
- pyworkflow/protocol/params.py +781 -0
- pyworkflow/protocol/protocol.py +2707 -0
- pyworkflow/tests/__init__.py +29 -0
- pyworkflow/tests/test_utils.py +25 -0
- pyworkflow/tests/tests.py +341 -0
- pyworkflow/utils/__init__.py +38 -0
- pyworkflow/utils/dataset.py +414 -0
- pyworkflow/utils/echo.py +104 -0
- pyworkflow/utils/graph.py +169 -0
- pyworkflow/utils/log.py +293 -0
- pyworkflow/utils/path.py +528 -0
- pyworkflow/utils/process.py +153 -0
- pyworkflow/utils/profiler.py +92 -0
- pyworkflow/utils/progressbar.py +154 -0
- pyworkflow/utils/properties.py +617 -0
- pyworkflow/utils/reflection.py +129 -0
- pyworkflow/utils/utils.py +880 -0
- pyworkflow/utils/which.py +229 -0
- pyworkflow/webservices/__init__.py +8 -0
- pyworkflow/webservices/config.py +8 -0
- pyworkflow/webservices/notifier.py +152 -0
- pyworkflow/webservices/repository.py +59 -0
- pyworkflow/webservices/workflowhub.py +74 -0
- pyworkflowtests/tests/__init__.py +0 -0
- pyworkflowtests/tests/test_canvas.py +72 -0
- pyworkflowtests/tests/test_domain.py +45 -0
- pyworkflowtests/tests/test_logs.py +74 -0
- pyworkflowtests/tests/test_mappers.py +392 -0
- pyworkflowtests/tests/test_object.py +507 -0
- pyworkflowtests/tests/test_project.py +42 -0
- pyworkflowtests/tests/test_protocol_execution.py +146 -0
- pyworkflowtests/tests/test_protocol_export.py +78 -0
- pyworkflowtests/tests/test_protocol_output.py +158 -0
- pyworkflowtests/tests/test_streaming.py +47 -0
- pyworkflowtests/tests/test_utils.py +210 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +2 -2
- scipion_pyworkflow-3.11.1.dist-info/RECORD +161 -0
- scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,332 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: Pablo Conesa (pconesa@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
import datetime
|
27
|
+
import platform
|
28
|
+
import abc
|
29
|
+
from abc import ABC
|
30
|
+
|
31
|
+
from pyworkflow.gui.project.constants import STATUS_COLORS, WARNING_COLOR
|
32
|
+
from pyworkflow.protocol import STATUS_FAILED
|
33
|
+
from pyworkflow.viewer import ProtocolViewer
|
34
|
+
|
35
|
+
|
36
|
+
def getStatusColorFromNode(node):
|
37
|
+
# If it is a run node (not PROJECT)
|
38
|
+
return getStatusColorFromRun(node.run)
|
39
|
+
|
40
|
+
|
41
|
+
def getStatusColorFromRun(prot):
|
42
|
+
""" Returns the color associated with the status. """
|
43
|
+
if prot:
|
44
|
+
if prot.hasSummaryWarnings():
|
45
|
+
return WARNING_COLOR
|
46
|
+
else:
|
47
|
+
return getStatusColor(prot.status.get(STATUS_FAILED))
|
48
|
+
else:
|
49
|
+
return getStatusColor()
|
50
|
+
|
51
|
+
|
52
|
+
def getStatusColor(status=None, default='#ADD8E6'):
|
53
|
+
"""
|
54
|
+
Parameters
|
55
|
+
----------
|
56
|
+
status status of the protocol
|
57
|
+
|
58
|
+
Returns the color associated with he status
|
59
|
+
-------
|
60
|
+
|
61
|
+
"""
|
62
|
+
return STATUS_COLORS[status] if status else default
|
63
|
+
|
64
|
+
# OS dependent behaviour. Add any OS dependent method here and later we might move
|
65
|
+
# or refactor this to a class or something else
|
66
|
+
|
67
|
+
|
68
|
+
class OSHandler(abc.ABC):
|
69
|
+
""" Abstract class: Handler for OS specific actions"""
|
70
|
+
def maximizeWindow(root):
|
71
|
+
pass
|
72
|
+
|
73
|
+
|
74
|
+
class LinuxHandler(OSHandler, ABC):
|
75
|
+
|
76
|
+
def maximizeWindow(root):
|
77
|
+
root.attributes("-zoomed", True)
|
78
|
+
|
79
|
+
|
80
|
+
class MacHandler(OSHandler, ABC):
|
81
|
+
|
82
|
+
def maximizeWindow(root):
|
83
|
+
root.state("zoomed")
|
84
|
+
|
85
|
+
|
86
|
+
class WindowsHandler(OSHandler, ABC):
|
87
|
+
|
88
|
+
def maximizeWindow(root):
|
89
|
+
root.state("zoomed")
|
90
|
+
|
91
|
+
|
92
|
+
class OS:
|
93
|
+
_handler = None
|
94
|
+
|
95
|
+
_handlers = {"Linux": LinuxHandler,
|
96
|
+
"Darwin": MacHandler,
|
97
|
+
"Windows": WindowsHandler} # Until testing this on windows
|
98
|
+
|
99
|
+
@staticmethod
|
100
|
+
def getPlatform():
|
101
|
+
return platform.system()
|
102
|
+
|
103
|
+
@classmethod
|
104
|
+
def handler(cls):
|
105
|
+
if cls._handler is None:
|
106
|
+
cls._handler = cls._handlers[cls.getPlatform()]
|
107
|
+
|
108
|
+
return cls._handler
|
109
|
+
|
110
|
+
|
111
|
+
def isAFinalProtocol(v, k):
|
112
|
+
if (issubclass(v, ProtocolViewer) or
|
113
|
+
v.isBase() or v.isDisabled()):
|
114
|
+
return False
|
115
|
+
|
116
|
+
return v.__name__ == k
|
117
|
+
|
118
|
+
|
119
|
+
def inspectObj(object, filename, prefix='', maxDeep=5, inspectDetail=2, memoryDict=None):
|
120
|
+
""" Creates a .CSV file in the filename path with
|
121
|
+
all its members and recursively with a certain maxDeep,
|
122
|
+
if maxDeep=0 means no maxDeep (until all members are inspected).
|
123
|
+
|
124
|
+
inspectDetail can be:
|
125
|
+
- 1: All attributes are shown
|
126
|
+
- 2: All attributes are shown and iterable values are also inspected
|
127
|
+
|
128
|
+
prefix and memoryDict will be updated in the recursive entries:
|
129
|
+
- prefix is a compound of the two first columns (DEEP and Tree)
|
130
|
+
- memoryDict is a dictionary with the memory address and an identifier
|
131
|
+
"""
|
132
|
+
END_LINE = '\n' # end of line char
|
133
|
+
COL_DELIM = '\t' # column delimiter
|
134
|
+
INDENT_COUNTER = '/' # character append in each indention (it's not written)
|
135
|
+
|
136
|
+
NEW_CHILD = ' |------> ' # new item indention
|
137
|
+
BAR_CHILD = ' | ' + INDENT_COUNTER # bar indention
|
138
|
+
END_CHILD = (' -- ' + COL_DELIM) * 4 + END_LINE # Child ending
|
139
|
+
column1 = ' - Name - ' + COL_DELIM
|
140
|
+
column2 = ' - Type - ' + COL_DELIM
|
141
|
+
column3 = ' - Value - ' + COL_DELIM
|
142
|
+
column4 = ' - Memory Address -'
|
143
|
+
|
144
|
+
# Constants to distinguish the first, last and middle rows
|
145
|
+
IS_FIRST = 1
|
146
|
+
IS_LAST = -1
|
147
|
+
IS_MIDDLE = 0
|
148
|
+
|
149
|
+
memoryDict = memoryDict or {}
|
150
|
+
|
151
|
+
def writeRow(name, value, prefix, posList=False):
|
152
|
+
""" Writes a row item. """
|
153
|
+
# we will avoid to recursively print the items wrote before
|
154
|
+
# (ie. with the same memory address), thus we store a dict with the
|
155
|
+
# addresses and the flag isNew is properly set
|
156
|
+
if str(hex(id(value))) in memoryDict:
|
157
|
+
memorySTR = memoryDict[str(hex(id(value)))]
|
158
|
+
isNew = False
|
159
|
+
else:
|
160
|
+
# if the item is new, we save its memory address in the memoryDict
|
161
|
+
# and we pass the name and the line on the file as a reference.
|
162
|
+
memorySTR = str(hex(id(value)))
|
163
|
+
file = open(filename, 'r')
|
164
|
+
lineNum = str(len(file.readlines()) + 1)
|
165
|
+
file.close()
|
166
|
+
nameDict = str(name)[0:15] + ' ...' if len(str(name)) > 25 else str(name)
|
167
|
+
memoryDict[str(hex(id(value)))] = '>>> ' + nameDict + ' - L:' + lineNum
|
168
|
+
isNew = True
|
169
|
+
|
170
|
+
if posList:
|
171
|
+
# if we have a List, the third column is 'pos/lenght'
|
172
|
+
thirdCol = posList
|
173
|
+
else:
|
174
|
+
# else, we print the value avoiding the EndOfLine char (// instead)
|
175
|
+
thirdCol = str(value).replace(END_LINE, ' // ')
|
176
|
+
|
177
|
+
# we will print the indentation deep number in the first row
|
178
|
+
indentionDeep = prefix.count(INDENT_COUNTER)
|
179
|
+
deepStr = str(indentionDeep) + COL_DELIM
|
180
|
+
|
181
|
+
# the prefix without the indentCounters is
|
182
|
+
# the tree to be printed in the 2nd row
|
183
|
+
prefixToWrite = prefix.replace(INDENT_COUNTER, '')
|
184
|
+
|
185
|
+
file = open(filename, 'a')
|
186
|
+
file.write(deepStr + prefixToWrite + COL_DELIM +
|
187
|
+
str(name) + COL_DELIM +
|
188
|
+
str(type(value)) + COL_DELIM +
|
189
|
+
thirdCol + COL_DELIM +
|
190
|
+
memorySTR + END_LINE)
|
191
|
+
file.close()
|
192
|
+
|
193
|
+
return isNew
|
194
|
+
|
195
|
+
def recursivePrint(value, prefix, isFirstOrLast):
|
196
|
+
""" We print the childs items of tuples, lists, dicts and classes. """
|
197
|
+
|
198
|
+
# if it's the last item, its childs has not the bar indention
|
199
|
+
if isFirstOrLast == IS_LAST: # void indention when no more items
|
200
|
+
prefixList = prefix.split(INDENT_COUNTER)
|
201
|
+
prefixList[-2] = prefixList[-2].replace('|', ' ')
|
202
|
+
prefix = INDENT_COUNTER.join(prefixList)
|
203
|
+
|
204
|
+
# recursive step with the new prefix and memory dict.
|
205
|
+
inspectObj(value, filename, prefix + BAR_CHILD, maxDeep, inspectDetail,
|
206
|
+
memoryDict)
|
207
|
+
|
208
|
+
if isFirstOrLast == IS_FIRST:
|
209
|
+
deepStr = str(indentionDeep) + COL_DELIM
|
210
|
+
else:
|
211
|
+
# When it was not the first item, the deep is increased
|
212
|
+
# to improve the readability when filter
|
213
|
+
deepStr = str(indentionDeep + 1) + COL_DELIM
|
214
|
+
|
215
|
+
prefix = prefix.replace(INDENT_COUNTER, '') + COL_DELIM
|
216
|
+
|
217
|
+
# We introduce the end of the child and
|
218
|
+
# also the next header while it is not the last
|
219
|
+
file = open(filename, 'a')
|
220
|
+
file.write(deepStr + prefix + END_CHILD)
|
221
|
+
if isFirstOrLast != IS_LAST:
|
222
|
+
# header
|
223
|
+
file.write(deepStr + prefix +
|
224
|
+
column1 + column2 + column3 + column4 + END_LINE)
|
225
|
+
file.close()
|
226
|
+
|
227
|
+
def isIterable(obj):
|
228
|
+
""" Returns true if obj is a tuple, list, dict or calls. """
|
229
|
+
isTupleListDict = (isinstance(obj, tuple) or
|
230
|
+
isinstance(obj, dict) or
|
231
|
+
isinstance(obj, list)) and len(value) > 1
|
232
|
+
|
233
|
+
# FIX ME: I don't know how to assert if is a class or not...
|
234
|
+
isClass = str(type(obj))[1] == 'c'
|
235
|
+
|
236
|
+
return isClass or (isTupleListDict and inspectDetail < 2)
|
237
|
+
|
238
|
+
indentionDeep = prefix.count(INDENT_COUNTER)
|
239
|
+
if indentionDeep == 0:
|
240
|
+
prefix = ' - Root - '
|
241
|
+
|
242
|
+
# dict with name and value pairs of the members
|
243
|
+
if len(object) == 1:
|
244
|
+
# if only one obj is passed in the input list,
|
245
|
+
# we directly inspect that obj.
|
246
|
+
obj_dict = object[0].__dict__
|
247
|
+
object = object[0]
|
248
|
+
|
249
|
+
# setting the header row
|
250
|
+
treeHeader = ' - Print on ' + str(datetime.datetime.now())
|
251
|
+
prefixHeader = '-DEEP-' + COL_DELIM + treeHeader + COL_DELIM
|
252
|
+
col1 = ' - Name - (value for Lists and Tuples)' + COL_DELIM
|
253
|
+
col3 = ' - Value - (Pos./Len for Lists and Tuples) ' + COL_DELIM
|
254
|
+
|
255
|
+
# writing the header row
|
256
|
+
file = open(filename, 'w')
|
257
|
+
file.write(prefixHeader + col1 + column2 + col3 + column4 + END_LINE)
|
258
|
+
file.close()
|
259
|
+
|
260
|
+
# writing the root object
|
261
|
+
writeRow(object.__class__.__name__, object, prefix)
|
262
|
+
# adding the child bar to the prefix
|
263
|
+
prefix = ' ' + BAR_CHILD
|
264
|
+
else:
|
265
|
+
# firsts settings depending on the type of the obj
|
266
|
+
if str(type(object))[1] == 'c':
|
267
|
+
obj_dict = object.__dict__
|
268
|
+
elif (isinstance(object, tuple) or
|
269
|
+
isinstance(object, list)):
|
270
|
+
column1 = ' - Value - ' + COL_DELIM
|
271
|
+
column3 = ' - Pos./Len. - ' + COL_DELIM
|
272
|
+
elif isinstance(object, dict):
|
273
|
+
column1 = ' - Key - ' + COL_DELIM
|
274
|
+
obj_dict = object
|
275
|
+
else: # if is not of the type above it not make sense to continue
|
276
|
+
return
|
277
|
+
|
278
|
+
indentionDeep = prefix.count(INDENT_COUNTER)
|
279
|
+
deepStr = str(indentionDeep) + COL_DELIM
|
280
|
+
isBelowMaxDeep = indentionDeep < maxDeep if maxDeep > 0 else True
|
281
|
+
|
282
|
+
prefixToWrite = prefix.replace(INDENT_COUNTER, '') + COL_DELIM
|
283
|
+
file = open(filename, 'a')
|
284
|
+
file.write(deepStr + prefixToWrite +
|
285
|
+
column1 + column2 + column3 + column4 + END_LINE)
|
286
|
+
file.close()
|
287
|
+
|
288
|
+
# we update the prefix to put the NEW_CHILD string ( |----> )
|
289
|
+
prefixList = prefix.split(INDENT_COUNTER)
|
290
|
+
prefixList[-2] = NEW_CHILD
|
291
|
+
# we return to the string structure
|
292
|
+
# with a certain indention if it's the root
|
293
|
+
prefixToWrite = ' ' + INDENT_COUNTER.join(prefixList) if indentionDeep == 1 \
|
294
|
+
else INDENT_COUNTER.join(prefixList)
|
295
|
+
|
296
|
+
isNew = True
|
297
|
+
if str(type(object))[1] == 'c' or isinstance(object, dict):
|
298
|
+
counter = 0
|
299
|
+
for key, value in obj_dict.items():
|
300
|
+
counter += 1
|
301
|
+
# write the variable
|
302
|
+
isNew = writeRow(key, value, prefixToWrite)
|
303
|
+
|
304
|
+
# managing the extremes of the loop
|
305
|
+
if counter == 1:
|
306
|
+
isFirstOrLast = IS_FIRST
|
307
|
+
elif counter == len(obj_dict):
|
308
|
+
isFirstOrLast = IS_LAST
|
309
|
+
else:
|
310
|
+
isFirstOrLast = IS_MIDDLE
|
311
|
+
|
312
|
+
# show attributes for objects and items for lists and tuples
|
313
|
+
if isBelowMaxDeep and isNew and isIterable(value):
|
314
|
+
recursivePrint(value, prefix, isFirstOrLast)
|
315
|
+
else:
|
316
|
+
for i in range(0, len(object)):
|
317
|
+
# write the variable
|
318
|
+
isNew = writeRow(object[i], object[i], prefixToWrite,
|
319
|
+
str(i + 1) + '/' + str(len(object)))
|
320
|
+
|
321
|
+
# managing the extremes of the loop
|
322
|
+
if i == 0:
|
323
|
+
isFirstOrLast = IS_FIRST
|
324
|
+
elif len(object) == i + 1:
|
325
|
+
isFirstOrLast = IS_LAST
|
326
|
+
else:
|
327
|
+
isFirstOrLast = IS_MIDDLE
|
328
|
+
|
329
|
+
# show attributes for objects and items for lists and tuples
|
330
|
+
if isBelowMaxDeep and isNew and isIterable(object[i]):
|
331
|
+
recursivePrint(object[i], prefix, isFirstOrLast)
|
332
|
+
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: Pablo Conesa (scipion@cnb.csic.es)
|
4
|
+
# *
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
6
|
+
# *
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
9
|
+
# * the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# * (at your option) any later version.
|
11
|
+
# *
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# * GNU General Public License for more details.
|
16
|
+
# *
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
18
|
+
# * along with this program; if not, write to the Free Software
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
20
|
+
# * 02111-1307 USA
|
21
|
+
# *
|
22
|
+
# * All comments concerning this program package may be sent to the
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
24
|
+
# *
|
25
|
+
# **************************************************************************
|
26
|
+
"""
|
27
|
+
Module for editing variables
|
28
|
+
"""
|
29
|
+
|
30
|
+
import tkinter as tk
|
31
|
+
|
32
|
+
from pyworkflow import Config, Variable, VariablesRegistry, VarTypes
|
33
|
+
from pyworkflow.gui import Icon, configureWeigths, getDefaultFont, askPath
|
34
|
+
from pyworkflow.gui.tree import TreeProvider
|
35
|
+
import pyworkflow.gui.dialog as dialog
|
36
|
+
|
37
|
+
|
38
|
+
class VariablesTreeProvider(TreeProvider):
|
39
|
+
""" Populate Tree from Labels. """
|
40
|
+
def __init__(self):
|
41
|
+
TreeProvider.__init__(self)
|
42
|
+
self._parentDict = {}
|
43
|
+
|
44
|
+
def getColumns(self):
|
45
|
+
return [('Name', 300), ('Value', 500), ('Default?', 60), ('DefaultV',200),('Description', 400), ('Source', 150)]
|
46
|
+
|
47
|
+
def getObjectInfo(self, variable: Variable):
|
48
|
+
|
49
|
+
return {'key': variable.name, 'parent': None,
|
50
|
+
'text': variable.name, 'values': (variable.value, "is default" if variable.isDefault else "Declared",
|
51
|
+
variable.default, variable.description, variable.source),
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
def getObjects(self):
|
56
|
+
return VariablesRegistry.__iter__()
|
57
|
+
|
58
|
+
|
59
|
+
class VariablesDialog(dialog.ToolbarListDialog):
|
60
|
+
"""
|
61
|
+
This dialog will allow editing variables coming either from pyworkflow or the plugins
|
62
|
+
"""
|
63
|
+
def __init__(self, parent, **kwargs):
|
64
|
+
""" From kwargs:
|
65
|
+
message: message tooltip to show when browsing.
|
66
|
+
selected: the item that should be selected.
|
67
|
+
validateSelectionCallback:
|
68
|
+
a callback function to validate selected items.
|
69
|
+
allowSelect: if set to False, the 'Select' button will not
|
70
|
+
be shown.
|
71
|
+
"""
|
72
|
+
toolbarButtons = [
|
73
|
+
# dialog.ToolbarButton('Add', self._addLabel, Icon.ACTION_NEW),
|
74
|
+
dialog.ToolbarButton('Edit', self._editVariable, Icon.ACTION_EDIT),
|
75
|
+
dialog.ToolbarButton('Set to default', self._setToDefault, Icon.ACTION_DELETE)
|
76
|
+
]
|
77
|
+
|
78
|
+
helpMsg =("Use this form to change core and plugin's variables. If \"Default\" is true, it currently is the default value and is not present in the config file. All changes here "
|
79
|
+
"will be persisted in the general %s config file. \"Source\" field tells you where the variable is defined."% Config.SCIPION_CONFIG)
|
80
|
+
|
81
|
+
dialog.ToolbarListDialog.__init__(self, parent,
|
82
|
+
"Edit config variables",
|
83
|
+
VariablesTreeProvider(),
|
84
|
+
helpMsg,
|
85
|
+
toolbarButtons,
|
86
|
+
allowsEmptySelection=True,
|
87
|
+
itemDoubleClick=self._editVariable,
|
88
|
+
cancelButton=False,
|
89
|
+
buttons=[('Save', dialog.RESULT_YES),
|
90
|
+
('Cancel', dialog.RESULT_CANCEL)],
|
91
|
+
**kwargs)
|
92
|
+
|
93
|
+
|
94
|
+
def apply(self):
|
95
|
+
VariablesRegistry.save(Config.SCIPION_CONFIG)
|
96
|
+
def _editVariable(self, e=None):
|
97
|
+
selection = self.tree.getSelectedObjects()
|
98
|
+
if selection:
|
99
|
+
variable = selection[0]
|
100
|
+
dlg = EditVariableDialog(self, "Edit variable", variable)
|
101
|
+
if dlg.resultYes():
|
102
|
+
self.refresh()
|
103
|
+
|
104
|
+
def _setToDefault(self, e=None):
|
105
|
+
selection = self.tree.getSelectedObjects()
|
106
|
+
if selection:
|
107
|
+
varsStr = '\n'.join('- %s' % v.name for v in selection)
|
108
|
+
if dialog.askYesNo(" The variables will be set it to its default value.",
|
109
|
+
"Are you sure to reset the %s"
|
110
|
+
"value?\n" % varsStr, self):
|
111
|
+
for variable in selection:
|
112
|
+
variable.setToDefault()
|
113
|
+
self.tree.update()
|
114
|
+
|
115
|
+
|
116
|
+
class EditVariableDialog(dialog.Dialog):
|
117
|
+
""" Dialog to edit a variable """
|
118
|
+
def __init__(self, parent, title, variable:Variable, **kwargs):
|
119
|
+
self.variable = variable
|
120
|
+
dialog.Dialog.__init__(self, parent, title)
|
121
|
+
|
122
|
+
def body(self, bodyFrame):
|
123
|
+
bodyFrame.config(bg=Config.SCIPION_BG_COLOR)
|
124
|
+
configureWeigths(bodyFrame, 1, 1)
|
125
|
+
|
126
|
+
# Name
|
127
|
+
label_text = tk.Label(bodyFrame, text=self.variable.name, bg=Config.SCIPION_BG_COLOR, bd=0)
|
128
|
+
label_text.grid(row=0, column=0, sticky='nw', padx=(15, 10), pady=15)
|
129
|
+
|
130
|
+
# Description
|
131
|
+
label_text = tk.Label(bodyFrame, text=self.variable.description, bg=Config.SCIPION_BG_COLOR, bd=0)
|
132
|
+
label_text.grid(row=0, column=1, sticky='nw', padx=(15, 10), pady=15)
|
133
|
+
|
134
|
+
# Value
|
135
|
+
label_text = tk.Label(bodyFrame, text="Value", bg=Config.SCIPION_BG_COLOR, bd=0)
|
136
|
+
label_text.grid(row=2, column=0, sticky='nw', padx=(15, 10), pady=15)
|
137
|
+
|
138
|
+
# Value Entry
|
139
|
+
if self.variable.var_type == VarTypes.INTEGER:
|
140
|
+
var=tk.IntVar()
|
141
|
+
else:
|
142
|
+
var = tk.StringVar()
|
143
|
+
|
144
|
+
var.set(self.variable.value)
|
145
|
+
self.valueVar = var
|
146
|
+
self.valueLabel = tk.Entry(bodyFrame, width=30, textvariable=var, font=getDefaultFont())
|
147
|
+
self.valueLabel.grid(row=2, column=1, sticky='news', padx=5, pady=5)
|
148
|
+
|
149
|
+
if self.variable.var_type in [VarTypes.PATH, VarTypes.FOLDER, VarTypes.FILENAME]:
|
150
|
+
self._addButton(bodyFrame, self.buttonClicked, icon=Icon.ACTION_BROWSE, row=2, col=2,tooltip="Click to browse file system for a file or folder")
|
151
|
+
|
152
|
+
def buttonClicked(self, e):
|
153
|
+
""" Callback for when the wizard button has been clicked"""
|
154
|
+
|
155
|
+
var_type = self.variable.var_type
|
156
|
+
|
157
|
+
if var_type in [VarTypes.PATH, VarTypes.FOLDER, VarTypes.FILENAME]:
|
158
|
+
|
159
|
+
onlyFolders = (var_type == VarTypes.FOLDER)
|
160
|
+
returnBaseName = (var_type ==var_type.FILENAME)
|
161
|
+
path = self.valueVar.get() if not returnBaseName else "."
|
162
|
+
|
163
|
+
value =askPath(path=path, master=self, onlyFolders=onlyFolders, returnBaseName=returnBaseName)
|
164
|
+
self.valueVar.set(value)
|
165
|
+
def apply(self):
|
166
|
+
self.variable.setValue(self.valueVar.get())
|
167
|
+
|
168
|
+
def validate(self):
|
169
|
+
|
170
|
+
validationMsg = None
|
171
|
+
|
172
|
+
if len(self.valueVar.get().strip()) == 0:
|
173
|
+
validationMsg = "Value name can't be empty.\n"
|
174
|
+
|
175
|
+
if validationMsg is not None:
|
176
|
+
dialog.showError("Validation error", validationMsg, self)
|
177
|
+
return False
|
178
|
+
|
179
|
+
return True
|