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
pyworkflow/gui/form.py
ADDED
@@ -0,0 +1,2726 @@
|
|
1
|
+
# **************************************************************************
|
2
|
+
# *
|
3
|
+
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
4
|
+
# * Jose Gutierrez (jose.gutierrez@cnb.csic.es) [2]
|
5
|
+
# *
|
6
|
+
# * [1] SciLifeLab, Stockholm University
|
7
|
+
# * [2] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
8
|
+
# *
|
9
|
+
# * This program is free software: you can redistribute it and/or modify
|
10
|
+
# * it under the terms of the GNU General Public License as published by
|
11
|
+
# * the Free Software Foundation, either version 3 of the License, or
|
12
|
+
# * (at your option) any later version.
|
13
|
+
# *
|
14
|
+
# * This program is distributed in the hope that it will be useful,
|
15
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# * GNU General Public License for more details.
|
18
|
+
# *
|
19
|
+
# * You should have received a copy of the GNU General Public License
|
20
|
+
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
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
|
+
This modules implements the automatic
|
28
|
+
creation of protocol form GUI from its
|
29
|
+
params definition.
|
30
|
+
"""
|
31
|
+
import logging
|
32
|
+
|
33
|
+
from typing import Type
|
34
|
+
|
35
|
+
logger = logging.getLogger(__name__)
|
36
|
+
import os
|
37
|
+
import tkinter as tk
|
38
|
+
import tkinter.ttk as ttk
|
39
|
+
from collections import OrderedDict
|
40
|
+
from datetime import datetime
|
41
|
+
|
42
|
+
import pyworkflow as pw
|
43
|
+
import pyworkflow.utils as pwutils
|
44
|
+
import pyworkflow.object as pwobj
|
45
|
+
import pyworkflow.protocol as pwprot
|
46
|
+
from pyworkflow.mapper import Mapper
|
47
|
+
from pyworkflow.viewer import DESKTOP_TKINTER
|
48
|
+
from pyworkflow.protocol.constants import MODE_RESTART, MODE_RESUME
|
49
|
+
|
50
|
+
from . import gui, RESULT_RUN_SINGLE
|
51
|
+
from pyworkflow.gui.project.utils import getStatusColorFromRun
|
52
|
+
from .gui import configureWeigths, Window
|
53
|
+
from .browser import FileBrowserWindow
|
54
|
+
from .widgets import Button, HotButton, IconButton
|
55
|
+
from .dialog import (showInfo, showError, showWarning, EditObjectDialog,
|
56
|
+
ListDialog, Dialog, RESULT_CANCEL, RESULT_RUN_ALL)
|
57
|
+
from .canvas import Canvas
|
58
|
+
from .tree import TreeProvider, BoundTree
|
59
|
+
from .text import Text
|
60
|
+
from ..project.project import ModificationNotAllowedException
|
61
|
+
|
62
|
+
THREADS = 'Threads'
|
63
|
+
MPI = 'MPI'
|
64
|
+
|
65
|
+
|
66
|
+
# ----------------- Variables wrappers around more complex objects ------------
|
67
|
+
class BoolVar:
|
68
|
+
"""Wrapper around tk.IntVar"""
|
69
|
+
|
70
|
+
def __init__(self, value=None):
|
71
|
+
self.tkVar = tk.IntVar()
|
72
|
+
self.set(value)
|
73
|
+
self.trace = self.tkVar.trace
|
74
|
+
|
75
|
+
def set(self, value):
|
76
|
+
if value is None:
|
77
|
+
self.tkVar.set(-1)
|
78
|
+
elif value:
|
79
|
+
self.tkVar.set(1)
|
80
|
+
else:
|
81
|
+
self.tkVar.set(0)
|
82
|
+
|
83
|
+
def get(self):
|
84
|
+
if self.tkVar.get() == -1:
|
85
|
+
return None
|
86
|
+
|
87
|
+
return self.tkVar.get() == 1
|
88
|
+
|
89
|
+
|
90
|
+
class PointerVar:
|
91
|
+
""" Wrapper around tk.StringVar to hold object pointers. """
|
92
|
+
|
93
|
+
def __init__(self, protocol):
|
94
|
+
self.tkVar = tk.StringVar()
|
95
|
+
self._pointer = pwobj.Pointer()
|
96
|
+
self.trace = self.tkVar.trace
|
97
|
+
self._protocol = protocol
|
98
|
+
|
99
|
+
def set(self, value):
|
100
|
+
if value is None:
|
101
|
+
value = pwobj.Pointer(None)
|
102
|
+
if not isinstance(value, pwobj.Pointer):
|
103
|
+
raise Exception('Pointer var should be used with pointers!!!\n'
|
104
|
+
' Passing: %s, type: %s' % (value, type(value)))
|
105
|
+
self._pointer.copy(value)
|
106
|
+
|
107
|
+
label, _ = getPointerLabelAndInfo(self._pointer,
|
108
|
+
self._protocol.getMapper())
|
109
|
+
self.tkVar.set(label)
|
110
|
+
|
111
|
+
def get(self):
|
112
|
+
return self._pointer
|
113
|
+
|
114
|
+
def getPointer(self):
|
115
|
+
return self._pointer
|
116
|
+
|
117
|
+
def remove(self):
|
118
|
+
self.set(None)
|
119
|
+
|
120
|
+
|
121
|
+
class ScalarWithPointerVar(tk.StringVar):
|
122
|
+
""" tk.StringVar to hold object pointers and scalars. """
|
123
|
+
|
124
|
+
def __init__(self, protocol, changeListener):
|
125
|
+
|
126
|
+
self._pointer = None
|
127
|
+
self._protocol = protocol
|
128
|
+
self.inInit = True
|
129
|
+
tk.StringVar.__init__(self)
|
130
|
+
self.inInit = False
|
131
|
+
self.insideSet = False
|
132
|
+
self.listeners = []
|
133
|
+
|
134
|
+
# Register inner listener
|
135
|
+
tk.StringVar.trace(self, 'w', self._listenDirectEntryChanges)
|
136
|
+
self.trace('w', changeListener)
|
137
|
+
|
138
|
+
def trace(self, mode, callback):
|
139
|
+
|
140
|
+
# let's ignore the mode for now all are "w"
|
141
|
+
self.listeners.append(callback)
|
142
|
+
|
143
|
+
def _listenDirectEntryChanges(self, *args):
|
144
|
+
""" We need to be aware of any change done in the entry.
|
145
|
+
When the user type anything, the set is not invoked.
|
146
|
+
We need to distinguish when this is invoked from the set(),
|
147
|
+
in this case we do nothing"""
|
148
|
+
if not self.insideSet:
|
149
|
+
self._pointer = None
|
150
|
+
|
151
|
+
# Call the listeners
|
152
|
+
for callback in self.listeners:
|
153
|
+
callback(*args)
|
154
|
+
|
155
|
+
def set(self, value):
|
156
|
+
# Flag we are inside the set method to avoid
|
157
|
+
# triggering _listenDirectEntryChanges
|
158
|
+
self.insideSet = True
|
159
|
+
if self.inInit:
|
160
|
+
return
|
161
|
+
|
162
|
+
# If a scalar is being set
|
163
|
+
if not isinstance(value, pwobj.Pointer):
|
164
|
+
|
165
|
+
# Reset the pointer
|
166
|
+
self._pointer = None
|
167
|
+
label = value
|
168
|
+
# it's a pointer
|
169
|
+
else:
|
170
|
+
|
171
|
+
self._pointer = value
|
172
|
+
label, _ = getPointerLabelAndInfo(self._pointer,
|
173
|
+
self._protocol.getMapper())
|
174
|
+
|
175
|
+
tk.StringVar.set(self, label)
|
176
|
+
|
177
|
+
# Cancel the flag.
|
178
|
+
self.insideSet = False
|
179
|
+
|
180
|
+
def get(self):
|
181
|
+
|
182
|
+
if self.hasPointer():
|
183
|
+
return self._pointer
|
184
|
+
else:
|
185
|
+
return tk.StringVar.get(self)
|
186
|
+
|
187
|
+
def hasPointer(self):
|
188
|
+
return self._pointer is not None
|
189
|
+
|
190
|
+
def getPointer(self):
|
191
|
+
return self._pointer if self.hasPointer() else None
|
192
|
+
|
193
|
+
|
194
|
+
class MultiPointerVar:
|
195
|
+
"""
|
196
|
+
Wrapper around tk.StringVar to hold object pointers.
|
197
|
+
This class is related with MultiPointerTreeProvider, which
|
198
|
+
stores the list of pointed objects and have the logic to
|
199
|
+
add and remove from the list.
|
200
|
+
"""
|
201
|
+
|
202
|
+
def __init__(self, provider, tree):
|
203
|
+
# keep a reference to tree provider to add or remove objects
|
204
|
+
self.provider = provider
|
205
|
+
self.tree = tree
|
206
|
+
self.tkVar = tk.StringVar()
|
207
|
+
self.trace = self.tkVar.trace
|
208
|
+
|
209
|
+
def _updateObjectsList(self):
|
210
|
+
self.tkVar.set(str(datetime.now())) # cause a trace to notify changes
|
211
|
+
self.tree.update() # Update the tkinter tree gui
|
212
|
+
|
213
|
+
def set(self, value):
|
214
|
+
if isinstance(value, pwobj.Object) or isinstance(value, list):
|
215
|
+
self.provider.addObject(value)
|
216
|
+
self._updateObjectsList()
|
217
|
+
|
218
|
+
def remove(self):
|
219
|
+
""" Remove first element selected. """
|
220
|
+
values = self.getSelectedObjects()
|
221
|
+
for v in values:
|
222
|
+
self.provider.removeObject(v)
|
223
|
+
self._updateObjectsList()
|
224
|
+
|
225
|
+
def clear(self):
|
226
|
+
self.provider.clear()
|
227
|
+
self._updateObjectsList()
|
228
|
+
|
229
|
+
|
230
|
+
def getSelectedObjects(self):
|
231
|
+
return self.tree.getSelectedObjects()
|
232
|
+
|
233
|
+
def get(self):
|
234
|
+
return self.provider.getObjects()
|
235
|
+
|
236
|
+
|
237
|
+
class MultiPointerTreeProvider(TreeProvider):
|
238
|
+
"""
|
239
|
+
Store several pointers to objects to be used in a BoundTree and as
|
240
|
+
storage from MultiPointerVar.
|
241
|
+
"""
|
242
|
+
|
243
|
+
def __init__(self, mapper):
|
244
|
+
TreeProvider.__init__(self)
|
245
|
+
self._objectDict = OrderedDict()
|
246
|
+
self._mapper = mapper
|
247
|
+
|
248
|
+
def _getObjKey(self, obj):
|
249
|
+
"""
|
250
|
+
This method will create an unique key to
|
251
|
+
identify the pointed object. The objId is not
|
252
|
+
enough because of pointers and extended values
|
253
|
+
to items inside a set or properties.
|
254
|
+
"""
|
255
|
+
strId = None
|
256
|
+
|
257
|
+
if isinstance(obj, pwobj.Pointer):
|
258
|
+
|
259
|
+
if obj.hasValue():
|
260
|
+
strId = obj.getObjValue().strId()
|
261
|
+
|
262
|
+
if obj.hasExtended():
|
263
|
+
strId += obj.getExtended()
|
264
|
+
|
265
|
+
else:
|
266
|
+
strId = obj.strId()
|
267
|
+
|
268
|
+
if strId is None:
|
269
|
+
raise Exception('ERROR: strId is None for MultiPointerTreeProvider!!!')
|
270
|
+
|
271
|
+
return strId
|
272
|
+
|
273
|
+
def _getObjPointer(self, obj):
|
274
|
+
""" If obj is a pointer return obj. If not
|
275
|
+
create a pointer and return it.
|
276
|
+
"""
|
277
|
+
if isinstance(obj, pwobj.Pointer):
|
278
|
+
ptr = obj
|
279
|
+
else:
|
280
|
+
ptr = pwobj.Pointer(value=obj)
|
281
|
+
|
282
|
+
return ptr
|
283
|
+
|
284
|
+
def _addObject(self, obj):
|
285
|
+
strId = self._getObjKey(obj)
|
286
|
+
ptr = self._getObjPointer(obj)
|
287
|
+
ptr._strId = strId
|
288
|
+
|
289
|
+
self._objectDict[strId] = ptr
|
290
|
+
|
291
|
+
def addObject(self, obj):
|
292
|
+
if isinstance(obj, list):
|
293
|
+
for o in obj:
|
294
|
+
self._addObject(o)
|
295
|
+
else:
|
296
|
+
self._addObject(obj)
|
297
|
+
|
298
|
+
def removeObject(self, obj):
|
299
|
+
strId = self._getObjKey(obj)
|
300
|
+
if strId in self._objectDict:
|
301
|
+
del self._objectDict[strId]
|
302
|
+
|
303
|
+
def getObjects(self):
|
304
|
+
return list(self._objectDict.values())
|
305
|
+
|
306
|
+
def getColumns(self):
|
307
|
+
return [('Object', 250), ('Info', 150)]
|
308
|
+
|
309
|
+
def getObjectInfo(self, obj):
|
310
|
+
label, info = getPointerLabelAndInfo(obj, self._mapper)
|
311
|
+
return {'key': obj._strId, 'text': label, 'values': (' ' + info,)}
|
312
|
+
|
313
|
+
def clear(self):
|
314
|
+
self._objectDict.clear()
|
315
|
+
|
316
|
+
|
317
|
+
class ComboVar:
|
318
|
+
""" Create a variable that display strings (for combobox)
|
319
|
+
but the values are integers (for the underlying EnumParam).
|
320
|
+
"""
|
321
|
+
|
322
|
+
def __init__(self, enum):
|
323
|
+
self.tkVar = tk.StringVar()
|
324
|
+
self.enum = enum
|
325
|
+
self.value = None
|
326
|
+
self.trace = self.tkVar.trace
|
327
|
+
|
328
|
+
def set(self, value):
|
329
|
+
self.value = value
|
330
|
+
if isinstance(value, int):
|
331
|
+
# self.enum.choices is an object of type odict_values, which
|
332
|
+
# cannot be indexed, so a type cast to list is required
|
333
|
+
self.tkVar.set(list(self.enum.choices)[value])
|
334
|
+
else:
|
335
|
+
self.tkVar.set(value) # also support string values
|
336
|
+
|
337
|
+
def get(self):
|
338
|
+
v = self.tkVar.get()
|
339
|
+
self.value = None
|
340
|
+
for i, c in enumerate(list(self.enum.choices)):
|
341
|
+
if c == v:
|
342
|
+
self.value = i
|
343
|
+
|
344
|
+
return self.value
|
345
|
+
|
346
|
+
|
347
|
+
class TextVar:
|
348
|
+
"""Wrapper around tk.StringVar to bind the value of a Text widget. """
|
349
|
+
|
350
|
+
def __init__(self, text, value=''):
|
351
|
+
"""
|
352
|
+
Params:
|
353
|
+
text: Text widget associated with this variable.
|
354
|
+
value: initial value for the widget.
|
355
|
+
"""
|
356
|
+
self.text = text
|
357
|
+
text.bind('<KeyRelease>', self._onTextChanged)
|
358
|
+
self.tkVar = tk.StringVar()
|
359
|
+
self.set(value)
|
360
|
+
self.trace = self.tkVar.trace
|
361
|
+
|
362
|
+
def set(self, value):
|
363
|
+
self.tkVar.set(value)
|
364
|
+
if value is None:
|
365
|
+
value = ''
|
366
|
+
self.text.setText(value)
|
367
|
+
|
368
|
+
def get(self):
|
369
|
+
return self.tkVar.get()
|
370
|
+
|
371
|
+
def _onTextChanged(self, e=None):
|
372
|
+
self.tkVar.set(self.text.getText().strip())
|
373
|
+
|
374
|
+
# ---------------- Some used providers for the TREES --------------------------
|
375
|
+
|
376
|
+
|
377
|
+
class ProtocolClassTreeProvider(TreeProvider):
|
378
|
+
"""Will implement the methods to provide the object info
|
379
|
+
of subclasses objects(of className) found by mapper"""
|
380
|
+
|
381
|
+
def __init__(self, protocolClassName):
|
382
|
+
TreeProvider.__init__(self)
|
383
|
+
self.protocolClassName = protocolClassName
|
384
|
+
|
385
|
+
def getObjects(self):
|
386
|
+
# FIXME: Maybe find a way to pass the current domain?
|
387
|
+
# FIXME: Or we just rely on the one defined in pw.Config?
|
388
|
+
domain = pw.Config.getDomain()
|
389
|
+
return [pwobj.String(s)
|
390
|
+
for s in domain.findSubClasses(domain.getProtocols(),
|
391
|
+
self.protocolClassName).keys()]
|
392
|
+
|
393
|
+
def getColumns(self):
|
394
|
+
return [('Protocol', 250)]
|
395
|
+
|
396
|
+
def getObjectInfo(self, obj):
|
397
|
+
return {'key': obj.get(),
|
398
|
+
'values': (obj.get(),)}
|
399
|
+
|
400
|
+
|
401
|
+
def getPointerLabelAndInfo(pobj, mapper):
|
402
|
+
"""
|
403
|
+
Return a string to represent selected objects
|
404
|
+
that are stored by pointers.
|
405
|
+
This function will be used from PointerVar and MultiPointerVar.
|
406
|
+
"""
|
407
|
+
label = getObjectLabel(pobj, mapper)
|
408
|
+
obj = pobj.get()
|
409
|
+
info = str(obj) if obj is not None else ''
|
410
|
+
|
411
|
+
return label, info
|
412
|
+
|
413
|
+
|
414
|
+
def getObjectLabel(pobj, mapper):
|
415
|
+
""" We will try to show in the list the string representation
|
416
|
+
that is more readable for the user to pick the desired object.
|
417
|
+
"""
|
418
|
+
# FIXME: maybe we can remove this function
|
419
|
+
obj = pobj.get()
|
420
|
+
prot = pobj.getObjValue()
|
421
|
+
|
422
|
+
if prot is None:
|
423
|
+
label = ''
|
424
|
+
elif obj is None:
|
425
|
+
label = '%s.%s' % (prot.getRunName(), pobj.getExtended())
|
426
|
+
else:
|
427
|
+
# This is for backward compatibility
|
428
|
+
# Now always the pobj.getObjValue() should
|
429
|
+
# be the protocol
|
430
|
+
extended = pobj.getExtended() if isinstance(prot, pwprot.Protocol) else ''
|
431
|
+
while not isinstance(prot, pwprot.Protocol):
|
432
|
+
extended = '%s.%s' % (prot.getLastName(), extended)
|
433
|
+
prot = mapper.getParent(prot)
|
434
|
+
label = obj.getObjLabel().strip()
|
435
|
+
if not len(label):
|
436
|
+
label = '%s.%s' % (prot.getRunName(), extended)
|
437
|
+
|
438
|
+
label = label.replace("\n", " ")
|
439
|
+
# if obj is not None:
|
440
|
+
# return label + " (%d)" % obj.getObjId()
|
441
|
+
return label
|
442
|
+
|
443
|
+
|
444
|
+
class SubclassesTreeProvider(TreeProvider):
|
445
|
+
"""Will implement the methods to provide the object info
|
446
|
+
of subclasses objects(of className) found by mapper"""
|
447
|
+
CREATION_COLUMN = 'Creation'
|
448
|
+
INFO_COLUMN = 'Info'
|
449
|
+
ID_COLUMN = 'Protocol Id'
|
450
|
+
|
451
|
+
def __init__(self, protocol, pointerParam, selected=None):
|
452
|
+
TreeProvider.__init__(self, sortingColumnName=self.CREATION_COLUMN,
|
453
|
+
sortingAscending=False)
|
454
|
+
|
455
|
+
self.param = pointerParam
|
456
|
+
self.selected = selected # FIXME
|
457
|
+
self.selectedDict = {}
|
458
|
+
self.protocol = protocol
|
459
|
+
self.mapper = protocol.mapper
|
460
|
+
self.maxNum = 200
|
461
|
+
|
462
|
+
def getObjects(self):
|
463
|
+
# Retrieve all objects of type className
|
464
|
+
project = self.protocol.getProject()
|
465
|
+
className = self.param.pointerClass.get()
|
466
|
+
condition = self.param.pointerCondition.get()
|
467
|
+
# Get the classes that are valid as input object in this Domain
|
468
|
+
domain = pw.Config.getDomain()
|
469
|
+
classes = [domain.findClass(c.strip()) for c in className.split(",")]
|
470
|
+
# Obtaining only the outputs of the protocols that do not violate the sense of processing,
|
471
|
+
# thus avoiding circular references between protocols
|
472
|
+
objects = project.getProtocolCompatibleOutputs(self.protocol, classes, condition)
|
473
|
+
|
474
|
+
# Sort objects before returning them
|
475
|
+
self._sortObjects(objects)
|
476
|
+
return objects
|
477
|
+
|
478
|
+
def _sortObjects(self, objects):
|
479
|
+
objects.sort(key=self.objectKey, reverse=not self.isSortingAscending())
|
480
|
+
|
481
|
+
def objectKey(self, pobj):
|
482
|
+
""" Returns the value to be evaluated during sorting based on _sortingColumnName"""
|
483
|
+
obj = self._getParentObject(pobj, pobj)
|
484
|
+
|
485
|
+
if self._sortingColumnName == SubclassesTreeProvider.CREATION_COLUMN:
|
486
|
+
return self._getObjectCreation(obj.get())
|
487
|
+
elif self._sortingColumnName == SubclassesTreeProvider.INFO_COLUMN:
|
488
|
+
return self._getObjectInfoValue(obj.get())
|
489
|
+
elif self._sortingColumnName == SubclassesTreeProvider.ID_COLUMN:
|
490
|
+
return self._getObjectId(pobj)
|
491
|
+
else:
|
492
|
+
return self._getPointerLabel(obj)
|
493
|
+
|
494
|
+
def getColumns(self):
|
495
|
+
return [('Object', 300), (SubclassesTreeProvider.INFO_COLUMN, 250),
|
496
|
+
(SubclassesTreeProvider.CREATION_COLUMN, 150),
|
497
|
+
(SubclassesTreeProvider.ID_COLUMN, 100)]
|
498
|
+
|
499
|
+
def isSelected(self, obj):
|
500
|
+
""" Check if an object is selected or not. """
|
501
|
+
if self.selected:
|
502
|
+
for s in self.selected:
|
503
|
+
if s and s.getObjId() == obj.getObjId():
|
504
|
+
return True
|
505
|
+
return False
|
506
|
+
|
507
|
+
@staticmethod
|
508
|
+
def _getParentObject(pobj, default=None):
|
509
|
+
return getattr(pobj, '_parentObject', default)
|
510
|
+
|
511
|
+
def getObjectInfo(self, pobj):
|
512
|
+
parent = self._getParentObject(pobj)
|
513
|
+
|
514
|
+
# Get the label
|
515
|
+
label = self._getPointerLabel(pobj, parent)
|
516
|
+
|
517
|
+
obj = pobj.get()
|
518
|
+
objId = pobj.getUniqueId()
|
519
|
+
isSelected = objId in self.selectedDict
|
520
|
+
self.selectedDict[objId] = True
|
521
|
+
|
522
|
+
return {'key': objId, 'text': label,
|
523
|
+
'values': (self._getObjectInfoValue(obj),
|
524
|
+
self._getObjectCreation(obj),
|
525
|
+
self._getObjectId(pobj)),
|
526
|
+
'selected': isSelected, 'parent': parent}
|
527
|
+
|
528
|
+
@staticmethod
|
529
|
+
def _getObjectId(pobj):
|
530
|
+
return pobj.getObjValue().getObjId()
|
531
|
+
|
532
|
+
@staticmethod
|
533
|
+
def _getObjectCreation(obj):
|
534
|
+
""" Returns the Object creation time stamp or 'Not ready' for those not yet ready or possibleOutputs"""
|
535
|
+
return obj.getObjCreation() if obj is not None and obj.getObjCreation() else "Not ready"
|
536
|
+
|
537
|
+
@staticmethod
|
538
|
+
def _getObjectInfoValue(obj):
|
539
|
+
""" Returns the best summary of the object in a string."""
|
540
|
+
if obj is not None:
|
541
|
+
return str(obj).replace(obj.getClassName(), '')
|
542
|
+
else: # possible Outputs are not output already so here comes None
|
543
|
+
return "Possible output"
|
544
|
+
def _getPointerLabel(self, pobj, parent=None):
|
545
|
+
|
546
|
+
# If parent is not provided, try to get it, it might have none.
|
547
|
+
if parent is None:
|
548
|
+
parent = self._getParentObject(pobj)
|
549
|
+
|
550
|
+
# If there is no parent
|
551
|
+
if parent is None:
|
552
|
+
return getObjectLabel(pobj, self.mapper)
|
553
|
+
else: # This is an item coming from a set
|
554
|
+
# If the object has label include the label
|
555
|
+
if pobj.get().getObjLabel():
|
556
|
+
return 'item %s - %s' % (pobj.get().strId(), pobj.get().getObjLabel())
|
557
|
+
else:
|
558
|
+
return 'item %s' % pobj.get().strId()
|
559
|
+
|
560
|
+
def getObjectActions(self, pobj):
|
561
|
+
obj = pobj.get()
|
562
|
+
actions = []
|
563
|
+
domain = pw.Config.getDomain()
|
564
|
+
viewers = domain.findViewers(obj, DESKTOP_TKINTER)
|
565
|
+
proj = self.protocol.getProject()
|
566
|
+
|
567
|
+
def addViewer(viewer):
|
568
|
+
actions.append((viewer.getName(),
|
569
|
+
lambda: viewer(project=proj).visualize(obj)))
|
570
|
+
|
571
|
+
for v in viewers:
|
572
|
+
addViewer(v)
|
573
|
+
return actions
|
574
|
+
|
575
|
+
|
576
|
+
# TODO: check if need to inherit from SubclassesTreeProvider
|
577
|
+
class RelationsTreeProvider(SubclassesTreeProvider):
|
578
|
+
"""Will implement the methods to provide the object info
|
579
|
+
of subclasses objects(of className) found by mapper"""
|
580
|
+
|
581
|
+
def __init__(self, protocol, relationParam, selected=None):
|
582
|
+
SubclassesTreeProvider.__init__(self, protocol, relationParam, selected)
|
583
|
+
self.item = protocol.getAttributeValue(relationParam.getAttributeName())
|
584
|
+
self.direction = relationParam.getDirection()
|
585
|
+
self.relationParam = relationParam
|
586
|
+
|
587
|
+
def getObjects(self):
|
588
|
+
objects = []
|
589
|
+
if self.item is not None:
|
590
|
+
project = self.protocol.getProject()
|
591
|
+
for pobj in project.getRelatedObjects(self.relationParam.getName(),
|
592
|
+
self.item, self.direction,
|
593
|
+
refresh=True):
|
594
|
+
objects.append(pobj.clone())
|
595
|
+
|
596
|
+
# Sort objects
|
597
|
+
self._sortObjects(objects)
|
598
|
+
|
599
|
+
return objects
|
600
|
+
|
601
|
+
|
602
|
+
class ScalarTreeProvider(TreeProvider):
|
603
|
+
"""Will implement the methods to provide the object info
|
604
|
+
of scalar outputs"""
|
605
|
+
CREATION_COLUMN = 'Creation'
|
606
|
+
INFO_COLUMN = 'Info'
|
607
|
+
|
608
|
+
def __init__(self, protocol, scalarParam, selected=None):
|
609
|
+
TreeProvider.__init__(self, sortingColumnName=self.CREATION_COLUMN,
|
610
|
+
sortingAscending=False)
|
611
|
+
|
612
|
+
self.param = scalarParam
|
613
|
+
self.selected = selected
|
614
|
+
self.selectedDict = {}
|
615
|
+
self.protocol = protocol
|
616
|
+
self.mapper = protocol.mapper
|
617
|
+
self.maxNum = 200
|
618
|
+
|
619
|
+
def getObjects(self):
|
620
|
+
# Retrieve all objects of type className
|
621
|
+
project = self.protocol.getProject()
|
622
|
+
className = self.param.paramClass
|
623
|
+
# Get the classes that are valid as input object
|
624
|
+
# em.findClass is very tight to the EMObjects...Since scalars are not
|
625
|
+
# EM object can't be used unless we do something
|
626
|
+
# For now this will work with exact class.
|
627
|
+
classes = [className]
|
628
|
+
objects = []
|
629
|
+
|
630
|
+
# Do no refresh again and take the runs that are loaded
|
631
|
+
# already in the project. We will prefer to save time
|
632
|
+
# here than have the 'very last' version of the runs and objects
|
633
|
+
runs = project.getRuns(refresh=False)
|
634
|
+
|
635
|
+
for prot in runs:
|
636
|
+
# Make sure we don't include previous output of the same
|
637
|
+
# protocol, it will cause a recursive loop
|
638
|
+
if prot.getObjId() != self.protocol.getObjId():
|
639
|
+
|
640
|
+
for paramName, attr in prot.iterOutputAttributes():
|
641
|
+
def _checkParam(paramName, attr):
|
642
|
+
# If attr is a sub-classes of any desired one, add it to the list
|
643
|
+
# we should also check if there is a condition, the object
|
644
|
+
# must comply with the condition
|
645
|
+
p = None
|
646
|
+
if any(isinstance(attr, c) for c in classes):
|
647
|
+
p = pwobj.Pointer(prot, extended=paramName)
|
648
|
+
p._allowsSelection = True
|
649
|
+
objects.append(p)
|
650
|
+
|
651
|
+
_checkParam(paramName, attr)
|
652
|
+
|
653
|
+
# Sort objects before returning them
|
654
|
+
self._sortObjects(objects)
|
655
|
+
|
656
|
+
return objects
|
657
|
+
|
658
|
+
def _sortObjects(self, objects):
|
659
|
+
objects.sort(key=self.objectKey, reverse=not self.isSortingAscending())
|
660
|
+
|
661
|
+
def objectKey(self, pobj):
|
662
|
+
|
663
|
+
obj = self._getParentObject(pobj, pobj)
|
664
|
+
|
665
|
+
if self._sortingColumnName == ScalarTreeProvider.CREATION_COLUMN:
|
666
|
+
return self._getObjectCreation(obj.get())
|
667
|
+
elif self._sortingColumnName == ScalarTreeProvider.INFO_COLUMN:
|
668
|
+
return self._getObjectInfoValue(obj.get())
|
669
|
+
else:
|
670
|
+
return self._getPointerLabel(obj)
|
671
|
+
|
672
|
+
def getColumns(self):
|
673
|
+
return [('Object', 300), (ScalarTreeProvider.INFO_COLUMN, 250),
|
674
|
+
(ScalarTreeProvider.CREATION_COLUMN, 150)]
|
675
|
+
|
676
|
+
def isSelected(self, obj):
|
677
|
+
""" Check if an object is selected or not. """
|
678
|
+
if self.selected:
|
679
|
+
for s in self.selected:
|
680
|
+
if s and s.getObjId() == obj.getObjId():
|
681
|
+
return True
|
682
|
+
return False
|
683
|
+
|
684
|
+
@staticmethod
|
685
|
+
def _getParentObject(pobj, default=None):
|
686
|
+
return getattr(pobj, '_parentObject', default)
|
687
|
+
|
688
|
+
def getObjectInfo(self, pobj):
|
689
|
+
parent = self._getParentObject(pobj)
|
690
|
+
|
691
|
+
# Get the label
|
692
|
+
label = self._getPointerLabel(pobj, parent)
|
693
|
+
|
694
|
+
obj = pobj.get()
|
695
|
+
objId = pobj.getUniqueId()
|
696
|
+
|
697
|
+
isSelected = objId in self.selectedDict
|
698
|
+
self.selectedDict[objId] = True
|
699
|
+
|
700
|
+
return {'key': objId, 'text': label,
|
701
|
+
'values': (self._getObjectInfoValue(obj),
|
702
|
+
self._getObjectCreation(obj)),
|
703
|
+
'selected': isSelected, 'parent': parent}
|
704
|
+
|
705
|
+
@staticmethod
|
706
|
+
def _getObjectCreation(obj):
|
707
|
+
|
708
|
+
return obj.getObjCreation()
|
709
|
+
|
710
|
+
@staticmethod
|
711
|
+
def _getObjectInfoValue(obj):
|
712
|
+
|
713
|
+
return str(obj).replace(obj.getClassName(), '')
|
714
|
+
|
715
|
+
def _getPointerLabel(self, pobj, parent=None):
|
716
|
+
|
717
|
+
# If parent is not provided, try to get it, it might have none.
|
718
|
+
if parent is None:
|
719
|
+
parent = self._getParentObject(pobj)
|
720
|
+
|
721
|
+
# If there is no parent
|
722
|
+
if parent is None:
|
723
|
+
return getObjectLabel(pobj, self.mapper)
|
724
|
+
else: # This is an item coming from a set
|
725
|
+
# If the object has label include the label
|
726
|
+
if pobj.get().getObjLabel():
|
727
|
+
return 'item %s - %s' % (
|
728
|
+
pobj.get().strId(), pobj.get().getObjLabel())
|
729
|
+
else:
|
730
|
+
return 'item %s' % pobj.get().strId()
|
731
|
+
|
732
|
+
|
733
|
+
# --------------------- Other widgets ----------------------------------------
|
734
|
+
# http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame
|
735
|
+
|
736
|
+
|
737
|
+
class VerticalScrolledFrame(tk.Frame):
|
738
|
+
"""A pure Tkinter scrollable frame that actually works!
|
739
|
+
* Use the 'interior' attribute to place widgets inside the scrollable frame
|
740
|
+
* Construct and pack/place/grid normally
|
741
|
+
* This frame only allows vertical scrolling
|
742
|
+
|
743
|
+
"""
|
744
|
+
|
745
|
+
def __init__(self, parent, *args, **kw):
|
746
|
+
tk.Frame.__init__(self, parent, *args, **kw)
|
747
|
+
|
748
|
+
# create a canvas object and a vertical scrollbar for scrolling it
|
749
|
+
vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
|
750
|
+
vscrollbar.pack(fill=tk.Y, side=tk.RIGHT, expand=tk.FALSE)
|
751
|
+
canvas = Canvas(self, bd=0, highlightthickness=0,
|
752
|
+
yscrollcommand=vscrollbar.set)
|
753
|
+
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.TRUE)
|
754
|
+
vscrollbar.config(command=canvas.yview)
|
755
|
+
|
756
|
+
# reset the view
|
757
|
+
canvas.xview_moveto(0)
|
758
|
+
canvas.yview_moveto(0)
|
759
|
+
|
760
|
+
# create a frame inside the canvas which will be scrolled with it
|
761
|
+
self.interior = interior = tk.Frame(canvas)
|
762
|
+
interior_id = canvas.create_window(0, 0, window=interior,
|
763
|
+
anchor=tk.NW)
|
764
|
+
|
765
|
+
# track changes to the canvas and frame width and sync them,
|
766
|
+
# also updating the scrollbar
|
767
|
+
def _configure_interior(event):
|
768
|
+
# update the scrollbars to match the size of the inner frame
|
769
|
+
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
|
770
|
+
canvas.config(scrollregion="0 0 %s %s" % size)
|
771
|
+
if interior.winfo_reqwidth() != canvas.winfo_width():
|
772
|
+
# update the canvas's width to fit the inner frame
|
773
|
+
canvas.config(width=interior.winfo_reqwidth())
|
774
|
+
|
775
|
+
interior.bind('<Configure>', _configure_interior)
|
776
|
+
|
777
|
+
def _configure_canvas(event):
|
778
|
+
if interior.winfo_reqwidth() != canvas.winfo_width():
|
779
|
+
# update the inner frame's width to fill the canvas
|
780
|
+
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
|
781
|
+
|
782
|
+
canvas.bind('<Configure>', _configure_canvas)
|
783
|
+
|
784
|
+
|
785
|
+
class SectionFrame(tk.Frame):
|
786
|
+
"""This class will be used to create a frame for the Section
|
787
|
+
That will have a header with red color and a content frame
|
788
|
+
with white background
|
789
|
+
"""
|
790
|
+
|
791
|
+
def __init__(self, master, label, callback=None, height=15, **args):
|
792
|
+
headerBgColor = args.get('headerBgColor', gui.cfgButtonBgColor)
|
793
|
+
if 'headerBgColor' in args:
|
794
|
+
del args['headerBgColor']
|
795
|
+
self.height = height
|
796
|
+
tk.Frame.__init__(self, master, **args)
|
797
|
+
configureWeigths(self, row=1)
|
798
|
+
self._createHeader(label, headerBgColor)
|
799
|
+
self._createContent()
|
800
|
+
tk.Frame.grid(self, row=0, column=0, sticky="new")
|
801
|
+
|
802
|
+
def _createHeader(self, label, bgColor):
|
803
|
+
self.headerFrame = tk.Frame(self, bd=2, relief=tk.RAISED, bg=bgColor,
|
804
|
+
name="sectionheaderframe")
|
805
|
+
self.headerFrame.grid(row=0, column=0, sticky='new')
|
806
|
+
configureWeigths(self.headerFrame)
|
807
|
+
self.headerFrame.columnconfigure(1, weight=1)
|
808
|
+
self.headerLabel = tk.Label(self.headerFrame, text=label, fg='white',
|
809
|
+
bg=bgColor, name="sectionheaderlabel")
|
810
|
+
self.headerLabel.grid(row=0, column=0, sticky='nw')
|
811
|
+
|
812
|
+
def _createContent(self):
|
813
|
+
self.canvasFrame = tk.Frame(self, name="sectioncontentframe")
|
814
|
+
configureWeigths(self.canvasFrame)
|
815
|
+
self.canvas = Canvas(self.canvasFrame, width=625, height=self.height,
|
816
|
+
bg=pw.Config.SCIPION_BG_COLOR, highlightthickness=0, name="sectioncanvas")
|
817
|
+
self.canvas.grid(row=0, column=0, sticky='news')
|
818
|
+
self.canvasFrame.grid(row=1, column=0, sticky='news')
|
819
|
+
|
820
|
+
configureWeigths(self.canvas)
|
821
|
+
|
822
|
+
self.contentFrame = tk.Frame(self.canvas, bg=pw.Config.SCIPION_BG_COLOR, bd=0,
|
823
|
+
name="sectioncanvasframe")
|
824
|
+
self.contentFrame.grid(row=1, column=0, sticky='news')
|
825
|
+
self.contentId = self.canvas.create_window(0, 0, anchor=tk.NW,
|
826
|
+
window=self.contentFrame)
|
827
|
+
|
828
|
+
self.contentFrame.bind('<Configure>', self._configure_interior)
|
829
|
+
self.canvas.bind('<Configure>', self._configure_canvas)
|
830
|
+
|
831
|
+
self.contentFrame.columnconfigure(1, weight=1)
|
832
|
+
self.columnconfigure(0, weight=1)
|
833
|
+
|
834
|
+
def _getReqSize(self, widget):
|
835
|
+
return widget.winfo_reqwidth(), widget.winfo_reqheight()
|
836
|
+
|
837
|
+
def _getSize(self, widget):
|
838
|
+
return widget.winfo_width(), widget.winfo_height()
|
839
|
+
|
840
|
+
# track changes to the canvas and frame width and sync them,
|
841
|
+
# also updating the scrollbar
|
842
|
+
def _configure_interior(self, event=None):
|
843
|
+
|
844
|
+
# update the scrollbars to match the size of the inner frame
|
845
|
+
fsize = self._getReqSize(self.contentFrame)
|
846
|
+
csize = self._getSize(self.canvas)
|
847
|
+
if fsize != csize:
|
848
|
+
# update the canvas's width to fit the inner frame
|
849
|
+
self.canvas.config(width=fsize[0], height=fsize[1])
|
850
|
+
self.canvas.config(scrollregion="0 0 %s %s" % fsize)
|
851
|
+
|
852
|
+
def _configure_canvas(self, event=None):
|
853
|
+
fsize = self._getReqSize(self.contentFrame)
|
854
|
+
csize = self._getContentSize()
|
855
|
+
|
856
|
+
# update the inner frame's width to fill the canvas
|
857
|
+
self.canvas.itemconfigure(self.contentId, width=csize[0], height=csize[1])
|
858
|
+
self.canvas.config(scrollregion="0 0 %s %s" % fsize)
|
859
|
+
|
860
|
+
def _getContentSize(self):
|
861
|
+
fsize = self._getReqSize(self.contentFrame)
|
862
|
+
cFrame = self._getSize(self.canvasFrame)
|
863
|
+
return (max(fsize[0], cFrame[0]), max(fsize[1], cFrame[1]))
|
864
|
+
|
865
|
+
def adjustContent(self):
|
866
|
+
self._configure_interior()
|
867
|
+
self.update_idletasks()
|
868
|
+
self._configure_canvas()
|
869
|
+
|
870
|
+
|
871
|
+
class SectionWidget(SectionFrame):
|
872
|
+
"""This class will be used to create a section in FormWindow"""
|
873
|
+
|
874
|
+
def __init__(self, form, master, section, height, callback=None, **args):
|
875
|
+
self.form = form
|
876
|
+
self.section = section
|
877
|
+
self.callback = callback
|
878
|
+
SectionFrame.__init__(self, master, self.section.label.get(),
|
879
|
+
height=height, **args)
|
880
|
+
|
881
|
+
def _createHeader(self, label, bgColor):
|
882
|
+
SectionFrame._createHeader(self, label, bgColor)
|
883
|
+
|
884
|
+
if self.section.hasQuestion():
|
885
|
+
question = self.section.getQuestion()
|
886
|
+
self.paramName = self.section.getQuestionName()
|
887
|
+
self.var = BoolVar()
|
888
|
+
self.var.set(question.get())
|
889
|
+
self.var.trace('w', self._onVarChanged)
|
890
|
+
|
891
|
+
self.chbLabel = tk.Label(self.headerFrame, text=question.label.get(),
|
892
|
+
fg='white', bg=bgColor)
|
893
|
+
self.chbLabel.grid(row=0, column=1, sticky='e', padx=2)
|
894
|
+
|
895
|
+
self.chb = tk.Checkbutton(self.headerFrame, variable=self.var.tkVar,
|
896
|
+
bg=bgColor,
|
897
|
+
activebackground=gui.cfgButtonActiveBgColor)
|
898
|
+
self.chb.grid(row=0, column=2, sticky='e')
|
899
|
+
|
900
|
+
def show(self):
|
901
|
+
self.contentFrame.grid(row=1, column=0, sticky='news', padx=5, pady=5)
|
902
|
+
|
903
|
+
def hide(self):
|
904
|
+
self.contentFrame.grid_remove()
|
905
|
+
|
906
|
+
def _onVarChanged(self, *args):
|
907
|
+
if self.get():
|
908
|
+
self.show()
|
909
|
+
else:
|
910
|
+
self.hide()
|
911
|
+
|
912
|
+
if self.callback is not None:
|
913
|
+
self.callback(self.paramName)
|
914
|
+
|
915
|
+
def get(self):
|
916
|
+
"""Return boolean value if is selected"""
|
917
|
+
return self.var.get()
|
918
|
+
|
919
|
+
def set(self, value):
|
920
|
+
self.var.set(value)
|
921
|
+
|
922
|
+
|
923
|
+
class ParamWidget:
|
924
|
+
"""For each one in the Protocol parameters, there will be
|
925
|
+
one of this in the Form GUI.
|
926
|
+
It is mainly composed by:
|
927
|
+
A Label: put in the left column
|
928
|
+
A Frame(content): in the middle column and container
|
929
|
+
of the specific components for this parameter
|
930
|
+
A Frame(buttons): a container for available actions buttons
|
931
|
+
It will also have a Variable that should be set when creating
|
932
|
+
the specific components"""
|
933
|
+
|
934
|
+
def __init__(self, row, paramName, param, window, parent, value,
|
935
|
+
callback=None, visualizeCallback=None, column=0,
|
936
|
+
showButtons=True):
|
937
|
+
self.window = window
|
938
|
+
self._protocol = self.window.protocol
|
939
|
+
|
940
|
+
self.row = row
|
941
|
+
self.column = column
|
942
|
+
self.paramName = paramName
|
943
|
+
self.param = param
|
944
|
+
self.parent = parent
|
945
|
+
self.visualizeCallback = visualizeCallback
|
946
|
+
self.var = None
|
947
|
+
|
948
|
+
self._btnCol = 0
|
949
|
+
self._labelFont = self.window.font
|
950
|
+
|
951
|
+
self._initialize(showButtons)
|
952
|
+
self._createLabel() # self.label should be set after this
|
953
|
+
self._createContent() # self.content and self.var should be set after this
|
954
|
+
|
955
|
+
if self.var: # Groups have not self.var
|
956
|
+
self.set(value)
|
957
|
+
self.callback = callback
|
958
|
+
self.var.trace('w', self._onVarChanged)
|
959
|
+
|
960
|
+
def _initialize(self, showButtons):
|
961
|
+
# Show buttons = False means the widget is inside a Line group
|
962
|
+
# then, some of the properties change accordingly
|
963
|
+
if showButtons:
|
964
|
+
self._labelSticky = 'ne'
|
965
|
+
self._padx, self._pady = 2, 2
|
966
|
+
self._entryWidth = 10
|
967
|
+
if self.param.isImportant():
|
968
|
+
self._labelFont = self.window.fontBold
|
969
|
+
self.parent.columnconfigure(0, minsize=250)
|
970
|
+
self.parent.columnconfigure(1, minsize=250)
|
971
|
+
self.btnFrame = tk.Frame(self.parent, bg=pw.Config.SCIPION_BG_COLOR)
|
972
|
+
else:
|
973
|
+
self.btnFrame = None
|
974
|
+
self._labelSticky = 'ne'
|
975
|
+
self._padx, self._pady = 2, 0
|
976
|
+
self._labelFont = self.window.fontItalic
|
977
|
+
self._entryWidth = 8
|
978
|
+
self._onlyLabel = False
|
979
|
+
|
980
|
+
def _getParamLabel(self):
|
981
|
+
return self.param.label.get()
|
982
|
+
|
983
|
+
def _createLabel(self):
|
984
|
+
bgColor = pw.Config.SCIPION_BG_COLOR
|
985
|
+
|
986
|
+
if self.param.isExpert():
|
987
|
+
bgColor = 'lightgrey'
|
988
|
+
|
989
|
+
self.label = tk.Label(self.parent, text=self._getParamLabel(),
|
990
|
+
bg=bgColor, font=self._labelFont, wraplength=500)
|
991
|
+
|
992
|
+
def _createContent(self):
|
993
|
+
self.content = tk.Frame(self.parent, bg=pw.Config.SCIPION_BG_COLOR)
|
994
|
+
gui.configureWeigths(self.content)
|
995
|
+
# self.var should be set after this
|
996
|
+
self._createContentWidgets(self.param, self.content)
|
997
|
+
|
998
|
+
def _addButton(self, text, imgPath, cmd):
|
999
|
+
if self.btnFrame:
|
1000
|
+
btn = IconButton(self.btnFrame, text, imgPath,
|
1001
|
+
highlightthickness=0, command=cmd)
|
1002
|
+
btn.grid(row=0, column=self._btnCol, sticky='nes', padx=1, pady=4)
|
1003
|
+
self.btnFrame.columnconfigure(self._btnCol, weight=1)
|
1004
|
+
self._btnCol += 1
|
1005
|
+
|
1006
|
+
def _showHelpMessage(self, e=None):
|
1007
|
+
showInfo("Help", self.param.help.get(), self.parent)
|
1008
|
+
|
1009
|
+
def _showInfo(self, msg):
|
1010
|
+
showInfo("Info", msg, self.parent)
|
1011
|
+
|
1012
|
+
def _showError(self, msg, exception=None):
|
1013
|
+
showError("Error", msg, self.parent, exception=exception)
|
1014
|
+
|
1015
|
+
def _showWarning(self, msg):
|
1016
|
+
showWarning("Warning", msg, self.parent)
|
1017
|
+
|
1018
|
+
def _showWizard(self, e=None):
|
1019
|
+
wizClass = self.window.wizards[self.wizParamName]
|
1020
|
+
wizard = wizClass()
|
1021
|
+
# wizParamName: form attribute, the wizard object can check from which parameter it was called
|
1022
|
+
# Used into VariableWizard objects (scipion-chem), where input and output parameters used
|
1023
|
+
# for each wizard are defined
|
1024
|
+
self.window.wizParamName = self.wizParamName
|
1025
|
+
wizard.show(self.window)
|
1026
|
+
|
1027
|
+
def _findParamWizard(self):
|
1028
|
+
""" Search if there are registered wizards for this param
|
1029
|
+
or any of its subparams (for the case of Line groups)
|
1030
|
+
"""
|
1031
|
+
if self.paramName in self.window.wizards:
|
1032
|
+
self.wizParamName = self.paramName
|
1033
|
+
return True
|
1034
|
+
|
1035
|
+
if isinstance(self.param, pwprot.Line):
|
1036
|
+
for name, _ in self.param.iterParams():
|
1037
|
+
if name in self.window.wizards:
|
1038
|
+
self.wizParamName = name
|
1039
|
+
return True
|
1040
|
+
# Search in sub-params
|
1041
|
+
return False
|
1042
|
+
|
1043
|
+
@staticmethod
|
1044
|
+
def createBoolWidget(parent, display=pwprot.BooleanParam.DISPLAY_YES_NO, **args):
|
1045
|
+
""" Return a BoolVar associated with a yes/no selection.
|
1046
|
+
**args: extra arguments passed to tk.Radiobutton and tk.Frame
|
1047
|
+
constructors.
|
1048
|
+
|
1049
|
+
:param checkbox: will use a Checkbutton instead.
|
1050
|
+
"""
|
1051
|
+
var = BoolVar()
|
1052
|
+
frameArgs = dict(args)
|
1053
|
+
if 'font' in frameArgs:
|
1054
|
+
del frameArgs['font']
|
1055
|
+
frame = tk.Frame(parent, **frameArgs)
|
1056
|
+
|
1057
|
+
if display == pwprot.BooleanParam.DISPLAY_CHECKBOX:
|
1058
|
+
chk = tk.Checkbutton(frame, variable=var.tkVar, **args)
|
1059
|
+
chk.grid(row=0, column=0, padx=2, sticky="w")
|
1060
|
+
else:
|
1061
|
+
rb1 = tk.Radiobutton(frame, text='Yes', variable=var.tkVar,
|
1062
|
+
highlightthickness=0, value=1, **args)
|
1063
|
+
rb1.grid(row=0, column=0, padx=2, sticky='w')
|
1064
|
+
rb2 = tk.Radiobutton(frame, text='No', variable=var.tkVar,
|
1065
|
+
highlightthickness=0, value=0, **args)
|
1066
|
+
rb2.grid(row=0, column=1, padx=2, sticky='w')
|
1067
|
+
|
1068
|
+
return var, frame
|
1069
|
+
|
1070
|
+
def _createContentWidgets(self, param, content):
|
1071
|
+
"""Create the specific widgets inside the content frame"""
|
1072
|
+
# Create widgets for each type of param
|
1073
|
+
t = type(param)
|
1074
|
+
entryWidth = 30
|
1075
|
+
sticky = "we"
|
1076
|
+
|
1077
|
+
# functions to select and remove
|
1078
|
+
selectFunc = None
|
1079
|
+
removeFunc = None
|
1080
|
+
|
1081
|
+
if t is pwprot.HiddenBooleanParam:
|
1082
|
+
var = 0
|
1083
|
+
|
1084
|
+
elif t is pwprot.BooleanParam:
|
1085
|
+
var, frame = ParamWidget.createBoolWidget(content, display=param.display,
|
1086
|
+
bg=pw.Config.SCIPION_BG_COLOR,
|
1087
|
+
font=self.window.font)
|
1088
|
+
frame.grid(row=0, column=0, sticky='w')
|
1089
|
+
|
1090
|
+
elif t is pwprot.EnumParam:
|
1091
|
+
var = ComboVar(param)
|
1092
|
+
if param.display == pwprot.EnumParam.DISPLAY_COMBO:
|
1093
|
+
combo = ttk.Combobox(content, textvariable=var.tkVar,
|
1094
|
+
state='readonly', font=self.window.font)
|
1095
|
+
combo['values'] = param.choices
|
1096
|
+
combo.grid(row=0, column=0, sticky='we')
|
1097
|
+
elif param.display == pwprot.EnumParam.DISPLAY_LIST:
|
1098
|
+
for i, opt in enumerate(param.choices):
|
1099
|
+
rb = tk.Radiobutton(content, text=opt, variable=var.tkVar,
|
1100
|
+
value=opt, font=self.window.font,
|
1101
|
+
bg=pw.Config.SCIPION_BG_COLOR, highlightthickness=0)
|
1102
|
+
rb.grid(row=i, column=0, sticky='w')
|
1103
|
+
elif param.display == pwprot.EnumParam.DISPLAY_HLIST:
|
1104
|
+
rbFrame = tk.Frame(content, bg=pw.Config.SCIPION_BG_COLOR)
|
1105
|
+
rbFrame.grid(row=0, column=0, sticky='w')
|
1106
|
+
for i, opt in enumerate(param.choices):
|
1107
|
+
rb = tk.Radiobutton(rbFrame, text=opt, variable=var.tkVar,
|
1108
|
+
value=opt, font=self.window.font,
|
1109
|
+
bg=pw.Config.SCIPION_BG_COLOR)
|
1110
|
+
rb.grid(row=0, column=i, sticky='w', padx=(0, 5))
|
1111
|
+
else:
|
1112
|
+
raise Exception("Invalid display value '%s' for EnumParam"
|
1113
|
+
% str(param.display))
|
1114
|
+
|
1115
|
+
elif t is pwprot.MultiPointerParam:
|
1116
|
+
tp = MultiPointerTreeProvider(self._protocol.mapper)
|
1117
|
+
tree = BoundTree(content, tp, height=5)
|
1118
|
+
var = MultiPointerVar(tp, tree)
|
1119
|
+
var.trace('w', self.window._onPointerChanged)
|
1120
|
+
tree.grid(row=0, column=0, sticky='we')
|
1121
|
+
self._addButton("Select", pwutils.Icon.ACTION_SEARCH, self._browseObject)
|
1122
|
+
self._addButton("Remove", pwutils.Icon.ACTION_DELETE, self._removeObject)
|
1123
|
+
self._selectmode = 'extended' # allows multiple object selection
|
1124
|
+
self.visualizeCallback = self._visualizeMultiPointerParam
|
1125
|
+
|
1126
|
+
elif t is pwprot.PointerParam or t is pwprot.RelationParam:
|
1127
|
+
var = PointerVar(self._protocol)
|
1128
|
+
var.trace('w', self.window._onPointerChanged)
|
1129
|
+
entry = tk.Label(content, textvariable=var.tkVar,
|
1130
|
+
font=self.window.font, anchor="w")
|
1131
|
+
entry.grid(row=0, column=0, sticky='we')
|
1132
|
+
|
1133
|
+
if t is pwprot.RelationParam:
|
1134
|
+
selectFunc = self._browseRelation
|
1135
|
+
removeFunc = self._removeRelation
|
1136
|
+
else:
|
1137
|
+
selectFunc = self._browseObject
|
1138
|
+
removeFunc = self._removeObject
|
1139
|
+
|
1140
|
+
self.visualizeCallback = self._visualizePointerParam
|
1141
|
+
self._selectmode = 'browse' # single object selection
|
1142
|
+
|
1143
|
+
elif t is pwprot.ProtocolClassParam:
|
1144
|
+
var = tk.StringVar()
|
1145
|
+
entry = tk.Label(content, textvariable=var, font=self.window.font,
|
1146
|
+
anchor="w")
|
1147
|
+
entry.grid(row=0, column=0, sticky='we')
|
1148
|
+
|
1149
|
+
protClassName = self.param.protocolClassName.get()
|
1150
|
+
|
1151
|
+
if self.param.allowSubclasses:
|
1152
|
+
classes = pw.Config.getDomain().findSubClasses(
|
1153
|
+
pw.Config.getDomain().getProtocols(), protClassName).keys()
|
1154
|
+
else:
|
1155
|
+
classes = [protClassName]
|
1156
|
+
|
1157
|
+
if len(classes) > 1:
|
1158
|
+
self._addButton("Select", pwutils.Icon.ACTION_SEARCH,
|
1159
|
+
self._browseProtocolClass)
|
1160
|
+
else:
|
1161
|
+
var.set(classes[0])
|
1162
|
+
|
1163
|
+
self._addButton("Edit", pwutils.Icon.ACTION_EDIT, self._openProtocolForm)
|
1164
|
+
|
1165
|
+
elif t is pwprot.Line:
|
1166
|
+
var = None
|
1167
|
+
|
1168
|
+
elif t is pwprot.LabelParam:
|
1169
|
+
var = None
|
1170
|
+
self._onlyLabel = True
|
1171
|
+
|
1172
|
+
elif t is pwprot.TextParam:
|
1173
|
+
w = max(entryWidth, param.width)
|
1174
|
+
text = Text(content, font=self.window.font, width=w,
|
1175
|
+
height=param.height)
|
1176
|
+
var = TextVar(text)
|
1177
|
+
text.grid(row=0, column=0, sticky='w')
|
1178
|
+
|
1179
|
+
else:
|
1180
|
+
|
1181
|
+
if not param.allowsPointers:
|
1182
|
+
var = tk.StringVar()
|
1183
|
+
|
1184
|
+
if issubclass(t, pwprot.FloatParam) or issubclass(t, pwprot.IntParam):
|
1185
|
+
# Reduce the entry width for numbers entries
|
1186
|
+
entryWidth = self._entryWidth
|
1187
|
+
sticky = 'w'
|
1188
|
+
else:
|
1189
|
+
selectFunc = self._browseScalar
|
1190
|
+
var = ScalarWithPointerVar(self._protocol,
|
1191
|
+
self.window._onPointerChanged)
|
1192
|
+
self._selectmode = 'browse'
|
1193
|
+
sticky = 'ew'
|
1194
|
+
state = tk.DISABLED if param.readOnly else tk.NORMAL
|
1195
|
+
entry = tk.Entry(content, width=entryWidth, textvariable=var,
|
1196
|
+
font=self.window.font, state=state)
|
1197
|
+
|
1198
|
+
# Select all content on focus
|
1199
|
+
entry.bind("<FocusIn>",
|
1200
|
+
lambda event: entry.selection_range(0, tk.END))
|
1201
|
+
|
1202
|
+
entry.grid(row=0, column=0, sticky=sticky)
|
1203
|
+
|
1204
|
+
if issubclass(t, pwprot.PathParam):
|
1205
|
+
self._entryPath = entry
|
1206
|
+
self._addButton('Browse', pwutils.Icon.ACTION_BROWSE,
|
1207
|
+
self._browsePath)
|
1208
|
+
|
1209
|
+
if selectFunc is not None:
|
1210
|
+
self._addButton("Select", pwutils.Icon.ACTION_SEARCH, selectFunc)
|
1211
|
+
|
1212
|
+
if removeFunc is not None:
|
1213
|
+
self._addButton("Remove", pwutils.Icon.ACTION_DELETE, removeFunc)
|
1214
|
+
|
1215
|
+
if self.visualizeCallback is not None:
|
1216
|
+
self._addButton(pwutils.Message.LABEL_BUTTON_VIS,
|
1217
|
+
pwutils.Icon.ACTION_VISUALIZE,
|
1218
|
+
self._visualizeVar)
|
1219
|
+
|
1220
|
+
if self._findParamWizard():
|
1221
|
+
self._addButton(pwutils.Message.LABEL_BUTTON_WIZ,
|
1222
|
+
pwutils.Icon.ACTION_WIZ,
|
1223
|
+
self._showWizard)
|
1224
|
+
|
1225
|
+
if param.help.hasValue():
|
1226
|
+
self._addButton(pwutils.Message.LABEL_BUTTON_HELP,
|
1227
|
+
pwutils.Icon.ACTION_HELP,
|
1228
|
+
self._showHelpMessage)
|
1229
|
+
|
1230
|
+
self.var = var
|
1231
|
+
|
1232
|
+
def _visualizeVar(self, e=None):
|
1233
|
+
""" Visualize specific variable. """
|
1234
|
+
self.visualizeCallback(self.paramName)
|
1235
|
+
|
1236
|
+
def _visualizePointer(self, pointer):
|
1237
|
+
obj = pointer.get()
|
1238
|
+
|
1239
|
+
if obj is None:
|
1240
|
+
label, _ = getPointerLabelAndInfo(pointer, self._protocol.getMapper())
|
1241
|
+
self._showInfo('*%s* points to *None*' % label)
|
1242
|
+
else:
|
1243
|
+
viewers = pw.Config.getDomain().findViewers(obj, DESKTOP_TKINTER)
|
1244
|
+
if len(viewers):
|
1245
|
+
ViewerClass = viewers[0] # Use the first viewer registered
|
1246
|
+
# Instantiate the viewer and visualize object
|
1247
|
+
viewer = ViewerClass(project=self._protocol.getProject(),
|
1248
|
+
protocol=self._protocol,
|
1249
|
+
parent=self.window)
|
1250
|
+
viewer.visualize(obj)
|
1251
|
+
else:
|
1252
|
+
self._showInfo("There is no viewer registered for "
|
1253
|
+
"*%s* object class." % obj.getClassName())
|
1254
|
+
|
1255
|
+
def _visualizePointerParam(self, paramName):
|
1256
|
+
pointer = self.var.get()
|
1257
|
+
if pointer.hasValue():
|
1258
|
+
self._visualizePointer(pointer)
|
1259
|
+
else:
|
1260
|
+
self._showInfo("Select input first.")
|
1261
|
+
|
1262
|
+
def _visualizeMultiPointerParam(self, paramName):
|
1263
|
+
selection = self.var.getSelectedObjects()
|
1264
|
+
for pointer in selection:
|
1265
|
+
self._visualizePointer(pointer)
|
1266
|
+
|
1267
|
+
def _browseObject(self, e=None):
|
1268
|
+
"""Select an object from DB
|
1269
|
+
This function is supposed to be used only for PointerParam"""
|
1270
|
+
value = self.get()
|
1271
|
+
selected = []
|
1272
|
+
if isinstance(value, list):
|
1273
|
+
selected = value
|
1274
|
+
else:
|
1275
|
+
|
1276
|
+
selected = [value]
|
1277
|
+
tp = SubclassesTreeProvider(self._protocol, self.param,
|
1278
|
+
selected=selected)
|
1279
|
+
|
1280
|
+
def validateSelected(selectedItems):
|
1281
|
+
for item in selectedItems:
|
1282
|
+
if not getattr(item, '_allowsSelection', True):
|
1283
|
+
return ("Please select object of types: %s"
|
1284
|
+
% self.param.pointerClass.get())
|
1285
|
+
|
1286
|
+
title = "Select object of types: %s" % self.param.pointerClass.get()
|
1287
|
+
|
1288
|
+
pointerCond = self.param.pointerCondition.get()
|
1289
|
+
|
1290
|
+
if pointerCond:
|
1291
|
+
title += " (condition: %s)" % pointerCond
|
1292
|
+
|
1293
|
+
dlg = ListDialog(self.parent, title, tp,
|
1294
|
+
"Double click selects the item, right-click allows "
|
1295
|
+
"you to visualize it",
|
1296
|
+
validateSelectionCallback=validateSelected,
|
1297
|
+
selectmode=self._selectmode, selectOnDoubleClick=True)
|
1298
|
+
|
1299
|
+
if dlg.values:
|
1300
|
+
if self.isMultiPointer():
|
1301
|
+
self.set(dlg.values)
|
1302
|
+
elif isinstance(self.param, pwprot.PointerParam):
|
1303
|
+
self.set(dlg.values[0])
|
1304
|
+
else:
|
1305
|
+
raise Exception('Invalid param class: %s' % type(self.param))
|
1306
|
+
|
1307
|
+
def isMultiPointer(self):
|
1308
|
+
""" True if dealing with MultiPointer params """
|
1309
|
+
return isinstance(self.param, pwprot.MultiPointerParam)
|
1310
|
+
|
1311
|
+
def _browseScalar(self, e=None):
|
1312
|
+
"""Select a scalar from outputs
|
1313
|
+
This function is supposed to be used only for Scalar Params
|
1314
|
+
It's a copy of browseObject...so there could be a refactor here."""
|
1315
|
+
value = self.get()
|
1316
|
+
selected = []
|
1317
|
+
if isinstance(value, list):
|
1318
|
+
selected = value
|
1319
|
+
else:
|
1320
|
+
selected = [value]
|
1321
|
+
tp = ScalarTreeProvider(self._protocol, self.param,
|
1322
|
+
selected=selected)
|
1323
|
+
|
1324
|
+
def validateSelected(selectedItems):
|
1325
|
+
for item in selectedItems:
|
1326
|
+
if not getattr(item, '_allowsSelection', True):
|
1327
|
+
return ("Please select object of types: %s"
|
1328
|
+
% self.param.paramClass.get())
|
1329
|
+
|
1330
|
+
title = "Select object of type %s" % self.param.paramClass.__name__
|
1331
|
+
|
1332
|
+
# Let's ignore conditions so far
|
1333
|
+
# pointerCond = self.param.pointerCondition.get()
|
1334
|
+
# if pointerCond:
|
1335
|
+
# title += " (condition: %s)" % pointerCond
|
1336
|
+
|
1337
|
+
dlg = ListDialog(self.parent, title, tp,
|
1338
|
+
"Double click selects the item",
|
1339
|
+
validateSelectionCallback=validateSelected,
|
1340
|
+
selectOnDoubleClick=True)
|
1341
|
+
|
1342
|
+
if dlg.values:
|
1343
|
+
self.set(dlg.values[0])
|
1344
|
+
|
1345
|
+
def _removeObject(self, e=None):
|
1346
|
+
""" Remove an object from a MultiPointer param. """
|
1347
|
+
self.var.remove()
|
1348
|
+
|
1349
|
+
def clear(self):
|
1350
|
+
|
1351
|
+
# If dealing with Multipointers ...
|
1352
|
+
if self.isMultiPointer():
|
1353
|
+
# .. use var clear to remove all eletents since
|
1354
|
+
# _removeObject() will remove only the selected ones
|
1355
|
+
self.var.clear()
|
1356
|
+
else:
|
1357
|
+
self._removeObject()
|
1358
|
+
|
1359
|
+
def _browseRelation(self, e=None):
|
1360
|
+
"""Select a relation from DB
|
1361
|
+
This function is suppose to be used only for RelationParam. """
|
1362
|
+
try:
|
1363
|
+
tp = RelationsTreeProvider(self._protocol, self.param,
|
1364
|
+
selected=self.get())
|
1365
|
+
dlg = ListDialog(self.parent, "Select object", tp,
|
1366
|
+
"Double click selects the item, right-click "
|
1367
|
+
"allows you to visualize it",
|
1368
|
+
selectmoded=self._selectmode,
|
1369
|
+
selectOnDoubleClick=True)
|
1370
|
+
if dlg.values:
|
1371
|
+
self.set(dlg.values[0])
|
1372
|
+
except AttributeError as exc:
|
1373
|
+
self._showError("Error loading possible inputs. "
|
1374
|
+
"This usually happens because the parameter "
|
1375
|
+
"needs info from other parameters... are "
|
1376
|
+
"previous mandatory parameters set?", exception=exc)
|
1377
|
+
|
1378
|
+
def _removeRelation(self, e=None):
|
1379
|
+
self.var.remove()
|
1380
|
+
|
1381
|
+
def _browseProtocolClass(self, e=None):
|
1382
|
+
tp = ProtocolClassTreeProvider(self.param.protocolClassName.get())
|
1383
|
+
dlg = ListDialog(self.parent, "Select protocol", tp,
|
1384
|
+
selectmode=self._selectmode)
|
1385
|
+
if dlg.value is not None:
|
1386
|
+
self.set(dlg.value)
|
1387
|
+
self._openProtocolForm()
|
1388
|
+
|
1389
|
+
def _browsePath(self, e=None):
|
1390
|
+
def onSelect(obj):
|
1391
|
+
self.set(obj.getPath())
|
1392
|
+
|
1393
|
+
v = self.get().strip()
|
1394
|
+
path = None
|
1395
|
+
if v:
|
1396
|
+
v = os.path.dirname(v)
|
1397
|
+
if os.path.exists(v):
|
1398
|
+
path = v
|
1399
|
+
if not path:
|
1400
|
+
path = pwutils.getHomePath()
|
1401
|
+
browser = FileBrowserWindow("Browsing", self.window, path=path,
|
1402
|
+
onSelect=onSelect)
|
1403
|
+
browser.show()
|
1404
|
+
|
1405
|
+
def _openProtocolForm(self, e=None):
|
1406
|
+
className = self.get().strip()
|
1407
|
+
if len(className):
|
1408
|
+
instanceName = self.paramName + "Instance"
|
1409
|
+
protocol = self._protocol
|
1410
|
+
# TODO: check if is present and is selected a different
|
1411
|
+
# class, so we need to delete that and create a new instance
|
1412
|
+
if not hasattr(protocol, instanceName):
|
1413
|
+
cls = pw.Config.getDomain().findClass(className)
|
1414
|
+
protocol._insertChild(instanceName, cls())
|
1415
|
+
|
1416
|
+
prot = getattr(protocol, instanceName)
|
1417
|
+
|
1418
|
+
prot.allowHeader.set(False)
|
1419
|
+
f = FormWindow("Sub-Protocol: " + instanceName, prot,
|
1420
|
+
self._protocolFormCallback, self.window,
|
1421
|
+
childMode=True)
|
1422
|
+
f.show()
|
1423
|
+
else:
|
1424
|
+
self._showInfo("Select the protocol class first")
|
1425
|
+
|
1426
|
+
def _protocolFormCallback(self, e=None):
|
1427
|
+
pass
|
1428
|
+
|
1429
|
+
def _onVarChanged(self, *args):
|
1430
|
+
if self.callback is not None:
|
1431
|
+
self.callback(self.paramName)
|
1432
|
+
|
1433
|
+
def show(self):
|
1434
|
+
"""Grid the label and content in the specified row"""
|
1435
|
+
c = self.column
|
1436
|
+
if self._onlyLabel:
|
1437
|
+
# Use two columns for this case since we are only displaying a label
|
1438
|
+
self.label.grid(row=self.row, column=c, sticky=self._labelSticky,
|
1439
|
+
padx=self._padx, pady=self._pady, columnspan=2)
|
1440
|
+
else:
|
1441
|
+
self.label.grid(row=self.row, column=c, sticky=self._labelSticky,
|
1442
|
+
padx=self._padx, pady=self._pady)
|
1443
|
+
|
1444
|
+
# Note: for params without label: 1st param in a line param,
|
1445
|
+
# label usually but take space and pushes the content, avoid
|
1446
|
+
# this by using it's column
|
1447
|
+
offset = 1 if not self._getParamLabel() else 0
|
1448
|
+
|
1449
|
+
self.content.grid(row=self.row, column=c + 1 - offset,
|
1450
|
+
columnspan=1 + offset, sticky='news',
|
1451
|
+
padx=self._padx, pady=self._pady)
|
1452
|
+
if self.btnFrame:
|
1453
|
+
self.btnFrame.grid(row=self.row, column=c + 2, padx=self._padx,
|
1454
|
+
pady=self._pady, sticky='nsew')
|
1455
|
+
|
1456
|
+
def hide(self):
|
1457
|
+
self.label.grid_remove()
|
1458
|
+
self.content.grid_remove()
|
1459
|
+
if self.btnFrame:
|
1460
|
+
self.btnFrame.grid_remove()
|
1461
|
+
|
1462
|
+
def display(self, condition):
|
1463
|
+
""" show or hide depending on the condition. """
|
1464
|
+
if condition:
|
1465
|
+
self.show()
|
1466
|
+
else:
|
1467
|
+
self.hide()
|
1468
|
+
|
1469
|
+
def set(self, value):
|
1470
|
+
if value is not None:
|
1471
|
+
self.var.set(value)
|
1472
|
+
|
1473
|
+
if hasattr(self, '_entryPath'):
|
1474
|
+
self._entryPath.xview_moveto(1)
|
1475
|
+
|
1476
|
+
def get(self):
|
1477
|
+
return self.var.get()
|
1478
|
+
|
1479
|
+
|
1480
|
+
class LineWidget(ParamWidget):
|
1481
|
+
def __init__(self, row, paramName, param, window, parent, value,
|
1482
|
+
callback=None, visualizeCallback=None, column=0,
|
1483
|
+
showButtons=True):
|
1484
|
+
ParamWidget.__init__(self, row, paramName, param, window, parent, None)
|
1485
|
+
self.show()
|
1486
|
+
|
1487
|
+
def show(self):
|
1488
|
+
self.label.grid(row=self.row, column=0, sticky=self._labelSticky, padx=2)
|
1489
|
+
self.content.grid(row=self.row, column=1, sticky='new', columnspan=1,
|
1490
|
+
padx=2)
|
1491
|
+
if self.btnFrame:
|
1492
|
+
self.btnFrame.grid(row=self.row, column=2, padx=2, sticky='new')
|
1493
|
+
|
1494
|
+
|
1495
|
+
class GroupWidget(ParamWidget):
|
1496
|
+
def __init__(self, row, paramName, param, window, parent):
|
1497
|
+
ParamWidget.__init__(self, row, paramName, param, window, parent, None)
|
1498
|
+
|
1499
|
+
def _initialize(self, showButtons):
|
1500
|
+
pass
|
1501
|
+
|
1502
|
+
def _createLabel(self):
|
1503
|
+
pass
|
1504
|
+
|
1505
|
+
def _createContent(self):
|
1506
|
+
self.content = tk.LabelFrame(self.parent, text=self.param.getLabel(),
|
1507
|
+
bg=pw.Config.SCIPION_BG_COLOR)
|
1508
|
+
gui.configureWeigths(self.content, column=1)
|
1509
|
+
|
1510
|
+
def show(self):
|
1511
|
+
self.content.grid(row=self.row, column=0, sticky='news', columnspan=6,
|
1512
|
+
padx=5, pady=5)
|
1513
|
+
|
1514
|
+
def hide(self):
|
1515
|
+
self.content.grid_remove()
|
1516
|
+
|
1517
|
+
|
1518
|
+
class Binding:
|
1519
|
+
def __init__(self, paramName, var, protocol, *callbacks):
|
1520
|
+
self.paramName = paramName
|
1521
|
+
self.var = var
|
1522
|
+
self.var.set(protocol.getAttributeValue(paramName, ''))
|
1523
|
+
self.var.trace('w', self._onVarChanged)
|
1524
|
+
self.callbacks = callbacks
|
1525
|
+
|
1526
|
+
def _onVarChanged(self, *args):
|
1527
|
+
for cb in self.callbacks:
|
1528
|
+
cb(self.paramName)
|
1529
|
+
|
1530
|
+
|
1531
|
+
class FormWindow(Window):
|
1532
|
+
""" This class will create the Protocol params GUI to fill in the parameters.
|
1533
|
+
The creation of input parameters will be based on the Protocol Form definition.
|
1534
|
+
This class will serve as a connection between the GUI variables (tk vars) and
|
1535
|
+
the Protocol variables.
|
1536
|
+
|
1537
|
+
Layout:
|
1538
|
+
There are 4 main blocks that goes each one in a different row1.
|
1539
|
+
1. Header: will contain the logo, title and some link buttons.
|
1540
|
+
2. Common: common execution parameters of each run.
|
1541
|
+
3. Params: the expert level and tabs with the Protocol parameters.
|
1542
|
+
4. Buttons: buttons at bottom for close, save and execute.
|
1543
|
+
"""
|
1544
|
+
|
1545
|
+
def __init__(self, title, protocol:pwprot.Protocol, callback, master=None, position=None,
|
1546
|
+
previousProt:pwprot.Protocol=None, **kwargs):
|
1547
|
+
""" Constructor of the Form window.
|
1548
|
+
Params:
|
1549
|
+
title: title string of the windows.
|
1550
|
+
protocol: protocol from which the form will be generated.
|
1551
|
+
callback: callback function to call when Save or Execute are press.
|
1552
|
+
"""
|
1553
|
+
|
1554
|
+
if position:
|
1555
|
+
title = title + " at %s,%s" % position
|
1556
|
+
Window.__init__(self, title, master, icon=pwutils.Icon.SCIPION_ICON_PROT,
|
1557
|
+
weight=False, minsize=(600, 450), **kwargs)
|
1558
|
+
|
1559
|
+
# Some initialization
|
1560
|
+
self.callback = callback
|
1561
|
+
self.widgetDict = {} # Store tkVars associated with params
|
1562
|
+
self.visualizeDict = kwargs.get('visualizeDict', {})
|
1563
|
+
self.disableRunMode = kwargs.get('disableRunMode', False)
|
1564
|
+
self.bindings = []
|
1565
|
+
self.protocol = protocol
|
1566
|
+
self.previousProt=previousProt
|
1567
|
+
self.position = position
|
1568
|
+
# This control when to close or not after execute
|
1569
|
+
self.visualizeMode = kwargs.get('visualizeMode', False)
|
1570
|
+
self.headerBgColor = pw.Config.SCIPION_MAIN_COLOR
|
1571
|
+
if self.visualizeMode:
|
1572
|
+
self.headerBgColor = pwutils.Color.ALT_COLOR_DARK
|
1573
|
+
# Allow to open child protocols form (for workflows)
|
1574
|
+
self.childMode = kwargs.get('childMode', False)
|
1575
|
+
self.updateProtocolCallback = kwargs.get('updateProtocolCallback', None)
|
1576
|
+
domain = pw.Config.getDomain()
|
1577
|
+
self.wizards = domain.findWizards(protocol, DESKTOP_TKINTER)
|
1578
|
+
|
1579
|
+
# Call legacy for compatibility on protocol
|
1580
|
+
protocol.legacyCheck()
|
1581
|
+
self._createGUI()
|
1582
|
+
|
1583
|
+
def hasPreviousProt(self):
|
1584
|
+
return self.previousProt is not None
|
1585
|
+
|
1586
|
+
def getPreviousProtOutput(self):
|
1587
|
+
""" Returns the previous protocol output"""
|
1588
|
+
if self.hasPreviousProt():
|
1589
|
+
|
1590
|
+
# NOTE: Should we cache this?.
|
1591
|
+
for attr, output in self.previousProt.iterOutputAttributes(includePossible=True):
|
1592
|
+
yield attr, output
|
1593
|
+
|
1594
|
+
def getParam(self, paramName):
|
1595
|
+
""" Returns a specific parameter from the form definition by its name
|
1596
|
+
|
1597
|
+
:param paramName: Name of the parameter
|
1598
|
+
"""
|
1599
|
+
|
1600
|
+
return self.protocol._definition.getParam(paramName)
|
1601
|
+
|
1602
|
+
def _createGUI(self):
|
1603
|
+
mainFrame = tk.Frame(self.root, name="main")
|
1604
|
+
configureWeigths(mainFrame, row=2)
|
1605
|
+
self.root.rowconfigure(0, weight=1)
|
1606
|
+
self.root.columnconfigure(0, weight=1)
|
1607
|
+
|
1608
|
+
# "Protocol: XXXXX - Cite Help
|
1609
|
+
headerFrame = self._createHeader(mainFrame)
|
1610
|
+
headerFrame.grid(row=0, column=0, sticky='new')
|
1611
|
+
|
1612
|
+
if self.protocol.allowHeader:
|
1613
|
+
# Run Section with common attributes (parallel,...)
|
1614
|
+
commonFrame = self._createCommon(mainFrame)
|
1615
|
+
commonFrame.grid(row=1, column=0, sticky='new')
|
1616
|
+
|
1617
|
+
if self._isLegacyProtocol():
|
1618
|
+
paramsFrame = self._createLegacyInfo(mainFrame)
|
1619
|
+
else:
|
1620
|
+
paramsFrame = self._createParams(mainFrame)
|
1621
|
+
paramsFrame.grid(row=2, column=0, sticky='news')
|
1622
|
+
|
1623
|
+
buttonsFrame = self._createButtons(mainFrame)
|
1624
|
+
buttonsFrame.grid(row=3, column=0, sticky='se')
|
1625
|
+
|
1626
|
+
mainFrame.grid(row=0, column=0, sticky='news')
|
1627
|
+
|
1628
|
+
def _createHeader(self, parent):
|
1629
|
+
""" Fill the header frame with the logo, title and cite-help buttons."""
|
1630
|
+
headerFrame = tk.Frame(parent, name="header")
|
1631
|
+
headerFrame.columnconfigure(0, weight=1)
|
1632
|
+
prot = self.protocol # shortcut
|
1633
|
+
package = prot.getClassPackage()
|
1634
|
+
|
1635
|
+
# Consider legacy protocols
|
1636
|
+
if self._isLegacyProtocol():
|
1637
|
+
t = (' Missing protocol: %s'
|
1638
|
+
% (Mapper.getObjectPersistingClassName(prot)))
|
1639
|
+
else:
|
1640
|
+
t = ' %s' % (prot.getClassLabel())
|
1641
|
+
|
1642
|
+
logoPath = prot.getPluginLogoPath() or getattr(package, '_logo', '')
|
1643
|
+
|
1644
|
+
if logoPath and os.path.exists(logoPath):
|
1645
|
+
# Tolerate error loading icons
|
1646
|
+
try:
|
1647
|
+
img = self.getImage(logoPath, maxheight=40)
|
1648
|
+
except Exception as e:
|
1649
|
+
print("Can't load plugin icon (%s): %s" % (logoPath, e))
|
1650
|
+
img = None
|
1651
|
+
|
1652
|
+
headerLabel = tk.Label(headerFrame, text=t, font=self.fontBig,
|
1653
|
+
image=img,
|
1654
|
+
compound=tk.LEFT)
|
1655
|
+
else:
|
1656
|
+
headerLabel = tk.Label(headerFrame, text=t, font=self.fontBig)
|
1657
|
+
headerLabel.grid(row=0, column=0, padx=5, pady=(5, 0), sticky='nw')
|
1658
|
+
|
1659
|
+
# Add status label
|
1660
|
+
status = prot.status.get()
|
1661
|
+
# For viewers and new protocols (status less object): skip this
|
1662
|
+
if status is not None:
|
1663
|
+
color = getStatusColorFromRun(prot)
|
1664
|
+
stLabel = tk.Label(headerFrame, text=status, background=color)
|
1665
|
+
stLabel.grid(row=0, column=1, padx=5, pady=5, sticky='e')
|
1666
|
+
|
1667
|
+
def _addButton(text, icon, command, col):
|
1668
|
+
btn = tk.Label(headerFrame, text=text, image=self.getImage(icon),
|
1669
|
+
compound=tk.LEFT, cursor='hand2', name=text.lower())
|
1670
|
+
btn.bind('<Button-1>', command)
|
1671
|
+
btn.grid(row=0, column=col, padx=5, sticky='e')
|
1672
|
+
|
1673
|
+
_addButton(pwutils.Message.LABEL_CITE,
|
1674
|
+
pwutils.Icon.ACTION_REFERENCES,
|
1675
|
+
self._showReferences, 2)
|
1676
|
+
_addButton(pwutils.Message.LABEL_HELP,
|
1677
|
+
pwutils.Icon.ACTION_HELP, self._showHelp, 3)
|
1678
|
+
|
1679
|
+
return headerFrame
|
1680
|
+
|
1681
|
+
def _showReferences(self, e=None):
|
1682
|
+
""" Show the list of references of the protocol. """
|
1683
|
+
self.showInfo('\n'.join(self.protocol.citations()), "References")
|
1684
|
+
|
1685
|
+
def _showHelp(self, e=None):
|
1686
|
+
""" Show the protocol help. """
|
1687
|
+
prot = self.protocol
|
1688
|
+
text = prot.getHelpText()
|
1689
|
+
|
1690
|
+
# Add protocol url
|
1691
|
+
url = prot.getUrl()
|
1692
|
+
|
1693
|
+
# If not empty...
|
1694
|
+
if url:
|
1695
|
+
text += "\nDocumentation or forum url for this protocol:\n" + url
|
1696
|
+
|
1697
|
+
self.showInfo(text, "Help")
|
1698
|
+
|
1699
|
+
def _createParallel(self, runFrame, r):
|
1700
|
+
""" Create the section for MPI, threads and GPU. """
|
1701
|
+
|
1702
|
+
# Legacy protocols retrieved from the DB may not have this param
|
1703
|
+
# and legacy mode will fail. Thus the try block
|
1704
|
+
try:
|
1705
|
+
|
1706
|
+
# some short notation
|
1707
|
+
prot = self.protocol # shortcut notation
|
1708
|
+
allowThreads = prot.allowThreads # short notation
|
1709
|
+
allowMpi = prot.allowMpi # short notation
|
1710
|
+
allowGpu = prot.allowsGpu()
|
1711
|
+
numberOfMpi = prot.numberOfMpi.get()
|
1712
|
+
numberOfThreads = prot.numberOfThreads.get()
|
1713
|
+
|
1714
|
+
if not (allowThreads or allowMpi or allowGpu):
|
1715
|
+
return
|
1716
|
+
|
1717
|
+
|
1718
|
+
if allowThreads or allowMpi:
|
1719
|
+
|
1720
|
+
threadsParam = self.getParam(pwutils.Message.VAR_THREADS)
|
1721
|
+
mpiParam = self.getParam(pwutils.Message.VAR_MPI)
|
1722
|
+
binThreads = self.getParam("binThreads")
|
1723
|
+
|
1724
|
+
if prot.modeParallel():
|
1725
|
+
if allowThreads and numberOfThreads > 0:
|
1726
|
+
|
1727
|
+
prot.numberOfMpi.set(1)
|
1728
|
+
self._createHeaderLabel(runFrame, threadsParam.getLabel(),
|
1729
|
+
sticky='e', row=r, column=0,
|
1730
|
+
pady=0, bold=True)
|
1731
|
+
entry = self._createBoundEntry(runFrame,
|
1732
|
+
pwutils.Message.VAR_THREADS)
|
1733
|
+
|
1734
|
+
entry.grid(row=r, column=1, padx=(0, 5), sticky='w')
|
1735
|
+
|
1736
|
+
btnHelp = IconButton(runFrame, pwutils.Message.TITLE_COMMENT,
|
1737
|
+
pwutils.Icon.ACTION_HELP,
|
1738
|
+
highlightthickness=0,
|
1739
|
+
command=self._createHelpCommand(
|
1740
|
+
threadsParam.getHelp()))
|
1741
|
+
btnHelp.grid(row=r, column=2, padx=(5, 0), pady=2, sticky='e')
|
1742
|
+
|
1743
|
+
r += 1
|
1744
|
+
|
1745
|
+
elif allowMpi and numberOfMpi > 1:
|
1746
|
+
self.showError("MPI parameter is deprecated for protocols "
|
1747
|
+
"with execution is set to STEPS_PARALLEL. "
|
1748
|
+
"Please use threads instead.")
|
1749
|
+
else:
|
1750
|
+
self.showError("If protocol execution is set to "
|
1751
|
+
"STEPS_PARALLEL number of threads "
|
1752
|
+
"should not be set to zero.")
|
1753
|
+
|
1754
|
+
# Either is serial (threads and/or mpi as argument, or is Scipion parallel with binThreads
|
1755
|
+
if prot.modeSerial() or binThreads:
|
1756
|
+
|
1757
|
+
helpMessage = pwutils.Message.HELP_PARALLEL_HEADER
|
1758
|
+
|
1759
|
+
label = pwutils.Message.LABEL_PARALLEL
|
1760
|
+
label = "%s compute" % prot.getClassPackageName()
|
1761
|
+
# Add the main header and its frame
|
1762
|
+
self._createHeaderLabel(runFrame, label, bold=True,
|
1763
|
+
sticky='e', row=r, pady=0)
|
1764
|
+
procFrame = tk.Frame(runFrame, bg=pw.Config.SCIPION_BG_COLOR)
|
1765
|
+
r2 = 0
|
1766
|
+
c2 = 0
|
1767
|
+
sticky = 'e'
|
1768
|
+
|
1769
|
+
# ---- THREADS----
|
1770
|
+
if binThreads or allowThreads:
|
1771
|
+
attrName = pwutils.Message.VAR_THREADS
|
1772
|
+
if binThreads:
|
1773
|
+
threadsParam = binThreads
|
1774
|
+
attrName = "binThreads"
|
1775
|
+
self._createHeaderLabel(procFrame, threadsParam.getLabel(),
|
1776
|
+
sticky=sticky, row=r2, column=c2,
|
1777
|
+
pady=0)
|
1778
|
+
entry = self._createBoundEntry(procFrame,
|
1779
|
+
attrName)
|
1780
|
+
entry.grid(row=r2, column=c2 + 1, padx=(0, 5), sticky='w')
|
1781
|
+
# Modify values to be used in MPI entry
|
1782
|
+
c2 += 2
|
1783
|
+
sticky = 'w'
|
1784
|
+
|
1785
|
+
helpMessage += threadsParam.getHelp()
|
1786
|
+
# ---- MPI ----
|
1787
|
+
if allowMpi:
|
1788
|
+
self._createHeaderLabel(procFrame, mpiParam.getLabel(),
|
1789
|
+
sticky=sticky, row=r2, column=c2,
|
1790
|
+
pady=0)
|
1791
|
+
entry = self._createBoundEntry(procFrame, pwutils.Message.VAR_MPI)
|
1792
|
+
entry.grid(row=r2, column=c2 + 1, padx=(0, 5), sticky='w')
|
1793
|
+
|
1794
|
+
helpMessage += '\n' + mpiParam.getHelp()
|
1795
|
+
|
1796
|
+
|
1797
|
+
btnHelp = IconButton(procFrame, pwutils.Message.TITLE_COMMENT,
|
1798
|
+
pwutils.Icon.ACTION_HELP,
|
1799
|
+
highlightthickness=0,
|
1800
|
+
command=self._createHelpCommand(
|
1801
|
+
helpMessage))
|
1802
|
+
btnHelp.grid(row=0, column=4, padx=(5, 0), pady=2, sticky='e')
|
1803
|
+
|
1804
|
+
procFrame.columnconfigure(0, minsize=60)
|
1805
|
+
procFrame.grid(row=r, column=1, sticky='ew', columnspan=2)
|
1806
|
+
|
1807
|
+
r += 1
|
1808
|
+
|
1809
|
+
if allowGpu:
|
1810
|
+
self._createHeaderLabel(runFrame, "GPU IDs", bold=True,
|
1811
|
+
sticky='e', row=r, column=0, pady=0)
|
1812
|
+
gpuFrame = tk.Frame(runFrame, bg=pw.Config.SCIPION_BG_COLOR)
|
1813
|
+
gpuFrame.grid(row=r, column=1, sticky='ew', columnspan=2)
|
1814
|
+
|
1815
|
+
self.useGpuVar = tk.IntVar()
|
1816
|
+
|
1817
|
+
# For protocols that require GPU, there is not the option to choose
|
1818
|
+
if not prot.requiresGpu():
|
1819
|
+
self.useGpuVar.set(int(prot.useGpu.get()))
|
1820
|
+
for i, opt in enumerate(['Yes', 'No']):
|
1821
|
+
rb = tk.Radiobutton(gpuFrame, text=opt,
|
1822
|
+
variable=self.useGpuVar,
|
1823
|
+
value=1 - i, bg=pw.Config.SCIPION_BG_COLOR,
|
1824
|
+
highlightthickness=0)
|
1825
|
+
rb.grid(row=0, column=i, sticky='w', padx=(0, 5), pady=5)
|
1826
|
+
|
1827
|
+
self.gpuListVar = tk.StringVar()
|
1828
|
+
self.gpuListVar.set(prot.getAttributeValue(pwprot.GPU_LIST, ''))
|
1829
|
+
gpuEntry = tk.Entry(gpuFrame, width=9, font=self.font,
|
1830
|
+
textvariable=self.gpuListVar)
|
1831
|
+
gpuEntry.grid(row=0, column=2, sticky='w',
|
1832
|
+
padx=(0, 5), pady=(0, 5))
|
1833
|
+
|
1834
|
+
# Legacy protocols retrieved from the DB will not have this param
|
1835
|
+
# and legacy mode will fail. try added at the top.
|
1836
|
+
gpuListParam = prot.getParam(pwprot.GPU_LIST)
|
1837
|
+
btnHelp = IconButton(gpuFrame, pwutils.Message.TITLE_COMMENT,
|
1838
|
+
pwutils.Icon.ACTION_HELP,
|
1839
|
+
highlightthickness=0,
|
1840
|
+
command=self._createHelpCommand(
|
1841
|
+
gpuListParam.getHelp()))
|
1842
|
+
btnHelp.grid(row=0, column=3, padx=(5, 0), pady=2, sticky='e')
|
1843
|
+
|
1844
|
+
# Trace changes in GPU related widgets to store values in protocol
|
1845
|
+
self.useGpuVar.trace('w', self._setGpu)
|
1846
|
+
self.gpuListVar.trace('w', self._setGpu)
|
1847
|
+
except Exception as e:
|
1848
|
+
print("Parallel section couldn't be created. %s" % e)
|
1849
|
+
|
1850
|
+
def _createCommon(self, parent):
|
1851
|
+
""" Create the second section with some common parameters. """
|
1852
|
+
commonFrame = tk.Frame(parent, name="commonparams")
|
1853
|
+
configureWeigths(commonFrame)
|
1854
|
+
|
1855
|
+
# ---------- Run section ---------
|
1856
|
+
runSection = SectionFrame(commonFrame, label=pwutils.Message.TITLE_RUN,
|
1857
|
+
headerBgColor=self.headerBgColor,
|
1858
|
+
name="runsection")
|
1859
|
+
|
1860
|
+
runFrame = tk.Frame(runSection.contentFrame, bg=pw.Config.SCIPION_BG_COLOR, name="runframe")
|
1861
|
+
runFrame.grid(row=0, column=0, sticky='new')
|
1862
|
+
|
1863
|
+
r = 0 # Run name
|
1864
|
+
self._createHeaderLabel(runFrame, pwutils.Message.LABEL_RUNNAME, bold=True,
|
1865
|
+
sticky='ne')
|
1866
|
+
self.runNameVar = tk.StringVar()
|
1867
|
+
entry = tk.Label(runFrame, font=self.font, width=25,
|
1868
|
+
textvariable=self.runNameVar, anchor="w")
|
1869
|
+
entry.grid(row=r, column=1, padx=(0, 5), pady=5, sticky='ew')
|
1870
|
+
btn = IconButton(runFrame, pwutils.Message.TITLE_COMMENT, pwutils.Icon.ACTION_EDIT,
|
1871
|
+
highlightthickness=0, command=self._editObjParams)
|
1872
|
+
btn.grid(row=r, column=2, padx=(5, 0), pady=5, sticky='w')
|
1873
|
+
|
1874
|
+
c = 3 # Comment
|
1875
|
+
self._createHeaderLabel(runFrame, pwutils.Message.TITLE_COMMENT, sticky='e',
|
1876
|
+
column=c)
|
1877
|
+
self.commentVar = tk.StringVar()
|
1878
|
+
entry = tk.Label(runFrame, font=self.font, width=25,
|
1879
|
+
textvariable=self.commentVar, anchor="w")
|
1880
|
+
entry.grid(row=r, column=c + 1, pady=5, sticky='ew')
|
1881
|
+
btn = IconButton(runFrame, pwutils.Message.TITLE_COMMENT, pwutils.Icon.ACTION_EDIT,
|
1882
|
+
highlightthickness=0, command=self._editObjParams)
|
1883
|
+
btn.grid(row=r, column=c + 2, padx=(5, 0), pady=5, sticky='w')
|
1884
|
+
|
1885
|
+
self.updateLabelAndCommentVars()
|
1886
|
+
|
1887
|
+
r = 1 # Run mode
|
1888
|
+
|
1889
|
+
modeFrame = tk.Frame(runFrame, bg=pw.Config.SCIPION_BG_COLOR)
|
1890
|
+
|
1891
|
+
if not self.disableRunMode:
|
1892
|
+
self._createHeaderLabel(runFrame, pwutils.Message.LABEL_EXECUTION,
|
1893
|
+
bold=True,
|
1894
|
+
sticky='e', row=r, pady=0)
|
1895
|
+
|
1896
|
+
runMode = self._createBoundOptions(modeFrame, pwutils.Message.VAR_RUN_MODE,
|
1897
|
+
pwprot.MODE_CHOICES,
|
1898
|
+
self.protocol.runMode.get(),
|
1899
|
+
self._onRunModeChanged,
|
1900
|
+
bg=pw.Config.SCIPION_BG_COLOR, font=self.font)
|
1901
|
+
runMode.grid(row=0, column=0, sticky='e', padx=(0, 5), pady=5)
|
1902
|
+
btnHelp = IconButton(modeFrame, pwutils.Message.TITLE_COMMENT, pwutils.Icon.ACTION_HELP,
|
1903
|
+
highlightthickness=0,
|
1904
|
+
command=self._createHelpCommand(pwutils.Message.HELP_RUNMODE))
|
1905
|
+
btnHelp.grid(row=0, column=2, padx=(5, 0), pady=2, sticky='e')
|
1906
|
+
modeFrame.columnconfigure(0, weight=1)
|
1907
|
+
modeFrame.grid(row=r, column=1, sticky='w', columnspan=2)
|
1908
|
+
|
1909
|
+
# Queue
|
1910
|
+
self._createHeaderLabel(runFrame, pwutils.Message.LABEL_QUEUE, row=r,
|
1911
|
+
sticky='e',
|
1912
|
+
column=c)
|
1913
|
+
|
1914
|
+
var, frame = ParamWidget.createBoolWidget(runFrame, bg=pw.Config.SCIPION_BG_COLOR,
|
1915
|
+
font=self.font)
|
1916
|
+
btn = IconButton(frame, pwutils.Message.LABEL_BUTTON_WIZ, pwutils.Icon.ACTION_EDIT,
|
1917
|
+
highlightthickness=0, command=self._editQueueParams, tooltip="Edit queue parameters")
|
1918
|
+
btn.grid(row=0, column=2, sticky='nes', padx=1, pady=4)
|
1919
|
+
frame.columnconfigure(2, weight=1)
|
1920
|
+
|
1921
|
+
self._addVarBinding(pwutils.Message.VAR_QUEUE, var)
|
1922
|
+
frame.grid(row=r, column=c + 1, pady=5, sticky='ew')
|
1923
|
+
|
1924
|
+
btnHelp = IconButton(runFrame, pwutils.Message.TITLE_COMMENT, pwutils.Icon.ACTION_HELP,
|
1925
|
+
highlightthickness=0,
|
1926
|
+
command=self._createHelpCommand(pwutils.Message.HELP_USEQUEUE %
|
1927
|
+
(pw.Config.SCIPION_HOSTS, pw.DOCSITEURLS.HOST_CONFIG)))
|
1928
|
+
|
1929
|
+
btnHelp.grid(row=r, column=c + 2, padx=(5, 0), pady=5, sticky='w')
|
1930
|
+
|
1931
|
+
r = 2 # Parallel and Wait for other protocols (SCHEDULE)
|
1932
|
+
self._createParallel(runFrame, r)
|
1933
|
+
|
1934
|
+
self._createHeaderLabel(runFrame, pwutils.Message.LABEL_WAIT_FOR, row=r, sticky='e',
|
1935
|
+
column=c, padx=(15, 5), pady=0)
|
1936
|
+
self.waitForVar = tk.StringVar()
|
1937
|
+
self.waitForVar.set(', '.join(self.protocol.getPrerequisites()))
|
1938
|
+
entryWf = tk.Entry(runFrame, font=self.font, width=25,
|
1939
|
+
textvariable=self.waitForVar)
|
1940
|
+
entryWf.grid(row=r, column=c + 1, padx=(0, 5), pady=5, sticky='ew')
|
1941
|
+
|
1942
|
+
self.waitForVar.trace('w', self._setWaitFor)
|
1943
|
+
|
1944
|
+
btnHelp = IconButton(runFrame, pwutils.Message.TITLE_COMMENT, pwutils.Icon.ACTION_HELP,
|
1945
|
+
highlightthickness=0,
|
1946
|
+
command=self._createHelpCommand(pwutils.Message.HELP_WAIT_FOR % pw.DOCSITEURLS.WAIT_FOR))
|
1947
|
+
btnHelp.grid(row=r, column=c + 2, padx=(5, 0), pady=2, sticky='e')
|
1948
|
+
|
1949
|
+
# Run Name not editable
|
1950
|
+
# entry.configure(state='readonly')
|
1951
|
+
# Run mode
|
1952
|
+
# self._createHeaderLabel(runFrame, pwutils.Message.LABEL_RUNMODE).grid(row=1, column=0, sticky='ne', padx=5, pady=5)
|
1953
|
+
# runSection.addContent()
|
1954
|
+
runSection.grid(row=0, column=0, sticky='news', padx=5, pady=5)
|
1955
|
+
|
1956
|
+
return commonFrame
|
1957
|
+
|
1958
|
+
def _createHelpCommand(self, msg):
|
1959
|
+
""" Show the help of some value of the header. """
|
1960
|
+
return lambda: showInfo("Help", msg, self.root)
|
1961
|
+
|
1962
|
+
def _editObjParams(self, e=None):
|
1963
|
+
""" Show a Text area to edit the protocol label and comment. """
|
1964
|
+
self.updateProtocolLabel()
|
1965
|
+
d = EditObjectDialog(self.root, pwutils.Message.TITLE_EDIT_OBJECT,
|
1966
|
+
self.protocol, self.protocol.mapper,
|
1967
|
+
labelText=pwutils.Message.LABEL_RUNNAME)
|
1968
|
+
|
1969
|
+
if d.resultYes():
|
1970
|
+
self.updateLabelAndCommentVars()
|
1971
|
+
if self.updateProtocolCallback:
|
1972
|
+
self.updateProtocolCallback(self.protocol)
|
1973
|
+
|
1974
|
+
def _getHostConfig(self):
|
1975
|
+
""" Retrieve the hostConfig object for the select hostname"""
|
1976
|
+
return self.protocol.getProject().getHostConfig(self.protocol.getHostName())
|
1977
|
+
|
1978
|
+
def _editQueueParams(self, e=None):
|
1979
|
+
""" Open the dialog to edit the queue parameters. """
|
1980
|
+
# Grab the host config from the project, since it
|
1981
|
+
# have not been set in the protocol
|
1982
|
+
hostConfig = self._getHostConfig()
|
1983
|
+
queues = hostConfig.queueSystem.queues
|
1984
|
+
if not queues:
|
1985
|
+
self.showError("No queues configured!")
|
1986
|
+
return False
|
1987
|
+
else:
|
1988
|
+
queues = OrderedDict(sorted(queues.items()))
|
1989
|
+
# If there is only one Queue and it has no parameters
|
1990
|
+
# don't bother to showing the QueueDialog
|
1991
|
+
noQueueChoices = len(queues) == 1 and len(list(queues.values())[0]) == 0
|
1992
|
+
if noQueueChoices:
|
1993
|
+
result = list(queues.keys())[0], {}
|
1994
|
+
else:
|
1995
|
+
dlg = QueueDialog(self, queues)
|
1996
|
+
|
1997
|
+
if not dlg.resultYes():
|
1998
|
+
return False
|
1999
|
+
result = dlg.value
|
2000
|
+
|
2001
|
+
self.protocol.setQueueParams(result)
|
2002
|
+
self.protocol.queueShown = True
|
2003
|
+
return True
|
2004
|
+
|
2005
|
+
def _createParams(self, parent):
|
2006
|
+
paramsFrame = tk.Frame(parent, name="params")
|
2007
|
+
configureWeigths(paramsFrame, row=1, column=0)
|
2008
|
+
# Expert level (only if the protocol has some param with expert level)
|
2009
|
+
if self.protocol.hasExpert():
|
2010
|
+
expFrame = tk.Frame(paramsFrame, name="expert")
|
2011
|
+
expLabel = tk.Label(expFrame, text=pwutils.Message.LABEL_EXPERT, font=self.fontBold)
|
2012
|
+
expLabel.grid(row=0, column=0, sticky='nw', padx=5)
|
2013
|
+
expCombo = self._createBoundOptions(expFrame, pwutils.Message.VAR_EXPERT, pwprot.LEVEL_CHOICES,
|
2014
|
+
self.protocol.expertLevel.get(),
|
2015
|
+
self._onExpertLevelChanged, font=self.font)
|
2016
|
+
expCombo.grid(row=0, column=1, sticky='nw', pady=5)
|
2017
|
+
expFrame.grid(row=0, column=0, sticky='nw')
|
2018
|
+
|
2019
|
+
contentFrame = self._createSections(paramsFrame)
|
2020
|
+
contentFrame.grid(row=1, column=0, sticky='news')
|
2021
|
+
|
2022
|
+
return paramsFrame
|
2023
|
+
|
2024
|
+
def _isLegacyProtocol(self):
|
2025
|
+
return isinstance(self.protocol, pwprot.LegacyProtocol)
|
2026
|
+
|
2027
|
+
def _createLegacyInfo(self, parent):
|
2028
|
+
frame = tk.Frame(parent, name="legacy")
|
2029
|
+
t = tk.Label(frame,
|
2030
|
+
text="This protocol is missing in this installation. "
|
2031
|
+
"\nThis could be because you are opening an old "
|
2032
|
+
"project and some of \nthe executed protocols do "
|
2033
|
+
"not exist anymore and were deprecated"
|
2034
|
+
",\n or because your scipion installation requires a "
|
2035
|
+
"plugin where this protocol can be found.\n\n"
|
2036
|
+
"If you are a developer, it could be the case that "
|
2037
|
+
"you have changed \nto another branch where the "
|
2038
|
+
"protocol does not exist.\n\n"
|
2039
|
+
"Anyway, you can still inspect the parameters by "
|
2040
|
+
"opening the DB from the toolbar activating the Debug mode."
|
2041
|
+
)
|
2042
|
+
t.grid(row=0, column=0, padx=5, pady=5)
|
2043
|
+
|
2044
|
+
return frame
|
2045
|
+
|
2046
|
+
def _createSections(self, parent):
|
2047
|
+
"""Create section widgets"""
|
2048
|
+
r = 0
|
2049
|
+
sectionsFrame = tk.Frame(parent)
|
2050
|
+
configureWeigths(sectionsFrame)
|
2051
|
+
tab = ttk.Notebook(sectionsFrame)
|
2052
|
+
tab.grid(row=0, column=0, sticky='news',
|
2053
|
+
padx=5, pady=5)
|
2054
|
+
self._sections = []
|
2055
|
+
|
2056
|
+
for section in self.protocol.iterDefinitionSections():
|
2057
|
+
label = section.getLabel()
|
2058
|
+
if label != 'General' and label != 'Parallelization':
|
2059
|
+
frame = SectionWidget(self, tab, section, height=150,
|
2060
|
+
callback=self._checkChanges,
|
2061
|
+
headerBgColor=self.headerBgColor)
|
2062
|
+
|
2063
|
+
tab.add(frame, text=section.getLabel())
|
2064
|
+
frame.columnconfigure(0, minsize=400)
|
2065
|
+
self._fillSection(section, frame)
|
2066
|
+
self._sections.append(frame)
|
2067
|
+
r += 1
|
2068
|
+
self._checkAllChanges()
|
2069
|
+
|
2070
|
+
return sectionsFrame
|
2071
|
+
|
2072
|
+
def _createButtons(self, parent):
|
2073
|
+
""" Create the bottom buttons: Close, Save and Execute. """
|
2074
|
+
btnFrame = tk.Frame(parent)
|
2075
|
+
|
2076
|
+
btnClose = self.createCloseButton(btnFrame)
|
2077
|
+
btnClose.grid(row=0, column=0, padx=5, pady=5, sticky='se')
|
2078
|
+
# Save button is not added in VISUALIZE or CHILD modes
|
2079
|
+
# Neither in the case of a LegacyProtocol
|
2080
|
+
if (not self.visualizeMode and not self.childMode and
|
2081
|
+
not self._isLegacyProtocol()):
|
2082
|
+
# Check editable or not:
|
2083
|
+
btnState = tk.DISABLED if (self.protocol.isActive()
|
2084
|
+
and not self.protocol.isInteractive()) \
|
2085
|
+
else tk.NORMAL
|
2086
|
+
|
2087
|
+
btnSaveState = tk.DISABLED if (btnState == tk.DISABLED or
|
2088
|
+
self.protocol.getOutputsSize()) \
|
2089
|
+
else tk.NORMAL
|
2090
|
+
|
2091
|
+
self.btnSave = Button(btnFrame, pwutils.Message.LABEL_BUTTON_RETURN,
|
2092
|
+
pwutils.Icon.ACTION_SAVE, command=self.save,
|
2093
|
+
state=btnSaveState)
|
2094
|
+
self.btnSave.grid(row=0, column=1, padx=5, pady=5, sticky='se')
|
2095
|
+
self.btnExecute = HotButton(btnFrame, pwutils.Message.LABEL_BUTTON_EXEC,
|
2096
|
+
pwutils.Icon.ACTION_EXECUTE,
|
2097
|
+
command=self.execute, state=btnState)
|
2098
|
+
self.btnExecute.grid(row=0, column=2, padx=(5, 28),
|
2099
|
+
pady=5, sticky='se')
|
2100
|
+
self._onPointerChanged()
|
2101
|
+
|
2102
|
+
return btnFrame
|
2103
|
+
|
2104
|
+
def _addVarBinding(self, paramName, var, func=None, *callbacks):
|
2105
|
+
if func is None:
|
2106
|
+
func = self.setParamFromVar
|
2107
|
+
binding = Binding(paramName, var, self.protocol,
|
2108
|
+
func, *callbacks)
|
2109
|
+
self.widgetDict[paramName] = var
|
2110
|
+
self.bindings.append(binding)
|
2111
|
+
|
2112
|
+
def _createBoundEntry(self, parent, paramName, width=5,
|
2113
|
+
func=None, value=None, **kwargs):
|
2114
|
+
var = tk.StringVar()
|
2115
|
+
setattr(self, paramName + 'Var', var)
|
2116
|
+
self._addVarBinding(paramName, var, func)
|
2117
|
+
if value is not None:
|
2118
|
+
var.set(value)
|
2119
|
+
return tk.Entry(parent, font=self.font, width=width,
|
2120
|
+
textvariable=var, **kwargs)
|
2121
|
+
|
2122
|
+
def _createEnumBinding(self, paramName, choices, value=None, *callbacks):
|
2123
|
+
param = pwprot.EnumParam(choices=choices)
|
2124
|
+
var = ComboVar(param)
|
2125
|
+
if value is not None:
|
2126
|
+
var.set(value)
|
2127
|
+
self._addVarBinding(paramName, var, None, *callbacks)
|
2128
|
+
return param, var
|
2129
|
+
|
2130
|
+
def _createBoundOptions(self, parent, paramName, choices, value, *callbacks, **kwargs):
|
2131
|
+
param, var = self._createEnumBinding(paramName, choices, value, *callbacks)
|
2132
|
+
rbArgs = {}
|
2133
|
+
frameArgs = dict(kwargs)
|
2134
|
+
if 'bg' in kwargs:
|
2135
|
+
rbArgs['bg'] = kwargs['bg']
|
2136
|
+
|
2137
|
+
if 'font' in kwargs:
|
2138
|
+
rbArgs['font'] = kwargs['font']
|
2139
|
+
del frameArgs['font']
|
2140
|
+
|
2141
|
+
frame = tk.Frame(parent, **frameArgs)
|
2142
|
+
for i, opt in enumerate(param.choices):
|
2143
|
+
rb = tk.Radiobutton(frame, text=opt, variable=var.tkVar, value=opt, highlightthickness=0, **rbArgs)
|
2144
|
+
rb.grid(row=0, column=i, sticky='nw', padx=(0, 5))
|
2145
|
+
|
2146
|
+
return frame
|
2147
|
+
|
2148
|
+
def _createHeaderLabel(self, parent, text, bold=False, **gridArgs):
|
2149
|
+
font = self.font
|
2150
|
+
if bold:
|
2151
|
+
font = self.fontBold
|
2152
|
+
label = tk.Label(parent, text=text, font=font, bg=pw.Config.SCIPION_BG_COLOR)
|
2153
|
+
if gridArgs:
|
2154
|
+
gridDefaults = {'row': 0, 'column': 0, 'padx': 5, 'pady': 5}
|
2155
|
+
gridDefaults.update(gridArgs)
|
2156
|
+
label.grid(**gridDefaults)
|
2157
|
+
return label
|
2158
|
+
|
2159
|
+
def resize(self, frame):
|
2160
|
+
self.root.update_idletasks()
|
2161
|
+
MaxHeight = 1200
|
2162
|
+
MaxWidth = 1600
|
2163
|
+
rh = frame.winfo_reqheight()
|
2164
|
+
rw = frame.winfo_reqwidth()
|
2165
|
+
height = min(rh + 100, MaxHeight)
|
2166
|
+
width = min(rw, MaxWidth)
|
2167
|
+
x = self.root.winfo_x()
|
2168
|
+
y = self.root.winfo_y()
|
2169
|
+
self.root.geometry("%dx%d%+d%+d" % (width, height, x, y))
|
2170
|
+
|
2171
|
+
return width, height
|
2172
|
+
|
2173
|
+
def adjustSize(self):
|
2174
|
+
self.resize(self.root)
|
2175
|
+
|
2176
|
+
def save(self, e=None):
|
2177
|
+
self._close(onlySave=True)
|
2178
|
+
|
2179
|
+
def schedule(self):
|
2180
|
+
if self.protocol.useQueue():
|
2181
|
+
if not self._getQueueReady():
|
2182
|
+
return
|
2183
|
+
|
2184
|
+
self._close(doSchedule=True)
|
2185
|
+
|
2186
|
+
def _getQueueReady(self):
|
2187
|
+
""" Check if queue is active, if so ask for params if missing"""
|
2188
|
+
if self.protocol.hasQueueParams() and self.protocol.queueShown:
|
2189
|
+
return True
|
2190
|
+
else:
|
2191
|
+
return self._editQueueParams()
|
2192
|
+
|
2193
|
+
def execute(self, e=None):
|
2194
|
+
if self.protocol.useQueue():
|
2195
|
+
if not self._getQueueReady():
|
2196
|
+
return
|
2197
|
+
else: # use queue = No
|
2198
|
+
hostConfig = self._getHostConfig()
|
2199
|
+
cores = self.protocol.numberOfMpi.get(1) * self.protocol.numberOfThreads.get(1)
|
2200
|
+
mandatory = hostConfig.queueSystem.getMandatory()
|
2201
|
+
|
2202
|
+
if mandatory and cores >= mandatory:
|
2203
|
+
self.showWarning("You need to submit the job to queue since you \n"
|
2204
|
+
"are requesting a total of *%d* cores (MPI * threads)\n\n"
|
2205
|
+
"*Note*: Your system is configured with MANDATORY = %d.\n"
|
2206
|
+
" This value can be changed in Scipion/config/hosts.conf" % (cores, mandatory))
|
2207
|
+
return
|
2208
|
+
|
2209
|
+
errors = []
|
2210
|
+
resultAction = RESULT_RUN_SINGLE
|
2211
|
+
mode = MODE_RESTART if self.protocol.getRunMode() == MODE_RESTART else MODE_RESUME
|
2212
|
+
|
2213
|
+
# we only take into account the protocols that are already part of the workflow
|
2214
|
+
if not self.protocol.isNew():
|
2215
|
+
from pyworkflow.gui.project.viewprotocols import ProtocolsView
|
2216
|
+
errors, resultAction = ProtocolsView._launchSubWorkflow(self.protocol,
|
2217
|
+
mode, self.root,
|
2218
|
+
askSingleAll=True)
|
2219
|
+
|
2220
|
+
if errors:
|
2221
|
+
self.showInfo(errors)
|
2222
|
+
return
|
2223
|
+
|
2224
|
+
if resultAction == RESULT_CANCEL:
|
2225
|
+
return
|
2226
|
+
elif resultAction == RESULT_RUN_ALL:
|
2227
|
+
if errors:
|
2228
|
+
self.showInfo(errors)
|
2229
|
+
self.close()
|
2230
|
+
return
|
2231
|
+
|
2232
|
+
# This code will happen when protocol is executed alone
|
2233
|
+
errors += self.protocol.validate()
|
2234
|
+
|
2235
|
+
if errors:
|
2236
|
+
self.showInfo(errors)
|
2237
|
+
else:
|
2238
|
+
warns = self.protocol.warnings()
|
2239
|
+
if warns and not self.askYesNo("There are some warnings",
|
2240
|
+
'\n'.join(warns + ['\nDo you want to continue?'])):
|
2241
|
+
return
|
2242
|
+
self._close()
|
2243
|
+
|
2244
|
+
def _close(self, onlySave=False, doSchedule=False):
|
2245
|
+
try:
|
2246
|
+
# Set the protocol label
|
2247
|
+
self.updateProtocolLabel()
|
2248
|
+
|
2249
|
+
# Clear parameters that are pointers and do not match the condition
|
2250
|
+
# to avoid ghost inputs
|
2251
|
+
self._checkAllChanges(toggleWidgetVisibility=False)
|
2252
|
+
|
2253
|
+
# This may be viewprotocols.py.ProtocolsView._executeSaveProtocol
|
2254
|
+
# Message is either a confirmation that the protocol has been saves or empty message
|
2255
|
+
message = self.callback(self.protocol, onlySave, doSchedule, position=self.position)
|
2256
|
+
if not self.visualizeMode:
|
2257
|
+
# If there is a message
|
2258
|
+
if len(message):
|
2259
|
+
if onlySave and not pw.Config.SCIPION_UNLOAD_FORM_ON_SAVE:
|
2260
|
+
self.showInfo(message, "Protocol action")
|
2261
|
+
else:
|
2262
|
+
logger.info(message)
|
2263
|
+
|
2264
|
+
if not onlySave or pw.Config.SCIPION_UNLOAD_FORM_ON_SAVE:
|
2265
|
+
self.close()
|
2266
|
+
|
2267
|
+
except ModificationNotAllowedException as ex:
|
2268
|
+
self.showInfo("Modification not allowed.\n\n %s\n" % ex)
|
2269
|
+
except Exception as ex:
|
2270
|
+
action = "EXECUTE"
|
2271
|
+
if onlySave:
|
2272
|
+
action = "SAVE"
|
2273
|
+
self.showError("Error during %s: \n%s" % (action, ex), exception=ex)
|
2274
|
+
|
2275
|
+
def getWidgetValue(self, protVar, param):
|
2276
|
+
""" Returns the value for the widget"""
|
2277
|
+
|
2278
|
+
widgetValue = protVar
|
2279
|
+
if (isinstance(param, pwprot.PointerParam) or
|
2280
|
+
isinstance(param, pwprot.MultiPointerParam) or
|
2281
|
+
isinstance(param, pwprot.RelationParam)):
|
2282
|
+
|
2283
|
+
# If protVar has already a value
|
2284
|
+
if protVar.hasValue():
|
2285
|
+
widgetValue = protVar
|
2286
|
+
elif self.protocol.isNew():
|
2287
|
+
# try to link it to the output of the previousProtocol
|
2288
|
+
return self.suggestValueFromPreviousProt(param, protVar)
|
2289
|
+
|
2290
|
+
# For Scalar params that allowPointers
|
2291
|
+
elif param.allowsPointers:
|
2292
|
+
if protVar.hasPointer():
|
2293
|
+
# Get the pointer
|
2294
|
+
widgetValue = protVar.getPointer()
|
2295
|
+
else:
|
2296
|
+
widgetValue = protVar.get()
|
2297
|
+
else:
|
2298
|
+
widgetValue = protVar.get(param.default.get())
|
2299
|
+
return widgetValue
|
2300
|
+
|
2301
|
+
def suggestValueFromPreviousProt(self, param, pointer:pwobj.Pointer):
|
2302
|
+
|
2303
|
+
"""Suggest an input from the previous selected protocol that matches the param type"""
|
2304
|
+
if not hasattr(param, "pointerClass"):
|
2305
|
+
return pointer
|
2306
|
+
|
2307
|
+
paramTypeStr = param.pointerClass.get().split(",")
|
2308
|
+
|
2309
|
+
# Iterate over the output of the selected protocol
|
2310
|
+
for attr, value in self.getPreviousProtOutput():
|
2311
|
+
|
2312
|
+
# Deal with possible outputs where value is the class and not an instance
|
2313
|
+
# TODO: We may want here to deal with inheritance and use isinstance...
|
2314
|
+
# but for that we need the class and not the string
|
2315
|
+
outputType = value.getClassName()
|
2316
|
+
if outputType in paramTypeStr:
|
2317
|
+
pointer.set(self.previousProt)
|
2318
|
+
pointer.setExtended(attr)
|
2319
|
+
# First found is enough
|
2320
|
+
break
|
2321
|
+
|
2322
|
+
return pointer
|
2323
|
+
def _visualize(self, paramName):
|
2324
|
+
protVar = getattr(self.protocol, paramName)
|
2325
|
+
if protVar.hasValue():
|
2326
|
+
obj = protVar.get() # Get the reference to the object
|
2327
|
+
viewers = pw.Config.getDomain().findViewers(obj, DESKTOP_TKINTER)
|
2328
|
+
if len(viewers):
|
2329
|
+
ViewerClass = viewers[0] # Use the first viewer registered
|
2330
|
+
v = ViewerClass(project=self.protocol.getProject(),
|
2331
|
+
protocol=self.protocol, parent=self)
|
2332
|
+
v.visualize(obj) # Instantiate the viewer and visualize object
|
2333
|
+
else:
|
2334
|
+
self.showInfo("There is no viewer registered for this object")
|
2335
|
+
else:
|
2336
|
+
self.showInfo("Select the object before visualize")
|
2337
|
+
|
2338
|
+
def _fillSection(self, sectionParam, sectionWidget):
|
2339
|
+
parent = sectionWidget.contentFrame
|
2340
|
+
r = 0
|
2341
|
+
for paramName, param in sectionParam.iterParams():
|
2342
|
+
if isinstance(param, pwprot.Group):
|
2343
|
+
widget = GroupWidget(r, paramName, param, self, parent)
|
2344
|
+
self._fillGroup(param, widget)
|
2345
|
+
elif isinstance(param, pwprot.Line):
|
2346
|
+
widget = LineWidget(r, paramName, param, self, parent, None)
|
2347
|
+
self._fillLine(param, widget)
|
2348
|
+
else:
|
2349
|
+
|
2350
|
+
# Attribute of the protocol
|
2351
|
+
protVar = getattr(self.protocol, paramName, None)
|
2352
|
+
|
2353
|
+
if protVar is None:
|
2354
|
+
raise Exception("_fillSection: param '%s' not found in protocol" % paramName)
|
2355
|
+
|
2356
|
+
if sectionParam.getQuestionName() == paramName:
|
2357
|
+
widget = sectionWidget
|
2358
|
+
if not protVar:
|
2359
|
+
widget.hide() # Show only if question var is True
|
2360
|
+
else:
|
2361
|
+
if isinstance(param, pwprot.PointerParam):
|
2362
|
+
visualizeCallback = self._visualize # Add visualize icon for pointer params
|
2363
|
+
else:
|
2364
|
+
visualizeCallback = self.visualizeDict.get(paramName, None)
|
2365
|
+
|
2366
|
+
widget = ParamWidget(r, paramName, param, self, parent,
|
2367
|
+
value=self.getWidgetValue(protVar, param),
|
2368
|
+
callback=self._checkChanges,
|
2369
|
+
visualizeCallback=visualizeCallback)
|
2370
|
+
|
2371
|
+
widget.show() # Show always, conditions will be checked later
|
2372
|
+
r += 1
|
2373
|
+
self.widgetDict[paramName] = widget
|
2374
|
+
# Ensure width and height needed
|
2375
|
+
w, h = parent.winfo_reqwidth(), parent.winfo_reqheight()
|
2376
|
+
sectionWidget.columnconfigure(0, minsize=w)
|
2377
|
+
sectionWidget.rowconfigure(0, minsize=h)
|
2378
|
+
|
2379
|
+
def _fillGroup(self, groupParam, groupWidget):
|
2380
|
+
parent = groupWidget.content
|
2381
|
+
r = 0
|
2382
|
+
for paramName, param in groupParam.iterParams():
|
2383
|
+
if isinstance(param, pwprot.Line):
|
2384
|
+
widget = LineWidget(r, paramName, param, self, parent, None)
|
2385
|
+
self._fillLine(param, widget)
|
2386
|
+
else:
|
2387
|
+
protVar = getattr(self.protocol, paramName, None)
|
2388
|
+
|
2389
|
+
if protVar is None:
|
2390
|
+
raise Exception("_fillSection: param '%s' not found in protocol" % paramName)
|
2391
|
+
|
2392
|
+
if isinstance(param, pwprot.PointerParam):
|
2393
|
+
visualizeCallback = self._visualize # Add visualize icon for pointer params
|
2394
|
+
else:
|
2395
|
+
visualizeCallback = self.visualizeDict.get(paramName, None)
|
2396
|
+
|
2397
|
+
widget = ParamWidget(r, paramName, param, self, parent,
|
2398
|
+
value=self.getWidgetValue(protVar, param),
|
2399
|
+
callback=self._checkChanges,
|
2400
|
+
visualizeCallback=visualizeCallback)
|
2401
|
+
widget.show() # Show always, conditions will be checked later
|
2402
|
+
r += 1
|
2403
|
+
self.widgetDict[paramName] = widget
|
2404
|
+
|
2405
|
+
def _fillLine(self, groupParam, groupWidget):
|
2406
|
+
parent = groupWidget.content
|
2407
|
+
c = 0
|
2408
|
+
for paramName, param in groupParam.iterParams():
|
2409
|
+
protVar = getattr(self.protocol, paramName, None)
|
2410
|
+
|
2411
|
+
if protVar is None:
|
2412
|
+
raise Exception("_fillSection: param '%s' not found in protocol" % paramName)
|
2413
|
+
|
2414
|
+
if isinstance(param, pwprot.PointerParam):
|
2415
|
+
visualizeCallback = self._visualize # Add visualize icon for pointer params
|
2416
|
+
else:
|
2417
|
+
visualizeCallback = self.visualizeDict.get(paramName, None)
|
2418
|
+
|
2419
|
+
widget = ParamWidget(0, paramName, param, self, parent,
|
2420
|
+
value=self.getWidgetValue(protVar, param),
|
2421
|
+
callback=self._checkChanges, visualizeCallback=visualizeCallback,
|
2422
|
+
column=c, showButtons=False)
|
2423
|
+
widget.show() # Show always, conditions will be checked later
|
2424
|
+
c += 2
|
2425
|
+
self.widgetDict[paramName] = widget
|
2426
|
+
|
2427
|
+
def _checkCondition(self, paramName, toggleWidgetVisibility=True):
|
2428
|
+
"""Check if the condition of a param is satisfied
|
2429
|
+
hide or show it depending on the result"""
|
2430
|
+
widget = self.widgetDict.get(paramName, None)
|
2431
|
+
|
2432
|
+
if isinstance(widget, ParamWidget): # Special vars like MPI, threads or runName are not real widgets
|
2433
|
+
if isinstance(widget, LineWidget) or isinstance(widget, GroupWidget):
|
2434
|
+
param = widget.param
|
2435
|
+
else:
|
2436
|
+
param = self.protocol.getParam(paramName)
|
2437
|
+
|
2438
|
+
showLevel = self.protocol.evalParamExpertLevel(param)
|
2439
|
+
showCondition = self.protocol.evalParamCondition(paramName)
|
2440
|
+
show = showCondition and showLevel
|
2441
|
+
|
2442
|
+
if toggleWidgetVisibility:
|
2443
|
+
widget.display(show)
|
2444
|
+
else:
|
2445
|
+
# If condition is false and param is a pointer, or Multipointer ...
|
2446
|
+
if (not showCondition) and isinstance(param, pwprot.PointerParam):
|
2447
|
+
widget.clear()
|
2448
|
+
|
2449
|
+
def _checkChanges(self, paramName):
|
2450
|
+
"""Check the conditions of all params affected
|
2451
|
+
by this param"""
|
2452
|
+
self.setParamFromVar(paramName)
|
2453
|
+
param = self.protocol.getParam(paramName)
|
2454
|
+
|
2455
|
+
for d in param._dependants:
|
2456
|
+
self._checkCondition(d)
|
2457
|
+
|
2458
|
+
self.adjustSections()
|
2459
|
+
|
2460
|
+
def _checkAllChanges(self, toggleWidgetVisibility=True):
|
2461
|
+
for paramName in self.widgetDict:
|
2462
|
+
self._checkCondition(paramName, toggleWidgetVisibility=toggleWidgetVisibility)
|
2463
|
+
|
2464
|
+
def _onExpertLevelChanged(self, *args):
|
2465
|
+
self._checkAllChanges()
|
2466
|
+
self.root.update_idletasks()
|
2467
|
+
self.adjustSections()
|
2468
|
+
|
2469
|
+
def adjustSections(self):
|
2470
|
+
|
2471
|
+
for s in self._sections:
|
2472
|
+
s.adjustContent()
|
2473
|
+
|
2474
|
+
def _setGpu(self, *args):
|
2475
|
+
prot = self.protocol # shortcut notation
|
2476
|
+
if not prot.requiresGpu(): # Only set this if gpu is optional
|
2477
|
+
prot.useGpu.set(self.useGpuVar.get())
|
2478
|
+
prot.gpuList.set(self.gpuListVar.get())
|
2479
|
+
|
2480
|
+
def _setWaitFor(self, *args):
|
2481
|
+
l1 = self.waitForVar.get().split(',')
|
2482
|
+
idList = []
|
2483
|
+
for p1 in l1:
|
2484
|
+
idList.extend([p2.strip() for p2 in p1.split(' ') if p2])
|
2485
|
+
try:
|
2486
|
+
idIntList = map(int, idList)
|
2487
|
+
self.protocol.setPrerequisites(*idIntList)
|
2488
|
+
except Exception as ex:
|
2489
|
+
pass
|
2490
|
+
|
2491
|
+
def _setHostName(self, *args):
|
2492
|
+
self.protocol.setHostName(self.hostVar.get())
|
2493
|
+
|
2494
|
+
def _onRunModeChanged(self, paramName):
|
2495
|
+
self.setParamFromVar(paramName)
|
2496
|
+
|
2497
|
+
def getVarValue(self, varName):
|
2498
|
+
"""This method should retrieve a value from """
|
2499
|
+
pass
|
2500
|
+
|
2501
|
+
def setVar(self, paramName, value):
|
2502
|
+
var = self.widgetDict[paramName]
|
2503
|
+
var.set(value)
|
2504
|
+
|
2505
|
+
def setVarFromParam(self, paramName):
|
2506
|
+
var = self.widgetDict[paramName]
|
2507
|
+
param = getattr(self.protocol, paramName, None)
|
2508
|
+
if param is not None:
|
2509
|
+
# Special treatment to pointer params
|
2510
|
+
if isinstance(param, pwobj.Pointer):
|
2511
|
+
var.set(param)
|
2512
|
+
else:
|
2513
|
+
var.set(param.get(''))
|
2514
|
+
|
2515
|
+
def setParamFromVar(self, paramName):
|
2516
|
+
param = getattr(self.protocol, paramName, None)
|
2517
|
+
if param is not None:
|
2518
|
+
widget = self.widgetDict[paramName]
|
2519
|
+
try:
|
2520
|
+
value = widget.get()
|
2521
|
+
|
2522
|
+
# Special treatment for pointer params
|
2523
|
+
if isinstance(param, pwobj.Pointer):
|
2524
|
+
param.copy(value)
|
2525
|
+
# Special treatment for Scalars that allow pointers
|
2526
|
+
# Combo widgets do not have .param!
|
2527
|
+
elif hasattr(widget, "param") and widget.param.allowsPointers:
|
2528
|
+
if isinstance(value, pwobj.Pointer):
|
2529
|
+
# Copy the pointer, otherwise changes in the
|
2530
|
+
# widget pointer will be reflected
|
2531
|
+
pointerCopy = pwobj.Pointer()
|
2532
|
+
pointerCopy.copy(value)
|
2533
|
+
param.setPointer(pointerCopy)
|
2534
|
+
else:
|
2535
|
+
param.setPointer(None)
|
2536
|
+
param.set(value)
|
2537
|
+
|
2538
|
+
elif isinstance(param, pwobj.Object):
|
2539
|
+
param.set(value)
|
2540
|
+
except ValueError:
|
2541
|
+
if len(value):
|
2542
|
+
print(">>> ERROR: setting param for: ", paramName,
|
2543
|
+
"value: '%s'" % value)
|
2544
|
+
param.set(None)
|
2545
|
+
|
2546
|
+
def updateLabelAndCommentVars(self):
|
2547
|
+
""" Read the label and comment first line to update
|
2548
|
+
the entry boxes in the form.
|
2549
|
+
"""
|
2550
|
+
self.runNameVar.set(self.protocol.getObjLabel())
|
2551
|
+
# Get only the first comment line
|
2552
|
+
comment = self.protocol.getObjComment()
|
2553
|
+
if comment:
|
2554
|
+
lines = comment.split('\n')
|
2555
|
+
if lines:
|
2556
|
+
comment = lines[0]
|
2557
|
+
self.commentVar.set(comment)
|
2558
|
+
|
2559
|
+
def updateProtocolLabel(self):
|
2560
|
+
self.protocol.setObjLabel(self.runNameVar.get())
|
2561
|
+
|
2562
|
+
def updateProtocolParams(self):
|
2563
|
+
""" This method is only used from WEB, since in Tk all params
|
2564
|
+
are updated when they are changed.
|
2565
|
+
"""
|
2566
|
+
for paramName, _ in self.protocol.iterDefinitionAttributes():
|
2567
|
+
self.setParamFromVar(paramName)
|
2568
|
+
|
2569
|
+
def _onPointerChanged(self, *args):
|
2570
|
+
btnExecute = getattr(self, 'btnExecute', None)
|
2571
|
+
|
2572
|
+
# This event can be fired even before the button is created
|
2573
|
+
if btnExecute is None:
|
2574
|
+
return
|
2575
|
+
btnState = tk.DISABLED if (self.protocol.isActive() and not self.protocol.isInteractive()) else tk.NORMAL
|
2576
|
+
emptyInput, openSetPointer, emptyPointers = self.protocol.getInputStatus()
|
2577
|
+
|
2578
|
+
if emptyInput:
|
2579
|
+
btnState = tk.DISABLED
|
2580
|
+
|
2581
|
+
if openSetPointer or emptyPointers:
|
2582
|
+
btnText = 'Schedule'
|
2583
|
+
cmd = self.schedule
|
2584
|
+
else:
|
2585
|
+
btnText = pwutils.Message.LABEL_BUTTON_EXEC
|
2586
|
+
cmd = self.execute
|
2587
|
+
|
2588
|
+
btnExecute.config(text=btnText, command=cmd, state=btnState)
|
2589
|
+
|
2590
|
+
|
2591
|
+
def takeScreenShot(self):
|
2592
|
+
""" Method to take a screenshot of itself.
|
2593
|
+
The idea is to, in the future, take a screenshot and collect parameter
|
2594
|
+
to either create a page of the protocol in rst or send it to the
|
2595
|
+
Scipion site and have there one page per protocol.
|
2596
|
+
|
2597
|
+
For now this is not used."""
|
2598
|
+
from PIL import ImageGrab
|
2599
|
+
|
2600
|
+
x,y, width, height = (self.root.winfo_x(), self.root.winfo_y(), self.root.winfo_width(), self.root.winfo_height())
|
2601
|
+
ss_region = (x,y,x+width, y+height)
|
2602
|
+
|
2603
|
+
ss_img = ImageGrab.grab(ss_region)
|
2604
|
+
ss_img.save("/tmp/form.png")
|
2605
|
+
|
2606
|
+
def editObject(self, title, root, obj, mapper):
|
2607
|
+
""" Show a Text area to edit the protocol label and comment. """
|
2608
|
+
return EditObjectDialog(root, title, obj, mapper)
|
2609
|
+
|
2610
|
+
|
2611
|
+
class QueueDialog(Dialog):
|
2612
|
+
""" Dialog to entry the queue parameters. """
|
2613
|
+
|
2614
|
+
def __init__(self, window, queueDict):
|
2615
|
+
self.value = None
|
2616
|
+
self.widgets = [] # widget list
|
2617
|
+
self.vars = []
|
2618
|
+
self.queueDict = queueDict
|
2619
|
+
self.window = window
|
2620
|
+
self.queueName, queueParams = window.protocol.getQueueParams()
|
2621
|
+
# If there is only one queue and not one selected, use the first one
|
2622
|
+
if not self.queueName and len(queueDict.keys()) == 1:
|
2623
|
+
self.queueName = list(queueDict.keys())[0]
|
2624
|
+
queueParams = {}
|
2625
|
+
# Store all selected queue parameters to
|
2626
|
+
# preserve values when temporarily changed
|
2627
|
+
# from one queue to another
|
2628
|
+
self.allQueueParams = {self.queueName: queueParams}
|
2629
|
+
|
2630
|
+
Dialog.__init__(self, window.root, "Queue parameters")
|
2631
|
+
|
2632
|
+
def body(self, bodyFrame):
|
2633
|
+
bodyFrame.config(bg=pw.Config.SCIPION_BG_COLOR)
|
2634
|
+
self.content = tk.Frame(bodyFrame, bg=pw.Config.SCIPION_BG_COLOR)
|
2635
|
+
self.content.grid(row=0, column=0, padx=20, pady=20)
|
2636
|
+
|
2637
|
+
label = tk.Label(self.content, text='Submit to queue',
|
2638
|
+
font=self.window.fontBold, bg=pw.Config.SCIPION_BG_COLOR)
|
2639
|
+
label.grid(row=0, column=0, sticky='ne', padx=5, pady=5)
|
2640
|
+
self.queueVar = tk.StringVar()
|
2641
|
+
self.queueVar.trace('w', self._onQueueChanged)
|
2642
|
+
combo = ttk.Combobox(self.content, textvariable=self.queueVar,
|
2643
|
+
state='readonly', width=14)
|
2644
|
+
combo.grid(row=0, column=1, sticky='nw', padx=5, pady=5)
|
2645
|
+
queueKeys = list(self.queueDict.keys())
|
2646
|
+
combo['values'] = queueKeys
|
2647
|
+
self.queueVar.set(self.queueName) # This will trigger queue params setup
|
2648
|
+
self.initial_focus = combo
|
2649
|
+
|
2650
|
+
def _onQueueChanged(self, *args):
|
2651
|
+
for w in self.widgets:
|
2652
|
+
w.destroy()
|
2653
|
+
|
2654
|
+
selected = self.queueVar.get()
|
2655
|
+
|
2656
|
+
if selected != self.queueName:
|
2657
|
+
# Store previous selection
|
2658
|
+
_, previousParams = self._getSelectedParams(self.queueName)
|
2659
|
+
self.allQueueParams[self.queueName] = previousParams
|
2660
|
+
self.queueName = selected
|
2661
|
+
|
2662
|
+
# Load default params from the queues
|
2663
|
+
params = self.queueDict.get(selected, {})
|
2664
|
+
# Load previous selected params
|
2665
|
+
selectedParams = self.allQueueParams.get(selected, {})
|
2666
|
+
|
2667
|
+
self.widgets = [] # clear the widget list
|
2668
|
+
self.vars = []
|
2669
|
+
r = 1 # starting row to place params
|
2670
|
+
for p in params:
|
2671
|
+
if len(p) == 3: # No help provided
|
2672
|
+
name, value, label = p
|
2673
|
+
helpMsg = None
|
2674
|
+
elif len(p) == 4:
|
2675
|
+
name, value, label, helpMsg = p
|
2676
|
+
else:
|
2677
|
+
raise Exception('Incorrect number of params for %s, expected 3 or 4' % p[0])
|
2678
|
+
|
2679
|
+
label = tk.Label(self.content, text=label, bg=pw.Config.SCIPION_BG_COLOR)
|
2680
|
+
label.grid(row=r, column=0, sticky='ne', padx=5, pady=(0, 5))
|
2681
|
+
var = tk.StringVar()
|
2682
|
+
# Set the value coming in the protocol
|
2683
|
+
var.set(selectedParams.get(name, value))
|
2684
|
+
|
2685
|
+
entry = tk.Entry(self.content, textvariable=var, width=15)
|
2686
|
+
entry.grid(row=r, column=1, sticky='nw', padx=5, pady=(0, 5))
|
2687
|
+
|
2688
|
+
if helpMsg:
|
2689
|
+
def addHelpButton(name, helpMsg):
|
2690
|
+
def showHelp():
|
2691
|
+
showInfo("Help", helpMsg, self)
|
2692
|
+
|
2693
|
+
btn = IconButton(self.content, pwutils.Message.LABEL_BUTTON_HELP,
|
2694
|
+
pwutils.Icon.ACTION_HELP,
|
2695
|
+
command=showHelp)
|
2696
|
+
btn.grid(row=r, column=2, sticky='ne', padx=5, pady=(0, 5))
|
2697
|
+
self.widgets.append(btn)
|
2698
|
+
|
2699
|
+
addHelpButton(name, helpMsg)
|
2700
|
+
|
2701
|
+
self.vars.append(var)
|
2702
|
+
self.widgets.append(label)
|
2703
|
+
self.widgets.append(entry)
|
2704
|
+
r += 1
|
2705
|
+
|
2706
|
+
def _getSelectedParams(self, selected):
|
2707
|
+
if selected in self.queueDict:
|
2708
|
+
paramsDict = {}
|
2709
|
+
params = self.queueDict[selected]
|
2710
|
+
for p, v in zip(params, self.vars):
|
2711
|
+
if len(p) == 3:
|
2712
|
+
name, value, label = p
|
2713
|
+
else:
|
2714
|
+
name, value, label, _ = p
|
2715
|
+
paramsDict[name] = v.get() # get the value from the corresponding tk var
|
2716
|
+
return selected, paramsDict
|
2717
|
+
return '', {}
|
2718
|
+
|
2719
|
+
def apply(self):
|
2720
|
+
# Set as value the queue selected and a dictionary
|
2721
|
+
# with the values of each parameter
|
2722
|
+
selected = self.queueVar.get()
|
2723
|
+
self.value = self._getSelectedParams(selected)
|
2724
|
+
|
2725
|
+
def validate(self):
|
2726
|
+
return True
|