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.
Files changed (57) hide show
  1. pyworkflow/config.py +131 -67
  2. pyworkflow/constants.py +2 -1
  3. pyworkflow/gui/browser.py +39 -5
  4. pyworkflow/gui/dialog.py +2 -0
  5. pyworkflow/gui/form.py +141 -52
  6. pyworkflow/gui/gui.py +8 -8
  7. pyworkflow/gui/project/project.py +6 -7
  8. pyworkflow/gui/project/searchprotocol.py +91 -7
  9. pyworkflow/gui/project/viewdata.py +1 -1
  10. pyworkflow/gui/project/viewprotocols.py +45 -22
  11. pyworkflow/gui/project/viewprotocols_extra.py +9 -6
  12. pyworkflow/gui/widgets.py +2 -2
  13. pyworkflow/mapper/sqlite.py +4 -4
  14. pyworkflow/plugin.py +93 -44
  15. pyworkflow/project/project.py +158 -70
  16. pyworkflow/project/usage.py +165 -0
  17. pyworkflow/protocol/executor.py +30 -18
  18. pyworkflow/protocol/hosts.py +9 -6
  19. pyworkflow/protocol/launch.py +15 -8
  20. pyworkflow/protocol/params.py +59 -19
  21. pyworkflow/protocol/protocol.py +124 -58
  22. pyworkflow/resources/showj/arrowDown.png +0 -0
  23. pyworkflow/resources/showj/arrowUp.png +0 -0
  24. pyworkflow/resources/showj/background_section.png +0 -0
  25. pyworkflow/resources/showj/colRowModeOff.png +0 -0
  26. pyworkflow/resources/showj/colRowModeOn.png +0 -0
  27. pyworkflow/resources/showj/delete.png +0 -0
  28. pyworkflow/resources/showj/doc_icon.png +0 -0
  29. pyworkflow/resources/showj/download_icon.png +0 -0
  30. pyworkflow/resources/showj/enabled_gallery.png +0 -0
  31. pyworkflow/resources/showj/galleryViewOff.png +0 -0
  32. pyworkflow/resources/showj/galleryViewOn.png +0 -0
  33. pyworkflow/resources/showj/goto.png +0 -0
  34. pyworkflow/resources/showj/menu.png +0 -0
  35. pyworkflow/resources/showj/separator.png +0 -0
  36. pyworkflow/resources/showj/tableViewOff.png +0 -0
  37. pyworkflow/resources/showj/tableViewOn.png +0 -0
  38. pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  39. pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  40. pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  41. pyworkflow/resources/showj/volumeOff.png +0 -0
  42. pyworkflow/resources/showj/volumeOn.png +0 -0
  43. pyworkflow/utils/log.py +15 -6
  44. pyworkflow/utils/properties.py +78 -92
  45. pyworkflow/utils/utils.py +3 -2
  46. pyworkflow/viewer.py +23 -1
  47. pyworkflow/webservices/config.py +0 -3
  48. pyworkflow/webservices/notifier.py +24 -34
  49. pyworkflowtests/protocols.py +1 -3
  50. pyworkflowtests/tests/test_protocol_execution.py +4 -0
  51. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/METADATA +13 -27
  52. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/RECORD +56 -35
  53. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/WHEEL +1 -1
  54. scipion_pyworkflow-3.10.6.dist-info/dependency_links.txt +0 -1
  55. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/entry_points.txt +0 -0
  56. {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.1.dist-info}/licenses/LICENSE.txt +0 -0
  57. {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.getClassName(), DESKTOP_TKINTER)
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
- actions.append((v.getName(),
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 for each wizard are defined
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.getClassName(), DESKTOP_TKINTER)
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 suppose to be used only for PointerParam"""
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 types: %s" % self.param.paramClass.__name__
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, **kwargs):
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
- helpMessage = pwutils.Message.HELP_PARALLEL_HEADER
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 mode == pwprot.STEPS_PARALLEL:
1724
+ if prot.modeParallel():
1704
1725
  if allowThreads and numberOfThreads > 0:
1726
+
1705
1727
  prot.numberOfMpi.set(1)
1706
- self._createHeaderLabel(procFrame, pwutils.Message.LABEL_SCIPION_THREADS,
1707
- sticky=sticky, row=r2, column=c2,
1708
- pady=0)
1709
- entry = self._createBoundEntry(procFrame,
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
- helpMessage += pwutils.Message.HELP_SCIPION_THREADS
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
- else:
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
- self._createHeaderLabel(procFrame, pwutils.Message.LABEL_THREADS,
1770
+ if binThreads or allowThreads:
1771
+ attrName = pwutils.Message.VAR_THREADS
1772
+ if binThreads:
1773
+ threadsParam = binThreads
1774
+ attrName = "binThreads"
1775
+ self._createHeaderLabel(procFrame, threadsParam.getLabel(),
1728
1776
  sticky=sticky, row=r2, column=c2,
1729
1777
  pady=0)
1730
1778
  entry = self._createBoundEntry(procFrame,
1731
- pwutils.Message.VAR_THREADS)
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 += pwutils.Message.HELP_PARALLEL_THREADS
1785
+ helpMessage += threadsParam.getHelp()
1738
1786
  # ---- MPI ----
1739
1787
  if allowMpi:
1740
- self._createHeaderLabel(procFrame, pwutils.Message.LABEL_MPI,
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' + pwutils.Message.HELP_PARALLEL_MPI
1794
+ helpMessage += '\n' + mpiParam.getHelp()
1747
1795
 
1748
1796
 
1749
- btnHelp = IconButton(procFrame, pwutils.Message.TITLE_COMMENT,
1750
- pwutils.Icon.ACTION_HELP,
1751
- highlightthickness=0,
1752
- command=self._createHelpCommand(
1753
- helpMessage))
1754
- btnHelp.grid(row=0, column=4, padx=(5, 0), pady=2, sticky='e')
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
- procFrame.columnconfigure(0, minsize=60)
1757
- procFrame.grid(row=r, column=1, sticky='ew', columnspan=2)
1804
+ procFrame.columnconfigure(0, minsize=60)
1805
+ procFrame.grid(row=r, column=1, sticky='ew', columnspan=2)
1758
1806
 
1759
- r += 1
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
- self.showInfo(message, "Protocol action")
2209
- if not onlySave:
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
- widgetValue = ""
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
- widgetValue = protVar
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.getClassName(), DESKTOP_TKINTER)
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(windows=None):
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 windows:
154
- windows.fontBig = tkFont.Font(size=pw.Config.SCIPION_FONT_SIZE + 2, family=pw.Config.SCIPION_FONT_NAME,
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
- windows.font = f
157
- windows.fontBold = fb
158
- windows.fontItalic = fi
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
- windows.root.option_add("*TCombobox*Listbox*Font", default_font)
163
- windows.root.option_add("*TCombobox*Font", default_font)
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
- if os.path.isfile(fpath):
247
- os.remove(fpath)
248
- n += 1
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 modules hosts code provider and window to search for a protocol"""
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 pyworkflow.gui as pwgui
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 the search search to show in the tree"""
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, weight))
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.getClassName(),
384
+ viewerClasses = self.domain.findViewers(obj,
385
385
  pwviewer.DESKTOP_TKINTER)
386
386
  if not viewerClasses:
387
387
  return # TODO: protest nicely