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.
Files changed (98) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +113 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +267 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +439 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +760 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +979 -0
  17. pyworkflow/gui/form.py +2726 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +566 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +192 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +238 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +510 -0
  35. pyworkflow/gui/project/viewprotocols.py +2116 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +771 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +222 -0
  43. pyworkflow/mapper/sqlite.py +1581 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/project/__init__.py +31 -0
  46. pyworkflow/project/config.py +454 -0
  47. pyworkflow/project/manager.py +180 -0
  48. pyworkflow/project/project.py +2095 -0
  49. pyworkflow/project/usage.py +165 -0
  50. pyworkflow/protocol/__init__.py +38 -0
  51. pyworkflow/protocol/bibtex.py +48 -0
  52. pyworkflow/protocol/constants.py +87 -0
  53. pyworkflow/protocol/executor.py +483 -0
  54. pyworkflow/protocol/hosts.py +317 -0
  55. pyworkflow/protocol/launch.py +277 -0
  56. pyworkflow/protocol/package.py +42 -0
  57. pyworkflow/protocol/params.py +781 -0
  58. pyworkflow/protocol/protocol.py +2707 -0
  59. pyworkflow/tests/__init__.py +29 -0
  60. pyworkflow/tests/test_utils.py +25 -0
  61. pyworkflow/tests/tests.py +341 -0
  62. pyworkflow/utils/__init__.py +38 -0
  63. pyworkflow/utils/dataset.py +414 -0
  64. pyworkflow/utils/echo.py +104 -0
  65. pyworkflow/utils/graph.py +169 -0
  66. pyworkflow/utils/log.py +293 -0
  67. pyworkflow/utils/path.py +528 -0
  68. pyworkflow/utils/process.py +153 -0
  69. pyworkflow/utils/profiler.py +92 -0
  70. pyworkflow/utils/progressbar.py +154 -0
  71. pyworkflow/utils/properties.py +617 -0
  72. pyworkflow/utils/reflection.py +129 -0
  73. pyworkflow/utils/utils.py +880 -0
  74. pyworkflow/utils/which.py +229 -0
  75. pyworkflow/webservices/__init__.py +8 -0
  76. pyworkflow/webservices/config.py +8 -0
  77. pyworkflow/webservices/notifier.py +152 -0
  78. pyworkflow/webservices/repository.py +59 -0
  79. pyworkflow/webservices/workflowhub.py +74 -0
  80. pyworkflowtests/tests/__init__.py +0 -0
  81. pyworkflowtests/tests/test_canvas.py +72 -0
  82. pyworkflowtests/tests/test_domain.py +45 -0
  83. pyworkflowtests/tests/test_logs.py +74 -0
  84. pyworkflowtests/tests/test_mappers.py +392 -0
  85. pyworkflowtests/tests/test_object.py +507 -0
  86. pyworkflowtests/tests/test_project.py +42 -0
  87. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  88. pyworkflowtests/tests/test_protocol_export.py +78 -0
  89. pyworkflowtests/tests/test_protocol_output.py +158 -0
  90. pyworkflowtests/tests/test_streaming.py +47 -0
  91. pyworkflowtests/tests/test_utils.py +210 -0
  92. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +2 -2
  93. scipion_pyworkflow-3.11.1.dist-info/RECORD +161 -0
  94. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  95. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +0 -0
  96. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
  97. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
  98. {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