scipion-pyworkflow 3.11.0__py3-none-any.whl → 3.11.2__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 (104) hide show
  1. pyworkflow/apps/__init__.py +29 -0
  2. pyworkflow/apps/pw_manager.py +37 -0
  3. pyworkflow/apps/pw_plot.py +51 -0
  4. pyworkflow/apps/pw_project.py +130 -0
  5. pyworkflow/apps/pw_protocol_list.py +143 -0
  6. pyworkflow/apps/pw_protocol_run.py +51 -0
  7. pyworkflow/apps/pw_run_tests.py +268 -0
  8. pyworkflow/apps/pw_schedule_run.py +322 -0
  9. pyworkflow/apps/pw_sleep.py +37 -0
  10. pyworkflow/apps/pw_sync_data.py +440 -0
  11. pyworkflow/apps/pw_viewer.py +78 -0
  12. pyworkflow/constants.py +1 -1
  13. pyworkflow/gui/__init__.py +36 -0
  14. pyworkflow/gui/browser.py +768 -0
  15. pyworkflow/gui/canvas.py +1190 -0
  16. pyworkflow/gui/dialog.py +981 -0
  17. pyworkflow/gui/form.py +2727 -0
  18. pyworkflow/gui/graph.py +247 -0
  19. pyworkflow/gui/graph_layout.py +271 -0
  20. pyworkflow/gui/gui.py +571 -0
  21. pyworkflow/gui/matplotlib_image.py +233 -0
  22. pyworkflow/gui/plotter.py +247 -0
  23. pyworkflow/gui/project/__init__.py +25 -0
  24. pyworkflow/gui/project/base.py +193 -0
  25. pyworkflow/gui/project/constants.py +139 -0
  26. pyworkflow/gui/project/labels.py +205 -0
  27. pyworkflow/gui/project/project.py +491 -0
  28. pyworkflow/gui/project/searchprotocol.py +240 -0
  29. pyworkflow/gui/project/searchrun.py +181 -0
  30. pyworkflow/gui/project/steps.py +171 -0
  31. pyworkflow/gui/project/utils.py +332 -0
  32. pyworkflow/gui/project/variables.py +179 -0
  33. pyworkflow/gui/project/viewdata.py +472 -0
  34. pyworkflow/gui/project/viewprojects.py +519 -0
  35. pyworkflow/gui/project/viewprotocols.py +2141 -0
  36. pyworkflow/gui/project/viewprotocols_extra.py +562 -0
  37. pyworkflow/gui/text.py +774 -0
  38. pyworkflow/gui/tooltip.py +185 -0
  39. pyworkflow/gui/tree.py +684 -0
  40. pyworkflow/gui/widgets.py +307 -0
  41. pyworkflow/mapper/__init__.py +26 -0
  42. pyworkflow/mapper/mapper.py +226 -0
  43. pyworkflow/mapper/sqlite.py +1583 -0
  44. pyworkflow/mapper/sqlite_db.py +145 -0
  45. pyworkflow/object.py +1 -0
  46. pyworkflow/plugin.py +4 -4
  47. pyworkflow/project/__init__.py +31 -0
  48. pyworkflow/project/config.py +454 -0
  49. pyworkflow/project/manager.py +180 -0
  50. pyworkflow/project/project.py +2095 -0
  51. pyworkflow/project/usage.py +165 -0
  52. pyworkflow/protocol/__init__.py +38 -0
  53. pyworkflow/protocol/bibtex.py +48 -0
  54. pyworkflow/protocol/constants.py +87 -0
  55. pyworkflow/protocol/executor.py +515 -0
  56. pyworkflow/protocol/hosts.py +318 -0
  57. pyworkflow/protocol/launch.py +277 -0
  58. pyworkflow/protocol/package.py +42 -0
  59. pyworkflow/protocol/params.py +781 -0
  60. pyworkflow/protocol/protocol.py +2712 -0
  61. pyworkflow/resources/protlabels.xcf +0 -0
  62. pyworkflow/resources/sprites.png +0 -0
  63. pyworkflow/resources/sprites.xcf +0 -0
  64. pyworkflow/template.py +1 -1
  65. pyworkflow/tests/__init__.py +29 -0
  66. pyworkflow/tests/test_utils.py +25 -0
  67. pyworkflow/tests/tests.py +342 -0
  68. pyworkflow/utils/__init__.py +38 -0
  69. pyworkflow/utils/dataset.py +414 -0
  70. pyworkflow/utils/echo.py +104 -0
  71. pyworkflow/utils/graph.py +169 -0
  72. pyworkflow/utils/log.py +293 -0
  73. pyworkflow/utils/path.py +528 -0
  74. pyworkflow/utils/process.py +154 -0
  75. pyworkflow/utils/profiler.py +92 -0
  76. pyworkflow/utils/progressbar.py +154 -0
  77. pyworkflow/utils/properties.py +618 -0
  78. pyworkflow/utils/reflection.py +129 -0
  79. pyworkflow/utils/utils.py +880 -0
  80. pyworkflow/utils/which.py +229 -0
  81. pyworkflow/webservices/__init__.py +8 -0
  82. pyworkflow/webservices/config.py +8 -0
  83. pyworkflow/webservices/notifier.py +152 -0
  84. pyworkflow/webservices/repository.py +59 -0
  85. pyworkflow/webservices/workflowhub.py +86 -0
  86. pyworkflowtests/tests/__init__.py +0 -0
  87. pyworkflowtests/tests/test_canvas.py +72 -0
  88. pyworkflowtests/tests/test_domain.py +45 -0
  89. pyworkflowtests/tests/test_logs.py +74 -0
  90. pyworkflowtests/tests/test_mappers.py +392 -0
  91. pyworkflowtests/tests/test_object.py +507 -0
  92. pyworkflowtests/tests/test_project.py +42 -0
  93. pyworkflowtests/tests/test_protocol_execution.py +146 -0
  94. pyworkflowtests/tests/test_protocol_export.py +78 -0
  95. pyworkflowtests/tests/test_protocol_output.py +158 -0
  96. pyworkflowtests/tests/test_streaming.py +47 -0
  97. pyworkflowtests/tests/test_utils.py +210 -0
  98. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/METADATA +2 -2
  99. scipion_pyworkflow-3.11.2.dist-info/RECORD +162 -0
  100. scipion_pyworkflow-3.11.0.dist-info/RECORD +0 -71
  101. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/WHEEL +0 -0
  102. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/entry_points.txt +0 -0
  103. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/licenses/LICENSE.txt +0 -0
  104. {scipion_pyworkflow-3.11.0.dist-info → scipion_pyworkflow-3.11.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,145 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
4
+ # *
5
+ # * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
6
+ # *
7
+ # * This program is free software; you can redistribute it and/or modify
8
+ # * it under the terms of the GNU General Public License as published by
9
+ # * the Free Software Foundation; either version 3 of the License, or
10
+ # * (at your option) any later version.
11
+ # *
12
+ # * This program is distributed in the hope that it will be useful,
13
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # * GNU General Public License for more details.
16
+ # *
17
+ # * You should have received a copy of the GNU General Public License
18
+ # * along with this program; if not, write to the Free Software
19
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20
+ # * 02111-1307 USA
21
+ # *
22
+ # * All comments concerning this program package may be sent to the
23
+ # * e-mail address 'scipion@cnb.csic.es'
24
+ # *
25
+ # **************************************************************************
26
+
27
+
28
+ """
29
+ This module contains some sqlite basic tools to handle Databases.
30
+ """
31
+
32
+ import logging
33
+ logger = logging.getLogger(__name__)
34
+ from sqlite3 import dbapi2 as sqlite
35
+ from sqlite3 import OperationalError as OperationalError
36
+ from pyworkflow.utils import STATUS, getExtraLogInfo, Config
37
+
38
+
39
+ class SqliteDb:
40
+ """Class to handle a Sqlite database.
41
+ It will create connection, execute queries and commands.
42
+ """
43
+ OPEN_CONNECTIONS = {} # Store all connections made
44
+
45
+ def __init__(self):
46
+ self._reuseConnections = False
47
+
48
+ def _createConnection(self, dbName, timeout):
49
+ """Establish db connection"""
50
+ self._dbName = dbName
51
+ if self._reuseConnections and dbName in self.OPEN_CONNECTIONS:
52
+ self.connection = self.OPEN_CONNECTIONS[dbName]
53
+ else:
54
+ # self.closeConnection(dbName) # Close the connect if exists for this db
55
+ self.connection = sqlite.Connection(dbName, timeout, check_same_thread=False)
56
+ self.connection.row_factory = sqlite.Row
57
+ self.OPEN_CONNECTIONS[dbName] = self.connection
58
+ logger.debug("Connection open for %s" % dbName, extra=getExtraLogInfo(
59
+ "CONNECTIONS",
60
+ STATUS.START,
61
+ dbfilename=dbName))
62
+
63
+ self.cursor = self.connection.cursor()
64
+ # Define some shortcuts functions
65
+ if Config.debugSQLOn():
66
+ self.executeCommand = self._debugExecute
67
+ else:
68
+ self.executeCommand = self.cursor.execute
69
+ self.commit = self.connection.commit
70
+
71
+ @classmethod
72
+ def closeConnection(cls, dbName):
73
+ if dbName in cls.OPEN_CONNECTIONS:
74
+ connection = cls.OPEN_CONNECTIONS[dbName]
75
+ del cls.OPEN_CONNECTIONS[dbName]
76
+ connection.close()
77
+ logger.debug("Connection closed for %s" % dbName,
78
+ extra=getExtraLogInfo('CONNECTIONS', STATUS.STOP, dbfilename=dbName))
79
+
80
+ def getDbName(self):
81
+ return self._dbName
82
+
83
+ def close(self):
84
+ self.connection.close()
85
+ logger.debug("Connection closed for %s" % self._dbName,
86
+ extra=getExtraLogInfo(
87
+ "CONNECTIONS",
88
+ STATUS.STOP,
89
+ dbfilename=self._dbName))
90
+ if self._dbName in self.OPEN_CONNECTIONS:
91
+ del self.OPEN_CONNECTIONS[self._dbName]
92
+
93
+ def _debugExecute(self, *args):
94
+ try:
95
+ logger.debug("COMMAND: %s; %s" %(args[0] , self._dbName),
96
+ extra=getExtraLogInfo("QUERY", STATUS.EVENT, dbfilename=self._dbName)
97
+ )
98
+ logger.debug("ARGUMENTS: " + str(args[1:]))
99
+ return self.cursor.execute(*args)
100
+ except Exception as ex:
101
+ print(">>>> FAILED cursor.execute on db: '%s'" % self._dbName)
102
+ raise ex
103
+
104
+ def _iterResults(self):
105
+ row = self.cursor.fetchone()
106
+ while row is not None:
107
+ yield row
108
+ row = self.cursor.fetchone()
109
+
110
+ def _results(self, iterate=False):
111
+ """ Return the results to which cursor, point to.
112
+ If iterates=True, iterate yielding each result independently"""
113
+ if not iterate:
114
+ return self.cursor.fetchall()
115
+ else:
116
+ return self._iterResults()
117
+
118
+ def getTables(self, tablePattern=None):
119
+ """ Return the table names existing in the Database.
120
+ If tablePattern is not None, only tables matching
121
+ the pattern will be returned.
122
+ """
123
+ self.executeCommand("SELECT name FROM sqlite_master "
124
+ "WHERE type='table' "
125
+ "AND name NOT LIKE 'sqlite_%';")
126
+ return [str(row['name']) for row in self._iterResults()]
127
+
128
+ def hasTable(self, tableName):
129
+ return tableName in self.getTables()
130
+
131
+ def getTableColumns(self, tableName):
132
+ self.executeCommand('PRAGMA table_info(%s)' % tableName)
133
+ return self.cursor.fetchall()
134
+
135
+ def getVersion(self):
136
+ """ Return the database 'version' that is used.
137
+ Internally it make use of the SQLite PRAGMA database.user_version;
138
+ """
139
+ self.executeCommand('PRAGMA user_version')
140
+ return self.cursor.fetchone()[0]
141
+
142
+ def setVersion(self, version):
143
+ self.executeCommand('PRAGMA user_version=%d' % version)
144
+ self.commit()
145
+
pyworkflow/object.py CHANGED
@@ -1344,6 +1344,7 @@ class Set(Object):
1344
1344
  else:
1345
1345
  self._idCount = max(self._idCount, item.getObjId())
1346
1346
  self._insertItem(item)
1347
+ # FIXME: Incrementing always!! When updating this is wrong.
1347
1348
  self._size.increment()
1348
1349
 
1349
1350
  def _insertItem(self, item):
pyworkflow/plugin.py CHANGED
@@ -410,11 +410,11 @@ class Domain:
410
410
  viewerClassName,
411
411
  doRaise=True)
