scipion-pyworkflow 3.10.6__py3-none-any.whl → 3.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyworkflow/config.py +131 -67
- pyworkflow/constants.py +2 -1
- pyworkflow/gui/browser.py +39 -5
- pyworkflow/gui/dialog.py +2 -0
- pyworkflow/gui/form.py +141 -52
- pyworkflow/gui/gui.py +8 -8
- pyworkflow/gui/project/project.py +6 -7
- pyworkflow/gui/project/searchprotocol.py +91 -7
- pyworkflow/gui/project/viewdata.py +1 -1
- pyworkflow/gui/project/viewprotocols.py +45 -22
- pyworkflow/gui/project/viewprotocols_extra.py +9 -6
- pyworkflow/gui/widgets.py +2 -2
- pyworkflow/mapper/sqlite.py +4 -4
- pyworkflow/plugin.py +93 -44
- pyworkflow/project/project.py +158 -70
- pyworkflow/project/usage.py +165 -0
- pyworkflow/protocol/executor.py +30 -18
- pyworkflow/protocol/hosts.py +9 -6
- pyworkflow/protocol/launch.py +15 -8
- pyworkflow/protocol/params.py +59 -19
- pyworkflow/protocol/protocol.py +124 -58
- pyworkflow/resources/showj/arrowDown.png +0 -0
- pyworkflow/resources/showj/arrowUp.png +0 -0
- pyworkflow/resources/showj/background_section.png +0 -0
- pyworkflow/resources/showj/colRowModeOff.png +0 -0
- pyworkflow/resources/showj/colRowModeOn.png +0 -0
- pyworkflow/resources/showj/delete.png +0 -0
- pyworkflow/resources/showj/doc_icon.png +0 -0
- pyworkflow/resources/showj/download_icon.png +0 -0
- pyworkflow/resources/showj/enabled_gallery.png +0 -0
- pyworkflow/resources/showj/galleryViewOff.png +0 -0
- pyworkflow/resources/showj/galleryViewOn.png +0 -0
- pyworkflow/resources/showj/goto.png +0 -0
- pyworkflow/resources/showj/menu.png +0 -0
- pyworkflow/resources/showj/separator.png +0 -0
- pyworkflow/resources/showj/tableViewOff.png +0 -0
- pyworkflow/resources/showj/tableViewOn.png +0 -0
- pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- pyworkflow/resources/showj/volumeOff.png +0 -0
- pyworkflow/resources/showj/volumeOn.png +0 -0
- pyworkflow/utils/log.py +15 -6
- pyworkflow/utils/properties.py +78 -92
- pyworkflow/utils/utils.py +3 -2
- pyworkflow/viewer.py +23 -1
- pyworkflow/webservices/config.py +0 -3
- pyworkflow/webservices/notifier.py +24 -34
- pyworkflowtests/protocols.py +1 -3
- pyworkflowtests/tests/test_protocol_execution.py +4 -0
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +13 -27
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/RECORD +56 -35
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +1 -1
- scipion_pyworkflow-3.10.6.dist-info/dependency_links.txt +0 -1
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/top_level.txt +0 -0
pyworkflow/gui/form.py
CHANGED
@@ -29,6 +29,9 @@ creation of protocol form GUI from its
|
|
29
29
|
params definition.
|
30
30
|
"""
|
31
31
|
import logging
|
32
|
+
|
33
|
+
from typing import Type
|
34
|
+
|
32
35
|
logger = logging.getLogger(__name__)
|
33
36
|
import os
|
34
37
|
import tkinter as tk
|
@@ -558,11 +561,15 @@ class SubclassesTreeProvider(TreeProvider):
|
|
558
561
|
obj = pobj.get()
|
559
562
|
actions = []
|
560
563
|
domain = pw.Config.getDomain()
|
561
|
-
viewers = domain.findViewers(obj
|
564
|
+
viewers = domain.findViewers(obj, DESKTOP_TKINTER)
|
562
565
|
proj = self.protocol.getProject()
|
566
|
+
|
567
|
+
def addViewer(viewer):
|
568
|
+
actions.append((viewer.getName(),
|
569
|
+
lambda: viewer(project=proj).visualize(obj)))
|
570
|
+
|
563
571
|
for v in viewers:
|
564
|
-
|
565
|
-
lambda: v(project=proj).visualize(obj)))
|
572
|
+
addViewer(v)
|
566
573
|
return actions
|
567
574
|
|
568
575
|
|
@@ -847,7 +854,7 @@ class SectionFrame(tk.Frame):
|
|
847
854
|
csize = self._getContentSize()
|
848
855
|
|
849
856
|
# update the inner frame's width to fill the canvas
|
850
|
-
self.canvas.itemconfigure(self.contentId, width=csize[0],height=csize[1])
|
857
|
+
self.canvas.itemconfigure(self.contentId, width=csize[0], height=csize[1])
|
851
858
|
self.canvas.config(scrollregion="0 0 %s %s" % fsize)
|
852
859
|
|
853
860
|
def _getContentSize(self):
|
@@ -929,9 +936,6 @@ class ParamWidget:
|
|
929
936
|
showButtons=True):
|
930
937
|
self.window = window
|
931
938
|
self._protocol = self.window.protocol
|
932
|
-
if self._protocol.getProject() is None:
|
933
|
-
logger.error(">>> ERROR: Project is None for protocol: %s, "
|
934
|
-
"start winpdb to debug it" % self._protocol)
|
935
939
|
|
936
940
|
self.row = row
|
937
941
|
self.column = column
|
@@ -1005,8 +1009,8 @@ class ParamWidget:
|
|
1005
1009
|
def _showInfo(self, msg):
|
1006
1010
|
showInfo("Info", msg, self.parent)
|
1007
1011
|
|
1008
|
-
def _showError(self, msg):
|
1009
|
-
showError("Error", msg, self.parent)
|
1012
|
+
def _showError(self, msg, exception=None):
|
1013
|
+
showError("Error", msg, self.parent, exception=exception)
|
1010
1014
|
|
1011
1015
|
def _showWarning(self, msg):
|
1012
1016
|
showWarning("Warning", msg, self.parent)
|
@@ -1015,7 +1019,8 @@ class ParamWidget:
|
|
1015
1019
|
wizClass = self.window.wizards[self.wizParamName]
|
1016
1020
|
wizard = wizClass()
|
1017
1021
|
# wizParamName: form attribute, the wizard object can check from which parameter it was called
|
1018
|
-
# Used into VariableWizard objects (scipion-chem), where input and output parameters used
|
1022
|
+
# Used into VariableWizard objects (scipion-chem), where input and output parameters used
|
1023
|
+
# for each wizard are defined
|
1019
1024
|
self.window.wizParamName = self.wizParamName
|
1020
1025
|
wizard.show(self.window)
|
1021
1026
|
|
@@ -1235,7 +1240,7 @@ class ParamWidget:
|
|
1235
1240
|
label, _ = getPointerLabelAndInfo(pointer, self._protocol.getMapper())
|
1236
1241
|
self._showInfo('*%s* points to *None*' % label)
|
1237
1242
|
else:
|
1238
|
-
viewers = pw.Config.getDomain().findViewers(obj
|
1243
|
+
viewers = pw.Config.getDomain().findViewers(obj, DESKTOP_TKINTER)
|
1239
1244
|
if len(viewers):
|
1240
1245
|
ViewerClass = viewers[0] # Use the first viewer registered
|
1241
1246
|
# Instantiate the viewer and visualize object
|
@@ -1261,7 +1266,7 @@ class ParamWidget:
|
|
1261
1266
|
|
1262
1267
|
def _browseObject(self, e=None):
|
1263
1268
|
"""Select an object from DB
|
1264
|
-
This function is
|
1269
|
+
This function is supposed to be used only for PointerParam"""
|
1265
1270
|
value = self.get()
|
1266
1271
|
selected = []
|
1267
1272
|
if isinstance(value, list):
|
@@ -1322,7 +1327,7 @@ class ParamWidget:
|
|
1322
1327
|
return ("Please select object of types: %s"
|
1323
1328
|
% self.param.paramClass.get())
|
1324
1329
|
|
1325
|
-
title = "Select object of
|
1330
|
+
title = "Select object of type %s" % self.param.paramClass.__name__
|
1326
1331
|
|
1327
1332
|
# Let's ignore conditions so far
|
1328
1333
|
# pointerCond = self.param.pointerCondition.get()
|
@@ -1364,11 +1369,11 @@ class ParamWidget:
|
|
1364
1369
|
selectOnDoubleClick=True)
|
1365
1370
|
if dlg.values:
|
1366
1371
|
self.set(dlg.values[0])
|
1367
|
-
except AttributeError:
|
1372
|
+
except AttributeError as exc:
|
1368
1373
|
self._showError("Error loading possible inputs. "
|
1369
1374
|
"This usually happens because the parameter "
|
1370
1375
|
"needs info from other parameters... are "
|
1371
|
-
"previous mandatory parameters set?")
|
1376
|
+
"previous mandatory parameters set?", exception=exc)
|
1372
1377
|
|
1373
1378
|
def _removeRelation(self, e=None):
|
1374
1379
|
self.var.remove()
|
@@ -1537,7 +1542,8 @@ class FormWindow(Window):
|
|
1537
1542
|
4. Buttons: buttons at bottom for close, save and execute.
|
1538
1543
|
"""
|
1539
1544
|
|
1540
|
-
def __init__(self, title, protocol, callback, master=None, position=None,
|
1545
|
+
def __init__(self, title, protocol:pwprot.Protocol, callback, master=None, position=None,
|
1546
|
+
previousProt:pwprot.Protocol=None, **kwargs):
|
1541
1547
|
""" Constructor of the Form window.
|
1542
1548
|
Params:
|
1543
1549
|
title: title string of the windows.
|
@@ -1557,6 +1563,7 @@ class FormWindow(Window):
|
|
1557
1563
|
self.disableRunMode = kwargs.get('disableRunMode', False)
|
1558
1564
|
self.bindings = []
|
1559
1565
|
self.protocol = protocol
|
1566
|
+
self.previousProt=previousProt
|
1560
1567
|
self.position = position
|
1561
1568
|
# This control when to close or not after execute
|
1562
1569
|
self.visualizeMode = kwargs.get('visualizeMode', False)
|
@@ -1573,6 +1580,25 @@ class FormWindow(Window):
|
|
1573
1580
|
protocol.legacyCheck()
|
1574
1581
|
self._createGUI()
|
1575
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
|
+
|
1576
1602
|
def _createGUI(self):
|
1577
1603
|
mainFrame = tk.Frame(self.root, name="main")
|
1578
1604
|
configureWeigths(mainFrame, row=2)
|
@@ -1684,34 +1710,38 @@ class FormWindow(Window):
|
|
1684
1710
|
allowGpu = prot.allowsGpu()
|
1685
1711
|
numberOfMpi = prot.numberOfMpi.get()
|
1686
1712
|
numberOfThreads = prot.numberOfThreads.get()
|
1687
|
-
mode = prot.stepsExecutionMode
|
1688
1713
|
|
1689
1714
|
if not (allowThreads or allowMpi or allowGpu):
|
1690
1715
|
return
|
1691
1716
|
|
1692
|
-
self._createHeaderLabel(runFrame, pwutils.Message.LABEL_PARALLEL, bold=True,
|
1693
|
-
sticky='e', row=r, pady=0)
|
1694
1717
|
|
1695
1718
|
if allowThreads or allowMpi:
|
1696
|
-
procFrame = tk.Frame(runFrame, bg=pw.Config.SCIPION_BG_COLOR)
|
1697
|
-
r2 = 0
|
1698
|
-
c2 = 0
|
1699
|
-
sticky = 'e'
|
1700
1719
|
|
1701
|
-
|
1720
|
+
threadsParam = self.getParam(pwutils.Message.VAR_THREADS)
|
1721
|
+
mpiParam = self.getParam(pwutils.Message.VAR_MPI)
|
1722
|
+
binThreads = self.getParam("binThreads")
|
1702
1723
|
|
1703
|
-
if
|
1724
|
+
if prot.modeParallel():
|
1704
1725
|
if allowThreads and numberOfThreads > 0:
|
1726
|
+
|
1705
1727
|
prot.numberOfMpi.set(1)
|
1706
|
-
self._createHeaderLabel(
|
1707
|
-
sticky=
|
1708
|
-
pady=0)
|
1709
|
-
entry = self._createBoundEntry(
|
1728
|
+
self._createHeaderLabel(runFrame, threadsParam.getLabel(),
|
1729
|
+
sticky='e', row=r, column=0,
|
1730
|
+
pady=0, bold=True)
|
1731
|
+
entry = self._createBoundEntry(runFrame,
|
1710
1732
|
pwutils.Message.VAR_THREADS)
|
1711
1733
|
|
1712
|
-
|
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
|
1713
1744
|
|
1714
|
-
entry.grid(row=r2, column=c2 + 1, padx=(0, 5), sticky='w')
|
1715
1745
|
elif allowMpi and numberOfMpi > 1:
|
1716
1746
|
self.showError("MPI parameter is deprecated for protocols "
|
1717
1747
|
"with execution is set to STEPS_PARALLEL. "
|
@@ -1721,42 +1751,60 @@ class FormWindow(Window):
|
|
1721
1751
|
"STEPS_PARALLEL number of threads "
|
1722
1752
|
"should not be set to zero.")
|
1723
1753
|
|
1724
|
-
|
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
|
+
|
1725
1769
|
# ---- THREADS----
|
1726
|
-
if allowThreads:
|
1727
|
-
|
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(),
|
1728
1776
|
sticky=sticky, row=r2, column=c2,
|
1729
1777
|
pady=0)
|
1730
1778
|
entry = self._createBoundEntry(procFrame,
|
1731
|
-
|
1779
|
+
attrName)
|
1732
1780
|
entry.grid(row=r2, column=c2 + 1, padx=(0, 5), sticky='w')
|
1733
1781
|
# Modify values to be used in MPI entry
|
1734
1782
|
c2 += 2
|
1735
1783
|
sticky = 'w'
|
1736
1784
|
|
1737
|
-
helpMessage +=
|
1785
|
+
helpMessage += threadsParam.getHelp()
|
1738
1786
|
# ---- MPI ----
|
1739
1787
|
if allowMpi:
|
1740
|
-
self._createHeaderLabel(procFrame,
|
1788
|
+
self._createHeaderLabel(procFrame, mpiParam.getLabel(),
|
1741
1789
|
sticky=sticky, row=r2, column=c2,
|
1742
1790
|
pady=0)
|
1743
1791
|
entry = self._createBoundEntry(procFrame, pwutils.Message.VAR_MPI)
|
1744
1792
|
entry.grid(row=r2, column=c2 + 1, padx=(0, 5), sticky='w')
|
1745
1793
|
|
1746
|
-
helpMessage += '\n' +
|
1794
|
+
helpMessage += '\n' + mpiParam.getHelp()
|
1747
1795
|
|
1748
1796
|
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
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')
|
1755
1803
|
|
1756
|
-
|
1757
|
-
|
1804
|
+
procFrame.columnconfigure(0, minsize=60)
|
1805
|
+
procFrame.grid(row=r, column=1, sticky='ew', columnspan=2)
|
1758
1806
|
|
1759
|
-
|
1807
|
+
r += 1
|
1760
1808
|
|
1761
1809
|
if allowGpu:
|
1762
1810
|
self._createHeaderLabel(runFrame, "GPU IDs", bold=True,
|
@@ -2202,12 +2250,20 @@ class FormWindow(Window):
|
|
2202
2250
|
# to avoid ghost inputs
|
2203
2251
|
self._checkAllChanges(toggleWidgetVisibility=False)
|
2204
2252
|
|
2253
|
+
# This may be viewprotocols.py.ProtocolsView._executeSaveProtocol
|
2254
|
+
# Message is either a confirmation that the protocol has been saves or empty message
|
2205
2255
|
message = self.callback(self.protocol, onlySave, doSchedule, position=self.position)
|
2206
2256
|
if not self.visualizeMode:
|
2257
|
+
# If there is a message
|
2207
2258
|
if len(message):
|
2208
|
-
|
2209
|
-
|
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:
|
2210
2265
|
self.close()
|
2266
|
+
|
2211
2267
|
except ModificationNotAllowedException as ex:
|
2212
2268
|
self.showInfo("Modification not allowed.\n\n %s\n" % ex)
|
2213
2269
|
except Exception as ex:
|
@@ -2217,11 +2273,20 @@ class FormWindow(Window):
|
|
2217
2273
|
self.showError("Error during %s: \n%s" % (action, ex), exception=ex)
|
2218
2274
|
|
2219
2275
|
def getWidgetValue(self, protVar, param):
|
2220
|
-
|
2276
|
+
""" Returns the value for the widget"""
|
2277
|
+
|
2278
|
+
widgetValue = protVar
|
2221
2279
|
if (isinstance(param, pwprot.PointerParam) or
|
2222
2280
|
isinstance(param, pwprot.MultiPointerParam) or
|
2223
2281
|
isinstance(param, pwprot.RelationParam)):
|
2224
|
-
|
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
|
+
|
2225
2290
|
# For Scalar params that allowPointers
|
2226
2291
|
elif param.allowsPointers:
|
2227
2292
|
if protVar.hasPointer():
|
@@ -2233,11 +2298,33 @@ class FormWindow(Window):
|
|
2233
2298
|
widgetValue = protVar.get(param.default.get())
|
2234
2299
|
return widgetValue
|
2235
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
|
2236
2323
|
def _visualize(self, paramName):
|
2237
2324
|
protVar = getattr(self.protocol, paramName)
|
2238
2325
|
if protVar.hasValue():
|
2239
2326
|
obj = protVar.get() # Get the reference to the object
|
2240
|
-
viewers = pw.Config.getDomain().findViewers(obj
|
2327
|
+
viewers = pw.Config.getDomain().findViewers(obj, DESKTOP_TKINTER)
|
2241
2328
|
if len(viewers):
|
2242
2329
|
ViewerClass = viewers[0] # Use the first viewer registered
|
2243
2330
|
v = ViewerClass(project=self.protocol.getProject(),
|
@@ -2259,6 +2346,8 @@ class FormWindow(Window):
|
|
2259
2346
|
widget = LineWidget(r, paramName, param, self, parent, None)
|
2260
2347
|
self._fillLine(param, widget)
|
2261
2348
|
else:
|
2349
|
+
|
2350
|
+
# Attribute of the protocol
|
2262
2351
|
protVar = getattr(self.protocol, paramName, None)
|
2263
2352
|
|
2264
2353
|
if protVar is None:
|
pyworkflow/gui/gui.py
CHANGED
@@ -133,7 +133,7 @@ def getBigFont():
|
|
133
133
|
return getNamedFont(FONT_BIG)
|
134
134
|
|
135
135
|
|
136
|
-
def setCommonFonts(
|
136
|
+
def setCommonFonts(window=None):
|
137
137
|
"""Set some predefined common fonts.
|
138
138
|
Same conditions of setFont applies here."""
|
139
139
|
f = setFont(FONT_NORMAL, family=pw.Config.SCIPION_FONT_NAME, size=pw.Config.SCIPION_FONT_SIZE)
|
@@ -150,17 +150,17 @@ def setCommonFonts(windows=None):
|
|
150
150
|
|
151
151
|
setFont(FONT_BIG, family=pw.Config.SCIPION_FONT_NAME, size=pw.Config.SCIPION_FONT_SIZE+8)
|
152
152
|
|
153
|
-
if
|
154
|
-
|
153
|
+
if window:
|
154
|
+
window.fontBig = tkFont.Font(size=pw.Config.SCIPION_FONT_SIZE + 2, family=pw.Config.SCIPION_FONT_NAME,
|
155
155
|
weight='bold')
|
156
|
-
|
157
|
-
|
158
|
-
|
156
|
+
window.font = f
|
157
|
+
window.fontBold = fb
|
158
|
+
window.fontItalic = fi
|
159
159
|
|
160
160
|
# This adds the default value for the listbox inside a combo box
|
161
161
|
# Which seems to not react to default font!!
|
162
|
-
|
163
|
-
|
162
|
+
window.root.option_add("*TCombobox*Listbox*Font", default_font)
|
163
|
+
window.root.option_add("*TCombobox*Font", default_font)
|
164
164
|
|
165
165
|
|
166
166
|
def changeFontSizeByDeltha(font, deltha, minSize=-999, maxSize=999):
|
@@ -136,8 +136,6 @@ class ProjectWindow(ProjectBaseWindow):
|
|
136
136
|
|
137
137
|
self.initProjectTCPServer() # Socket thread to communicate with clients
|
138
138
|
|
139
|
-
ProjectWorkflowNotifier(self.project).notifyWorkflow()
|
140
|
-
|
141
139
|
|
142
140
|
def createHeaderFrame(self, parent):
|
143
141
|
"""Create the header and add the view selection frame at the right."""
|
@@ -175,6 +173,9 @@ class ProjectWindow(ProjectBaseWindow):
|
|
175
173
|
def _onClosing(self):
|
176
174
|
if not self.project.openedAsReadOnly():
|
177
175
|
self.saveSettings()
|
176
|
+
# Send usage stats when closing scipion.
|
177
|
+
ProjectWorkflowNotifier(self.project).notifyWorkflow()
|
178
|
+
|
178
179
|
|
179
180
|
ProjectBaseWindow._onClosing(self)
|
180
181
|
|
@@ -243,11 +244,9 @@ class ProjectWindow(ProjectBaseWindow):
|
|
243
244
|
try:
|
244
245
|
for fname in os.listdir(tmpPath):
|
245
246
|
fpath = "%s/%s" % (tmpPath, fname)
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
# TODO: think what to do with directories. Delete? Report?
|
250
|
-
self.showInfo("Deleted content of %s -- %d file(s)." % (tmpPath, n))
|
247
|
+
pwutils.cleanPath(fpath)
|
248
|
+
n += 1
|
249
|
+
self.showInfo("Deleted content of %s -- %d files or folders." % (tmpPath, n))
|
251
250
|
except Exception as e:
|
252
251
|
self.showError(str(e))
|
253
252
|
|
@@ -22,16 +22,17 @@
|
|
22
22
|
# * e-mail address 'scipion@cnb.csic.es'
|
23
23
|
# *
|
24
24
|
# **************************************************************************
|
25
|
-
""" This
|
25
|
+
""" This module contains the provider and dialog to search for a protocol"""
|
26
26
|
import tkinter as tk
|
27
|
-
|
28
27
|
from pyworkflow import Config
|
29
|
-
import
|
28
|
+
import pyworkflow.gui as pwgui
|
30
29
|
import pyworkflow.object as pwobj
|
31
30
|
from pyworkflow.gui.dialog import SearchBaseWindow
|
32
31
|
|
33
32
|
from pyworkflow.gui.project.utils import isAFinalProtocol
|
34
33
|
from pyworkflow.gui.project.viewprotocols_extra import ProtocolTreeConfig
|
34
|
+
from pyworkflow.project.usage import getNextProtocolSuggestions
|
35
|
+
from pyworkflow.utils import Icon
|
35
36
|
|
36
37
|
UPDATED = "updated"
|
37
38
|
|
@@ -63,22 +64,60 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
63
64
|
'#0': ('Status', {'width': 50, 'minwidth': 50, 'stretch': tk.NO}, 5),
|
64
65
|
# Heading, tree column kwargs, casting for sorting
|
65
66
|
'protocol': ('Protocol', {'width': 300, 'stretch': tk.FALSE}, 10),
|
67
|
+
'score': ('Score/Freq.', {'width': 50, 'stretch': tk.FALSE}, 5, int),
|
66
68
|
'streaming': ('Streamified', {'width': 100, 'stretch': tk.FALSE}, 5),
|
67
69
|
'installed': ('Installation', {'width': 110, 'stretch': tk.FALSE}, 5),
|
68
70
|
'help': ('Help', {'minwidth': 300, 'stretch': tk.YES}, 5),
|
69
|
-
'score': ('Score', {'width': 50, 'stretch': tk.FALSE}, 5, int)
|
70
71
|
}
|
71
72
|
|
72
|
-
def __init__(self, parentWindow, position=None):
|
73
|
+
def __init__(self, parentWindow, position=None, selectionGetter=None):
|
73
74
|
|
74
75
|
posStr = "" if position is None else " at (%s,%s)" % position
|
75
76
|
self.position = position
|
77
|
+
self.selectionGetter = selectionGetter
|
78
|
+
self.selectedProtocol = None
|
79
|
+
self._infoLbl = None # Label to show information
|
76
80
|
super().__init__(parentWindow,
|
77
81
|
title="Add a protocol" + posStr)
|
78
82
|
|
83
|
+
self.root.bind("<FocusIn>", self._onWindowFocusIn)
|
84
|
+
|
85
|
+
def _onWindowFocusIn(self, event):
|
86
|
+
"""
|
87
|
+
To refresh the selected protocol in the graph upon window activation.
|
88
|
+
:param event: event information
|
89
|
+
:return: Nothing
|
90
|
+
"""
|
91
|
+
if event.widget == self.root and self.selectionGetter:
|
92
|
+
self.selectedProtocol = self.selectionGetter()
|
93
|
+
if self._isSuggestionActive():
|
94
|
+
self._onSearchClick()
|
95
|
+
def _isSuggestionActive(self):
|
96
|
+
"""
|
97
|
+
:return: Returns true if current mode is suggestion mode.
|
98
|
+
"""
|
99
|
+
return self._searchVar.get().lower().strip() ==""
|
100
|
+
|
101
|
+
def _createSearchBox(self, content):
|
102
|
+
frame = super()._createSearchBox(content)
|
103
|
+
|
104
|
+
btn = pwgui.widgets.IconButton(frame, "Suggest",
|
105
|
+
tooltip="Suggestions for active protocol based on usage.",
|
106
|
+
imagePath=Icon.LIGHTBULB,
|
107
|
+
command=self.showSuggestions)
|
108
|
+
btn.grid(row=0, column=3, sticky='nw')
|
109
|
+
|
110
|
+
self.lbl = tk.StringVar()
|
111
|
+
lbl = tk.Label(frame, text="", bg=Config.SCIPION_BG_COLOR, textvariable=self.lbl, font=self.font)
|
112
|
+
lbl.grid(row=0, column=4, sticky='news')
|
113
|
+
|
79
114
|
def _createResultsTree(self, frame, show, columns):
|
115
|
+
# This code is where the callback (on double click) is defined.
|
80
116
|
return self.master.getViewWidget()._createProtocolsTree(frame, show=show, columns=columns, position=self.position)
|
81
117
|
|
118
|
+
def showSuggestions(self, e=None):
|
119
|
+
self._searchVar.set("")
|
120
|
+
self._onSearchClick()
|
82
121
|
def _onSearchClick(self, e=None):
|
83
122
|
|
84
123
|
self._resultsTree.clear()
|
@@ -92,7 +131,12 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
92
131
|
|
93
132
|
def scoreProtocols(self):
|
94
133
|
|
134
|
+
if self._isSuggestionActive():
|
135
|
+
return self.addSuggestions()
|
136
|
+
|
95
137
|
keyword = self._searchVar.get().lower().strip()
|
138
|
+
self.lbl.set('Showing text search matches for "%s"' % keyword)
|
139
|
+
|
96
140
|
emProtocolsDict = Config.getDomain().getProtocols()
|
97
141
|
protList = []
|
98
142
|
|
@@ -114,6 +158,46 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
114
158
|
|
115
159
|
return protList
|
116
160
|
|
161
|
+
def addSuggestions(self):
|
162
|
+
|
163
|
+
if self.selectedProtocol is None:
|
164
|
+
self.lbl.set("Showing suggestions for a first protocol")
|
165
|
+
protName =str(None)
|
166
|
+
else:
|
167
|
+
protName = self.selectedProtocol.getClassName()
|
168
|
+
self.lbl.set("Usage suggestions for selected protocol: %s" % self.selectedProtocol.getClassLabel())
|
169
|
+
|
170
|
+
protList = []
|
171
|
+
suggestions = getNextProtocolSuggestions(protName)
|
172
|
+
for suggestion in suggestions:
|
173
|
+
#Fields comming from the site:
|
174
|
+
# https://scipion.i2pc.es/report_protocols/api/v2/nextprotocol/suggestion/None/
|
175
|
+
# 'next_protocol__name', 'count', 'next_protocol__friendlyName', 'next_protocol__package', 'next_protocol__description'
|
176
|
+
nextProtName, count, name, package, descr = suggestion
|
177
|
+
streamstate = "unknown"
|
178
|
+
installed = "Missing. Available in %s plugin." % package
|
179
|
+
protClass = Config.getDomain().getProtocols().get(nextProtName, None)
|
180
|
+
|
181
|
+
# Get accurate values from existing installations
|
182
|
+
if protClass is not None:
|
183
|
+
name = protClass.getClassLabel().lower()
|
184
|
+
descr = protClass.getHelpText().strip().replace('\r', '').replace('\n', '').lower()
|
185
|
+
streamstate = "streamified" if protClass.worksInStreaming() else "static"
|
186
|
+
installed = "installed" if protClass.isInstalled() else "missing installation"
|
187
|
+
|
188
|
+
line = (nextProtName, name,
|
189
|
+
installed,
|
190
|
+
descr,
|
191
|
+
streamstate,
|
192
|
+
"",
|
193
|
+
"",
|
194
|
+
"",
|
195
|
+
count)
|
196
|
+
|
197
|
+
protList.append(line)
|
198
|
+
|
199
|
+
return protList
|
200
|
+
|
117
201
|
@staticmethod
|
118
202
|
def _addSearchWeight(line2Search, searchtext):
|
119
203
|
# Adds a weight value for the search
|
@@ -139,7 +223,7 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
139
223
|
def _addProtocolToTree(self, protList):
|
140
224
|
""" Adds the items in protList to the tree
|
141
225
|
|
142
|
-
:param protList: List of tuples with all the values/columns used in
|
226
|
+
:param protList: List of tuples with all the values/columns used in search and shown in the tree"""
|
143
227
|
|
144
228
|
for key, label, installed, help, streamified, beta, new, updated, weight in protList:
|
145
229
|
tag = ProtocolTreeConfig.getProtocolTag(installed == 'installed',
|
@@ -149,6 +233,6 @@ class SearchProtocolWindow(SearchBaseWindow):
|
|
149
233
|
|
150
234
|
self._resultsTree.insert(
|
151
235
|
'', 'end', key, text="", tags=tag,
|
152
|
-
values=(label, streamified, installed, help
|
236
|
+
values=(label, weight, streamified, installed, help))
|
153
237
|
|
154
238
|
|
@@ -381,7 +381,7 @@ class ProjectDataView(tk.Frame):
|
|
381
381
|
def _viewObject(self, objId):
|
382
382
|
""" Call appropriate viewer for objId. """
|
383
383
|
obj = self.project.getObject(int(objId))
|
384
|
-
viewerClasses = self.domain.findViewers(obj
|
384
|
+
viewerClasses = self.domain.findViewers(obj,
|
385
385
|
pwviewer.DESKTOP_TKINTER)
|
386
386
|
if not viewerClasses:
|
387
387
|
return # TODO: protest nicely
|