scipion-pyworkflow 3.7.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 (140) hide show
  1. pyworkflow/__init__.py +33 -0
  2. pyworkflow/apps/__init__.py +29 -0
  3. pyworkflow/apps/pw_manager.py +37 -0
  4. pyworkflow/apps/pw_plot.py +51 -0
  5. pyworkflow/apps/pw_project.py +113 -0
  6. pyworkflow/apps/pw_protocol_list.py +143 -0
  7. pyworkflow/apps/pw_protocol_run.py +51 -0
  8. pyworkflow/apps/pw_run_tests.py +267 -0
  9. pyworkflow/apps/pw_schedule_run.py +322 -0
  10. pyworkflow/apps/pw_sleep.py +37 -0
  11. pyworkflow/apps/pw_sync_data.py +439 -0
  12. pyworkflow/apps/pw_viewer.py +78 -0
  13. pyworkflow/config.py +536 -0
  14. pyworkflow/constants.py +212 -0
  15. pyworkflow/exceptions.py +18 -0
  16. pyworkflow/gui/__init__.py +36 -0
  17. pyworkflow/gui/browser.py +726 -0
  18. pyworkflow/gui/canvas.py +1190 -0
  19. pyworkflow/gui/dialog.py +976 -0
  20. pyworkflow/gui/form.py +2627 -0
  21. pyworkflow/gui/graph.py +247 -0
  22. pyworkflow/gui/graph_layout.py +271 -0
  23. pyworkflow/gui/gui.py +566 -0
  24. pyworkflow/gui/matplotlib_image.py +233 -0
  25. pyworkflow/gui/plotter.py +247 -0
  26. pyworkflow/gui/project/__init__.py +25 -0
  27. pyworkflow/gui/project/base.py +192 -0
  28. pyworkflow/gui/project/constants.py +139 -0
  29. pyworkflow/gui/project/labels.py +205 -0
  30. pyworkflow/gui/project/project.py +484 -0
  31. pyworkflow/gui/project/searchprotocol.py +154 -0
  32. pyworkflow/gui/project/searchrun.py +181 -0
  33. pyworkflow/gui/project/steps.py +166 -0
  34. pyworkflow/gui/project/utils.py +332 -0
  35. pyworkflow/gui/project/variables.py +179 -0
  36. pyworkflow/gui/project/viewdata.py +472 -0
  37. pyworkflow/gui/project/viewprojects.py +510 -0
  38. pyworkflow/gui/project/viewprotocols.py +2093 -0
  39. pyworkflow/gui/project/viewprotocols_extra.py +560 -0
  40. pyworkflow/gui/text.py +771 -0
  41. pyworkflow/gui/tooltip.py +185 -0
  42. pyworkflow/gui/tree.py +684 -0
  43. pyworkflow/gui/widgets.py +307 -0
  44. pyworkflow/mapper/__init__.py +26 -0
  45. pyworkflow/mapper/mapper.py +222 -0
  46. pyworkflow/mapper/sqlite.py +1578 -0
  47. pyworkflow/mapper/sqlite_db.py +145 -0
  48. pyworkflow/object.py +1512 -0
  49. pyworkflow/plugin.py +712 -0
  50. pyworkflow/project/__init__.py +31 -0
  51. pyworkflow/project/config.py +451 -0
  52. pyworkflow/project/manager.py +179 -0
  53. pyworkflow/project/project.py +1990 -0
  54. pyworkflow/project/scripts/clean_projects.py +77 -0
  55. pyworkflow/project/scripts/config.py +92 -0
  56. pyworkflow/project/scripts/create.py +77 -0
  57. pyworkflow/project/scripts/edit_workflow.py +90 -0
  58. pyworkflow/project/scripts/fix_links.py +39 -0
  59. pyworkflow/project/scripts/load.py +87 -0
  60. pyworkflow/project/scripts/refresh.py +83 -0
  61. pyworkflow/project/scripts/schedule.py +111 -0
  62. pyworkflow/project/scripts/stack2volume.py +41 -0
  63. pyworkflow/project/scripts/stop.py +81 -0
  64. pyworkflow/protocol/__init__.py +38 -0
  65. pyworkflow/protocol/bibtex.py +48 -0
  66. pyworkflow/protocol/constants.py +86 -0
  67. pyworkflow/protocol/executor.py +334 -0
  68. pyworkflow/protocol/hosts.py +313 -0
  69. pyworkflow/protocol/launch.py +270 -0
  70. pyworkflow/protocol/package.py +42 -0
  71. pyworkflow/protocol/params.py +744 -0
  72. pyworkflow/protocol/protocol.py +2554 -0
  73. pyworkflow/resources/Imagej.png +0 -0
  74. pyworkflow/resources/chimera.png +0 -0
  75. pyworkflow/resources/fa-exclamation-triangle_alert.png +0 -0
  76. pyworkflow/resources/fa-info-circle_alert.png +0 -0
  77. pyworkflow/resources/fa-search.png +0 -0
  78. pyworkflow/resources/fa-times-circle_alert.png +0 -0
  79. pyworkflow/resources/file_vol.png +0 -0
  80. pyworkflow/resources/loading.gif +0 -0
  81. pyworkflow/resources/no-image128.png +0 -0
  82. pyworkflow/resources/scipion_bn.png +0 -0
  83. pyworkflow/resources/scipion_icon.png +0 -0
  84. pyworkflow/resources/scipion_icon.svg +397 -0
  85. pyworkflow/resources/scipion_icon_proj.png +0 -0
  86. pyworkflow/resources/scipion_icon_projs.png +0 -0
  87. pyworkflow/resources/scipion_icon_prot.png +0 -0
  88. pyworkflow/resources/scipion_logo.png +0 -0
  89. pyworkflow/resources/scipion_logo_normal.png +0 -0
  90. pyworkflow/resources/scipion_logo_small.png +0 -0
  91. pyworkflow/resources/sprites.png +0 -0
  92. pyworkflow/resources/sprites.xcf +0 -0
  93. pyworkflow/resources/wait.gif +0 -0
  94. pyworkflow/template.py +322 -0
  95. pyworkflow/tests/__init__.py +29 -0
  96. pyworkflow/tests/test_utils.py +25 -0
  97. pyworkflow/tests/tests.py +341 -0
  98. pyworkflow/utils/__init__.py +38 -0
  99. pyworkflow/utils/dataset.py +414 -0
  100. pyworkflow/utils/echo.py +104 -0
  101. pyworkflow/utils/graph.py +196 -0
  102. pyworkflow/utils/log.py +284 -0
  103. pyworkflow/utils/path.py +527 -0
  104. pyworkflow/utils/process.py +132 -0
  105. pyworkflow/utils/profiler.py +92 -0
  106. pyworkflow/utils/progressbar.py +154 -0
  107. pyworkflow/utils/properties.py +627 -0
  108. pyworkflow/utils/reflection.py +129 -0
  109. pyworkflow/utils/utils.py +877 -0
  110. pyworkflow/utils/which.py +229 -0
  111. pyworkflow/viewer.py +328 -0
  112. pyworkflow/webservices/__init__.py +8 -0
  113. pyworkflow/webservices/config.py +11 -0
  114. pyworkflow/webservices/notifier.py +162 -0
  115. pyworkflow/webservices/repository.py +59 -0
  116. pyworkflow/webservices/workflowhub.py +74 -0
  117. pyworkflow/wizard.py +64 -0
  118. pyworkflowtests/__init__.py +51 -0
  119. pyworkflowtests/bibtex.py +51 -0
  120. pyworkflowtests/objects.py +830 -0
  121. pyworkflowtests/protocols.py +154 -0
  122. pyworkflowtests/tests/__init__.py +0 -0
  123. pyworkflowtests/tests/test_canvas.py +72 -0
  124. pyworkflowtests/tests/test_domain.py +45 -0
  125. pyworkflowtests/tests/test_logs.py +74 -0
  126. pyworkflowtests/tests/test_mappers.py +392 -0
  127. pyworkflowtests/tests/test_object.py +507 -0
  128. pyworkflowtests/tests/test_project.py +42 -0
  129. pyworkflowtests/tests/test_protocol_execution.py +72 -0
  130. pyworkflowtests/tests/test_protocol_export.py +78 -0
  131. pyworkflowtests/tests/test_protocol_output.py +158 -0
  132. pyworkflowtests/tests/test_streaming.py +47 -0
  133. pyworkflowtests/tests/test_utils.py +210 -0
  134. scipion_pyworkflow-3.7.0.dist-info/LICENSE.txt +674 -0
  135. scipion_pyworkflow-3.7.0.dist-info/METADATA +107 -0
  136. scipion_pyworkflow-3.7.0.dist-info/RECORD +140 -0
  137. scipion_pyworkflow-3.7.0.dist-info/WHEEL +5 -0
  138. scipion_pyworkflow-3.7.0.dist-info/dependency_links.txt +1 -0
  139. scipion_pyworkflow-3.7.0.dist-info/entry_points.txt +5 -0
  140. scipion_pyworkflow-3.7.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,510 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
