scipion-pyworkflow 3.10.6__py3-none-any.whl → 3.11.0__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 (123) hide show
  1. pyworkflow/config.py +131 -67
  2. pyworkflow/constants.py +2 -1
  3. pyworkflow/plugin.py +93 -44
  4. pyworkflow/resources/showj/arrowDown.png +0 -0
  5. pyworkflow/resources/showj/arrowUp.png +0 -0
  6. pyworkflow/resources/showj/background_section.png +0 -0
  7. pyworkflow/resources/showj/colRowModeOff.png +0 -0
  8. pyworkflow/resources/showj/colRowModeOn.png +0 -0
  9. pyworkflow/resources/showj/delete.png +0 -0
  10. pyworkflow/resources/showj/doc_icon.png +0 -0
  11. pyworkflow/resources/showj/download_icon.png +0 -0
  12. pyworkflow/resources/showj/enabled_gallery.png +0 -0
  13. pyworkflow/resources/showj/galleryViewOff.png +0 -0
  14. pyworkflow/resources/showj/galleryViewOn.png +0 -0
  15. pyworkflow/resources/showj/goto.png +0 -0
  16. pyworkflow/resources/showj/menu.png +0 -0
  17. pyworkflow/resources/showj/separator.png +0 -0
  18. pyworkflow/resources/showj/tableViewOff.png +0 -0
  19. pyworkflow/resources/showj/tableViewOn.png +0 -0
  20. pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  21. pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  22. pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  23. pyworkflow/resources/showj/volumeOff.png +0 -0
  24. pyworkflow/resources/showj/volumeOn.png +0 -0
  25. pyworkflow/viewer.py +23 -1
  26. pyworkflowtests/protocols.py +1 -3
  27. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +13 -27
  28. scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
  29. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
  30. pyworkflow/apps/__init__.py +0 -29
  31. pyworkflow/apps/pw_manager.py +0 -37
  32. pyworkflow/apps/pw_plot.py +0 -51
  33. pyworkflow/apps/pw_project.py +0 -113
  34. pyworkflow/apps/pw_protocol_list.py +0 -143
  35. pyworkflow/apps/pw_protocol_run.py +0 -51
  36. pyworkflow/apps/pw_run_tests.py +0 -267
  37. pyworkflow/apps/pw_schedule_run.py +0 -322
  38. pyworkflow/apps/pw_sleep.py +0 -37
  39. pyworkflow/apps/pw_sync_data.py +0 -439
  40. pyworkflow/apps/pw_viewer.py +0 -78
  41. pyworkflow/gui/__init__.py +0 -36
  42. pyworkflow/gui/browser.py +0 -726
  43. pyworkflow/gui/canvas.py +0 -1190
  44. pyworkflow/gui/dialog.py +0 -977
  45. pyworkflow/gui/form.py +0 -2637
  46. pyworkflow/gui/graph.py +0 -247
  47. pyworkflow/gui/graph_layout.py +0 -271
  48. pyworkflow/gui/gui.py +0 -566
  49. pyworkflow/gui/matplotlib_image.py +0 -233
  50. pyworkflow/gui/plotter.py +0 -247
  51. pyworkflow/gui/project/__init__.py +0 -25
  52. pyworkflow/gui/project/base.py +0 -192
  53. pyworkflow/gui/project/constants.py +0 -139
  54. pyworkflow/gui/project/labels.py +0 -205
  55. pyworkflow/gui/project/project.py +0 -492
  56. pyworkflow/gui/project/searchprotocol.py +0 -154
  57. pyworkflow/gui/project/searchrun.py +0 -181
  58. pyworkflow/gui/project/steps.py +0 -171
  59. pyworkflow/gui/project/utils.py +0 -332
  60. pyworkflow/gui/project/variables.py +0 -179
  61. pyworkflow/gui/project/viewdata.py +0 -472
  62. pyworkflow/gui/project/viewprojects.py +0 -510
  63. pyworkflow/gui/project/viewprotocols.py +0 -2093
  64. pyworkflow/gui/project/viewprotocols_extra.py +0 -559
  65. pyworkflow/gui/text.py +0 -771
  66. pyworkflow/gui/tooltip.py +0 -185
  67. pyworkflow/gui/tree.py +0 -684
  68. pyworkflow/gui/widgets.py +0 -307
  69. pyworkflow/mapper/__init__.py +0 -26
  70. pyworkflow/mapper/mapper.py +0 -222
  71. pyworkflow/mapper/sqlite.py +0 -1581
  72. pyworkflow/mapper/sqlite_db.py +0 -145
  73. pyworkflow/project/__init__.py +0 -31
  74. pyworkflow/project/config.py +0 -454
  75. pyworkflow/project/manager.py +0 -180
  76. pyworkflow/project/project.py +0 -2007
  77. pyworkflow/protocol/__init__.py +0 -38
  78. pyworkflow/protocol/bibtex.py +0 -48
  79. pyworkflow/protocol/constants.py +0 -87
  80. pyworkflow/protocol/executor.py +0 -471
  81. pyworkflow/protocol/hosts.py +0 -314
  82. pyworkflow/protocol/launch.py +0 -270
  83. pyworkflow/protocol/package.py +0 -42
  84. pyworkflow/protocol/params.py +0 -741
  85. pyworkflow/protocol/protocol.py +0 -2641
  86. pyworkflow/tests/__init__.py +0 -29
  87. pyworkflow/tests/test_utils.py +0 -25
  88. pyworkflow/tests/tests.py +0 -341
  89. pyworkflow/utils/__init__.py +0 -38
  90. pyworkflow/utils/dataset.py +0 -414
  91. pyworkflow/utils/echo.py +0 -104
  92. pyworkflow/utils/graph.py +0 -169
  93. pyworkflow/utils/log.py +0 -284
  94. pyworkflow/utils/path.py +0 -528
  95. pyworkflow/utils/process.py +0 -153
  96. pyworkflow/utils/profiler.py +0 -92
  97. pyworkflow/utils/progressbar.py +0 -154
  98. pyworkflow/utils/properties.py +0 -631
  99. pyworkflow/utils/reflection.py +0 -129
  100. pyworkflow/utils/utils.py +0 -879
  101. pyworkflow/utils/which.py +0 -229
  102. pyworkflow/webservices/__init__.py +0 -8
  103. pyworkflow/webservices/config.py +0 -11
  104. pyworkflow/webservices/notifier.py +0 -162
  105. pyworkflow/webservices/repository.py +0 -59
  106. pyworkflow/webservices/workflowhub.py +0 -74
  107. pyworkflowtests/tests/__init__.py +0 -0
  108. pyworkflowtests/tests/test_canvas.py +0 -72
  109. pyworkflowtests/tests/test_domain.py +0 -45
  110. pyworkflowtests/tests/test_logs.py +0 -74
  111. pyworkflowtests/tests/test_mappers.py +0 -392
  112. pyworkflowtests/tests/test_object.py +0 -507
  113. pyworkflowtests/tests/test_project.py +0 -42
  114. pyworkflowtests/tests/test_protocol_execution.py +0 -142
  115. pyworkflowtests/tests/test_protocol_export.py +0 -78
  116. pyworkflowtests/tests/test_protocol_output.py +0 -158
  117. pyworkflowtests/tests/test_streaming.py +0 -47
  118. pyworkflowtests/tests/test_utils.py +0 -210
  119. scipion_pyworkflow-3.10.6.dist-info/RECORD +0 -140
  120. scipion_pyworkflow-3.10.6.dist-info/dependency_links.txt +0 -1
  121. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/entry_points.txt +0 -0
  122. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/licenses/LICENSE.txt +0 -0
  123. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/top_level.txt +0 -0