412
412
  viewers.append(prefViewer)
413
- except Exception as e:
414
- logger.error("Couldn't load \"%s\" as preferred viewer for %s.\n"
413
+ except Exception:
414
+ logger.info("Couldn't load \"%s\" as preferred viewer for %s.\n"
415
415
  "There might be a typo in your VIEWERS variable "
416
- "or an error in the viewer's plugin installation"
417
- % (prefViewerStr, className), exc_info=e)
416
+ "or an error in the viewer's plugin installation or simply the plugin is not installed."
417
+ % (prefViewerStr, target))
418
418
 
419
419
  cls._preferred_viewers[target] = viewers
420
420
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python
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, write to the Free Software
20
+ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21
+ # * 02111-1307 USA
22
+ # *
23
+ # * All comments concerning this program package may be sent to the
24
+ # * e-mail address 'scipion@cnb.csic.es'
25
+ # *
26
+ # **************************************************************************
27
+
28
+ from .project import Project, MissingProjectDbException
29
+ from .manager import Manager, ProjectInfo
30
+ from .config import (ProjectSettings, MenuConfig,
31
+ NodeConfig, NodeConfigList, Label, LabelsList)
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # **************************************************************************
4
+ # *
5
+ # * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
6
+ # *
7
+ # * [1] SciLifeLab, Stockholm University
8
+ # *
9
+ # * This program is free software: you can redistribute it and/or modify
10
+ # * it under the terms of the GNU General Public License as published by
11
+ # * the Free Software Foundation, either version 3 of the License, or
12
+ # * (at your option) any later version.
13
+ # *
14
+ # * This program is distributed in the hope that it will be useful,
15
+ # * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # * GNU General Public License for more details.
18
+ # *
19
+ # * You should have received a copy of the GNU General Public License
20
+ # * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
+ # *
22
+ # * All comments concerning this program package may be sent to the
23
+ # * e-mail address 'scipion@cnb.csic.es'
24
+ # *
25
+ # **************************************************************************
26
+
27
+ import logging
28
+ logger = logging.getLogger(__name__)
29
+ import json
30
+ import datetime as dt
31
+
32
+ import pyworkflow.object as pwobj
33
+ from pyworkflow.mapper import SqliteMapper
34
+
35
+
36
+ class ProjectSettings(pwobj.Object):
37
+ """ Store settings related to a project. """
38
+
39
+ COLOR_MODE_STATUS = 0
40
+ COLOR_MODE_LABELS = 1
41
+ COLOR_MODE_AGE = 2
42
+ COLOR_MODE_SIZE = 3
43
+ COLOR_MODES = (COLOR_MODE_STATUS, COLOR_MODE_LABELS, COLOR_MODE_AGE) # This has poor performance many cases, COLOR_MODE_SIZE)
44
+
45
+ def __init__(self, confs={}, **kwargs):
46
+ super().__init__(**kwargs)
47
+ # Store the current view selected by the user
48
+ self.currentProtocolsView = pwobj.String()
49
+ # Store the color mode: 0= Status, 1=Labels, ...
50
+ self.colorMode = pwobj.Integer(ProjectSettings.COLOR_MODE_STATUS)
51
+ # Store graph nodes positions and other info
52
+ self.nodeList = NodeConfigList()
53
+ self.labelsList = LabelsList() # Label list
54
+ self.mapper = None # This should be set when load, or write
55
+ self.runsView = pwobj.Integer(1) # by default the graph view
56
+ self.readOnly = pwobj.Boolean(False)
57
+ self.runSelection = pwobj.CsvList(int) # Store selected runs
58
+ self.dataSelection = pwobj.CsvList(int) # Store selected runs
59
+ # Some extra settings stored, now mainly used
60
+ # from the webtools
61
+ # Time when the project was created
62
+ self.creationTime = pwobj.String(dt.datetime.now())
63
+ # Number of days that this project is active
64
+ # if None, the project will not expire
65
+ # This is used in webtools where a limited time
66
+ # is allowed for each project
67
+ self.lifeTime = pwobj.Integer()
68
+ # Set a disk quota for the project (in Gb)
69
+ # if None, quota is unlimited
70
+ self.diskQuota = pwobj.Integer()
71
+
72
+ def commit(self):
73
+ """ Commit changes made. """
74
+ self.mapper.commit()
75
+
76
+ def getRunsView(self):
77
+ return self.runsView.get()
78
+
79
+ def setRunsView(self, value):
80
+ self.runsView.set(value)
81
+
82
+ def getReadOnly(self):
83
+ return self.readOnly.get()
84
+
85
+ def setReadOnly(self, value):
86
+ self.readOnly.set(value)
87
+
88
+ def getCreationTime(self):
89
+ return self.creationTime.datetime()
90
+
91
+ def setCreationTime(self, value):
92
+ self.creationTime.set(value)
93
+
94
+ def getLifeTime(self):
95
+ return self.lifeTime.get()
96
+
97
+ def setLifeTime(self, value):
98
+ self.lifeTime.set(value)
99
+
100
+ def getProtocolView(self):
101
+ return self.currentProtocolsView.get()
102
+
103
+ def setProtocolView(self, protocolView):
104
+ """ Set the new protocol Menu given its index.
105
+ The new ProtocolMenu will be returned.
106
+ """
107
+ self.currentProtocolsView.set(protocolView)
108
+
109
+ def getColorMode(self):
110
+ return self.colorMode.get()
111
+
112
+ def setColorMode(self, colorMode):
113
+ """ Set the color mode to use when drawing the graph.
114
+ """
115
+ # Skip LABELS color mode to avoid double iteration
116
+ if colorMode == self.COLOR_MODE_LABELS:
117
+ colorMode+=1
118
+ self.colorMode.set(colorMode)
119
+
120
+ def statusColorMode(self):
121
+ return self.getColorMode() == self.COLOR_MODE_STATUS
122
+
123
+ def labelsColorMode(self):
124
+ return self.getColorMode() == self.COLOR_MODE_LABELS
125
+
126
+ def ageColorMode(self):
127
+ return self.getColorMode() == self.COLOR_MODE_AGE
128
+
129
+ def sizeColorMode(self):
130
+ return self.getColorMode() == self.COLOR_MODE_SIZE
131
+
132
+ def write(self, dbPath=None):
133
+ self.setName('ProjectSettings')
134
+ if dbPath is not None:
135
+ self.mapper = SqliteMapper(dbPath, globals())
136
+ else:
137
+ if self.mapper is None:
138
+ raise Exception("Can't write ProjectSettings without "
139
+ "mapper or dbPath")
140
+
141
+ self.mapper.deleteAll()
142
+ self.mapper.insert(self)
143
+ self.mapper.commit()
144
+
145
+ def getNodes(self):
146
+ return self.nodeList
147
+
148
+ def cleanUpNodes(self, runsIds, toRemove=True):
149
+ """ This will clean up all the nodes that do not have a matching run.
150
+ This is because until now, the nodes here weren't removes when protocols were removed.
151
+
152
+ :param runsIds: iterable with protocol's objId to be removed.
153
+ :param toRemove: Passed is are to be removed. Otherwise, are the ones to keep
154
+ """
155
+
156
+ try:
157
+ logger.info("Cleaning up unused graphical nodes.")
158
+
159
+ nodesToDelete = []
160
+ for node in self.getNodes():
161
+ nodeId = str(node.getId())
162
+ # if it is not the root node
163
+ if nodeId != '0':
164
+
165
+ if (nodeId in runsIds) == toRemove:
166
+ nodesToDelete.append(node.getId())
167
+
168
+ logger.info("Following graphical nodes %s unmatched. Deleting them" % nodesToDelete)
169
+ for key in nodesToDelete:
170
+ self.getNodes().removeNode(key)
171
+
172
+ except Exception as e:
173
+ logger.error("Couldn't clean up graphical nodes.", exc_info=e)
174
+
175
+ def getNodeById(self, nodeId):
176
+ return self.nodeList.getNode(nodeId)
177
+
178
+ def addNode(self, nodeId, **kwargs):
179
+ return self.nodeList.addNode(nodeId, **kwargs)
180
+
181
+ def removeNode(self, nodeId):
182
+ """ Removes a graphical node based on its id"""
183
+ self.nodeList.removeNode(nodeId)
184
+
185
+ def getLabels(self):
186
+ return self.labelsList
187
+
188
+ @classmethod
189
+ def load(cls, dbPath):
190
+ """ Load a ProjectSettings from dbPath. """
191
+ classDict = dict(globals())
192
+ classDict.update(pwobj.__dict__)
193
+ mapper = SqliteMapper(dbPath, classDict)
194
+ settingList = mapper.selectByClass('ProjectSettings')
195
+ n = len(settingList)
196
+
197
+ if n == 0:
198
+ raise Exception("Can't load ProjectSettings from %s" % dbPath)
199
+ elif n > 1:
200
+ raise Exception("Only one ProjectSettings is expected in db, "
201
+ "found %d in %s" % (n, dbPath))
202
+
203
+ settings = settingList[0]
204
+ settings.mapper = mapper
205
+
206
+ return settings
207
+
208
+
209
+ class MenuConfig(object):
210
+ """Menu configuration in a tree fashion.
211
+ Each menu can contain submenus.
212
+ Leaf elements can contain actions"""
213
+
214
+ def __init__(self, text=None, value=None,
215
+ icon=None, tag=None, shortCut=None, openItem=False, visible=True):
216
+ """Constructor for the Menu config item.
217
+ Arguments:
218
+ text: text to be displayed
219
+ value: internal value associated with the item.
220
+ icon: display an icon with the item
221
+ tag: put some tags to items
222
+ **args: pass other options to base class.
223
+ """
224
+ self.text = text
225
+ self.value = value
226
+ self.icon = icon
227
+ self.tag = tag
228
+ self.shortCut = shortCut
229
+ self.openItem = openItem
230
+ self.visible = visible
231
+ self.childs = pwobj.List()
232
+
233
+ def addSubMenu(self, text, value=None, **args):
234
+ subMenu = type(self)(text, value, **args)
235
+ self.childs.append(subMenu)
236
+ return subMenu
237
+
238
+ def __iter__(self):
239
+ for v in self.childs:
240
+ yield v
241
+
242
+ def __len__(self):
243
+ return len(self.childs)
244
+
245
+ def isEmpty(self):
246
+ return len(self.childs) == 0
247
+
248
+
249
+ class NodeConfig(pwobj.Scalar):
250
+ """ Store Graph node information such as x, y. """
251
+
252
+ def __init__(self, nodeId=0, x=None, y=None, selected=False, expanded=True,
253
+ visible=True):
254
+ pwobj.Scalar.__init__(self)
255
+ # Special node id 0 for project node
256
+ self._values = {'id': nodeId,
257
+ 'x': pwobj.Integer(x).get(0),
258
+ 'y': pwobj.Integer(y).get(0),
259
+ 'selected': selected,
260
+ 'expanded': expanded,
261
+ 'visible': pwobj.Boolean(visible).get(0),
262
+ 'labels': []}
263
+
264
+ def _convertValue(self, value):
265
+ """Value should be a str with comma separated values
266
+ or a list.
267
+ """
268
+ self._values = json.loads(value)
269
+
270
+ def getObjValue(self):
271
+ self._objValue = json.dumps(self._values)
272
+ return self._objValue
273
+
274
+ def get(self):
275
+ return self.getObjValue()
276
+
277
+ def getId(self):
278
+ return self._values['id']
279
+
280
+ def setX(self, x):
281
+ self._values['x'] = x
282
+
283
+ def getX(self):
284
+ return self._values['x']
285
+
286
+ def setY(self, y):
287
+ self._values['y'] = y
288
+
289
+ def getY(self):
290
+ return self._values['y']
291
+
292
+ def setPosition(self, x, y):
293
+ self.setX(x)
294
+ self.setY(y)
295
+
296
+ def getPosition(self):
297
+ return self.getX(), self.getY()
298
+
299
+ def setSelected(self, selected):
300
+ self._values['selected'] = selected
301
+
302
+ def isSelected(self):
303
+ return self._values['selected']
304
+
305
+ def setExpanded(self, expanded):
306
+ self._values['expanded'] = expanded
307
+
308
+ def isExpanded(self):
309
+ return self._values['expanded']
310
+
311
+ def setVisible(self, visible):
312
+ self._values['visible'] = visible
313
+
314
+ def isVisible(self):
315
+ if self._values.get('visible') is None:
316
+ self._values['visible'] = True
317
+ return self._values['visible']
318
+
319
+ def setLabels(self, labels):
320
+ self._values['labels'] = labels
321
+
322
+ def getLabels(self):
323
+ return self._values.get('labels', None)
324
+
325
+ def __str__(self):
326
+ return 'NodeConfig: %s' % self._values
327
+
328
+
329
+ class NodeConfigList(pwobj.List):
330
+ """ Store all nodes information items and
331
+ also store a dictionary for quick access
332
+ to nodes query.
333
+ """
334
+
335
+ def __init__(self):
336
+ self._nodesDict = {}
337
+ pwobj.List.__init__(self)
338
+
339
+ def getNode(self, nodeId):
340
+ return self._nodesDict.get(nodeId, None)
341
+
342
+ def addNode(self, nodeId, **kwargs):
343
+ node = NodeConfig(nodeId, **kwargs)
344
+ self._nodesDict[node.getId()] = node
345
+ self.append(node)
346
+ return node
347
+
348
+ def removeNode(self, nodeId):
349
+ """ Removes a node with the id = nodeId"""
350
+ nodeToRemove = self._nodesDict[nodeId]
351
+ self._nodesDict.pop(nodeId)
352
+ self.remove(nodeToRemove)
353
+
354
+ def updateDict(self):
355
+ self._nodesDict.clear()
356
+ for node in self:
357
+ self._nodesDict[node.getId()] = node
358
+
359
+ def clear(self):
360
+ pwobj.List.clear(self)
361
+ self._nodesDict.clear()
362
+
363
+
364
+ class Label(pwobj.Scalar):
365
+ """ Store Label information """
366
+
367
+ EMPTY_OLD_NAME = None
368
+
369
+ def __init__(self, name='', color=None):
370
+ pwobj.Scalar.__init__(self)
371
+ # Special node id 0 for project node
372
+ self._values = {'name': name,
373
+ 'color': color}
374
+ self._oldName = self.EMPTY_OLD_NAME
375
+
376
+ def _convertValue(self, value):
377
+ """Value should be a str with comma separated values
378
+ or a list.
379
+ """
380
+ self._values = json.loads(value)
381
+
382
+ # Clean unused "id" field
383
+ if "id" in self._values:
384
+ self._values.pop("id")
385
+
386
+ def getObjValue(self):
387
+ self._objValue = json.dumps(self._values)
388
+
389
+ return self._objValue
390
+
391
+ def get(self):
392
+ return self.getObjValue()
393
+
394
+
395
+ def getName(self)->str:
396
+ return self._values['name']
397
+
398
+ def setName(self, newName):
399
+ # For recurrent edit,
400
+ # we keep the old name only the first time
401
+ if not self.hasOldName():
402
+ self._oldName = self._values['name']
403
+
404
+ self._values['name'] = newName
405
+
406
+ def hasOldName(self)->bool:
407
+ return self._oldName != self.EMPTY_OLD_NAME
408
+
409
+ def clearOldName(self):
410
+ self._oldName = self.EMPTY_OLD_NAME
411
+
412
+ def getOldName(self)->str:
413
+ return self._oldName
414
+
415
+ def setColor(self, color):
416
+ self._values['color'] = color
417
+
418
+ def getColor(self)->str:
419
+ return self._values.get('color', None)
420
+
421
+ def __str__(self):
422
+ return 'Label: %s' % self._values
423
+
424
+ def __eq__(self, other):
425
+ return self.getName() == other.getName()
426
+
427
+
428
+ class LabelsList(pwobj.List):
429
+ """ Store all labels information"""
430
+
431
+ def __init__(self):
432
+ self._labelsDict = {}
433
+ pwobj.List.__init__(self)
434
+
435
+ def getLabel(self, name):
436
+ return self._labelsDict.get(name, None)
437
+
438
+ def addLabel(self, label):
439
+ self._labelsDict[label.getName()] = label
440
+ self.append(label)
441
+ return label
442
+
443
+ def updateDict(self):
444
+ self._labelsDict.clear()
445
+ for label in self:
446
+ self._labelsDict[label.getName()] = label
447
+
448
+ def deleteLabel(self, label):
449
+ self._labelsDict.pop(label.getName())
450
+ self.remove(label)
451
+
452
+ def clear(self):
453
+ pwobj.List.clear(self)
454
+ self._labelDict.clear()