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,726 @@
1
+ # **************************************************************************
2
+ # *
3
+ # * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es) [1]
4
+ # * Jose Gutierrez (jose.gutierrez@cnb.csic.es) [2]
5
+ # *
6
+ # * [1] SciLifeLab, Stockholm University
7
+ # * [2] Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
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
+ In this module a simple ObjectBrowser is implemented.
28
+ This class can be subclasses to extend its functionality.
29
+ A concrete use of ObjectBrowser is FileBrowser, where the
30
+ elements to inspect and preview are files.
31
+ """
32
+ import os.path
33
+ import stat
34
+ import tkinter as tk
35
+ import time
36
+ import logging
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ import pyworkflow.utils as pwutils
41
+ from . import gui, LIST_TREEVIEW
42
+ from .tree import BoundTree, TreeProvider
43
+ from .text import TaggedText, openTextFileEditor
44
+ from .widgets import Button, HotButton
45
+ from .. import Config
46
+
47
+ PARENT_FOLDER = ".."
48
+
49
+
50
+ class ObjectBrowser(tk.Frame):
51
+ """ This class will implement a simple object browser.
52
+ Basically, it will display a list of elements at the left
53
+ panel and can display a preview and description on the
54
+ right panel for the selected element.
55
+ An ObjectView will be used to grab information for
56
+ each element such as: icon, preview and description.
57
+ A TreeProvider will be used to populate the list (Tree).
58
+ """
59
+
60
+ def __init__(self, parent, treeProvider,
61
+ showPreview=True, showPreviewTop=True,
62
+ **args):
63
+ tk.Frame.__init__(self, parent, **args)
64
+ self.treeProvider = treeProvider
65
+ self._lastSelected = None
66
+ gui.configureWeigths(self)
67
+ self.showPreviewTop = showPreviewTop
68
+ # The main layout will be two panes,
69
+ # At the left containing the elements list
70
+ # and the right containing the preview and description
71
+ p = tk.PanedWindow(self, orient=tk.HORIZONTAL)
72
+ p.grid(row=0, column=0, sticky='news')
73
+
74
+ leftPanel = tk.Frame(p)
75
+ self._fillLeftPanel(leftPanel)
76
+ p.add(leftPanel, padx=5, pady=5)
77
+ p.paneconfig(leftPanel, minsize=300)
78
+
79
+ if showPreview:
80
+ rightPanel = tk.Frame(p)
81
+ self._fillRightPanel(rightPanel)
82
+ p.add(rightPanel, padx=5, pady=5)
83
+ p.paneconfig(rightPanel, minsize=200)
84
+
85
+ # Register a callback when the item is clicked
86
+ self.tree.itemClick = self._itemClicked
87
+
88
+ def _fillLeftPanel(self, frame):
89
+ gui.configureWeigths(frame)
90
+ self.tree = BoundTree(frame, self.treeProvider, style=LIST_TREEVIEW)
91
+ self.tree.grid(row=0, column=0, sticky='news')
92
+ self.itemConfig = self.tree.itemConfig
93
+ self.getImage = self.tree.getImage
94
+
95
+ def _fillRightPanel(self, frame):
96
+ frame.columnconfigure(0, weight=1)
97
+
98
+ if self.showPreviewTop:
99
+ top = tk.Frame(frame)
100
+ top.grid(row=0, column=0, sticky='news')
101
+ frame.rowconfigure(0, weight=3)
102
+ gui.configureWeigths(top)
103
+ top.rowconfigure(0, minsize=200)
104
+ self._fillRightTop(top)
105
+
106
+ bottom = tk.Frame(frame)
107
+ bottom.grid(row=1, column=0, sticky='news')
108
+ frame.rowconfigure(1, weight=1)
109
+ gui.configureWeigths(bottom)
110
+ bottom.rowconfigure(1, weight=1)
111
+ self._fillRightBottom(bottom)
112
+
113
+ def _fillRightTop(self, top):
114
+ self.noImage = self.getImage(pwutils.Icon.NO_IMAGE_128)
115
+ self.label = tk.Label(top, image=self.noImage)
116
+ self.label.grid(row=0, column=0, sticky='news')
117
+
118
+ def _fillRightBottom(self, bottom):
119
+ self.text = TaggedText(bottom, width=40, height=15, bg=Config.SCIPION_BG_COLOR,
120
+ takefocus=0)
121
+ self.text.grid(row=0, column=0, sticky='news')
122
+
123
+ def _itemClicked(self, obj):
124
+ self._lastSelected = obj
125
+ img, desc = self.treeProvider.getObjectPreview(obj)
126
+ # Update image preview
127
+ if self.showPreviewTop:
128
+ if isinstance(img, (str, pwutils.SpriteImage)):
129
+ img = self.getImage(img)
130
+ if img is None:
131
+ img = self.noImage
132
+ self.label.config(image=img)
133
+
134
+ # Update text preview
135
+ self.text.setReadOnly(False)
136
+ self.text.clear()
137
+
138
+ if desc is not None:
139
+ self.text.addText(desc)
140
+ self.text.setReadOnly(True)
141
+ if hasattr(self, 'entryLabel') and not self._lastSelected.isDir():
142
+ self.entryVar.set(self._lastSelected.getFileName())
143
+
144
+ def getSelected(self):
145
+ """ Return the selected object. """
146
+ return self._lastSelected
147
+
148
+
149
+ # ------------ Classes and Functions related to File browsing --------------
150
+
151
+ class FileInfo(object):
152
+ """ This class will store some information about a file.
153
+ It will serve to display files items in the Tree.
154
+ """
155
+
156
+ def __init__(self, path, filename):
157
+ self._fullpath = os.path.join(path, filename)
158
+ self._filename = filename
159
+ if os.path.exists(self._fullpath):
160
+ self._stat = os.stat(self._fullpath)
161
+ else:
162
+ self._stat = None
163
+
164
+ def isDir(self):
165
+ return stat.S_ISDIR(self._stat.st_mode) if self._stat else False
166
+
167
+ def getFileName(self):
168
+ return self._filename
169
+
170
+ def getPath(self):
171
+ return self._fullpath
172
+
173
+ def getSize(self):
174
+ return self._stat.st_size if self._stat else 0
175
+
176
+ def getSizeStr(self):
177
+ """ Return a human readable string of the file size."""
178
+ return pwutils.prettySize(self.getSize()) if self._stat else '0'
179
+
180
+ def getDateStr(self):
181
+ return pwutils.dateStr(self.getDate()) if self._stat else '0'
182
+
183
+ def getDate(self):
184
+ return self._stat.st_mtime if self._stat else 0
185
+
186
+ def isLink(self):
187
+ return os.path.islink(self._fullpath)
188
+
189
+
190
+ class FileHandler(object):
191
+ """ This class will be used to get the icon, preview and info
192
+ from the different types of objects.
193
+ It should be used with FileTreeProvider, where different
194
+ types of handlers can be registered.
195
+ """
196
+
197
+ def getFileIcon(self, objFile):
198
+ """ Return the icon name for a given file. """
199
+ if objFile.isDir():
200
+ icon = pwutils.Icon.FOLDER if not objFile.isLink() else pwutils.Icon.FOLDER_LINK
201
+ else:
202
+ icon = pwutils.Icon.FILE if not objFile.isLink() else pwutils.Icon.FILE_LINK
203
+
204
+ return icon
205
+
206
+ def getFilePreview(self, objFile):
207
+ """ Return the preview image and description for the specific object."""
208
+ if objFile.isDir():
209
+ return pwutils.Icon.FOLDER_OPEN, None
210
+ return None, None
211
+
212
+ def getFileActions(self, objFile):
213
+ """ Return actions that can be done with this object.
214
+ Actions will be displayed in the context menu
215
+ and the first one will be the default when double-click.
216
+ """
217
+ return []
218
+
219
+
220
+ class TextFileHandler(FileHandler):
221
+ def __init__(self, textIcon):
222
+ FileHandler.__init__(self)
223
+ self._icon = textIcon
224
+
225
+ def getFileIcon(self, objFile):
226
+ return self._icon
227
+
228
+
229
+ class SqlFileHandler(FileHandler):
230
+ def getFileIcon(self, objFile):
231
+ return pwutils.Icon.DB
232
+
233
+
234
+ class FileTreeProvider(TreeProvider):
235
+ """ Populate a tree with files and folders of a given path """
236
+
237
+ _FILE_HANDLERS = {}
238
+ _DEFAULT_HANDLER = FileHandler()
239
+ FILE_COLUMN = 'File'
240
+ SIZE_COLUMN = 'Size'
241
+
242
+ @classmethod
243
+ def registerFileHandler(cls, fileHandler, *extensions):
244
+ """ Register a FileHandler for a given file extension.
245
+ Params:
246
+ fileHandler: the FileHandler that will take care of extensions.
247
+ *extensions: the extensions list that will be associated to this
248
+ FileHandler.
249
+ """
250
+ for fileExt in extensions:
251
+ handlersList = cls._FILE_HANDLERS.get(fileExt, [])
252
+ handlersList.append(fileHandler)
253
+ cls._FILE_HANDLERS[fileExt] = handlersList
254
+
255
+ def __init__(self, currentDir=None, showHidden=False, onlyFolders=False):
256
+ TreeProvider.__init__(self, sortingColumnName=self.FILE_COLUMN)
257
+ self._currentDir = os.path.abspath(currentDir)
258
+ self._showHidden = showHidden
259
+ self._onlyFolders = onlyFolders
260
+ self.getColumns = lambda: [(self.FILE_COLUMN, 300),
261
+ (self.SIZE_COLUMN, 70), ('Time', 150)]
262
+
263
+ def getFileHandlers(self, obj):
264
+ filename = obj.getFileName()
265
+ fileExt = pwutils.getExt(filename)
266
+ return self._FILE_HANDLERS.get(fileExt, [self._DEFAULT_HANDLER])
267
+
268
+ def getObjectInfo(self, obj):
269
+ filename = obj.getFileName()
270
+ fileHandlers = self.getFileHandlers(obj)
271
+ icon = fileHandlers[0].getFileIcon(obj)
272
+
273
+ info = {'key': filename, 'text': filename,
274
+ 'values': (obj.getSizeStr(), obj.getDateStr()), 'image': icon
275
+ }
276
+
277
+ return info
278
+
279
+ def getObjectPreview(self, obj):
280
+
281
+ try:
282
+ # Look for any preview available
283
+ fileHandlers = self.getFileHandlers(obj)
284
+
285
+ for fileHandler in fileHandlers:
286
+ preview = fileHandler.getFilePreview(obj)
287
+ if preview:
288
+ img, desc = preview
289
+ if obj.isLink():
290
+ desc = "Is a link" if desc is None else desc + "\nIs a link."
291
+ return img, desc
292
+
293
+ except Exception as e:
294
+ msg = "Couldn't get preview for %s" % obj
295
+ logger.error(msg, exc_info=e)
296
+ return None, msg + " See scipion GUI log window for more details."
297
+
298
+ def getObjectActions(self, obj):
299
+ fileHandlers = self.getFileHandlers(obj)
300
+ actions = []
301
+ for fileHandler in fileHandlers:
302
+ actions += fileHandler.getFileActions(obj)
303
+ # Always allow the option to open as text
304
+ # specially useful for unknown formats
305
+ fn = obj.getPath()
306
+ actions.append(("Open external Editor",
307
+ lambda: openTextFileEditor(fn), pwutils.Icon.ACTION_REFERENCES))
308
+
309
+ return actions
310
+
311
+ def getObjects(self):
312
+
313
+ fileInfoList = []
314
+ if not self._currentDir == pwutils.ROOT:
315
+ fileInfoList.append(FileInfo(self._currentDir, PARENT_FOLDER))
316
+
317
+ try:
318
+ # This might fail if there is not granted
319
+ files = os.listdir(self._currentDir)
320
+
321
+ for f in files:
322
+
323
+ fullPath = os.path.join(self._currentDir, f)
324
+ # If f is a file and only need folders
325
+ if self._onlyFolders and not os.path.isdir(fullPath):
326
+ continue
327
+
328
+ # Do not add hidden files if not requested
329
+ if not self._showHidden and f.startswith('.'):
330
+ continue
331
+
332
+ # All ok...add item.
333
+ fileInfoList.append(FileInfo(self._currentDir, f))
334
+ except Exception as e:
335
+ logger.info("Can't list files at " + self._currentDir, e)
336
+
337
+ # Sort objects
338
+ fileInfoList.sort(key=self.fileKey, reverse=not self.isSortingAscending())
339
+
340
+ return fileInfoList
341
+
342
+ def fileKey(self, f):
343
+ sortDict = {self.FILE_COLUMN: 'getFileName',
344
+ self.SIZE_COLUMN: 'getSize'}
345
+ return getattr(f, sortDict.get(self._sortingColumnName, 'getDate'))()
346
+
347
+ def getDir(self):
348
+ return self._currentDir
349
+
350
+ def setDir(self, newPath):
351
+ self._currentDir = newPath
352
+
353
+
354
+ # Some constants for the type of selection
355
+ # when the file browser is opened
356
+
357
+ SELECT_NONE = 0 # No selection, just browse files
358
+ SELECT_FILE = 1
359
+ SELECT_FOLDER = 2
360
+ SELECT_PATH = 3 # Can be either file or folder
361
+
362
+
363
+ class FileBrowser(ObjectBrowser):
364
+ """ The FileBrowser is a particular class of ObjectBrowser (Tk.Frame)
365
+ where the "objects" are just files and directories.
366
+ """
367
+
368
+ _lastSelectedFile = None
369
+ "Class scope attribute to keep the lastSelected file"
370
+
371
+ _fileSelectedAtLoading = None
372
+ "Class scope attribute to offer *Recent* shortcut"
373
+
374
+ def __init__(self, parent, initialDir='.',
375
+ selectionType=SELECT_FILE,
376
+ selectionSingle=True,
377
+ allowFilter=True,
378
+ filterFunction=None,
379
+ previewDim=144,
380
+ showHidden=False, # Show hidden files or not?
381
+ selectButton='Select', # Change the Select button text
382
+ entryLabel=None, # Display an entry for some input
383
+ entryValue='', # Display a value in the entry field
384
+ showInfo=None, # Used to notify errors or messages
385
+ shortCuts=None, # Shortcuts to common locations/paths
386
+ onlyFolders=False
387
+ ):
388
+ """
389
+
390
+ :param parent: Parent tkinter window.
391
+ :param initialDir: Folder to show when loading the dialog.
392
+ :param selectionType: Any of SELECT_NONE, SELECT_FILE, SELECT_FOLDER, SELECT_PATH.
393
+ :param showHidden: Pass True to show hidden files.
394
+ :param selectButton: text for the select button. Defaults to *Select*.
395
+ :param entryLabel: text for the entry widget. Default None. There will be no entry.
396
+ :param entryValue: default value for the entry. Needs entryLabel.
397
+ :param showInfo: callback to show a string message, otherwise _showInfo will be used.
398
+ :param shortCuts: list of extra :class:`ShortCut`
399
+ :param onlyFolders: Pass True to show only folders.
400
+ """
401
+ self.pathVar = tk.StringVar()
402
+ self.pathVar.set(os.path.abspath(initialDir))
403
+ self.pathEntry = None
404
+ self.previousSearch = None
405
+ self.previousSearchTS = None
406
+ self.shortCuts = shortCuts
407
+ self._provider = FileTreeProvider(initialDir, showHidden, onlyFolders)
408
+ self.selectButton = selectButton
409
+ self.entryLabel = entryLabel
410
+ self.entryVar = tk.StringVar()
411
+ self.entryVar.set(entryValue)
412
+
413
+ self.showInfo = showInfo or self._showInfo
414
+
415
+ ObjectBrowser.__init__(self, parent, self._provider)
416
+
417
+ # focuses on the browser in order to allow to move with the keyboard
418
+ self._goDir(os.path.abspath(initialDir))
419
+
420
+ buttonsFrame = tk.Frame(self)
421
+ self._fillButtonsFrame(buttonsFrame)
422
+ buttonsFrame.grid(row=1, column=0)
423
+
424
+ # Callback to be called "on Select" button key press
425
+ self.onSelect=None
426
+
427
+ def _showInfo(self, msg):
428
+ """ Default way (logger.info to console) to show a message with a given info.
429
+ """
430
+ logger.info(msg)
431
+
432
+ def _fillLeftPanel(self, frame):
433
+ """ Redefine this method to include a buttons toolbar and
434
+ also include a filter bar at the bottom of the Tree.
435
+ """
436
+ # Tree with files
437
+ frame.columnconfigure(0, weight=1)
438
+
439
+ treeFrame = tk.Frame(frame)
440
+ ObjectBrowser._fillLeftPanel(self, treeFrame)
441
+ # Register the double-click event
442
+ self.tree.itemDoubleClick = self._itemDoubleClick
443
+ # Register keypress event
444
+ self.tree.itemKeyPressed = self._itemKeyPressed
445
+
446
+ treeRow = 3
447
+ treeFrame.grid(row=treeRow, column=0, sticky='news')
448
+ # Toolbar frame
449
+ toolbarFrame = tk.Frame(frame)
450
+ self._fillToolbar(toolbarFrame)
451
+ toolbarFrame.grid(row=0, column=0, sticky='new')
452
+
453
+ pathFrame = tk.Frame(frame)
454
+ pathLabel = tk.Label(pathFrame, text='Path')
455
+ pathLabel.grid(row=0, column=0, padx=0, pady=3)
456
+ pathEntry = tk.Entry(pathFrame, bg='white', width=65,
457
+ textvariable=self.pathVar, font=gui.getDefaultFont())
458
+ pathEntry.grid(row=0, column=1, sticky='new', pady=3)
459
+ pathEntry.bind("<Return>", self._onEnterPath)
460
+ pathEntry.bind("<KP_Enter>", self._onEnterPath)
461
+ self.pathEntry = pathEntry
462
+ pathFrame.grid(row=1, column=0, sticky='new')
463
+
464
+ # Entry frame, could be used for filter
465
+ if self.entryLabel:
466
+ entryFrame = tk.Frame(frame)
467
+ entryFrame.grid(row=2, column=0, sticky='new')
468
+ tk.Label(entryFrame, text=self.entryLabel).grid(row=0, column=0,
469
+ sticky='nw', pady=3)
470
+ tk.Entry(entryFrame,
471
+ textvariable=self.entryVar,
472
+ bg=Config.SCIPION_BG_COLOR,
473
+ width=65,
474
+ font=gui.getDefaultFont()).grid(row=0, column=1, sticky='nw', pady=3)
475
+
476
+ frame.rowconfigure(treeRow, weight=1)
477
+
478
+ def _addButton(self, frame, text, image, command):
479
+ btn = tk.Label(frame, text=text, image=self.getImage(image),
480
+ compound=tk.LEFT, cursor='hand2')
481
+ btn.bind('<Button-1>', command)
482
+ btn.grid(row=0, column=self._col, sticky='nw',
483
+ padx=(0, 5), pady=5)
484
+ self._col += 1
485
+
486
+ def _fillToolbar(self, frame):
487
+ """ Fill the toolbar frame with some buttons. """
488
+ self._col = 0
489
+
490
+ self._addButton(frame, 'Refresh', pwutils.Icon.ACTION_REFRESH,
491
+ self._actionRefresh)
492
+ self._addButton(frame, 'Home', pwutils.Icon.HOME, self._actionHome)
493
+ self._addButton(frame, 'Launch folder', pwutils.Icon.ROCKET,
494
+ self._actionLaunchFolder)
495
+ self._addButton(frame, 'Working dir', pwutils.Icon.ACTION_BROWSE,
496
+ self._actionWorkingDir)
497
+ self._addButton(frame, 'Up', pwutils.Icon.ARROW_UP, self._actionUp)
498
+
499
+ self._fileSelectedAtLoading = FileBrowser._lastSelectedFile
500
+
501
+ if self._fileSelectedAtLoading is not None:
502
+ self._addButton(frame, 'Recent', None, self._actionRecent)
503
+
504
+ # Add shortcuts
505
+ self._addShortCuts(frame)
506
+
507
+ def _addShortCuts(self, frame):
508
+ """ Add shortcuts if available"""
509
+ if self.shortCuts:
510
+ for shortCut in self.shortCuts:
511
+ self._addButton(frame,
512
+ shortCut.name,
513
+ shortCut.icon,
514
+ lambda e: self._goDir(shortCut.path))
515
+
516
+ def _fillButtonsFrame(self, frame):
517
+ """ Add button to the bottom frame if the selectMode
518
+ is distinct from SELECT_NONE.
519
+ """
520
+ Button(frame, "Close", pwutils.Icon.BUTTON_CLOSE,
521
+ command=self._close).grid(row=0, column=0, padx=(0, 5))
522
+ if self.selectButton:
523
+ HotButton(frame, self.selectButton, pwutils.Icon.BUTTON_SELECT,
524
+ command=self._select).grid(row=0, column=1)
525
+
526
+ def _actionRefresh(self, e=None):
527
+ self.tree.update()
528
+
529
+ def _goDir(self, newDir):
530
+
531
+ newDir = os.path.abspath(newDir)
532
+
533
+ # Add a final "/" to the path: abspath is removing it except for "/"
534
+ if not newDir.endswith(os.path.sep):
535
+ newDir += os.path.sep
536
+
537
+ self.pathVar.set(newDir)
538
+ self.pathEntry.icursor(len(newDir))
539
+ self.treeProvider.setDir(newDir)
540
+ self.tree.update()
541
+ self.tree.focus_set()
542
+
543
+ itemKeyToFocus = PARENT_FOLDER
544
+ if PARENT_FOLDER not in self.tree._objDict:
545
+ itemKeyToFocus = self.tree.get_children()[0]
546
+
547
+ # Focusing on a item, but nothing is selected
548
+ # Current dir remains in _lastSelected
549
+ self._lastSelected = FileInfo(os.path.dirname(newDir),
550
+ os.path.basename(newDir))
551
+
552
+ FileBrowser._lastSelectedFile = self._lastSelected
553
+
554
+ self.tree.focus(itemKeyToFocus)
555
+
556
+ def _actionUp(self, e=None):
557
+ parentFolder = pwutils.getParentFolder(self.treeProvider.getDir())
558
+ self._goDir(parentFolder)
559
+
560
+ def _actionRecent(self, e=None):
561
+ self._goDir(self._fileSelectedAtLoading.getPath())
562
+
563
+ def _actionHome(self, e=None):
564
+ self._goDir(pwutils.getHomePath())
565
+
566
+ def _actionRoot(self, e=None):
567
+ self._goDir("/")
568
+
569
+ def _actionLaunchFolder(self, e=None):
570
+ self._goDir(Config.SCIPION_CWD)
571
+
572
+ def _actionWorkingDir(self, e=None):
573
+ self._goDir(os.getcwd())
574
+
575
+ def _itemDoubleClick(self, obj):
576
+ if obj.isDir():
577
+ self._goDir(obj.getPath())
578
+ else:
579
+ actions = self._provider.getObjectActions(obj)
580
+ if actions:
581
+ # actions[0] = first Action, [1] = the action callback
582
+ actions[0][1]()
583
+
584
+ def _itemKeyPressed(self, obj, e=None):
585
+
586
+ if e.keysym in [pwutils.KEYSYM.RETURN]:
587
+ self._itemDoubleClick(obj)
588
+ return
589
+
590
+ textToSearch = self._composeTextToSearch(e.char)
591
+
592
+ # locate an item in starting with that letter.
593
+ self._searchItem(textToSearch)
594
+
595
+ def _composeTextToSearch(self, newChar):
596
+
597
+ currentMiliseconds = time.time()
598
+
599
+ if (self.previousSearchTS is not None) and \
600
+ ((currentMiliseconds - self.previousSearchTS) < 0.3):
601
+ newChar = self.previousSearch + newChar
602
+
603
+ self.previousSearch = newChar
604
+ self.previousSearchTS = currentMiliseconds
605
+
606
+ return newChar
607
+
608
+ def _searchItem(self, char):
609
+ """ locate an item in starting with that letter."""
610
+ try:
611
+ self.tree.search(char)
612
+ except Exception as e:
613
+ # seems to raise an exception but selects things right.
614
+ pass
615
+
616
+ def _onEnterPath(self, e=None):
617
+ path = os.path.abspath(self.pathVar.get())
618
+ if os.path.exists(path):
619
+ self._goDir(path)
620
+
621
+ else:
622
+ self.showInfo("Path '%s' does not exists. " % path)
623
+ self.pathEntry.focus()
624
+
625
+ def onClose(self):
626
+ """ This onClose is replaced at init time in the FileBrowserWindow with its own callback"""
627
+ pass
628
+
629
+ def _close(self, e=None):
630
+ """ This _close is bound to the close button"""
631
+ self.onClose()
632
+
633
+ def _select(self, e=None):
634
+
635
+ self._lastSelected = self.getSelected()
636
+
637
+ if self._lastSelected is not None:
638
+ if self.onSelect:
639
+ self.onSelect(self._lastSelected)
640
+ else:
641
+ self.onClose()
642
+ else:
643
+ self.showInfo('Select a valid file/folder')
644
+
645
+ def getEntryValue(self):
646
+ return self.entryVar.get()
647
+
648
+ def getCurrentDir(self):
649
+ return self.treeProvider.getDir()
650
+
651
+
652
+ class ShortCut:
653
+ """ Shortcuts to paths to be displayed in the file browser"""
654
+
655
+ @staticmethod
656
+ def factory(path, name, icon=None, toolTip=""):
657
+ """ Factory method to create shortcuts"""
658
+ return ShortCut(path, name, icon, toolTip)
659
+
660
+ def __init__(self, path, name, icon=None, toolTip=""):
661
+ self.path = path
662
+ self.name = name
663
+ self.icon = icon
664
+ self.toolTip = toolTip
665
+
666
+
667
+ class BrowserWindow(gui.Window):
668
+ """ Windows to hold a browser frame inside. """
669
+
670
+ def __init__(self, title, master=None, **kwargs):
671
+ if 'minsize' not in kwargs:
672
+ kwargs['minsize'] = (800, 400)
673
+ gui.Window.__init__(self, title, master, **kwargs)
674
+
675
+ def setBrowser(self, browser, row=0, column=0):
676
+ self.browser = browser
677
+ browser.grid(row=row, column=column, sticky='news')
678
+ self.itemConfig = browser.tree.itemConfig
679
+
680
+
681
+ STANDARD_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg']
682
+
683
+
684
+ def isStandardImage(filename):
685
+ """ Check if a filename have an standard image extension. """
686
+ fnLower = filename.lower()
687
+ return any(fnLower.endswith(ext) for ext in STANDARD_IMAGE_EXTENSIONS)
688
+
689
+
690
+ class FileBrowserWindow(BrowserWindow):
691
+ """ Windows to hold a file browser frame inside. """
692
+
693
+ lastValue=None
694
+ def __init__(self, title, master=None, path=None,
695
+ onSelect=None, shortCuts=None, **kwargs):
696
+ BrowserWindow.__init__(self, title, master, **kwargs)
697
+ self.registerHandlers()
698
+ browser = FileBrowser(self.root, path,
699
+ showInfo=lambda msg: self.showInfo(msg, "Info"),
700
+ shortCuts=shortCuts,
701
+ **kwargs)
702
+ if onSelect:
703
+ def selected(obj):
704
+ self.close()
705
+ onSelect(obj)
706
+
707
+ browser.onSelect = selected
708
+ browser.onClose = self.close
709
+ self.setBrowser(browser)
710
+
711
+ def getEntryValue(self):
712
+ return self.browser.getEntryValue()
713
+
714
+ def getLastSelection(self):
715
+ return self.browser._lastSelected.getPath()
716
+ def getCurrentDir(self):
717
+ return self.browser.getCurrentDir()
718
+
719
+ def registerHandlers(self):
720
+ register = FileTreeProvider.registerFileHandler # shortcut
721
+
722
+ register(TextFileHandler(pwutils.Icon.TXT_FILE),
723
+ '.txt', '.log', '.out', '.err', '.stdout', '.stderr', '.emx',
724
+ '.json', '.xml', '.pam')
725
+ register(TextFileHandler(pwutils.Icon.PYTHON_FILE), '.py')
726
+ register(SqlFileHandler(), '.sqlite', '.db')