4
+ # *
5
+ # * [1] SciLifeLab, Stockholm University
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, see <https://www.gnu.org/licenses/>.
19
+ # *
20
+ # * All comments concerning this program package may be sent to the
21
+ # * e-mail address 'scipion@cnb.csic.es'
22
+ # *
23
+ # **************************************************************************
24
+ import datetime
25
+ import logging
26
+
27
+ REFRESH_WAIT_SEC = 60
28
+ logger = logging.getLogger(__name__)
29
+
30
+ import os
31
+ import tkinter as tk
32
+ import tkinter.font as tkFont
33
+
34
+ import pyworkflow as pw
35
+ from pyworkflow.project import Project
36
+ from pyworkflow.utils.utils import prettyDate, prettyTime
37
+ from pyworkflow.utils.path import getHomePath
38
+
39
+ from pyworkflow.project import Manager
40
+ from pyworkflow.gui.text import TaggedText
41
+ from pyworkflow.gui.dialog import askString, askYesNo, showError
42
+
43
+ from pyworkflow.gui import Message, Window, cfgEntryBgColor, ToolTip
44
+ from pyworkflow.gui.browser import FileBrowserWindow
45
+ from pyworkflow.gui.widgets import IconButton, HotButton, Button
46
+ from pyworkflow.utils.properties import Icon
47
+
48
+
49
+ class ProjectsView(tk.Frame):
50
+ _PROJ_CONTAINER = "projectsframe"
51
+
52
+ def __init__(self, parent, windows, **args):
53
+ tk.Frame.__init__(self, parent, bg=pw.Config.SCIPION_BG_COLOR, **args)
54
+ self.windows = windows
55
+ self.manager = windows.manager
56
+ self.root = windows.root
57
+ self.lastLoad = None
58
+
59
+ # Bind to root "focus in"
60
+ self.root.bind("<FocusIn>", self._onWindowFocusIn)
61
+ smallSize = pw.Config.SCIPION_FONT_SIZE - 2
62
+ fontName = pw.Config.SCIPION_FONT_NAME
63
+
64
+ self.projNameFont = tkFont.Font(size=pw.Config.SCIPION_FONT_SIZE+2, family=fontName,
65
+ weight='bold')
66
+ self.projDateFont = tkFont.Font(size=smallSize, family=fontName)
67
+ self.projDelFont = tkFont.Font(size=smallSize, family=fontName,
68
+ weight='bold')
69
+ self.manager = Manager()
70
+
71
+ self.filter = tk.StringVar()
72
+ self.filterBox = None
73
+ self.addActionsFrame()
74
+
75
+ self.columnconfigure(0, weight=1)
76
+ self.rowconfigure(1, weight=1)
77
+ text = TaggedText(self, width=40, height=15, bd=0, bg=pw.Config.SCIPION_BG_COLOR)
78
+ text.grid(row=1, columnspan=2, column=0, sticky='news')
79
+ text.setReadOnly(True)
80
+ self.text = text
81
+ self.filterBox.focus_set()
82
+
83
+ # Content load happens automatically _onWindowFocusIn
84
+
85
+ def _onWindowFocusIn(self, event):
86
+ """ Refresh on windows get focus """
87
+ if event.widget == self.root:
88
+ # Get the delta from the last time refreshed
89
+ delta = REFRESH_WAIT_SEC if self.lastLoad is None else (datetime.datetime.now() - self.lastLoad).seconds
90
+ if delta >= REFRESH_WAIT_SEC:
91
+ self.createProjectList()
92
+
93
+ def addActionsFrame(self):
94
+ """ Add the "toolbar" for actions like create project, import
95
+ project or filter"""
96
+ # Add the create project button
97
+ bg = pw.Config.SCIPION_BG_COLOR
98
+ btnFrame = tk.Frame(self, bg=bg)
99
+ btn = HotButton(btnFrame, text=Message.LABEL_CREATE_PROJECT,
100
+ font=self.projNameFont,
101
+ command=self._onCreateProject)
102
+ btn.grid(row=0, column=0, sticky='nw', padx=10, pady=10)
103
+ # Add the Import project button
104
+ btn = Button(btnFrame, text=Message.LABEL_IMPORT_PROJECT,
105
+ font=self.projNameFont,
106
+ command=self._onImportProject)
107
+ btn.grid(row=0, column=1, sticky='nw', padx=10, pady=10)
108
+ btnFrame.grid(row=0, column=0, sticky='nw')
109
+
110
+ # Add a filter box
111
+ # Add the Import project button
112
+ btn = tk.Label(btnFrame, bg=bg, text="Filter:", font=self.projNameFont)
113
+ btn.grid(row=0, column=2, sticky='nse', padx=10, pady=10)
114
+ self.filterBox = tk.Entry(btnFrame, font=self.projNameFont, textvariable=self.filter)
115
+ self.filterBox.grid(row=0, column=3, sticky='ne', padx=10, pady=12)
116
+ self.filterBox.bind('<Return>', self._onFilter)
117
+ self.filterBox.bind('<KP_Enter>', self._onFilter)
118
+ self.filterBox.bind("<F5>", self.createProjectList)
119
+ ToolTip(self.filterBox, "Return/enter to filter. F5 to refresh the list")
120
+
121
+
122
+ def createProjectList(self, event=None):
123
+ """Load the list of projects"""
124
+
125
+ self.lastLoad = datetime.datetime.now()
126
+ r = 0
127
+ text = self.text
128
+ text.setReadOnly(False)
129
+ text.clear()
130
+ parent = tk.Frame(text, bg=pw.Config.SCIPION_BG_COLOR, name=self._PROJ_CONTAINER)
131
+ parent.columnconfigure(0, weight=1)
132
+ colors = [pw.Config.SCIPION_BG_COLOR, '#EAEBFF']
133
+ for i, p in enumerate(self.manager.listProjects()):
134
+ try:
135
+ # Add creation time to project info
136
+ # Add if it's a link
137
+ p.index = "index%s" % i
138
+
139
+ # Consider the filter
140
+ if not self._doesProjectMatchFilter(p):
141
+ continue
142
+
143
+ frame = self.createProjectLabel(parent, p, color=colors[i % 2])
144
+ frame.grid(row=r, column=0, padx=10, pady=5, sticky='new')
145
+ r += 1
146
+
147
+ except Exception as ex:
148
+ logger.error("Couldn't load project %s" % p.getName(), exc_info=True)
149
+
150
+ text.window_create(tk.INSERT, window=parent)
151
+ text.bindWidget(parent)
152
+ text.setReadOnly(True)
153
+
154
+ def createProjectLabel(self, parent, projInfo, color):
155
+ frame = tk.Frame(parent, bg=color, name=projInfo.index)
156
+ # ROW1
157
+ # Project name
158
+ label = tk.Label(frame, text=projInfo.projName, anchor='nw', bg=color,
159
+ justify=tk.LEFT, font=self.projNameFont, cursor='hand1', width=50)
160
+ label.grid(row=0, column=0, padx=2, pady=2, sticky='nw')
161
+ label.bind('<Button-1>', lambda e: self.openProject(projInfo.projName))
162
+
163
+ # ROW2
164
+ # Timestamp line
165
+ dateMsg = '%s%s %s%s' % (Message.LABEL_MODIFIED, prettyDate(projInfo.mTime),
166
+ Message.LABEL_CREATED, prettyTime(projInfo.cTime, time=False))
167
+ dateLabel = tk.Label(frame, text=dateMsg, font=self.projDateFont, bg=color)
168
+ dateLabel.grid(row=1, column=0, sticky='nw')
169
+ # Delete action
170
+ delLabel = tk.Label(frame, text=Message.LABEL_DELETE_PROJECT, font=self.projDelFont, bg=color, cursor='hand1')
171
+ delLabel.grid(row=1, column=1, padx=10)
172
+ delLabel.bind('<Button-1>', lambda e: self.deleteProject(projInfo))
173
+ # Rename action
174
+ mvLabel = tk.Label(frame, text=Message.LABEL_RENAME_PROJECT, font=self.projDelFont, bg=color, cursor='hand1')
175
+ mvLabel.grid(row=1, column=2)
176
+ mvLabel.bind('<Button-1>', lambda e: self.renameProject(projInfo.projName))
177
+
178
+ # ROW3
179
+ if projInfo.isLink():
180
+ linkMsg = 'link --> ' + projInfo.realPath()
181
+ lblLink = tk.Label(frame, text=linkMsg, font=self.projDateFont, bg=color, fg='grey', justify=tk.LEFT)
182
+ lblLink.grid(row=2, column=0, columnspan=3, sticky='w')
183
+
184
+ return frame
185
+
186
+ def createNewProject(self, projName, projLocation):
187
+ proj = self.manager.createProject(projName, location=projLocation)
188
+ self.createProjectList()
189
+ self.openProject(proj.getShortName())
190
+
191
+ def _onCreateProject(self, e=None):
192
+ projWindow = ProjectCreateWindow("Create project", self)
193
+ projWindow.show()
194
+
195
+ def _onImportProject(self, e=None):
196
+ importProjWindow = ProjectImportWindow("Import project", self)
197
+ importProjWindow.show()
198
+
199
+ def _onFilter(self, e=None):
200
+ self.createProjectList()
201
+
202
+ def _setFocusToList(self, e=None):
203
+ self.text.focus_set()
204
+
205
+ def _doesProjectMatchFilter(self, project):
206
+ """ Returns true if the project matches the filter"""
207
+ # Lets' use the name anc creation date for now:
208
+ searchString = "~".join([project.getName().lower(),
209
+ prettyDate(project.mTime),
210
+ prettyTime(project.cTime, time=False)])
211
+
212
+ return self.filter.get().lower() in searchString
213
+
214
+ def importProject(self, projLocation, copyFiles, projName, searchLocation):
215
+
216
+ self.manager.importProject(projLocation, copyFiles, projName, searchLocation)
217
+ self.createProjectList()
218
+ self.openProject(projName)
219
+
220
+ def openProject(self, projName):
221
+ from subprocess import Popen
222
+ script = pw.join(pw.APPS, 'pw_project.py')
223
+ Popen([pw.PYTHON, script, projName])
224
+
225
+ def deleteProject(self, projInfo):
226
+
227
+ projName = projInfo.projName
228
+
229
+ if askYesNo(Message.TITLE_DELETE_PROJECT,
230
+ "Project *%s*. " % projName + Message.MESSAGE_DELETE_PROJECT, self.root):
231
+
232
+ logger.info("User agreed to delete project %s" % projName)
233
+ self.manager.deleteProject(projName)
234
+
235
+ #Delete the frame
236
+ self.text.children[self._PROJ_CONTAINER].children[projInfo.index].grid_forget()
237
+
238
+
239
+ def renameProject(self, projName):
240
+ newName = askString("Rename project %s" % projName, "Enter new name:", self.root)
241
+ if not newName or newName == projName:
242
+ return
243
+ if self.manager.hasProject(newName):
244
+ showError("Rename cancelled",
245
+ "Project name already exists: %s" % newName, self.root)
246
+ return
247
+ self.manager.renameProject(projName, newName)
248
+ self.createProjectList()
249
+
250
+
251
+ class ProjectCreateWindow(Window):
252
+ """ Windows to create a project. """
253
+
254
+ def __init__(self, title, parent=None, weight=True, minsize=(400, 110),
255
+ icon=Icon.SCIPION_ICON, **args):
256
+ """
257
+ We assume the parent should be of ProjectsView
258
+ """
259
+ Window.__init__(self, title, parent.windows, weight=weight,
260
+ icon=icon, minsize=minsize, enableQueue=True)
261
+ self.root['background'] = pw.Config.SCIPION_BG_COLOR
262
+
263
+ self.parent = parent
264
+ self.projectsPath = self.parent.manager.PROJECTS
265
+ self.projName = tk.StringVar()
266
+ self.projName.set('')
267
+ self.projLocation = tk.StringVar()
268
+ self.projLocation.set(self.projectsPath)
269
+
270
+ content = tk.Frame(self.root)
271
+ content.columnconfigure(0, weight=1)
272
+ content.columnconfigure(1, weight=3)
273
+ content.config(bg=pw.Config.SCIPION_BG_COLOR)
274
+ content.grid(row=0, column=0, sticky='news', padx=5, pady=5)
275
+
276
+ # Info line
277
+ labelInfo = tk.Label(content, text="Spaces will be replaced by underscores!", bg=pw.Config.SCIPION_BG_COLOR, bd=0)
278
+ labelInfo.grid(row=0, sticky=tk.W, padx=5, pady=5)
279
+ # Project name line
280
+ labelName = tk.Label(content, text=Message.LABEL_PROJECT + ' name', bg=pw.Config.SCIPION_BG_COLOR, bd=0)
281
+ labelName.grid(row=1, sticky=tk.W, padx=5, pady=5)
282
+ entryName = tk.Entry(content, bg=cfgEntryBgColor, width=20, textvariable=self.projName)
283
+ entryName.grid(row=1, column=1, columnspan=2, sticky=tk.EW, padx=5, pady=5)
284
+ entryName.bind("<Return>", self._create)
285
+ entryName.bind("<KP_Enter>", self._create)
286
+ # Project location line
287
+ labelLocation = tk.Label(content, text=Message.LABEL_PROJECT + ' location', bg=pw.Config.SCIPION_BG_COLOR, bd=0)
288
+ labelLocation.grid(row=2, column=0, sticky='nw', padx=5, pady=5)
289
+
290
+ self.entryBrowse = tk.Entry(content, bg=cfgEntryBgColor, width=40, textvariable=self.projLocation)
291
+ self.entryBrowse.grid(row=2, column=1, sticky='nw', padx=5, pady=5)
292
+ self.btnBrowse = IconButton(content, 'Browse', Icon.ACTION_BROWSE,
293
+ highlightthickness=0, command=self._browsePath)
294
+ self.btnBrowse.grid(row=2, column=2, sticky='e', padx=5, pady=5)
295
+
296
+ self.initial_focus = entryName
297
+ self.initial_focus.focus()
298
+
299
+ btnFrame = tk.Frame(content)
300
+ btnFrame.columnconfigure(0, weight=1)
301
+ btnFrame.grid(row=3, column=0, sticky='sew', padx=5, pady=(0, 5), columnspan=2)
302
+ btnFrame.config(bg=pw.Config.SCIPION_BG_COLOR)
303
+
304
+ # Create buttons
305
+ btnCreate = HotButton(btnFrame, 'Create', Icon.BUTTON_SELECT, command=self._create)
306
+ btnCreate.grid(row=0, column=0, sticky='e', padx=5, pady=5)
307
+ btnCancel = Button(btnFrame, 'Cancel', Icon.BUTTON_CANCEL, command=self.close)
308
+ btnCancel.grid(row=0, column=1, sticky='e', padx=5, pady=5)
309
+
310
+ def _browsePath(self, e=None):
311
+ def onSelect(obj):
312
+ self.projLocation.set(obj.getPath())
313
+
314
+ v = self.projLocation.get().strip()
315
+ path = None
316
+ if v:
317
+ v = os.path.dirname(v)
318
+ if os.path.exists(v):
319
+ path = v
320
+ if not path:
321
+ path = self.projectsPath
322
+
323
+ browser = FileBrowserWindow("Browsing", self, path=path, onSelect=onSelect, onlyFolders=True)
324
+ browser.show()
325
+
326
+ def _create(self, e=None):
327
+ projName = self.projName.get().strip()
328
+ projLocation = self.projLocation.get().strip()
329
+
330
+ # Validate that project name is not empty
331
+ if not projName:
332
+ showError("Validation error", "Project name is empty", self.root)
333
+ # Validate that project location is not empty
334
+ elif not projLocation:
335
+ showError("Validation error", "Project location is empty", self.root)
336
+ # Validate that project location exists
337
+ elif not os.path.exists(projLocation):
338
+ showError("Validation error", "Project location does not exist", self.root)
339
+ # Validate that project location is a directory
340
+ elif not os.path.isdir(projLocation):
341
+ showError("Validation error", "Project location is not a directory", self.root)
342
+ # Validate that project path (location + name) does not exists
343
+ elif os.path.exists(os.path.join(projLocation, projName)):
344
+ showError("Validation error", "Project path already exists", self.root)
345
+ else:
346
+ self.parent.createNewProject(projName, projLocation)
347
+ self.close()
348
+
349
+
350
+ class ProjectImportWindow(Window):
351
+ """ Windows to import a project. """
352
+
353
+ def __init__(self, title, parent=None, weight=True, minsize=(400, 150),
354
+ icon=Icon.SCIPION_ICON, **args):
355
+ """
356
+ We assume the parent should be ProjectsView
357
+ """
358
+ Window.__init__(self, title, parent.windows, weight=weight,
359
+ icon=icon, minsize=minsize, enableQueue=True)
360
+ self.root['background'] = pw.Config.SCIPION_BG_COLOR
361
+ self.parent = parent
362
+ # Dirty hack, need to add a slash for the explorer to pick up the right default path.
363
+ self.projectsPath = getHomePath() + "/"
364
+ self.projLocation = tk.StringVar()
365
+ self.projLocation.set(self.projectsPath)
366
+
367
+ self.projName = tk.StringVar()
368
+ self.projName.set('')
369
+
370
+ self.searchLocation = tk.StringVar()
371
+ self.searchLocation.set('')
372
+
373
+ content = tk.Frame(self.root)
374
+ content.columnconfigure(0, weight=1)
375
+ content.columnconfigure(1, weight=1)
376
+ content.config(bg=pw.Config.SCIPION_BG_COLOR)
377
+ content.grid(row=0, column=0, sticky='news',
378
+ padx=5, pady=5)
379
+
380
+ # Path explorer
381
+ labelProjectLocation = tk.Label(content, text="Project location", bg=pw.Config.SCIPION_BG_COLOR, bd=0)
382
+ labelProjectLocation.grid(row=0, column=0, sticky='nw', padx=5, pady=5)
383
+ # it seems tk.Entry does not uses default font...grrrr!!
384
+ self.entryBrowse = tk.Entry(content, bg=cfgEntryBgColor, width=40,
385
+ textvariable=self.projLocation, font=self.font)
386
+ self.entryBrowse.grid(row=0, column=1, sticky='nw', padx=5, pady=5)
387
+ self.btnBrowse = IconButton(content, 'Browse', Icon.ACTION_BROWSE, highlightthickness=0,
388
+ command=self._browseProjectLocation)
389
+ self.btnBrowse.grid(row=0, column=2, sticky='e', padx=5, pady=5)
390
+
391
+ # Copy files check
392
+ labelCheck = tk.Label(content, text="Copy project", bg=pw.Config.SCIPION_BG_COLOR, borderwidth=0)
393
+ labelCheck.grid(row=1, column=0, sticky='nw', padx=5, pady=5)
394
+
395
+ self.tkCheckVar = tk.IntVar()
396
+ btnCheck = tk.Checkbutton(content, variable=self.tkCheckVar, highlightthickness=0, activebackground=pw.Config.SCIPION_BG_COLOR,
397
+ bg=pw.Config.SCIPION_BG_COLOR, bd=0)
398
+ btnCheck.grid(row=1, column=1, sticky='nw', padx=0, pady=5)
399
+
400
+ btnCopyHelp = IconButton(content, Message.LABEL_BUTTON_HELP, Icon.ACTION_HELP, highlightthickness=0,
401
+ command=lambda: self.showInfo(
402
+ 'If checked, \"Project location\" will be copied. Otherwise a soft link to it will be created.'))
403
+ btnCopyHelp.grid(row=1, column=3, sticky='e', padx=2, pady=2)
404
+
405
+ # Project name
406
+ labelName = tk.Label(content, text='Project name (Optional)', bg=pw.Config.SCIPION_BG_COLOR, bd=0)
407
+ labelName.grid(row=2, column=0, sticky='nw', padx=5, pady=5)
408
+ entryName = tk.Entry(content, bg='white', width=20, textvariable=self.projName, font=self.font)
409
+ entryName.grid(row=2, column=1, sticky='nw', padx=5, pady=5)
410
+
411
+ # Path to search for raw data and restore broken links.
412
+ labelSearchLocation = tk.Label(content, text="Raw files location (Optional)", bg=pw.Config.SCIPION_BG_COLOR, bd=0)
413
+ labelSearchLocation.grid(row=3, column=0, sticky='nw', padx=5, pady=5)
414
+
415
+ self.entrySearchLocation = tk.Entry(content, bg='white', width=40,
416
+ textvariable=self.searchLocation, font=self.font)
417
+ self.entrySearchLocation.grid(row=3, column=1, sticky='nw', padx=5, pady=5)
418
+ self.btnSearch = IconButton(content, 'Browse', Icon.ACTION_BROWSE,
419
+ highlightthickness=0, command=self._browseSearchLocation)
420
+ self.btnSearch.grid(row=3, column=2, sticky='e', padx=5, pady=5)
421
+ btnSearchHelp = IconButton(content, Message.LABEL_BUTTON_HELP, Icon.ACTION_HELP, highlightthickness=0,
422
+ command=lambda: self.showInfo(
423
+ 'Optional: Folder where raw files, binaries (movies, micrographs,..) can be found. Used to repair broken links.'))
424
+ btnSearchHelp.grid(row=3, column=3, sticky='e', padx=2, pady=2)
425
+
426
+ self.initial_focus = entryName
427
+ self.initial_focus.focus()
428
+ btnCheck.select()
429
+
430
+ btnFrame = tk.Frame(content)
431
+ btnFrame.columnconfigure(0, weight=1)
432
+ btnFrame.grid(row=4, column=0, sticky='sew', padx=5, pady=(0, 5), columnspan=2)
433
+ btnFrame.config(bg=pw.Config.SCIPION_BG_COLOR)
434
+
435
+ # Create buttons
436
+ btnSelect = HotButton(btnFrame, 'Import', Icon.BUTTON_SELECT, command=self._select)
437
+ btnSelect.grid(row=0, column=0, sticky='e', padx=5, pady=5)
438
+ btnCancel = Button(btnFrame, 'Cancel', Icon.BUTTON_CANCEL, command=self.close)
439
+ btnCancel.grid(row=0, column=1, sticky='e', padx=5, pady=5)
440
+
441
+ def _browseProjectLocation(self, e=None):
442
+ self._browsePath(self.projLocation)
443
+
444
+ def _browseSearchLocation(self, e=None):
445
+ self._browsePath(self.searchLocation)
446
+
447
+ def _browsePath(self, location):
448
+ def onSelect(obj):
449
+ location.set(obj.getPath())
450
+
451
+ v = location.get().strip()
452
+ path = None
453
+ if v:
454
+ v = os.path.dirname(v)
455
+ if os.path.exists(v):
456
+ path = v
457
+ if not path:
458
+ path = self.projectsPath
459
+
460
+ browser = FileBrowserWindow("Browsing",
461
+ self, path=path,
462
+ onSelect=onSelect,
463
+ onlyFolders=True)
464
+ browser.show()
465
+
466
+ def _select(self):
467
+ projName = self.projName.get().strip()
468
+ projLocation = self.projLocation.get().strip()
469
+ copyFiles = self.tkCheckVar.get() != 0
470
+ searchLocation = self.searchLocation.get().strip()
471
+ manager = Manager()
472
+
473
+ # If project name is empty we will use the same name as the source
474
+ if not projName:
475
+ projName = os.path.basename(projLocation)
476
+
477
+ errorMessage = ''
478
+
479
+ # Validate that project location is not empty
480
+ if not projLocation:
481
+ errorMessage = "Project location is empty\n"
482
+
483
+ # Validate that project location exists
484
+ elif not os.path.exists(projLocation):
485
+ errorMessage += "Project location does not exist\n"
486
+
487
+ # Validate that project location is a directory
488
+ elif not os.path.isdir(projLocation):
489
+ errorMessage += "Project location is not a directory\n"
490
+ # Validate that the project location is a scipion project folder
491
+ elif not os.path.exists(os.path.join(projLocation, Project.getDbName())):
492
+ errorMessage += "Project location doesn't look like a scipion folder\n"
493
+
494
+ # Validate that there isn't already a project with the same name
495
+ if manager.hasProject(projName):
496
+ errorMessage += "Project [%s] already exists\n" % projName
497
+
498
+ # Validate that search location exists
499
+ if searchLocation:
500
+ if not os.path.exists(searchLocation):
501
+ errorMessage += "Raw files location does not exist\n"
502
+ # Validate that search location is a directory
503
+ elif not os.path.isdir(searchLocation):
504
+ errorMessage += "Raw files location is not a directory\n"
505
+
506
+ if errorMessage:
507
+ showError("Validation error", errorMessage, self.root)
508
+ else:
509
+ self.parent.importProject(projLocation, copyFiles, projName, searchLocation)
510
+ self.close()