@@ -1,2093 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # **************************************************************************
3
- # *
4
- # * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
5
- # *
6
- # * [1] SciLifeLab, Stockholm University
7
- # *
8
- # * This program is free software: you can redistribute it and/or modify
9
- # * it under the terms of the GNU General Public License as published by
10
- # * the Free Software Foundation, either version 3 of the License, or
11
- # * (at your option) any later version.
12
- # *
13
- # * This program is distributed in the hope that it will be useful,
14
- # * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- # * GNU General Public License for more details.
17
- # *
18
- # * You should have received a copy of the GNU General Public License
19
- # * along with this program. If not, see <https://www.gnu.org/licenses/>.
20
- # *
21
- # * All comments concerning this program package may be sent to the
22
- # * e-mail address 'scipion@cnb.csic.es'
23
- # *
24
- # **************************************************************************
25
- import logging
26
- logger = logging.getLogger(__name__)
27
-
28
- from pyworkflow import Config, DEFAULT_EXECUTION_ACTION_ASK, DEFAULT_EXECUTION_ACTION_SINGLE
29
- from pyworkflow.gui import LIST_TREEVIEW, \
30
- ShortCut, ToolTip, RESULT_RUN_ALL, RESULT_RUN_SINGLE, RESULT_CANCEL, BORDERLESS_TREEVIEW
31
- from pyworkflow.gui.project.constants import *
32
- from pyworkflow.protocol import SIZE_1MB, SIZE_1GB, SIZE_1TB
33
-
34
- INIT_REFRESH_SECONDS = Config.SCIPION_GUI_REFRESH_INITIAL_WAIT
35
-
36
- """
37
- View with the protocols inside the main project window.
38
- """
39
-
40
- import os
41
- import json
42
- import re
43
- import tempfile
44
- from collections import OrderedDict
45
- import tkinter as tk
46
- import tkinter.ttk as ttk
47
- import datetime as dt
48
-
49
- from pyworkflow import Config, TK
50
- import pyworkflow.utils as pwutils
51
- import pyworkflow.protocol as pwprot
52
- from pyworkflow.viewer import DESKTOP_TKINTER, ProtocolViewer
53
- from pyworkflow.utils.properties import Color, KEYSYM, Icon, Message
54
- from pyworkflow.webservices import WorkflowRepository
55
-
56
- import pyworkflow.gui as pwgui
57
- from pyworkflow.gui.form import FormWindow
58
- from pyworkflow.gui.project.utils import getStatusColorFromNode, inspectObj
59
- from pyworkflow.gui.project.searchprotocol import SearchProtocolWindow, ProtocolTreeProvider
60
- from pyworkflow.gui.project.steps import StepsWindow
61
- from pyworkflow.gui.project.viewprotocols_extra import RunIOTreeProvider, ProtocolTreeConfig
62
- from pyworkflow.gui.project.searchrun import RunsTreeProvider, SearchRunWindow
63
-
64
- DEFAULT_BOX_COLOR = '#f8f8f8'
65
-
66
-
67
- RUNS_TREE = Icon.RUNS_TREE
68
-
69
- VIEW_LIST = 0
70
- VIEW_TREE = 1
71
- VIEW_TREE_SMALL = 2
72
-
73
-
74
- # noinspection PyAttributeOutsideInit
75
- class ProtocolsView(tk.Frame):
76
- """ What you see when the "Protocols" tab is selected.
77
-
78
- In the main project window there are three tabs: "Protocols | Data | Hosts".
79
- This extended tk.Frame is what will appear when Protocols is on.
80
- """
81
-
82
- RUNS_CANVAS_NAME = "runs_canvas"
83
-
84
- SIZE_COLORS = {SIZE_1MB: "green",
85
- SIZE_1GB: "orange",
86
- SIZE_1TB: "red"}
87
-
88
- _protocolViews = None
89
-
90
- def __init__(self, parent, window, **args):
91
- tk.Frame.__init__(self, parent, **args)
92
- # Load global configuration
93
- self.window = window
94
- self.project = window.project
95
- self.domain = self.project.getDomain()
96
- self.root = window.root
97
- self.getImage = window.getImage
98
- self.protCfg = self.getCurrentProtocolView()
99
- self.settings = window.getSettings()
100
- self.runsView = self.settings.getRunsView()
101
- self._loadSelection()
102
- self._items = {}
103
- self._lastSelectedProtId = None
104
- self._lastStatus = None
105
- self.selectingArea = False
106
- self._lastRightClickPos = None # Keep last right-clicked position
107
-
108
- self.style = ttk.Style()
109
- self.root.bind("<Control-a>", self._selectAllProtocols)
110
- self.root.bind("<Control-t>", self._toggleColorScheme)
111
- self.root.bind("<Control-D>", self._toggleDebug)
112
- self.root.bind("<Control-l>", self._locateProtocol)
113
-
114
- if Config.debugOn():
115
- self.root.bind("<Control-i>", self._inspectProtocols)
116
-
117
-
118
- self.__autoRefresh = None
119
- self.__autoRefreshCounter = INIT_REFRESH_SECONDS # start by 3 secs
120
-
121
- self.refreshSemaphore = True
122
- self.repeatRefresh = False
123
-
124
- c = self.createContent()
125
- pwgui.configureWeigths(self)
126
- c.grid(row=0, column=0, sticky='news')
127
-
128
-
129
- def createContent(self):
130
- """ Create the Protocols View for the Project.
131
- It has two panes:
132
- Left: containing the Protocol classes tree
133
- Right: containing the Runs list
134
- """
135
- p = tk.PanedWindow(self, orient=tk.HORIZONTAL, bg=Config.SCIPION_BG_COLOR)
136
- bgColor = Color.ALT_COLOR
137
- # Left pane, contains Protocols Pane
138
- leftFrame = tk.Frame(p, bg=bgColor)
139
- leftFrame.columnconfigure(0, weight=1)
140
- leftFrame.rowconfigure(1, weight=1)
141
-
142
- # Protocols Tree Pane
143
- protFrame = tk.Frame(leftFrame, width=300, height=500, bg=bgColor)
144
- protFrame.grid(row=1, column=0, sticky='news', padx=5, pady=5)
145
- protFrame.columnconfigure(0, weight=1)
146
- protFrame.rowconfigure(1, weight=1)
147
- self._createProtocolsPanel(protFrame, bgColor)
148
- self.updateProtocolsTree(self.protCfg)
149
- # Create the right Pane that will be composed by:
150
- # a Action Buttons TOOLBAR in the top
151
- # and another vertical Pane with:
152
- # Runs History (at Top)
153
-
154
- # Selected run info (at Bottom)
155
- rightFrame = tk.Frame(p, bg=Config.SCIPION_BG_COLOR)
156
- rightFrame.columnconfigure(0, weight=1)
157
- rightFrame.rowconfigure(1, weight=1)
158
- # rightFrame.rowconfigure(0, minsize=label.winfo_reqheight())
159
-
160
- # Create the Action Buttons TOOLBAR
161
- toolbar = tk.Frame(rightFrame, bg=Config.SCIPION_BG_COLOR)
162
- toolbar.grid(row=0, column=0, sticky='news')
163
- pwgui.configureWeigths(toolbar)
164
- # toolbar.columnconfigure(0, weight=1)
165
- toolbar.columnconfigure(1, weight=1)
166
-
167
- self.runsToolbar = tk.Frame(toolbar, bg=Config.SCIPION_BG_COLOR)
168
- self.runsToolbar.grid(row=0, column=0, sticky='sw')
169
- # On the left of the toolbar will be other
170
- # actions that can be applied to all runs (refresh, graph view...)
171
- self.allToolbar = tk.Frame(toolbar, bg=Config.SCIPION_BG_COLOR)
172
- self.allToolbar.grid(row=0, column=10, sticky='se')
173
- self.createActionToolbar()
174
-
175
- # Create the Run History tree
176
- v = ttk.PanedWindow(rightFrame, orient=tk.VERTICAL)
177
- # runsFrame = ttk.Labelframe(v, text=' History ', width=500, height=500)
178
- runsFrame = tk.Frame(v, bg=Config.SCIPION_BG_COLOR)
179
- # runsFrame.grid(row=1, column=0, sticky='news', pady=5)
180
- self.runsTree = self.createRunsTree(runsFrame)
181
- pwgui.configureWeigths(runsFrame)
182
-
183
- self.createRunsGraph(runsFrame)
184
-
185
- if self.runsView == VIEW_LIST:
186
- treeWidget = self.runsTree
187
- else:
188
- treeWidget = self.runsGraphCanvas
189
-
190
- treeWidget.grid(row=0, column=0, sticky='news')
191
-
192
- # Create the Selected Run Info
193
- infoFrame = tk.Frame(v)
194
- infoFrame.columnconfigure(0, weight=1)
195
- infoFrame.rowconfigure(1, weight=1)
196
- # Create the info label
197
- self.infoLabel = tk.Label(infoFrame)
198
- self.infoLabel.grid(row=0, column=0, sticky='w', padx=3)
199
- # Create the Analyze results button
200
- self.btnAnalyze = pwgui.Button(infoFrame, text=Message.LABEL_ANALYZE,
201
- fg='white', bg=Config.SCIPION_MAIN_COLOR,
202
- image=self.getImage(Icon.ACTION_VISUALIZE),
203
- compound=tk.LEFT,
204
- activeforeground='white',
205
- activebackground=Config.getActiveColor())
206
- # command=self._analyzeResultsClicked)
207
- self.btnAnalyze.bind("<Shift-Button-1>", lambda e: self._analyzeResultsClicked(KEYSYM.SHIFT))
208
- self.btnAnalyze.bind("<Control-Button-1>", lambda e: self._analyzeResultsClicked(KEYSYM.CONTROL))
209
- self.btnAnalyze.bind("<Button-1>", lambda e: self._analyzeResultsClicked(None))
210
-
211
- # self.btnAnalyze.bind("<Button-1>", self._analyzeResultsClicked)
212
-
213
- self.btnAnalyze.grid(row=0, column=0, sticky='ne', padx=15)
214
- # self.style.configure("W.TNotebook")#, background='white')
215
- tab = ttk.Notebook(infoFrame) # , style='W.TNotebook')
216
-
217
- # Summary tab
218
- dframe = tk.Frame(tab, bg=Config.SCIPION_BG_COLOR)
219
- pwgui.configureWeigths(dframe, row=0)
220
- pwgui.configureWeigths(dframe, row=2)
221
- # Just configure the provider, later below, in updateSelection, it will be
222
- # provided with the protocols.
223
- provider = RunIOTreeProvider(self, None,
224
- self.project.mapper, self.info)
225
-
226
- self.infoTree = pwgui.browser.BoundTree(dframe, provider, height=6,
227
- show='tree',
228
- style=BORDERLESS_TREEVIEW)
229
- self.infoTree.grid(row=0, column=0, sticky='news')
230
- label = tk.Label(dframe, text='SUMMARY', bg=Config.SCIPION_BG_COLOR,
231
- font=self.window.fontBold)
232
- label.grid(row=1, column=0, sticky='nw', padx=(15, 0))
233
-
234
- hView = {'sci-open': self._viewObject,
235
- 'sci-bib': self._bibExportClicked}
236
-
237
- self.summaryText = pwgui.text.TaggedText(dframe, width=40, height=5,
238
- bg=Config.SCIPION_BG_COLOR, bd=0,
239
- font=self.window.font,
240
- handlers=hView)
241
- self.summaryText.grid(row=2, column=0, sticky='news', padx=(30, 0))
242
-
243
- # Method tab
244
- mframe = tk.Frame(tab)
245
- pwgui.configureWeigths(mframe)
246
- # Methods text box
247
- self.methodText = pwgui.text.TaggedText(mframe, width=40, height=15,
248
- bg=Config.SCIPION_BG_COLOR, handlers=hView)
249
- self.methodText.grid(row=0, column=0, sticky='news')
250
-
251
- # Output Logs
252
- ologframe = tk.Frame(tab)
253
- pwgui.configureWeigths(ologframe)
254
- self.outputViewer = pwgui.text.TextFileViewer(ologframe, allowOpen=True,
255
- font=self.window.font)
256
- self.outputViewer.grid(row=0, column=0, sticky='news')
257
- self.outputViewer.windows = self.window
258
-
259
- # Project log
260
- projLogFrame = tk.Frame(tab)
261
- pwgui.configureWeigths(projLogFrame)
262
- self.projLog = pwgui.text.TextFileViewer(projLogFrame, allowOpen=True,
263
- font=self.window.font)
264
- self.projLog.grid(row=0, column=0, sticky='news')
265
- self.projLog.windows = self.window
266
- self.projLog.addFile(self.project.getProjectLog())
267
-
268
- # Move to the selected protocol
269
- if self._isSingleSelection():
270
- prot = self.getSelectedProtocol()
271
- node = self.runsGraph.getNode(str(prot.getObjId()))
272
- self._selectNode(node)
273
- else:
274
- self._updateSelection()
275
-
276
- # Add all tabs
277
-
278
- tab.add(dframe, text=Message.LABEL_SUMMARY)
279
- tab.add(mframe, text=Message.LABEL_METHODS)
280
- tab.add(ologframe, text=Message.LABEL_LOGS_OUTPUT)
281
- # tab.add(elogframe, text=Message.LABEL_LOGS_ERROR)
282
- tab.add(projLogFrame, text=Message.LABEL_LOGS_SCIPION)
283
- tab.grid(row=1, column=0, sticky='news')
284
-
285
- v.add(runsFrame, weight=1)
286
- v.add(infoFrame, weight=20)
287
- v.grid(row=1, column=0, sticky='news')
288
-
289
- # Add sub-windows to PanedWindows
290
- p.add(leftFrame, padx=0, pady=0, sticky='news')
291
- p.add(rightFrame, padx=0, pady=0)
292
- p.paneconfig(leftFrame, minsize=5)
293
- leftFrame.config(width=235)
294
- p.paneconfig(rightFrame, minsize=10)
295
-
296
- return p
297
-
298
- def _viewObject(self, objId):
299
- """ Call appropriate viewer for objId. """
300
- proj = self.project
301
- obj = proj.getObject(int(objId))
302
- viewerClasses = self.domain.findViewers(obj.getClassName(), DESKTOP_TKINTER)
303
- if not viewerClasses:
304
- return # TODO: protest nicely
305
- viewer = viewerClasses[0](project=proj, parent=self.window)
306
- viewer.visualize(obj)
307
-
308
- def _loadSelection(self):
309
- """ Load selected items, remove if some do not exists. """
310
- self._selection = self.settings.runSelection
311
- for protId in list(self._selection):
312
-
313
- if not self.project.doesProtocolExists(protId):
314
- self._selection.remove(protId)
315
-
316
- def _isMultipleSelection(self):
317
- return len(self._selection) > 1
318
-
319
- def _isSingleSelection(self):
320
- return len(self._selection) == 1
321
-
322
- def _noSelection(self):
323
- return len(self._selection) == 0
324
-
325
- def info(self, message):
326
- self.infoLabel.config(text=message)
327
- self.infoLabel.update_idletasks()
328
-
329
- def cleanInfo(self):
330
- self.info("")
331
-
332
- def refreshRuns(self, e=None, initRefreshCounter=True, checkPids=False, position=None):
333
- """
334
- Refresh the protocol runs workflow. If the variable REFRESH_WITH_THREADS
335
- exits, then use a threads to refresh, i.o.c use normal behavior
336
- """
337
- useThreads = Config.refreshInThreads()
338
- if useThreads:
339
- import threading
340
- # Refresh the status of displayed runs.
341
- if self.refreshSemaphore:
342
-
343
- threadRefreshRuns = threading.Thread(name="Refreshing runs",
344
- target=self.refreshDisplayedRuns,
345
- args=(e, initRefreshCounter,
346
- checkPids))
347
- threadRefreshRuns.start()
348
- else:
349
- self.repeatRefresh = True
350
- else:
351
- self.refreshDisplayedRuns(e, initRefreshCounter, checkPids, position=position)
352
-
353
- # noinspection PyUnusedLocal
354
- def refreshDisplayedRuns(self, e=None, initRefreshCounter=True, checkPids=False, position=None):
355
- """ Refresh the status of displayed runs.
356
- Params:
357
- e: Tk event input
358
- initRefreshCounter: if True the refresh counter will be set to 3 secs
359
- then only case when False is from _automaticRefreshRuns where the
360
- refresh time is doubled each time to avoid refreshing too often.
361
- """
362
- self.viewButtons[ACTION_REFRESH]['state'] = tk.DISABLED
363
- self.info('Refreshing...')
364
- self.refreshSemaphore = False
365
-
366
- if self.runsView == VIEW_LIST:
367
- self.updateRunsTree(True)
368
- else:
369
- self.updateRunsGraph(True, checkPids=checkPids, position=position)
370
- self._updateSelection()
371
-
372
- if initRefreshCounter:
373
-
374
- self.__autoRefreshCounter = INIT_REFRESH_SECONDS # start by 3 secs
375
- if self.__autoRefresh:
376
- self.runsTree.after_cancel(self.__autoRefresh)
377
- self.__autoRefresh = self.runsTree.after(
378
- self.__autoRefreshCounter * 1000,
379
- self._automaticRefreshRuns)
380
- self.refreshSemaphore = True
381
- if self.repeatRefresh:
382
- self.repeatRefresh = False
383
- self.refreshRuns()
384
- self.cleanInfo()
385
- self.viewButtons[ACTION_REFRESH]['state'] = tk.NORMAL
386
-
387
- # noinspection PyUnusedLocal
388
- def _automaticRefreshRuns(self, e=None):
389
- """ Schedule automatic refresh increasing the time between refreshes. """
390
- if Config.SCIPION_GUI_CANCEL_AUTO_REFRESH:
391
- return
392
-
393
- self.refreshRuns(initRefreshCounter=False, checkPids=True)
394
- secs = self.__autoRefreshCounter
395
- # double the number of seconds up to 30 min
396
- self.__autoRefreshCounter = min(2 * secs, 1800)
397
- self.__autoRefresh = self.runsTree.after(secs * 1000,
398
- self._automaticRefreshRuns)
399
-
400
- # noinspection PyUnusedLocal
401
- def _findProtocol(self, event=None):
402
- """ Find a desired protocol by typing some keyword. """
403
-
404
- if event is not None and event.widget.widgetName=="canvas":
405
- position = self.runsGraphCanvas.getCoordinates(event)
406
- else:
407
- position = None
408
-
409
- window = SearchProtocolWindow(self.window, position=position)
410
- window.show()
411
-
412
- def _locateProtocol(self, e=None):
413
-
414
- window = SearchRunWindow(self.window, self.runsGraph, onDoubleClick=self._onRunClick)
415
- window.show()
416
- # self._moveCanvas(0,1)
417
-
418
- def _onRunClick(self, e=None):
419
- """ Callback to be called when a click happens o a run in the SearchRunWindow.tree"""
420
- tree = e.widget
421
- protId = tree.getFirst()
422
- node = self.runsGraph.getNode(protId)
423
- self._selectNode(node)
424
-
425
- def _selectNode(self, node):
426
-
427
- x = node.x
428
- y = node.y
429
- self._moveCanvas(x, y)
430
-
431
- # Select the protocol
432
- self._selectItemProtocol(node.run)
433
- # We comment the refresh because when the project is loaded,
434
- # the workflow is traversed twice.
435
- # self.refreshDisplayedRuns()
436
-
437
- def _moveCanvas(self, X, Y):
438
-
439
- self.runsGraphCanvas.moveTo(X, Y)
440
-
441
- def createActionToolbar(self):
442
- """ Prepare the buttons that will be available for protocol actions. """
443
-
444
- self.actionButtons = {}
445
- actionList = [
446
- ACTION_NEW, ACTION_EDIT, ACTION_RENAME, ACTION_DUPLICATE, ACTION_COPY, ACTION_PASTE, ACTION_DELETE,
447
- ACTION_BROWSE,
448
- ACTION_STOP, ACTION_STOP_WORKFLOW, ACTION_CONTINUE, ACTION_CONTINUE_WORKFLOW, ACTION_RESTART_WORKFLOW, ACTION_RESET_WORKFLOW,
449
- ACTION_RESULTS,
450
- ACTION_EXPORT, ACTION_EXPORT_UPLOAD,
451
- ACTION_COLLAPSE, ACTION_EXPAND,
452
- ACTION_LABELS, ACTION_SEARCH,
453
- ACTION_SELECT_FROM, ACTION_SELECT_TO,
454
- ACTION_STEPS, ACTION_DB
455
- ]
456
-
457
- def addButton(action, text, toolbar):
458
-
459
- labelTxt = text if Config.SCIPION_SHOW_TEXT_IN_TOOLBAR else ""
460
- btn = tk.Label(toolbar, text=labelTxt,
461
- image=self.getImage(ActionIcons.get(action, None)),
462
- compound=tk.TOP, cursor='hand2', bg=Config.SCIPION_BG_COLOR)
463
-
464
- callback = lambda e: self._runActionClicked(action, event=e)
465
- btn.bind(TK.LEFT_CLICK, callback)
466
-
467
- # Shortcuts:
468
- shortCut = ActionShortCuts.get(action, None)
469
- if shortCut:
470
- text += " (%s)" % shortCut
471
- self.root.bind(shortCut, callback)
472
-
473
- ToolTip(btn, text, 500)
474
-
475
- return btn
476
-
477
- for action in actionList:
478
- self.actionButtons[action] = addButton(action, action,
479
- self.runsToolbar)
480
-
481
- ActionIcons[ACTION_TREE] = RUNS_TREE
482
-
483
- self.viewButtons = {}
484
-
485
- # Add combo for switch between views
486
- viewFrame = tk.Frame(self.allToolbar)
487
- viewFrame.grid(row=0, column=0)
488
- self._createViewCombo(viewFrame)
489
-
490
- # Add refresh Tree button
491
- btn = addButton(ACTION_TREE, "Organize", self.allToolbar)
492
- pwgui.tooltip.ToolTip(btn, "Organize the node positions.", 1500)
493
- self.viewButtons[ACTION_TREE] = btn
494
- if self.runsView != VIEW_LIST:
495
- btn.grid(row=0, column=1)
496
-
497
- # Add refresh button
498
- btn = addButton(ACTION_REFRESH, ACTION_REFRESH, self.allToolbar)
499
- btn.grid(row=0, column=2)
500
- self.viewButtons[ACTION_REFRESH] = btn
501
-
502
- def _createViewCombo(self, parent):
503
- """ Create the select-view combobox. """
504
- label = tk.Label(parent, text='View:', bg=Config.SCIPION_BG_COLOR)
505
- label.grid(row=0, column=0)
506
- viewChoices = ['List', 'Tree', 'Tree - small']
507
- self.switchCombo = pwgui.widgets.ComboBox(parent, width=10,
508
- choices=viewChoices,
509
- values=[VIEW_LIST, VIEW_TREE, VIEW_TREE_SMALL],
510
- initial=viewChoices[self.runsView],
511
- onChange=lambda e: self._runActionClicked(
512
- ACTION_SWITCH_VIEW))
513
- self.switchCombo.grid(row=0, column=1)
514
-
515
- def _updateActionToolbar(self):
516
- """ Update which action buttons should be visible. """
517
-
518
- def displayAction(actionToDisplay, column, condition=True):
519
-
520
- """ Show/hide the action button if the condition is met. """
521
-
522
- # If action present (set color is not in the toolbar but in the
523
- # context menu)
524
- action = self.actionButtons.get(actionToDisplay, None)
525
- if action is not None:
526
- if condition:
527
- action.grid(row=0, column=column, sticky='sw',
528
- padx=(0, 5), ipadx=0)
529
- else:
530
- action.grid_remove()
531
-
532
- for i, actionTuple in enumerate(self.provider.getActionsFromSelection()):
533
- action, cond = actionTuple
534
- displayAction(action, i, cond)
535
-
536
- def _createProtocolsTree(self, parent,
537
- show='tree', columns=None, position=None):
538
-
539
- t = pwgui.tree.Tree(parent, show=show, style=LIST_TREEVIEW,
540
- columns=columns)
541
- t.column('#0', minwidth=300)
542
-
543
- def configureTag(tag, img):
544
- # Protocol nodes
545
- t.tag_configure(tag, image=self.getImage(img))
546
- t.tag_bind(tag, TK.LEFT_DOUBLE_CLICK, lambda e: self._protocolItemClick(e, position))
547
- t.tag_bind(tag, TK.RETURN, lambda e: self._protocolItemClick(e, position))
548
- t.tag_bind(tag, TK.ENTER, lambda e: self._protocolItemClick(e, position))
549
-
550
- # Protocol nodes
551
- configureTag(ProtocolTreeConfig.TAG_PROTOCOL, Icon.PRODUCTION)
552
- # New protocols
553
- configureTag(ProtocolTreeConfig.TAG_PROTOCOL_NEW, Icon.NEW)
554
- # Beta protocols
555
- configureTag(ProtocolTreeConfig.TAG_PROTOCOL_BETA, Icon.BETA)
556
- # Disable protocols (not installed) are allowed to be added.
557
- configureTag(ProtocolTreeConfig.TAG_PROTOCOL_DISABLED,
558
- Icon.PROT_DISABLED)
559
- # Updated protocols
560
- configureTag(ProtocolTreeConfig.TAG_PROTOCOL_UPDATED,
561
- Icon.UPDATED)
562
- t.tag_configure('protocol_base', image=self.getImage(Icon.GROUP))
563
- t.tag_configure('protocol_group', image=self.getImage(Icon.GROUP))
564
- t.tag_configure('section', font=self.window.fontBold)
565
- return t
566
-
567
- def _createProtocolsPanel(self, parent, bgColor):
568
- """Create the protocols Tree displayed in left panel"""
569
- comboFrame = tk.Frame(parent, bg=bgColor)
570
- tk.Label(comboFrame, text='View', bg=bgColor).grid(row=0, column=0,
571
- padx=(0, 5), pady=5)
572
- choices = self.getProtocolViews()
573
- initialChoice = self.settings.getProtocolView()
574
- combo = pwgui.widgets.ComboBox(comboFrame, choices=choices,
575
- initial=initialChoice)
576
- combo.setChangeCallback(self._onSelectProtocols)
577
- combo.grid(row=0, column=1)
578
- comboFrame.grid(row=0, column=0, padx=5, pady=5, sticky='nw')
579
-
580
- t = self._createProtocolsTree(parent)
581
- t.grid(row=1, column=0, sticky='news')
582
- # Program automatic refresh
583
- t.after(3000, self._automaticRefreshRuns)
584
- self.protTree = t
585
-
586
- def getProtocolViews(self):
587
-
588
- if self._protocolViews is None:
589
- self._loadProtocols()
590
-
591
- return list(self._protocolViews.keys())
592
-
593
- def getCurrentProtocolView(self):
594
- """ Select the view that is currently selected.
595
- Read from the settings the last selected view
596
- and get the information from the self._protocolViews dict.
597
- """
598
- currentView = self.project.getProtocolView()
599
- if currentView in self.getProtocolViews():
600
- viewKey = currentView
601
- else:
602
- viewKey = self.getProtocolViews()[0]
603
- self.project.settings.setProtocolView(viewKey)
604
- if currentView is not None:
605
- logger.warning("PROJECT: Warning, protocol view '%s' not found. Using '%s' instead." % (currentView, viewKey))
606
-
607
- return self._protocolViews[viewKey]
608
-
609
- def _loadProtocols(self):
610
- """ Load protocol configuration from a .conf file. """
611
- # If the host file is not passed as argument...
612
- configProtocols = Config.SCIPION_PROTOCOLS
613
-
614
- localDir = Config.SCIPION_LOCAL_CONFIG
615
- protConf = os.path.join(localDir, configProtocols)
616
- self._protocolViews = ProtocolTreeConfig.load(self.project.getDomain(),
617
- protConf)
618
-
619
- def _onSelectProtocols(self, combo):
620
- """ This function will be called when a protocol menu
621
- is selected. The index of the new menu is passed.
622
- """
623
- protView = combo.getText()
624
- self.settings.setProtocolView(protView)
625
- self.protCfg = self.getCurrentProtocolView()
626
- self.updateProtocolsTree(self.protCfg)
627
-
628
- def populateTree(self, tree, treeItems, prefix, obj, level=0):
629
-
630
- # If node does not have leaves (protocols) do not add it
631
- if not obj.visible:
632
- return
633
-
634
- text = obj.text
635
- if text:
636
- value = obj.value if obj.value is not None else text
637
- key = '%s.%s' % (prefix, value)
638
- img = obj.icon if obj.icon is not None else ''
639
- tag = obj.tag if obj.tag is not None else ''
640
-
641
- if img:
642
- if isinstance(img,str) and "bookmark" in img:
643
- img = pwutils.Icon.FAVORITE
644
- img = self.getImage(img)
645
- # If image is none
646
- img = img if img is not None else ''
647
-
648
- protClassName = value.split('.')[-1] # Take last part
649
- emProtocolsDict = self.domain.getProtocols()
650
- prot = emProtocolsDict.get(protClassName, None)
651
-
652
- if tag == 'protocol' and text == 'default':
653
- if prot is None:
654
- logger.warning("Protocol className '%s' not found!!!. \n"
655
- "Fix your config/protocols.conf configuration."
656
- % protClassName)
657
- return
658
-
659
- text = prot.getClassLabel()
660
-
661
- item = tree.insert(prefix, 'end', key, text=text, image=img, tags=tag)
662
- treeItems[item] = obj
663
- # Check if the attribute should be open or close
664
- openItem = getattr(obj, 'openItem', level < 2)
665
- if openItem:
666
- tree.item(item, open=openItem)
667
-
668
- # I think this mode is deprecated
669
- if obj.value is not None and tag == 'protocol_base':
670
- logger.warning('protocol_base tags are deprecated')
671
- else:
672
- key = prefix
673
-
674
- for sub in obj:
675
- self.populateTree(tree, treeItems, key, sub,
676
- level + 1)
677
-
678
- def updateProtocolsTree(self, protCfg):
679
-
680
- try:
681
- self.protCfg = protCfg
682
- self.protTree.clear()
683
- self.protTree.unbind(TK.TREEVIEW_OPEN)
684
- self.protTree.unbind(TK.TREEVIEW_CLOSE)
685
- self.protTreeItems = {}
686
- self.populateTree(self.protTree, self.protTreeItems, '', self.protCfg)
687
- self.protTree.bind(TK.TREEVIEW_OPEN,
688
- lambda e: self._treeViewItemChange(True))
689
- self.protTree.bind(TK.TREEVIEW_CLOSE,
690
- lambda e: self._treeViewItemChange(False))
691
- except Exception as e:
692
- # Tree can't be loaded report back, but continue
693
- logger.error("Protocols tree couldn't be loaded.", exc_info=e)
694
-
695
- def _treeViewItemChange(self, openItem):
696
- item = self.protTree.focus()
697
- if item in self.protTreeItems:
698
- self.protTreeItems[item].openItem = openItem
699
-
700
- def createRunsTree(self, parent):
701
- self.provider = RunsTreeProvider(self.project, self._runActionClicked)
702
-
703
- # This line triggers the getRuns for the first time.
704
- # Ne need to force the check pids here, temporary
705
- self.provider._checkPids = True
706
-
707
- t = pwgui.tree.BoundTree(parent, self.provider, style=LIST_TREEVIEW)
708
- self.provider._checkPids = False
709
-
710
- t.itemDoubleClick = self._runItemDoubleClick
711
- t.itemClick = self._runTreeItemClick
712
-
713
- return t
714
-
715
- def updateRunsTree(self, refresh=False):
716
- self.provider.setRefresh(refresh)
717
- self.runsTree.update()
718
- self.updateRunsTreeSelection()
719
-
720
- def updateRunsTreeSelection(self):
721
- for prot in self._iterSelectedProtocols():
722
- treeId = self.provider.getObjectFromId(prot.getObjId())._treeId
723
- self.runsTree.selection_add(treeId)
724
-
725
- def createRunsGraph(self, parent):
726
- self.runsGraphCanvas = pwgui.Canvas(parent, width=400, height=400,
727
- tooltipCallback=self._runItemTooltip,
728
- tooltipDelay=1000,
729
- name=ProtocolsView.RUNS_CANVAS_NAME,
730
- takefocus=True,
731
- highlightthickness=0)
732
-
733
- self.runsGraphCanvas.onClickCallback = self._runItemClick
734
- self.runsGraphCanvas.onDoubleClickCallback = self._runItemDoubleClick
735
- self.runsGraphCanvas.onRightClickCallback = self._runItemRightClick
736
- self.runsGraphCanvas.onControlClickCallback = self._runItemControlClick
737
- self.runsGraphCanvas.onAreaSelected = self._selectItemsWithinArea
738
- self.runsGraphCanvas.onMiddleMouseClickCallback = self._runItemMiddleClick
739
-
740
- parent.grid_columnconfigure(0, weight=1)
741
- parent.grid_rowconfigure(0, weight=1)
742
-
743
- self.settings.getNodes().updateDict()
744
- self.settings.getLabels().updateDict()
745
-
746
- self.updateRunsGraph()
747
-
748
- def updateRunsGraph(self, refresh=False, checkPids=False, position=None):
749
-
750
- self.runsGraph = self.project.getRunsGraph(refresh=refresh,
751
- checkPids=checkPids)
752
- self.drawRunsGraph(position=position)
753
-
754
- def drawRunsGraph(self, reorganize=False, position=None):
755
-
756
- # Check if there are positions stored
757
- if reorganize:
758
- # Create layout to arrange nodes as a level tree
759
- layout = pwgui.LevelTreeLayout()
760
- self.runsGraphCanvas.reorganizeGraph(self.runsGraph, layout)
761
- else:
762
- self.runsGraphCanvas.clear()
763
- layout = pwgui.LevelTreeLayout(partial=True)
764
-
765
- # Create empty nodeInfo for new runs
766
- for node in self.runsGraph.getNodes():
767
- nodeId = node.run.getObjId() if node.run else 0
768
- nodeInfo = self.settings.getNodeById(nodeId)
769
- if nodeInfo is None:
770
- if position is None:
771
- position = (0,0)
772
- self.settings.addNode(nodeId, x=position[0], y=position[1], expanded=True,
773
- visible=True)
774
-
775
- self.runsGraphCanvas.drawGraph(self.runsGraph, layout,
776
- drawNode=self.createRunItem,
777
- nodeList=self.settings.nodeList)
778
-
779
- projectSize = len(self.runsGraph.getNodes())
780
- settingsNodeSize = len(self.settings.getNodes())
781
- if projectSize < settingsNodeSize -1:
782
- logger.info("Settings nodes list (%s) is bigger than current project nodes (%s). "
783
- "Clean up needed?" % (settingsNodeSize, projectSize) )
784
- self.settings.cleanUpNodes(self.runsGraph.getNodeNames(), toRemove=False)
785
-
786
- def createRunItem(self, canvas, node):
787
-
788
- nodeId = node.run.getObjId() if node.run else 0
789
- nodeInfo = self.settings.getNodeById(nodeId)
790
-
791
- # Extend attributes: use some from nodeInfo
792
- node.expanded = nodeInfo.isExpanded()
793
- node.x, node.y = nodeInfo.getPosition()
794
- node.visible = nodeInfo.isVisible()
795
- nodeText = self._getNodeText(node)
796
-
797
- # Get status color
798
- statusColor = getStatusColorFromNode(node)
799
-
800
- # Get the box color (depends on color mode: label or status)
801
- boxColor = self._getBoxColor(nodeInfo, statusColor, node)
802
-
803
- # Draw the box
804
- item = RunBox(nodeInfo, self.runsGraphCanvas,
805
- nodeText, node.x, node.y,
806
- bgColor=boxColor, textColor='black')
807
- # No border
808
- item.margin = 0
809
-
810
- # Paint the oval..if apply.
811
- #self._paintOval(item, statusColor)
812
-
813
- # Paint the bottom line (for now only labels are painted there).
814
- self._paintBottomLine(item)
815
-
816
- item.setSelected(nodeId in self._selection)
817
- return item
818
-
819
- def _getBoxColor(self, nodeInfo, statusColor, node):
820
-
821
- try:
822
-
823
- # If the color has to go to the box
824
- if self.settings.statusColorMode() or self.settings.labelsColorMode():
825
- boxColor = statusColor
826
-
827
- elif self.settings.ageColorMode():
828
-
829
- if node.run:
830
-
831
- # Project elapsed time
832
- elapsedTime = node.run.getProject().getElapsedTime()
833
- creationTime = node.run.getProject().getCreationTime()
834
-
835
- # Get the latest activity timestamp
836
- ts = node.run.endTime.datetime()
837
-
838
- if elapsedTime is None or creationTime is None or ts is None:
839
- boxColor = DEFAULT_BOX_COLOR
840
-
841
- else:
842
-
843
- # tc closer to the end are younger
844
- protAge = ts - creationTime
845
-
846
- boxColor = self._ageColor('#6666ff', elapsedTime,
847
- protAge)
848
- else:
849
- boxColor = DEFAULT_BOX_COLOR
850
-
851
- elif self.settings.sizeColorMode():
852
-
853
- # Get the protocol size
854
- protSize = self._getRunSize(node)
855
-
856
- boxColor = self._sizeColor(protSize)
857
-
858
- # ... box is for the labels.
859
- elif self.settings.labelsColorMode():
860
- # If there is only one label use the box for the color.
861
- if self._getLabelsCount(nodeInfo) == 1:
862
-
863
- labelId = nodeInfo.getLabels()[0]
864
- label = self.settings.getLabels().getLabel(labelId)
865
-
866
- # If there is no label (it has been deleted)
867
- if label is None:
868
- nodeInfo.getLabels().remove(labelId)
869
- boxColor = DEFAULT_BOX_COLOR
870
- else:
871
- boxColor = label.getColor()
872
-
873
- else:
874
- boxColor = DEFAULT_BOX_COLOR
875
- else:
876
- boxColor = DEFAULT_BOX_COLOR
877
-
878
- return boxColor
879
- except Exception as e:
880
- logger.debug("Can't get color for %s. %s" % (node, e))
881
- return DEFAULT_BOX_COLOR
882
-
883
- @staticmethod
884
- def _getRunSize(node):
885
- """
886
- Returns the size "recursively" of a run
887
-
888
- :param node: node of the graph.
889
- :return: size in bytes
890
-
891
- """
892
-
893
- if not node.run:
894
- return 0
895
- else:
896
- return node.run.getSize()
897
-
898
- @classmethod
899
- def _sizeColor(cls, size):
900
- """
901
- Returns the color that corresponds to the size
902
- :param size:
903
- :return:
904
- """
905
-
906
- for threshold, color in cls.SIZE_COLORS.items():
907
- if size <= threshold:
908
- return color
909
-
910
- return "#000000"
911
- @staticmethod
912
- def _ageColor(rgbColorStr, projectAge, protocolAge):
913
-
914
- # Get the ratio
915
- ratio = protocolAge.seconds / float(projectAge.seconds)
916
-
917
- # Invert direction: older = white = 100%, newest = rgbColor = 0%
918
- ratio = 1 - ratio
919
-
920
- # There are cases coming with protocols older than the project.
921
- ratio = 0 if ratio < 0 else ratio
922
-
923
- hexTuple = pwutils.hex_to_rgb(rgbColorStr)
924
- lighterTuple = pwutils.lighter(hexTuple, ratio)
925
- return pwutils.rgb_to_hex(lighterTuple)
926
-
927
- @staticmethod
928
- def _getLabelsCount(nodeInfo):
929
-
930
- return 0 if nodeInfo.getLabels() is None else len(nodeInfo.getLabels())
931
-
932
- def _paintBottomLine(self, item):
933
-
934
- if self.settings.labelsColorMode() or self.settings.statusColorMode():
935
- self._addLabels(item)
936
-
937
- def _paintOval(self, item, statusColor):
938
- # Show the status as a circle in the top right corner
939
- if not self.settings.statusColorMode():
940
- # Option: Status item.
941
- (topLeftX, topLeftY, bottomRightX,
942
- bottomRightY) = self.runsGraphCanvas.bbox(item.id)
943
- statusSize = 10
944
- statusX = bottomRightX - (statusSize + 3)
945
- statusY = topLeftY + 3
946
-
947
- pwgui.Oval(self.runsGraphCanvas, statusX, statusY, statusSize,
948
- color=statusColor, anchor=item)
949
-
950
- # in statusColorMode
951
- else:
952
- # Show a black circle if there is any label
953
- if self._getLabelsCount(item.nodeInfo) > 0:
954
- (topLeftX, topLeftY, bottomRightX,
955
- bottomRightY) = self.runsGraphCanvas.bbox(item.id)
956
- statusSize = 10
957
- statusX = bottomRightX - (statusSize + 3)
958
- statusY = topLeftY + 3
959
-
960
- pwgui.Oval(self.runsGraphCanvas, statusX, statusY, statusSize,
961
- color='black', anchor=item)
962
-
963
- def _getNodeText(self, node):
964
- nodeText = node.getLabel()
965
- # Truncate text to prevent overflow
966
- if len(nodeText) > 40:
967
- nodeText = nodeText[:37] + "..."
968
-
969
- if node.run:
970
- expandedStr = '' if node.expanded else '\n ➕ %s more' % str(node.countChildren({}))
971
- if self.runsView == VIEW_TREE_SMALL:
972
- nodeText = node.getName() + expandedStr
973
- else:
974
- nodeText += expandedStr + '\n' + node.run.getStatusMessage() if not expandedStr else expandedStr
975
- if node.run.summaryWarnings:
976
- nodeText += u' \u26a0'
977
- return nodeText
978
-
979
- def _addLabels(self, item):
980
- # If there is only one label it should be already used in the box color.
981
- if self._getLabelsCount(item.nodeInfo) < 1:
982
- return
983
- # Get the positions of the box
984
- (topLeftX, topLeftY, bottomRightX,
985
- bottomRightY) = self.runsGraphCanvas.bbox(item.id)
986
-
987
- # Get the width of the box
988
- boxWidth = bottomRightX - topLeftX
989
-
990
- # Set the size
991
- marginV = 3
992
- marginH = 2
993
- labelWidth = (boxWidth - (2 * marginH)) / len(item.nodeInfo.getLabels())
994
- labelHeight = 8
995
-
996
- # Leave some margin on the right and bottom
997
- labelX = bottomRightX - marginH
998
- labelY = bottomRightY - (labelHeight + marginV)
999
-
1000
- for index, labelId in enumerate(item.nodeInfo.getLabels()):
1001
-
1002
- # Get the label
1003
- label = self.settings.getLabels().getLabel(labelId)
1004
-
1005
- # If not none
1006
- if label is not None:
1007
- # Move X one label to the left
1008
- if index == len(item.nodeInfo.getLabels()) - 1:
1009
- labelX = topLeftX + marginH
1010
- else:
1011
- labelX -= labelWidth
1012
-
1013
- pwgui.Rectangle(self.runsGraphCanvas, labelX, labelY,
1014
- labelWidth, labelHeight, color=label.getColor(),
1015
- anchor=item)
1016
- else:
1017
-
1018
- item.nodeInfo.getLabels().remove(labelId)
1019
-
1020
- def switchRunsView(self):
1021
- viewValue = self.switchCombo.getValue()
1022
- self.runsView = viewValue
1023
- self.settings.setRunsView(viewValue)
1024
-
1025
- if viewValue == VIEW_LIST:
1026
- self.runsTree.grid(row=0, column=0, sticky='news')
1027
- self.runsGraphCanvas.frame.grid_remove()
1028
- self.updateRunsTree()
1029
- self.viewButtons[ACTION_TREE].grid_remove()
1030
- self._lastRightClickPos = None
1031
- else:
1032
- self.runsTree.grid_remove()
1033
- self.updateRunsGraph()
1034
- self.runsGraphCanvas.frame.grid(row=0, column=0, sticky='news')
1035
- self.viewButtons[ACTION_TREE].grid(row=0, column=1)
1036
-
1037
- def _protocolItemClick(self, e=None, position=None):
1038
- """ Callback for the window to add a new protocol."""
1039
-
1040
- # Get the tree widget that originated the event
1041
- # it could be the left panel protocols tree or just
1042
- # the search protocol dialog tree
1043
- tree = e.widget
1044
- protClassName = tree.getFirst().split('.')[-1]
1045
- protClass = self.domain.getProtocols().get(protClassName)
1046
- prot = self.project.newProtocol(protClass)
1047
- self._openProtocolForm(prot, disableRunMode=True, position=position)
1048
-
1049
- def _toggleColorScheme(self, e=None):
1050
-
1051
- currentMode = self.settings.getColorMode()
1052
-
1053
- if currentMode >= len(self.settings.COLOR_MODES) - 1:
1054
- currentMode = -1
1055
-
1056
- nextColorMode = currentMode + 1
1057
-
1058
- self.settings.setColorMode(nextColorMode)
1059
- # WHY? self._updateActionToolbar()
1060
- # self.updateRunsGraph()
1061
- self.drawRunsGraph()
1062
- self._infoAboutColorScheme()
1063
-
1064
- def _infoAboutColorScheme(self):
1065
- """ Writes in the info widget a brief description abot the color scheme."""
1066
-
1067
- colorScheme = self.settings.getColorMode()
1068
-
1069
- msg = "Color mode changed to %s. %s"
1070
- if colorScheme == self.settings.COLOR_MODE_AGE:
1071
- msg = msg % ("AGE", "Young boxes will have an darker color.")
1072
- elif colorScheme == self.settings.COLOR_MODE_SIZE:
1073
- keys = list(self.SIZE_COLORS.keys())
1074
- msg = msg % ("SIZE", "Semaphore color scheme. Green <= %s, Orange <=%s, Red <=%s, Dark quite big." %
1075
- (pwutils.prettySize(keys[0]),
1076
- pwutils.prettySize(keys[1]),
1077
- pwutils.prettySize(keys[2]))
1078
- )
1079
- elif colorScheme == self.settings.COLOR_MODE_STATUS:
1080
- msg = msg % ("STATUS", "Color based on the status. A black circle indicates it has labels")
1081
- elif colorScheme == self.settings.COLOR_MODE_LABELS:
1082
- msg = msg % ("LABELS", "Color based on custom labels you've assigned. Small circles reflect the protocol status")
1083
-
1084
- self.info(msg)
1085
- def _toggleDebug(self, e=None):
1086
- Config.toggleDebug()
1087
-
1088
- def _selectAllProtocols(self, e=None):
1089
- self._selection.clear()
1090
-
1091
- # WHY GOING TO THE db?
1092
- # Let's try using in memory data.
1093
- # for prot in self.project.getRuns():
1094
- for prot in self.project.runs:
1095
- self._selection.append(prot.getObjId())
1096
- self._updateSelection()
1097
-
1098
- # self.updateRunsGraph()
1099
- self.drawRunsGraph()
1100
-
1101
- def _inspectProtocols(self, e=None):
1102
- objs = self._getSelectedProtocols()
1103
- # We will inspect the selected objects or
1104
- # the whole project is no protocol is selected
1105
- if len(objs) > 0:
1106
- objs.sort(key=lambda obj: obj._objId, reverse=True)
1107
- filePath = objs[0]._getLogsPath('inspector.csv')
1108
- doInspect = True
1109
- else:
1110
- proj = self.project
1111
- filePath = proj.getLogPath('inspector.csv')
1112
- objs = [proj]
1113
- doInspect = pwgui.dialog.askYesNo(Message.TITLE_INSPECTOR,
1114
- Message.LABEL_INSPECTOR, self.root)
1115
-
1116
- if doInspect:
1117
- inspectObj(objs, filePath)
1118
- # we open the resulting CSV file with the OS default software
1119
- pwgui.text.openTextFileEditor(filePath)
1120
-
1121
- # NOt used!: pconesa 02/11/2016.
1122
- # def _deleteSelectedProtocols(self, e=None):
1123
- #
1124
- # for selection in self._selection:
1125
- # self.project.getProtocol(self._selection[0])
1126
- #
1127
- #
1128
- # self._updateSelection()
1129
- # self.updateRunsGraph()
1130
-
1131
- def _updateSelection(self):
1132
- self._fillSummary()
1133
- self._fillMethod()
1134
- self._fillLogs()
1135
- self._showHideAnalyzeResult()
1136
-
1137
- if self._isSingleSelection():
1138
- last = self.getSelectedProtocol()
1139
- self._lastSelectedProtId = last.getObjId() if last else None
1140
-
1141
- self._updateActionToolbar()
1142
-
1143
- def _runTreeItemClick(self, item=None):
1144
- self._selection.clear()
1145
- for prot in self.runsTree.iterSelectedObjects():
1146
- self._selection.append(prot.getObjId())
1147
- self._updateSelection()
1148
-
1149
- def _selectItemProtocol(self, prot):
1150
- """ Call this function when a new box (item) of a protocol
1151
- is selected. It should be called either from itemClick
1152
- or itemRightClick
1153
- """
1154
- self._selection.clear()
1155
- self.settings.dataSelection.clear()
1156
- self._selection.append(prot.getObjId())
1157
-
1158
- # Select output data too
1159
- self.toggleDataSelection(prot, True)
1160
-
1161
- self._updateSelection()
1162
- self.runsGraphCanvas.update_idletasks()
1163
-
1164
- def _deselectItems(self, exception):
1165
- """ Deselect all items except the item one. Pass item=None to deselect all
1166
- """
1167
- g = self.project.getRunsGraph()
1168
-
1169
- for node in g.getNodes():
1170
- if node.run and node.run.getObjId() in self._selection:
1171
- # This option is only for compatibility with all projects
1172
- if hasattr(node, 'item'):
1173
- node.item.setSelected(False)
1174
-
1175
- # clear the selection
1176
- self._selection.clear()
1177
-
1178
- if exception:
1179
- exception.setSelected(True)
1180
- self._selection.append(exception.id)
1181
- def _runItemClick(self, item=None, event=None):
1182
-
1183
- # If click is in a empty area....start panning
1184
- if item is None:
1185
- return
1186
-
1187
- self.runsGraphCanvas.focus_set()
1188
-
1189
- # Get last selected item for tree or graph
1190
- if self.runsView == VIEW_LIST:
1191
- prot = self.project.mapper.selectById(int(self.runsTree.getFirst()))
1192
- else:
1193
- prot = item.node.run
1194
- if prot is None: # in case it is the main "Project" node
1195
- return
1196
- self._deselectItems(item)
1197
- self._selectItemProtocol(prot)
1198
-
1199
- def _runItemDoubleClick(self, item=None, e=None):
1200
- if item.nodeInfo.isExpanded():
1201
- self._runActionClicked(ACTION_EDIT)
1202
-
1203
- def _runItemMiddleClick(self, e=None):
1204
- self._runActionClicked(ACTION_SELECT_TO)
1205
-
1206
- def _runItemRightClick(self, item=None, e=None):
1207
- """ Right click on the canvas callback
1208
-
1209
- :param item: item right-clicked. None if clicked in the void
1210
- :param e: event object with context information"""
1211
- prot = None
1212
- # If there's been a click in a box
1213
- if item is not None:
1214
-
1215
- # Get the protocol associated
1216
- prot = item.node.run
1217
-
1218
- if prot is None: # in case it is the main "Project" node
1219
- return
1220
-
1221
- # Only select item with right-click if there is a single
1222
- # item selection, not for multiple selection
1223
- if len(self._selection) == 1:
1224
- self._deselectItems(item)
1225
- self._selectItemProtocol(prot)
1226
- self._lastRightClickPos = self.runsGraphCanvas.eventPos
1227
- else: # Click on empty area
1228
- self._deselectItems(None)
1229
- self._updateSelection()
1230
-
1231
- return self.provider.getObjectActions(prot,withEvent=True)
1232
-
1233
- def _runItemControlClick(self, item=None, event=None):
1234
- # Get last selected item for tree or graph
1235
- if self.runsView == VIEW_LIST:
1236
- # TODO: Prot is not used!!
1237
- prot = self.project.mapper.selectById(int(self.runsTree.getFirst()))
1238
- else:
1239
- prot = item.node.run
1240
- protId = prot.getObjId()
1241
- if protId in self._selection:
1242
- item.setSelected(False)
1243
- self._selection.remove(protId)
1244
-
1245
- # Remove data selected
1246
- self.toggleDataSelection(prot, False)
1247
- else:
1248
-
1249
- item.setSelected(True)
1250
- if len(self._selection) == 1: # repaint first selected item
1251
- firstSelectedNode = self.runsGraph.getNode(str(self._selection[0]))
1252
- if hasattr(firstSelectedNode, 'item'):
1253
- firstSelectedNode.item.setSelected(False)
1254
- firstSelectedNode.item.setSelected(True)
1255
- self._selection.append(prot.getObjId())
1256
-
1257
- # Select output data too
1258
- self.toggleDataSelection(prot, True)
1259
-
1260
- self._updateSelection()
1261
-
1262
- def toggleDataSelection(self, prot, append):
1263
-
1264
- # Go through the data selection
1265
- for paramName, output in prot.iterOutputAttributes():
1266
- if append:
1267
- self.settings.dataSelection.append(output.getObjId())
1268
- else:
1269
- self.settings.dataSelection.remove(output.getObjId())
1270
-
1271
- def _runItemTooltip(self, tw, item):
1272
- """ Create the contents of the tooltip to be displayed
1273
- for the given item.
1274
- Params:
1275
- tw: a tk.TopLevel instance (ToolTipWindow)
1276
- item: the selected item.
1277
- """
1278
- prot = item.node.run
1279
-
1280
- if prot:
1281
- tm = '*%s*\n' % prot.getRunName()
1282
- tm += 'Identifier :%s\n' % prot.getObjId()
1283
- tm += 'Status: %s\n' % prot.getStatusMessage()
1284
- tm += 'Wall time: %s\n' % pwutils.prettyDelta(prot.getElapsedTime())
1285
- tm += 'CPU time: %s\n' % pwutils.prettyDelta(dt.timedelta(seconds=prot.cpuTime))
1286
- # tm += 'Folder size: %s\n' % pwutils.prettySize(prot.getSize())
1287
-
1288
- if not hasattr(tw, 'tooltipText'):
1289
- frame = tk.Frame(tw)
1290
- frame.grid(row=0, column=0)
1291
- tw.tooltipText = pwgui.dialog.createMessageBody(frame, tm, None,
1292
- textPad=0,
1293
- textBg=Color.ALT_COLOR_2)
1294
- tw.tooltipText.config(bd=1, relief=tk.RAISED)
1295
- else:
1296
- pwgui.dialog.fillMessageText(tw.tooltipText, tm)
1297
-
1298
- @staticmethod
1299
- def _selectItemsWithinArea(x1, y1, x2, y2, enclosed=False):
1300
- """
1301
- Parameters
1302
- ----------
1303
- x1: x coordinate of first corner of the area
1304
- y1: y coordinate of first corner of the area
1305
- x2: x coordinate of second corner of the area
1306
- y2: y coordinate of second corner of the area
1307
- enclosed: Default True. Returns enclosed items,
1308
- overlapping items otherwise.
1309
- Returns
1310
- -------
1311
- Nothing
1312
-
1313
- """
1314
-
1315
- return
1316
- # NOT working properly: Commented for the moment.
1317
- # if enclosed:
1318
- # items = self.runsGraphCanvas.find_enclosed(x1, y1, x2, y2)
1319
- # else:
1320
- # items = self.runsGraphCanvas.find_overlapping(x1, y1, x2, y2)
1321
- #
1322
- # update = False
1323
- #
1324
- # for itemId in items:
1325
- # if itemId in self.runsGraphCanvas.items:
1326
- #
1327
- # item = self.runsGraphCanvas.items[itemId]
1328
- # if not item.node.isRoot():
1329
- # item.setSelected(True)
1330
- # self._selection.append(itemId)
1331
- # update = True
1332
- #
1333
- # if update is not None: self._updateSelection()
1334
-
1335
- def _openProtocolForm(self, prot, disableRunMode=False, position=None):
1336
- """Open the Protocol GUI Form given a Protocol instance"""
1337
-
1338
- w = FormWindow(Message.TITLE_NAME_RUN + prot.getClassName(),
1339
- prot, self._executeSaveProtocol, self.window,
1340
- updateProtocolCallback=self._updateProtocol,
1341
- disableRunMode=disableRunMode, position=position)
1342
- w.adjustSize()
1343
- w.show(center=True)
1344
-
1345
- def _browseSteps(self):
1346
- """ Open a new window with the steps list. """
1347
- window = StepsWindow(Message.TITLE_BROWSE_DATA, self.window,
1348
- self.getSelectedProtocol())
1349
- window.show()
1350
-
1351
- def _browseRunData(self):
1352
- provider = ProtocolTreeProvider(self.getSelectedProtocol())
1353
- window = pwgui.browser.BrowserWindow(Message.TITLE_BROWSE_DATA,
1354
- self.window)
1355
- window.setBrowser(pwgui.browser.ObjectBrowser(window.root, provider))
1356
- window.itemConfig(self.getSelectedProtocol(), open=True)
1357
- window.show()
1358
-
1359
- def _browseRunDirectory(self):
1360
- """ Open a file browser to inspect the files generated by the run. """
1361
- protocol = self.getSelectedProtocol()
1362
- workingDir = protocol.getWorkingDir()
1363
- if os.path.exists(workingDir):
1364
-
1365
- protFolderShortCut = ShortCut.factory(workingDir,name="Protocol folder", icon=None ,toolTip="Protocol directory")
1366
- window = pwgui.browser.FileBrowserWindow("Browsing: " + workingDir,
1367
- master=self.window,
1368
- path=workingDir,
1369
- shortCuts=[protFolderShortCut])
1370
- window.show()
1371
- else:
1372
- self.window.showInfo("Protocol working dir does not exists: \n %s"
1373
- % workingDir)
1374
-
1375
- def _iterSelectedProtocols(self):
1376
- for protId in sorted(self._selection):
1377
- prot = self.project.getRunsGraph().getNode(str(protId)).run
1378
- if prot:
1379
- yield prot
1380
-
1381
- def _getSelectedProtocols(self):
1382
- return [prot for prot in self._iterSelectedProtocols()]
1383
-
1384
- def _iterSelectedNodes(self):
1385
-
1386
- for protId in sorted(self._selection):
1387
- node = self.settings.getNodeById(protId)
1388
-
1389
- yield node
1390
-
1391
- def _getSelectedNodes(self):
1392
- return [node for node in self._iterSelectedNodes()]
1393
-
1394
- def getSelectedProtocol(self):
1395
- if self._selection:
1396
- return self.project.getProtocol(self._selection[0], fromRuns=True)
1397
- return None
1398
-
1399
- def _showHideAnalyzeResult(self):
1400
-
1401
- if self._selection:
1402
- self.btnAnalyze.grid()
1403
- else:
1404
- self.btnAnalyze.grid_remove()
1405
-
1406
- def _fillSummary(self):
1407
- self.summaryText.setReadOnly(False)
1408
- self.summaryText.clear()
1409
- self.infoTree.clear()
1410
- n = len(self._selection)
1411
-
1412
- if n == 1:
1413
- prot = self.getSelectedProtocol()
1414
-
1415
- if prot:
1416
- provider = RunIOTreeProvider(self, prot, self.project.mapper, self.info)
1417
- self.infoTree.setProvider(provider)
1418
- self.infoTree.grid(row=0, column=0, sticky='news')
1419
- self.infoTree.update_idletasks()
1420
- # Update summary
1421
- self.summaryText.addText(prot.summary())
1422
- else:
1423
- self.infoTree.clear()
1424
-
1425
- elif n > 1:
1426
- self.infoTree.clear()
1427
- for prot in self._iterSelectedProtocols():
1428
- self.summaryText.addLine('> _%s_' % prot.getRunName())
1429
- for line in prot.summary():
1430
- self.summaryText.addLine(line)
1431
- self.summaryText.addLine('')
1432
- self.summaryText.setReadOnly(True)
1433
-
1434
- def _fillMethod(self):
1435
-
1436
- try:
1437
- self.methodText.setReadOnly(False)
1438
- self.methodText.clear()
1439
- self.methodText.addLine("*METHODS:*")
1440
- cites = OrderedDict()
1441
-
1442
- for prot in self._iterSelectedProtocols():
1443
- self.methodText.addLine('> _%s_' % prot.getRunName())
1444
- for line in prot.getParsedMethods():
1445
- self.methodText.addLine(line)
1446
- cites.update(prot.getCitations())
1447
- cites.update(prot.getPackageCitations())
1448
- self.methodText.addLine('')
1449
-
1450
- if cites:
1451
- self.methodText.addLine('*REFERENCES:* '
1452
- ' [[sci-bib:][<<< Open as bibtex >>>]]')
1453
- for cite in cites.values():
1454
- self.methodText.addLine(cite)
1455
-
1456
- self.methodText.setReadOnly(True)
1457
- except Exception as e:
1458
- self.methodText.addLine('Could not load all methods:' + str(e))
1459
-
1460
- def _fillLogs(self):
1461
- try:
1462
- prot = self.getSelectedProtocol()
1463
-
1464
- if not self._isSingleSelection() or not prot:
1465
- self.outputViewer.clear()
1466
- self._lastStatus = None
1467
- elif prot.getObjId() != self._lastSelectedProtId:
1468
- self._lastStatus = prot.getStatus()
1469
- i = self.outputViewer.getIndex()
1470
- self.outputViewer.clear()
1471
- # Right now skip the err tab since we are redirecting
1472
- # stderr to stdout
1473
- out, err, schedule = prot.getLogPaths()
1474
- self.outputViewer.addFile(out)
1475
- self.outputViewer.addFile(err)
1476
- if os.path.exists(schedule):
1477
- self.outputViewer.addFile(schedule)
1478
- elif i == 2:
1479
- i = 0
1480
- self.outputViewer.setIndex(i) # Preserve the last selected tab
1481
- self.outputViewer.selectedText().goEnd()
1482
- # when there are not logs, force re-load next time
1483
- if (not os.path.exists(out) or
1484
- not os.path.exists(err)):
1485
- self._lastStatus = None
1486
-
1487
- elif prot.isActive() or prot.getStatus() != self._lastStatus:
1488
- doClear = self._lastStatus is None
1489
- self._lastStatus = prot.getStatus()
1490
- self.outputViewer.refreshAll(clear=doClear, goEnd=doClear)
1491
- except Exception as e:
1492
- self.info("Something went wrong filling %s's logs: %s. Check terminal for details" % (prot, e))
1493
- import traceback
1494
- traceback.print_exc()
1495
-
1496
- def _scheduleRunsUpdate(self, secs=1, position=None):
1497
- # self.runsTree.after(secs*1000, self.refreshRuns)
1498
- self.window.enqueue(lambda : self.refreshRuns(position=position))
1499
-
1500
- def executeProtocol(self, prot):
1501
- """ Function to execute a protocol called not
1502
- directly from the Form "Execute" button.
1503
- """
1504
- # We need to equeue the execute action
1505
- # to be executed in the same thread
1506
- self.window.enqueue(lambda: self._executeSaveProtocol(prot))
1507
-
1508
- def _executeSaveProtocol(self, prot, onlySave=False, doSchedule=False, position=None):
1509
- if onlySave:
1510
- self.project.saveProtocol(prot)
1511
- msg = Message.LABEL_SAVED_FORM
1512
- # msg = "Protocol successfully saved."
1513
-
1514
- else:
1515
- if doSchedule:
1516
- self.project.scheduleProtocol(prot)
1517
- else:
1518
- self.project.launchProtocol(prot)
1519
- # Select the launched protocol to display its summary, methods..etc
1520
- self._selection.clear()
1521
- self._selection.append(prot.getObjId())
1522
- self._updateSelection()
1523
- self._lastStatus = None # clear lastStatus to force re-load the logs
1524
- msg = ""
1525
-
1526
- # Update runs list display, even in save we
1527
- # need to get the updated copy of the protocol
1528
- self._scheduleRunsUpdate(position=position)
1529
- self._selectItemProtocol(prot)
1530
-
1531
- return msg
1532
-
1533
- def _updateProtocol(self, prot):
1534
- """ Callback to notify about the change of a protocol
1535
- label or comment.
1536
- """
1537
- self._scheduleRunsUpdate()
1538
-
1539
- def _continueProtocol(self, prot):
1540
- self.project.continueProtocol(prot)
1541
- self._scheduleRunsUpdate()
1542
-
1543
- def _onDelPressed(self):
1544
- # This function will be connected to the key 'Del' press event
1545
- # We need to check if the canvas have the focus and then
1546
- # proceed with the delete action
1547
-
1548
- # get the widget with the focus
1549
- widget = self.focus_get()
1550
-
1551
- # Call the delete action only if the widget is the canvas
1552
- if str(widget).endswith(ProtocolsView.RUNS_CANVAS_NAME):
1553
- try:
1554
- self._deleteProtocol()
1555
- except Exception as ex:
1556
- self.window.showError(str(ex))
1557
-
1558
- def _deleteProtocol(self):
1559
- protocols = self._getSelectedProtocols()
1560
-
1561
- if len(protocols) == 0:
1562
- return
1563
-
1564
- protStr = '\n - '.join(['*%s*' % p.getRunName() for p in protocols])
1565
-
1566
- if pwgui.dialog.askYesNo(Message.TITLE_DELETE_FORM,
1567
- Message.LABEL_DELETE_FORM % protStr,
1568
- self.root):
1569
- self.info('Deleting protocols...')
1570
- self.project.deleteProtocol(*protocols)
1571
- self.settings.cleanUpNodes([str(prot.getObjId()) for prot in protocols])
1572
- self._selection.clear()
1573
- self._updateSelection()
1574
- self._scheduleRunsUpdate()
1575
- self.cleanInfo()
1576
-
1577
-
1578
- def _editProtocol(self, protocol):
1579
- disableRunMode = False
1580
- if protocol.isSaved():
1581
- disableRunMode = True
1582
- self._openProtocolForm(protocol, disableRunMode=disableRunMode)
1583
-
1584
- def _pasteProtocolsFromClipboard(self, e=None):
1585
- """ Pastes the content of the clipboard providing is a json workflow"""
1586
-
1587
- try:
1588
-
1589
- self.project.loadProtocols(jsonStr=self.clipboard_get())
1590
- self.info("Clipboard content pasted successfully.")
1591
- self.updateRunsGraph(False)
1592
- except Exception as e:
1593
- self.info("Paste failed, maybe clipboard content is not valid content? See GUI log for details.")
1594
- logger.error("Clipboard content couldn't be pasted." , exc_info=e)
1595
-
1596
- def _copyProtocolsToClipboard(self, e=None):
1597
-
1598
- protocols = self._getSelectedProtocols()
1599
-
1600
- jsonStr = self.project.getProtocolsJson(protocols)
1601
-
1602
- self.clipboard_clear()
1603
- self.clipboard_append(jsonStr)
1604
- self.info("Protocols copied to the clipboard. Now you can paste them here, another project or in a template or ... anywhere!.")
1605
-
1606
- def _copyProtocols(self, e=None):
1607
- protocols = self._getSelectedProtocols()
1608
- if len(protocols) == 1:
1609
- newProt = self.project.copyProtocol(protocols[0])
1610
- if newProt is None:
1611
- self.window.showError("Error copying protocol.!!!")
1612
- else:
1613
- self._openProtocolForm(newProt, disableRunMode=True)
1614
- else:
1615
- self.info('Copying the protocols...')
1616
- self.project.copyProtocol(protocols)
1617
- self.refreshRuns()
1618
- self.cleanInfo()
1619
-
1620
- def _stopWorkFlow(self, action):
1621
-
1622
- protocols = self._getSelectedProtocols()
1623
-
1624
- # TODO: use filterCallback param and we may not need to return 2 elements
1625
- workflowProtocolList, activeProtList = self.project._getSubworkflow(protocols[0],
1626
- fixProtParam=False,
1627
- getStopped=False)
1628
- if activeProtList:
1629
- errorProtList = []
1630
- if pwgui.dialog.askYesNo(Message.TITLE_STOP_WORKFLOW_FORM,
1631
- Message.TITLE_STOP_WORKFLOW, self.root):
1632
- self.info('Stopping the workflow...')
1633
- errorProtList = self.project.stopWorkFlow(activeProtList)
1634
- self.cleanInfo()
1635
- self.refreshRuns()
1636
- if errorProtList:
1637
- msg = '\n'
1638
- for prot in errorProtList:
1639
- msg += str(prot.getObjLabel()) + '\n'
1640
- pwgui.dialog.MessageDialog(
1641
- self, Message.TITLE_STOPPED_WORKFLOW_FAILED,
1642
- Message.TITLE_STOPPED_WORKFLOW_FAILED + ' with: ' + msg,
1643
- Icon.ERROR)
1644
-
1645
- def _resetWorkFlow(self, action):
1646
-
1647
- protocols = self._getSelectedProtocols()
1648
- errorProtList = []
1649
- if pwgui.dialog.askYesNo(Message.TITLE_RESET_WORKFLOW_FORM,
1650
- Message.TITLE_RESET_WORKFLOW, self.root):
1651
- self.info('Resetting the workflow...')
1652
- workflowProtocolList, activeProtList = self.project._getSubworkflow(protocols[0])
1653
- errorProtList = self.project.resetWorkFlow(workflowProtocolList)
1654
- self.cleanInfo()
1655
- self.refreshRuns()
1656
- if errorProtList:
1657
- msg = '\n'
1658
- for prot in errorProtList:
1659
- msg += str(prot.getObjLabel()) + '\n'
1660
- pwgui.dialog.MessageDialog(
1661
- self, Message.TITLE_RESETED_WORKFLOW_FAILED,
1662
- Message.TITLE_RESETED_WORKFLOW_FAILED + ' with: ' + msg,
1663
- Icon.ERROR)
1664
-
1665
- def _launchWorkFlow(self, action):
1666
- """
1667
- This function can launch a workflow from a selected protocol in two
1668
- modes depending on the 'action' value (RESTART, CONTINUE)
1669
- """
1670
- protocols = self._getSelectedProtocols()
1671
- mode = pwprot.MODE_RESTART if action == ACTION_RESTART_WORKFLOW else pwprot.MODE_RESUME
1672
- errorList, _ = self._launchSubWorkflow(protocols[0], mode, self.root)
1673
-
1674
- if errorList:
1675
- msg = ''
1676
- for errorProt in errorList:
1677
- msg += str(errorProt) + '\n'
1678
- pwgui.dialog.MessageDialog(
1679
- self, Message.TITLE_LAUNCHED_WORKFLOW_FAILED_FORM,
1680
- Message.TITLE_LAUNCHED_WORKFLOW_FAILED + "\n" + msg,
1681
- Icon.ERROR)
1682
- self.refreshRuns()
1683
-
1684
- @staticmethod
1685
- def _launchSubWorkflow(protocol, mode, root, askSingleAll=False):
1686
- """
1687
- Method to launch a subworkflow
1688
- mode: mode value (RESTART, CONTINUE)
1689
- askSingleAll: specify if this method was launched from the form or from the menu
1690
- """
1691
- project = protocol.getProject()
1692
- workflowProtocolList, activeProtList = project._getSubworkflow(protocol)
1693
-
1694
- # Check if exists active protocols
1695
- activeProtocols = ""
1696
- if activeProtList:
1697
- for protId, activeProt in activeProtList.items():
1698
- activeProtocols += ("\n* " + activeProt.getRunName())
1699
-
1700
- # by default, we assume RESTART workflow option
1701
- title = Message.TITLE_RESTART_WORKFLOW_FORM
1702
- message = Message.MESSAGE_RESTART_WORKFLOW_WITH_RESULTS % ('%s\n' % activeProtocols) if len(activeProtList) else Message.MESSAGE_RESTART_WORKFLOW
1703
-
1704
- if mode == pwprot.MODE_RESUME:
1705
- message = Message.MESSAGE_CONTINUE_WORKFLOW_WITH_RESULTS % ('%s\n' % activeProtocols) if len(activeProtList) else Message.MESSAGE_CONTINUE_WORKFLOW
1706
- title = Message.TITLE_CONTINUE_WORKFLOW_FORM
1707
-
1708
- errorList=[]
1709
-
1710
- if not askSingleAll:
1711
- if pwgui.dialog.askYesNo(title, message, root):
1712
- errorList = project.launchWorkflow(workflowProtocolList, mode)
1713
- return errorList, RESULT_RUN_ALL
1714
- return [], RESULT_CANCEL
1715
- else: # launching from a form
1716
- if len(workflowProtocolList) > 1:
1717
- if Config.SCIPION_DEFAULT_EXECUTION_ACTION == DEFAULT_EXECUTION_ACTION_ASK:
1718
- title = Message.TITLE_RESTART_FORM if mode == pwprot.MODE_RESTART else Message.TITLE_CONTINUE_FORM
1719
- message += Message.MESSAGE_ASK_SINGLE_ALL
1720
- result = pwgui.dialog.askSingleAllCancel(title, message,
1721
- root)
1722
- elif Config.SCIPION_DEFAULT_EXECUTION_ACTION == DEFAULT_EXECUTION_ACTION_SINGLE:
1723
- result = RESULT_RUN_SINGLE
1724
- else:
1725
- result = RESULT_RUN_ALL
1726
-
1727
- if result == RESULT_RUN_ALL:
1728
- errorList = []
1729
- if mode == pwprot.MODE_RESTART:
1730
- project._restartWorkflow(errorList, workflowProtocolList)
1731
- else:
1732
- project._continueWorkflow(errorList, workflowProtocolList)
1733
-
1734
- return errorList, RESULT_RUN_ALL
1735
-
1736
- elif result == RESULT_RUN_SINGLE:
1737
- # If mode resume, we should not reset the "current" protocol
1738
- if mode == pwprot.MODE_RESUME:
1739
- workflowProtocolList.pop(protocol.getObjId())
1740
- errorList = project.resetWorkFlow(workflowProtocolList)
1741
- return errorList, RESULT_RUN_SINGLE
1742
-
1743
- elif result == RESULT_CANCEL:
1744
- return [], RESULT_CANCEL
1745
-
1746
- else: # is a single protocol
1747
- if not protocol.isSaved():
1748
- title = Message.TITLE_RESTART_FORM
1749
- message = Message.MESSAGE_RESTART_FORM % ('%s\n' % protocol.getRunName())
1750
- if mode == pwprot.MODE_RESUME:
1751
- title = Message.TITLE_CONTINUE_FORM
1752
- message = Message.MESSAGE_CONTINUE_FORM % ('%s\n' % protocol.getRunName())
1753
-
1754
- result = pwgui.dialog.askYesNo(title, message, root)
1755
- resultRun = RESULT_RUN_SINGLE if result else RESULT_CANCEL
1756
- return [], resultRun
1757
-
1758
- return [], RESULT_RUN_SINGLE
1759
-
1760
- def _selectLabels(self):
1761
-
1762
- dlg = self.window.manageLabels()
1763
-
1764
- selectedNodes = self._getSelectedNodes()
1765
-
1766
- if dlg.resultYes() and selectedNodes:
1767
-
1768
- for node in selectedNodes:
1769
- node.setLabels([label.getName() for label in dlg.values])
1770
-
1771
- # self.updateRunsGraph()
1772
- self.drawRunsGraph()
1773
-
1774
- # Save settings in any case
1775
- self.window.saveSettings()
1776
-
1777
- def _selectAncestors(self):
1778
- self._selectNodes(down=False)
1779
-
1780
- def _selectDescendants(self):
1781
- self._selectNodes(down=True)
1782
-
1783
- def _selectNodes(self, down=True, fromRun=None):
1784
- """ Selects all nodes in the specified direction, defaults to down."""
1785
- nodesToSelect = []
1786
- # If parent param not passed...
1787
- if fromRun is None:
1788
- # ..use selection, must be first call
1789
- for protId in self._selection:
1790
- run = self.runsGraph.getNode(str(protId))
1791
- nodesToSelect.append(run)
1792
- else:
1793
- name = fromRun.getName()
1794
-
1795
- if not name.isdigit():
1796
- return
1797
- else:
1798
- name = int(name)
1799
-
1800
- # If already selected (may be this should be centralized)
1801
- if name not in self._selection:
1802
- nodesToSelect = (fromRun,)
1803
- self._selection.append(name)
1804
-
1805
- # Go in the direction .
1806
- for run in nodesToSelect:
1807
- # Choose the direction: down or up.
1808
- direction = run.getChildren if down else run.getParents
1809
-
1810
- # Select himself plus ancestors
1811
- for parent in direction():
1812
- self._selectNodes(down, parent)
1813
-
1814
- # Only update selection at the end, avoid recursion
1815
- if fromRun is None:
1816
- self._lastSelectedProtId = None
1817
- self._updateSelection()
1818
- self.drawRunsGraph()
1819
-
1820
-
1821
- def _exportProtocols(self, defaultPath=None, defaultBasename=None):
1822
- protocols = self._getSelectedProtocols()
1823
-
1824
- def _export(obj):
1825
- filename = os.path.join(browser.getCurrentDir(),
1826
- browser.getEntryValue())
1827
- try:
1828
- if (not os.path.exists(filename) or
1829
- self.window.askYesNo("File already exists",
1830
- "*%s* already exists, do you want "
1831
- "to overwrite it?" % filename)):
1832
- self.project.exportProtocols(protocols, filename)
1833
- logger.info("Workflow successfully saved to '%s' "
1834
- % filename)
1835
- else: # try again
1836
- self._exportProtocols(defaultPath=browser.getCurrentDir(),
1837
- defaultBasename=browser.getEntryValue())
1838
- except Exception as ex:
1839
- import traceback
1840
- traceback.print_exc()
1841
- self.window.showError(str(ex))
1842
-
1843
- browser = pwgui.browser.FileBrowserWindow(
1844
- "Choose .json file to save workflow",
1845
- master=self.window,
1846
- path=defaultPath or self.project.getPath(''),
1847
- onSelect=_export,
1848
- entryLabel='File ', entryValue=defaultBasename or 'workflow.json')
1849
- browser.show()
1850
-
1851
- def _exportUploadProtocols(self):
1852
- try:
1853
- jsonFn = os.path.join(tempfile.mkdtemp(), 'workflow.json')
1854
- self.project.exportProtocols(self._getSelectedProtocols(), jsonFn)
1855
- WorkflowRepository().upload(jsonFn)
1856
- pwutils.cleanPath(jsonFn)
1857
- except Exception as ex:
1858
- self.window.showError("Error connecting to workflow repository:\n"
1859
- + str(ex))
1860
-
1861
- def _stopProtocol(self, prot):
1862
- if pwgui.dialog.askYesNo(Message.TITLE_STOP_FORM,
1863
- Message.LABEL_STOP_FORM, self.root):
1864
- self.project.stopProtocol(prot)
1865
- self._lastStatus = None # force logs to re-load
1866
- self._scheduleRunsUpdate()
1867
-
1868
- def _analyzeResults(self, prot, keyPressed):
1869
- viewers = self.domain.findViewers(prot.getClassName(), DESKTOP_TKINTER)
1870
- if len(viewers):
1871
- # Instantiate the first available viewer
1872
- # TODO: If there are more than one viewer we should display
1873
- # TODO: a selection menu
1874
- firstViewer = viewers[0](project=self.project, protocol=prot,
1875
- parent=self.window, keyPressed=keyPressed)
1876
-
1877
- if isinstance(firstViewer, ProtocolViewer):
1878
- firstViewer.visualize(prot, windows=self.window)
1879
- else:
1880
- firstViewer.visualize(prot)
1881
- else:
1882
- outputList = []
1883
- for _, output in prot.iterOutputAttributes():
1884
- outputList.append(output)
1885
-
1886
- for output in outputList:
1887
- viewers = self.domain.findViewers(output.getClassName(), DESKTOP_TKINTER)
1888
- if len(viewers):
1889
- # Instantiate the first available viewer
1890
- # TODO: If there are more than one viewer we should display
1891
- # TODO: a selection menu
1892
- viewerclass = viewers[0]
1893
- firstViewer = viewerclass(project=self.project,
1894
- protocol=prot,
1895
- parent=self.window,
1896
- keyPressed=keyPressed)
1897
- # FIXME:Probably o longer needed protocol on args, already provided on init
1898
- firstViewer.visualize(output, windows=self.window,
1899
- protocol=prot)
1900
-
1901
- def _analyzeResultsClicked(self, keyPressed=None):
1902
- """ Function called when button "Analyze results" is called. """
1903
- prot = self.getSelectedProtocol()
1904
-
1905
- # Nothing selected
1906
- if prot is None:
1907
- return
1908
-
1909
- if os.path.exists(prot._getPath()):
1910
- # self.info('"Analyze result" clicked with %s key pressed.' % keyPressed)
1911
- self._analyzeResults(prot, keyPressed)
1912
- else:
1913
- self.window.showInfo("Selected protocol hasn't been run yet.")
1914
-
1915
- def _bibExportClicked(self, e=None):
1916
- try:
1917
- bibTexCites = OrderedDict()
1918
- for prot in self._iterSelectedProtocols():
1919
- bibTexCites.update(prot.getCitations(bibTexOutput=True))
1920
- bibTexCites.update(prot.getPackageCitations(bibTexOutput=True))
1921
-
1922
- if bibTexCites:
1923
- with tempfile.NamedTemporaryFile(suffix='.bib') as bibFile:
1924
- for refId, refDict in bibTexCites.items():
1925
- # getCitations does not always return a dictionary
1926
- # if the citation is not found in the bibtex file it adds just
1927
- # the refId: like "Ramirez-Aportela-2019"
1928
- # we need to exclude this
1929
- if isinstance(refDict, dict):
1930
- refType = refDict['ENTRYTYPE']
1931
- # remove 'type' and 'id' keys
1932
- refDict = {k: v for k, v in refDict.items()
1933
- if k not in ['ENTRYTYPE', 'ID']}
1934
- jsonStr = json.dumps(refDict, indent=4,
1935
- ensure_ascii=False)[1:]
1936
- jsonStr = jsonStr.replace('": "', '"= "')
1937
- jsonStr = re.sub(r'(?<!= )"(\S*?)"', '\\1', jsonStr)
1938
- jsonStr = jsonStr.replace('= "', ' = "')
1939
- refStr = '@%s{%s,%s\n\n' % (refType, refId, jsonStr)
1940
- bibFile.write(refStr.encode('utf-8'))
1941
- else:
1942
- logger.warning("Reference %s not properly defined or unpublished." % refId)
1943
- # flush so we can see content when opening
1944
- bibFile.flush()
1945
- pwgui.text.openTextFileEditor(bibFile.name)
1946
-
1947
- except Exception as ex:
1948
- self.window.showError(str(ex))
1949
-
1950
- return
1951
-
1952
- def _renameProtocol(self, prot):
1953
- """ Open the EditObject dialog to edit the protocol name. """
1954
- kwargs = {}
1955
- if self._lastRightClickPos:
1956
- kwargs['position'] = self._lastRightClickPos
1957
-
1958
- dlg = pwgui.dialog.EditObjectDialog(self.runsGraphCanvas, Message.TITLE_EDIT_OBJECT,
1959
- prot, self.project.mapper, **kwargs)
1960
- if dlg.resultYes():
1961
- self._updateProtocol(prot)
1962
-
1963
- def _runActionClicked(self, action, event=None):
1964
-
1965
- if event is not None:
1966
- # log Search box events are reaching here
1967
- # Since this method is bound to the window events
1968
- if event.widget.widgetName == 'entry':
1969
- return
1970
-
1971
- # Following actions do not need a select run
1972
- if action == ACTION_TREE:
1973
- self.drawRunsGraph(reorganize=True)
1974
- elif action == ACTION_REFRESH:
1975
- self.refreshRuns(checkPids=True)
1976
- elif action == ACTION_PASTE:
1977
- self._pasteProtocolsFromClipboard()
1978
-
1979
- elif action == ACTION_SWITCH_VIEW:
1980
- self.switchRunsView()
1981
- elif action == ACTION_NEW:
1982
- self._findProtocol(event)
1983
- elif action == ACTION_LABELS:
1984
- self._selectLabels()
1985
- else:
1986
- prot = self.getSelectedProtocol()
1987
- if prot:
1988
- try:
1989
- if action == ACTION_DEFAULT:
1990
- pass
1991
- elif action == ACTION_EDIT:
1992
- self._editProtocol(prot)
1993
- elif action == ACTION_RENAME:
1994
- self._renameProtocol(prot)
1995
- elif action == ACTION_DUPLICATE:
1996
- self._copyProtocols()
1997
- elif action == ACTION_COPY:
1998
- self._copyProtocolsToClipboard()
1999
- elif action == ACTION_DELETE:
2000
- self._deleteProtocol()
2001
- elif action == ACTION_STEPS:
2002
- self._browseSteps()
2003
- elif action == ACTION_BROWSE:
2004
- self._browseRunDirectory()
2005
- elif action == ACTION_DB:
2006
- self._browseRunData()
2007
- elif action == ACTION_STOP:
2008
- self._stopProtocol(prot)
2009
- elif action == ACTION_CONTINUE:
2010
- self._continueProtocol(prot)
2011
- elif action == ACTION_RESULTS:
2012
- self._analyzeResults(prot, None)
2013
- elif action == ACTION_EXPORT:
2014
- self._exportProtocols(defaultPath=pwutils.getHomePath())
2015
- elif action == ACTION_EXPORT_UPLOAD:
2016
- self._exportUploadProtocols()
2017
- elif action == ACTION_COLLAPSE:
2018
- node = self.runsGraph.getNode(str(prot.getObjId()))
2019
- nodeInfo = self.settings.getNodeById(prot.getObjId())
2020
- nodeInfo.setExpanded(False)
2021
- self.setVisibleNodes(node, visible=False)
2022
- self.updateRunsGraph(False)
2023
- self._updateActionToolbar()
2024
- elif action == ACTION_EXPAND:
2025
- node = self.runsGraph.getNode(str(prot.getObjId()))
2026
- nodeInfo = self.settings.getNodeById(prot.getObjId())
2027
- nodeInfo.setExpanded(True)
2028
- self.setVisibleNodes(node, visible=True)
2029
- self.updateRunsGraph(False)
2030
- self._updateActionToolbar()
2031
-
2032
- elif action == ACTION_SELECT_FROM:
2033
- self._selectDescendants()
2034
- elif action == ACTION_SELECT_TO:
2035
- self._selectAncestors()
2036
- elif action == ACTION_RESTART_WORKFLOW:
2037
- self._launchWorkFlow(action)
2038
- elif action == ACTION_CONTINUE_WORKFLOW:
2039
- self._launchWorkFlow(action)
2040
- elif action == ACTION_STOP_WORKFLOW:
2041
- self._stopWorkFlow(action)
2042
- elif action == ACTION_RESET_WORKFLOW:
2043
- self._resetWorkFlow(action)
2044
- elif action == ACTION_SEARCH:
2045
- self._searchProtocol()
2046
-
2047
- except Exception as ex:
2048
- self.window.showError(str(ex), exception=ex)
2049
- if Config.debugOn():
2050
- import traceback
2051
- traceback.print_exc()
2052
- else:
2053
- self.info("Action '%s' not implemented." % action)
2054
-
2055
- def setVisibleNodes(self, node, visible=True):
2056
- hasParentHidden = False
2057
- for child in node.getChildren():
2058
- prot = child.run
2059
- nodeInfo = self.settings.getNodeById(prot.getObjId())
2060
- if visible:
2061
- hasParentHidden = self.hasParentHidden(child)
2062
- if not hasParentHidden:
2063
- nodeInfo.setVisible(visible)
2064
- self.setVisibleNodes(child, visible)
2065
-
2066
- def hasParentHidden(self, node):
2067
- for parent in node.getParents():
2068
- prot = parent.run
2069
- nodeInfo = self.settings.getNodeById(prot.getObjId())
2070
- if not nodeInfo.isVisible() or not nodeInfo.isExpanded():
2071
- return True
2072
- return False
2073
-
2074
-
2075
- class RunBox(pwgui.TextBox):
2076
- """ Just override TextBox move method to keep track of
2077
- position changes in the graph.
2078
- """
2079
-
2080
- def __init__(self, nodeInfo, canvas, text, x, y, bgColor, textColor):
2081
- pwgui.TextBox.__init__(self, canvas, text, x, y, bgColor, textColor)
2082
- self.nodeInfo = nodeInfo
2083
- canvas.addItem(self)
2084
-
2085
- def move(self, dx, dy):
2086
- pwgui.TextBox.move(self, dx, dy)
2087
- self.nodeInfo.setPosition(self.x, self.y)
2088
-
2089
- def moveTo(self, x, y):
2090
- pwgui.TextBox.moveTo(self, x, y)
2091
- self.nodeInfo.setPosition(self.x, self.y)
2092
-
2093
-