scipion-pyworkflow 3.10.5__py3-none-any.whl → 3.11.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 (127) hide show
  1. pyworkflow/config.py +131 -67
  2. pyworkflow/constants.py +12 -2
  3. pyworkflow/object.py +3 -2
  4. pyworkflow/plugin.py +93 -44
  5. pyworkflow/project/scripts/fix_links.py +4 -1
  6. pyworkflow/resources/showj/arrowDown.png +0 -0
  7. pyworkflow/resources/showj/arrowUp.png +0 -0
  8. pyworkflow/resources/showj/background_section.png +0 -0
  9. pyworkflow/resources/showj/colRowModeOff.png +0 -0
  10. pyworkflow/resources/showj/colRowModeOn.png +0 -0
  11. pyworkflow/resources/showj/delete.png +0 -0
  12. pyworkflow/resources/showj/doc_icon.png +0 -0
  13. pyworkflow/resources/showj/download_icon.png +0 -0
  14. pyworkflow/resources/showj/enabled_gallery.png +0 -0
  15. pyworkflow/resources/showj/galleryViewOff.png +0 -0
  16. pyworkflow/resources/showj/galleryViewOn.png +0 -0
  17. pyworkflow/resources/showj/goto.png +0 -0
  18. pyworkflow/resources/showj/menu.png +0 -0
  19. pyworkflow/resources/showj/separator.png +0 -0
  20. pyworkflow/resources/showj/tableViewOff.png +0 -0
  21. pyworkflow/resources/showj/tableViewOn.png +0 -0
  22. pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  23. pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  24. pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  25. pyworkflow/resources/showj/volumeOff.png +0 -0
  26. pyworkflow/resources/showj/volumeOn.png +0 -0
  27. pyworkflow/viewer.py +23 -1
  28. pyworkflowtests/objects.py +2 -2
  29. pyworkflowtests/protocols.py +1 -3
  30. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +21 -25
  31. scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
  32. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
  33. scipion_pyworkflow-3.11.0.dist-info/entry_points.txt +2 -0
  34. pyworkflow/apps/__init__.py +0 -29
  35. pyworkflow/apps/pw_manager.py +0 -37
  36. pyworkflow/apps/pw_plot.py +0 -51
  37. pyworkflow/apps/pw_project.py +0 -113
  38. pyworkflow/apps/pw_protocol_list.py +0 -143
  39. pyworkflow/apps/pw_protocol_run.py +0 -51
  40. pyworkflow/apps/pw_run_tests.py +0 -267
  41. pyworkflow/apps/pw_schedule_run.py +0 -322
  42. pyworkflow/apps/pw_sleep.py +0 -37
  43. pyworkflow/apps/pw_sync_data.py +0 -439
  44. pyworkflow/apps/pw_viewer.py +0 -78
  45. pyworkflow/gui/__init__.py +0 -36
  46. pyworkflow/gui/browser.py +0 -726
  47. pyworkflow/gui/canvas.py +0 -1190
  48. pyworkflow/gui/dialog.py +0 -977
  49. pyworkflow/gui/form.py +0 -2637
  50. pyworkflow/gui/graph.py +0 -247
  51. pyworkflow/gui/graph_layout.py +0 -271
  52. pyworkflow/gui/gui.py +0 -566
  53. pyworkflow/gui/matplotlib_image.py +0 -233
  54. pyworkflow/gui/plotter.py +0 -247
  55. pyworkflow/gui/project/__init__.py +0 -25
  56. pyworkflow/gui/project/base.py +0 -192
  57. pyworkflow/gui/project/constants.py +0 -139
  58. pyworkflow/gui/project/labels.py +0 -205
  59. pyworkflow/gui/project/project.py +0 -492
  60. pyworkflow/gui/project/searchprotocol.py +0 -154
  61. pyworkflow/gui/project/searchrun.py +0 -181
  62. pyworkflow/gui/project/steps.py +0 -171
  63. pyworkflow/gui/project/utils.py +0 -332
  64. pyworkflow/gui/project/variables.py +0 -179
  65. pyworkflow/gui/project/viewdata.py +0 -472
  66. pyworkflow/gui/project/viewprojects.py +0 -510
  67. pyworkflow/gui/project/viewprotocols.py +0 -2093
  68. pyworkflow/gui/project/viewprotocols_extra.py +0 -560
  69. pyworkflow/gui/text.py +0 -771
  70. pyworkflow/gui/tooltip.py +0 -185
  71. pyworkflow/gui/tree.py +0 -684
  72. pyworkflow/gui/widgets.py +0 -307
  73. pyworkflow/mapper/__init__.py +0 -26
  74. pyworkflow/mapper/mapper.py +0 -222
  75. pyworkflow/mapper/sqlite.py +0 -1578
  76. pyworkflow/mapper/sqlite_db.py +0 -145
  77. pyworkflow/project/__init__.py +0 -31
  78. pyworkflow/project/config.py +0 -454
  79. pyworkflow/project/manager.py +0 -180
  80. pyworkflow/project/project.py +0 -2010
  81. pyworkflow/protocol/__init__.py +0 -38
  82. pyworkflow/protocol/bibtex.py +0 -48
  83. pyworkflow/protocol/constants.py +0 -87
  84. pyworkflow/protocol/executor.py +0 -455
  85. pyworkflow/protocol/hosts.py +0 -313
  86. pyworkflow/protocol/launch.py +0 -270
  87. pyworkflow/protocol/package.py +0 -42
  88. pyworkflow/protocol/params.py +0 -741
  89. pyworkflow/protocol/protocol.py +0 -2582
  90. pyworkflow/tests/__init__.py +0 -29
  91. pyworkflow/tests/test_utils.py +0 -25
  92. pyworkflow/tests/tests.py +0 -341
  93. pyworkflow/utils/__init__.py +0 -38
  94. pyworkflow/utils/dataset.py +0 -414
  95. pyworkflow/utils/echo.py +0 -104
  96. pyworkflow/utils/graph.py +0 -169
  97. pyworkflow/utils/log.py +0 -284
  98. pyworkflow/utils/path.py +0 -528
  99. pyworkflow/utils/process.py +0 -132
  100. pyworkflow/utils/profiler.py +0 -92
  101. pyworkflow/utils/progressbar.py +0 -154
  102. pyworkflow/utils/properties.py +0 -631
  103. pyworkflow/utils/reflection.py +0 -129
  104. pyworkflow/utils/utils.py +0 -879
  105. pyworkflow/utils/which.py +0 -229
  106. pyworkflow/webservices/__init__.py +0 -8
  107. pyworkflow/webservices/config.py +0 -11
  108. pyworkflow/webservices/notifier.py +0 -162
  109. pyworkflow/webservices/repository.py +0 -59
  110. pyworkflow/webservices/workflowhub.py +0 -74
  111. pyworkflowtests/tests/__init__.py +0 -0
  112. pyworkflowtests/tests/test_canvas.py +0 -72
  113. pyworkflowtests/tests/test_domain.py +0 -45
  114. pyworkflowtests/tests/test_logs.py +0 -74
  115. pyworkflowtests/tests/test_mappers.py +0 -392
  116. pyworkflowtests/tests/test_object.py +0 -507
  117. pyworkflowtests/tests/test_project.py +0 -42
  118. pyworkflowtests/tests/test_protocol_execution.py +0 -135
  119. pyworkflowtests/tests/test_protocol_export.py +0 -78
  120. pyworkflowtests/tests/test_protocol_output.py +0 -158
  121. pyworkflowtests/tests/test_streaming.py +0 -47
  122. pyworkflowtests/tests/test_utils.py +0 -210
  123. scipion_pyworkflow-3.10.5.dist-info/RECORD +0 -140
  124. scipion_pyworkflow-3.10.5.dist-info/dependency_links.txt +0 -1
  125. scipion_pyworkflow-3.10.5.dist-info/entry_points.txt +0 -5
  126. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info/licenses}/LICENSE.txt +0 -0
  127. {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/top_level.txt +0 -0
pyworkflow/gui/dialog.py DELETED
@@ -1,977 +0,0 @@
1
- # **************************************************************************
2
- # *
3
- # * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
4
- # * Jose Gutierrez (jose.gutierrez@cnb.csic.es)
5
- # *
6
- # * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
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
- Module to handling Dialogs
29
- some code was taken from tkSimpleDialog
30
- """
31
- import os.path
32
- import tkinter as tk
33
- import traceback
34
- from tkcolorpicker import askcolor as _askColor
35
- from pyworkflow import Config
36
- from pyworkflow.exceptions import PyworkflowException
37
- from pyworkflow.utils import Message, Icon, Color
38
- from . import gui, Window, widgets, configureWeigths, LIST_TREEVIEW, defineStyle, ToolTip, getDefaultFont
39
- from .tree import BoundTree, Tree
40
- from .text import Text, TaggedText
41
-
42
- # Possible result values for a Dialog
43
- from .. import TK
44
-
45
- RESULT_YES = 0
46
- RESULT_NO = 1
47
- RESULT_CANCEL = 2
48
- RESULT_RUN_SINGLE = 3
49
- RESULT_RUN_ALL = 4
50
- RESULT_CLOSE = 5
51
-
52
-
53
- class Dialog(tk.Toplevel):
54
- _images = {} # Images cache
55
- """Implementation of our own dialog to display messages
56
- It will have by default a three buttons: YES, NO and CANCEL
57
- Subclasses can rename the labels of the buttons like: OK, CLOSE or others
58
- The buttons(and theirs order) can be changed.
59
- An image name can be passed to display left to the message.
60
- """
61
-
62
- def __init__(self, parent, title, lockGui=True, **kwargs):
63
- """Initialize a dialog.
64
- Arguments:
65
- parent -- a parent window (the application window)
66
- title -- the dialog title
67
- **args accepts:
68
- buttons -- list of buttons tuples containing which buttons to display
69
- """
70
-
71
- if parent is None:
72
- parent = tk.Tk()
73
- parent.withdraw()
74
- gui.setCommonFonts()
75
- # invoke the button on the return key
76
- parent.bind_class("Button", "<Key-Return>", lambda event: event.widget.invoke())
77
-
78
- tk.Toplevel.__init__(self, parent)
79
-
80
- defineStyle()
81
-
82
- self.withdraw() # remain invisible for now
83
- # If the master is not viewable, don't
84
- # make the child transient, or else it
85
- # would be opened withdrawn
86
- if parent.winfo_viewable() and lockGui:
87
- self.transient(parent)
88
-
89
- if title:
90
- self.title(title)
91
-
92
- self.parent = parent
93
-
94
- # Default to CANCEL so if window is "Closed" behaves the same.
95
- self.result = RESULT_CANCEL
96
- self.initial_focus = None
97
-
98
- bodyFrame = tk.Frame(self)
99
- # Call subclass method body to create that region
100
- self.body(bodyFrame)
101
- bodyFrame.grid(row=0, column=0, sticky='news',
102
- padx=5, pady=5)
103
-
104
- # Frame for the info/message label
105
- infoFrame = tk.Frame(self)
106
- infoFrame.grid(row=1, column=0, sticky='sew',
107
- padx=5, pady=(0, 5))
108
- self.floatingMessage = tk.Label(infoFrame, text="", fg=Config.SCIPION_MAIN_COLOR)
109
- self.floatingMessage.grid(row=0, column=0, sticky='news')
110
-
111
- # Create buttons
112
- self.icons = kwargs.get('icons',
113
- {RESULT_YES: Icon.BUTTON_SELECT,
114
- RESULT_NO: Icon.BUTTON_CLOSE,
115
- RESULT_CANCEL: Icon.BUTTON_CANCEL,
116
- RESULT_CLOSE: Icon.BUTTON_CLOSE})
117
-
118
- self.buttons = kwargs.get('buttons', [('OK', RESULT_YES),
119
- ('Cancel', RESULT_CANCEL)])
120
- self.defaultButton = kwargs.get('default', 'OK')
121
-
122
- # Frame for buttons
123
- btnFrame = tk.Frame(self)
124
- self.buttonbox(btnFrame)
125
- btnFrame.grid(row=2, column=0, sticky='sew',
126
- padx=5, pady=(0, 5))
127
-
128
- gui.configureWeigths(self)
129
-
130
- if self.initial_focus is None:
131
- self.initial_focus = self
132
-
133
- self.protocol("WM_DELETE_WINDOW", self.cancel)
134
-
135
- if self.parent is not None:
136
- position = kwargs.get('position', (parent.winfo_rootx() + 50,
137
- parent.winfo_rooty() + 50))
138
- self.geometry("+%d+%d" % position)
139
-
140
- self.deiconify() # become visible now
141
- self.initial_focus.focus_set()
142
- # Pablo: I've commented this when migrating to python3 since I was getting and exception:
143
- # window ".139897767953072.139897384058440" was deleted before its visibility changed
144
- # wait for window to appear on screen before calling grab_set
145
- self.wait_visibility()
146
- if lockGui:
147
- self.grab_set()
148
- self.wait_window(self)
149
-
150
- def getRoot(self):
151
- return self
152
-
153
- def destroy(self):
154
- """Destroy the window"""
155
- self.initial_focus = None
156
- tk.Toplevel.destroy(self)
157
-
158
- #
159
- # construction hooks
160
-
161
- def body(self, master):
162
- """create dialog body.
163
- return widget that should have initial focus.
164
- This method should be overridden, and is called
165
- by the __init__ method.
166
- """
167
- pass
168
-
169
- def _createButton(self, frame, text, result):
170
- icon = None
171
- if result in self.icons.keys():
172
- icon = self.getImage(self.icons[result])
173
- return tk.Button(frame, text=text, image=icon,
174
- compound=tk.LEFT,
175
- command=lambda: self._handleResult(result))
176
-
177
- def buttonbox(self, btnFrame):
178
- frame = tk.Frame(btnFrame)
179
- btnFrame.columnconfigure(0, weight=1)
180
- frame.grid(row=0, column=0)
181
- col = 0
182
- for btnLabel, btnResult in self.buttons:
183
- btn = self._createButton(frame, btnLabel, btnResult)
184
- btn.grid(row=0, column=col, padx=5, pady=5)
185
- if (btnLabel == self.defaultButton and
186
- self.initial_focus is None):
187
- self.initial_focus = btn
188
- col += 1
189
- self.bind("<Return>", self._handleReturn)
190
- self.bind("<KP_Enter>", self._handleReturn)
191
- self.bind("<Escape>", lambda e: self._handleResult(RESULT_CANCEL))
192
-
193
- def _handleResult(self, resultValue):
194
- """This method will be called when any button is pressed.
195
- It will set the resultValue associated with the button
196
- and close the Dialog"""
197
- self.result = resultValue
198
- noCancel = self.result != RESULT_CANCEL and self.result != RESULT_CLOSE
199
-
200
- callBack = self.validate if noCancel else self.validateClose
201
- if not callBack():
202
- self.initial_focus.focus_set() # put focus back
203
- return
204
-
205
- self.withdraw()
206
- self.update_idletasks()
207
-
208
- try:
209
- if noCancel:
210
- self.apply()
211
- finally:
212
- self.cancel()
213
-
214
- def _handleReturn(self, e=None):
215
- """Handle press return key"""
216
- # Check which of the buttons is the default
217
- for button, result in self.buttons:
218
- if self.defaultButton == button:
219
- self._handleResult(result)
220
-
221
- def cancel(self, event=None):
222
- # put focus back to the parent window
223
- if self.parent is not None:
224
- self.parent.focus_set()
225
- self.destroy()
226
-
227
- #
228
- # command hooks
229
-
230
- def validate(self):
231
- """validate the data
232
- This method is called automatically to validate the data before the
233
- dialog is destroyed. By default, it always validates OK.
234
- """
235
- return 1 # override
236
-
237
- def validateClose(self):
238
- return True
239
-
240
- def apply(self):
241
- """process the data
242
- This method is called automatically to process the data, *after*
243
- the dialog is destroyed. By default, it does nothing.
244
- """
245
- pass # override
246
-
247
- def getImage(self, imgName):
248
- """A shortcut to get an image from its name"""
249
- return gui.getImage(imgName)
250
-
251
- def getResult(self):
252
- return self.result
253
-
254
- def resultYes(self):
255
- return self.result == RESULT_YES
256
-
257
- def resultNo(self):
258
- return self.result == RESULT_NO
259
-
260
- def resultCancel(self):
261
- return self.result == RESULT_CANCEL
262
-
263
- def info(self, message):
264
- """ Shows a info message for long running processes to inform the user GUI is not frozen"""
265
- self.floatingMessage.config(text=message)
266
-
267
- ### Basic GUI helper methods
268
- def _addButton(self, frame, callback, text="", icon=None, row=0, col=0, tooltip=None, shortcut=""):
269
- """ Adds a label button"""
270
- btn = tk.Label(frame, text=text,
271
- image=self.getImage(icon),
272
- compound=tk.LEFT, cursor='hand2')
273
- btn.grid(row=row, column=col, sticky='nw', padx=(5, 0), pady=(5, 0))
274
- btn.bind('<Button-1>', callback)
275
- if tooltip:
276
- tooltip = tooltip + ' (%s)' % shortcut if shortcut else tooltip
277
- ToolTip(btn, tooltip, delay=150)
278
- if shortcut:
279
- self.bind(shortcut, callback)
280
-
281
-
282
- def fillMessageText(text, message):
283
- # Insert lines of text
284
- if isinstance(message, list):
285
- lines = message
286
- else:
287
- lines = message.splitlines()
288
- text.setReadOnly(False)
289
- text.clear()
290
- w = 0
291
- for l in lines:
292
- w = max(w, len(l))
293
- text.addLine(l)
294
- w = min(w + 5, 80)
295
- h = min(len(lines) + 3, 30)
296
- text.config(height=h, width=w)
297
- text.addNewline()
298
- text.setReadOnly(True)
299
-
300
-
301
- def createMessageBody(bodyFrame, message, image,
302
- frameBg=Config.SCIPION_BG_COLOR,
303
- textBg=Config.SCIPION_BG_COLOR,
304
- textPad=5):
305
- """ Create a Text containing the message.
306
- Params:
307
- bodyFrame: tk.Frame to be filled.
308
- msg: a str or list with the lines.
309
- """
310
- bodyFrame.config(bg=frameBg, bd=0)
311
- text = TaggedText(bodyFrame, bg=textBg, bd=0, highlightthickness=0)
312
- # Insert image
313
- if image:
314
- label = tk.Label(bodyFrame, image=image, bg=textBg, bd=0)
315
- label.grid(row=0, column=0, sticky='nw')
316
-
317
- text.frame.grid(row=0, column=1, sticky='news',
318
- padx=textPad, pady=textPad)
319
- fillMessageText(text, message)
320
- bodyFrame.rowconfigure(0, weight=1)
321
- bodyFrame.columnconfigure(1, weight=1)
322
-
323
- return text
324
-
325
-
326
- class MessageDialog(Dialog):
327
- """Dialog subclasses to show message info, questions or errors.
328
- It can display an icon with the message"""
329
-
330
- def __init__(self, parent, title, msg, iconPath, **args):
331
- self.msg = msg
332
- self.iconPath = iconPath
333
- if 'buttons' not in args:
334
- args['buttons'] = [('OK', RESULT_YES)]
335
- args['default'] = 'OK'
336
- Dialog.__init__(self, parent, title, **args)
337
-
338
- def body(self, bodyFrame):
339
- self.image = gui.getImage(self.iconPath)
340
- createMessageBody(bodyFrame, self.msg, self.image)
341
-
342
-
343
- class ExceptionDialog(MessageDialog):
344
- def __init__(self, *args, **kwargs):
345
- self._exception = None if "exception" not in kwargs else kwargs['exception']
346
- super().__init__(*args, **kwargs)
347
-
348
- def body(self, bodyFrame):
349
- super().body(bodyFrame)
350
-
351
- def addTraceback(event):
352
- detailsText = TaggedText(bodyFrame, bg=Config.SCIPION_BG_COLOR, bd=0, highlightthickness=0)
353
- traceStr = traceback.format_exc()
354
- fillMessageText(detailsText, traceStr)
355
- detailsText.frame.grid(row=row + 1, column=0, columnspan=2, sticky='news', padx=5, pady=5)
356
- event.widget.grid_forget()
357
-
358
- row = 1
359
- if self._exception:
360
-
361
- if isinstance(self._exception, PyworkflowException):
362
- helpUrl = self._exception.getUrl()
363
- labelUrl = TaggedText(bodyFrame, bg=Config.SCIPION_BG_COLOR, bd=0, highlightthickness=0)
364
- fillMessageText(labelUrl, "Please go here for more details: %s" % helpUrl)
365
- labelUrl.grid(row=row, column=0, columnspan=2, sticky='news')
366
- row += 1
367
-
368
- label = tk.Label(bodyFrame, text="Show details...", bg=Config.SCIPION_BG_COLOR, bd=0)
369
- label.grid(row=row, column=0, columnspan=2, sticky='news')
370
- label.bind("<Button-1>", addTraceback)
371
-
372
-
373
- class YesNoDialog(MessageDialog):
374
- """Ask a question with YES/NO answer"""
375
-
376
- def __init__(self, master, title, msg, **kwargs):
377
- buttonList = [('Yes', RESULT_YES), ('No', RESULT_NO)]
378
-
379
- if kwargs.get('showCancel', False):
380
- buttonList.append(('Cancel', RESULT_CANCEL))
381
-
382
- MessageDialog.__init__(self, master, title, msg,
383
- Icon.ALERT, default='No',
384
- buttons=buttonList)
385
-
386
-
387
- class GenericDialog(Dialog):
388
- """
389
- Create a dialog with many buttons
390
- Arguments:
391
- parent -- a parent window (the application window)
392
- title -- the dialog title
393
- msg -- message to display into the dialog
394
- iconPath -- path of the image to show into the dialog
395
-
396
- **args accepts:
397
- buttons -- list of buttons tuples containing which buttons to display and theirs values
398
- icons -- list of icons for all buttons
399
- default -- button default
400
-
401
- Example:
402
- buttons=[('Single', RESULT_RUN_SINGLE),
403
- ('All', RESULT_RUN_ALL),
404
- ('Cancel', RESULT_CANCEL)],
405
- default='Cancel',
406
- icons={RESULT_CANCEL: Icon.BUTTON_CANCEL,
407
- RESULT_RUN_SINGLE: Icon.BUTTON_SELECT,
408
- RESULT_RUN_ALL: Icon.ACTION_EXECUTE})
409
- """
410
-
411
- def __init__(self, master, title, msg, iconPath, **kwargs):
412
- self.msg = msg
413
- self.iconPath = iconPath
414
- Dialog.__init__(self, master, title, **kwargs)
415
-
416
- def body(self, bodyFrame):
417
- self.image = gui.getImage(self.iconPath)
418
- createMessageBody(bodyFrame, self.msg, self.image)
419
-
420
-
421
- class EntryDialog(Dialog):
422
- """Dialog to ask some entry"""
423
-
424
- def __init__(self, parent, title, entryLabel, entryWidth=20,
425
- defaultValue='', headerLabel=None):
426
- self.entryLabel = entryLabel
427
- self.entryWidth = entryWidth
428
- self.headerLabel = headerLabel
429
- self.tkvalue = tk.StringVar()
430
- self.tkvalue.set(defaultValue)
431
- self.value = None
432
- Dialog.__init__(self, parent, title)
433
-
434
- def body(self, bodyFrame):
435
- bodyFrame.config(bg=Config.SCIPION_BG_COLOR)
436
- frame = tk.Frame(bodyFrame, bg=Config.SCIPION_BG_COLOR)
437
- frame.grid(row=0, column=0, padx=20, pady=20)
438
- row = 0
439
- if self.headerLabel:
440
- label = tk.Label(bodyFrame, text=self.headerLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
441
- label.grid(row=row, column=0, columnspan=2, sticky='nw', padx=(15, 10), pady=15)
442
- row += 1
443
- label = tk.Label(bodyFrame, text=self.entryLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
444
- label.grid(row=row, column=0, sticky='nw', padx=(15, 10), pady=15)
445
- self.entry = tk.Entry(bodyFrame, bg=gui.cfgEntryBgColor,
446
- width=self.entryWidth, textvariable=self.tkvalue,
447
- font=getDefaultFont())
448
- self.entry.grid(row=row, column=1, sticky='new', padx=(0, 15), pady=15)
449
- self.initial_focus = self.entry
450
-
451
- def apply(self):
452
- self.value = self.entry.get()
453
-
454
- def validate(self):
455
- if len(self.entry.get().strip()) == 0:
456
- showError("Validation error", "Value is empty", self)
457
- return False
458
- return True
459
-
460
-
461
- class EditObjectDialog(Dialog):
462
- """Dialog to edit some text"""
463
-
464
- def __init__(self, parent, title, obj, mapper, **kwargs):
465
- self.obj = obj
466
- self.mapper = mapper
467
-
468
- self.textWidth = 5
469
- self.textHeight = 1
470
- self.labelText = kwargs.get('labelText', Message.TITLE_LABEL)
471
- self.valueText = self.obj.getObjLabel()
472
-
473
- self.commentLabel = Message.TITLE_COMMENT
474
- self.commentWidth = 50
475
- self.commentHeight = 15
476
- self.valueComment = self.obj.getObjComment()
477
-
478
- Dialog.__init__(self, parent, title, **kwargs)
479
-
480
- def body(self, bodyFrame):
481
- bodyFrame.config(bg=Config.SCIPION_BG_COLOR)
482
- frame = tk.Frame(bodyFrame, bg=Config.SCIPION_BG_COLOR)
483
- frame.grid(row=0, column=0, padx=20, pady=20)
484
-
485
- # Label
486
- label_text = tk.Label(bodyFrame, text=self.labelText, bg=Config.SCIPION_BG_COLOR, bd=0)
487
- label_text.grid(row=0, column=0, sticky='nw', padx=(15, 10), pady=15)
488
- # Label box
489
- var = tk.StringVar()
490
- var.set(self.valueText)
491
- self.textLabel = tk.Entry(bodyFrame, width=self.textWidth, textvariable=var, font=gui.getDefaultFont())
492
- self.textLabel.grid(row=0, column=1, sticky='news', padx=5, pady=5)
493
-
494
- # Comment
495
- label_comment = tk.Label(bodyFrame, text=self.commentLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
496
- label_comment.grid(row=1, column=0, sticky='nw', padx=(15, 10), pady=15)
497
- # Comment box
498
- self.textComment = Text(bodyFrame, height=self.commentHeight,
499
- width=self.commentWidth)
500
- self.textComment.setReadOnly(False)
501
- self.textComment.setText(self.valueComment)
502
- self.textComment.grid(row=1, column=1, sticky='news', padx=5, pady=5)
503
- self.initial_focus = self.textLabel
504
-
505
- def getLabel(self):
506
- return self.textLabel.get()
507
-
508
- def getComment(self):
509
- return self.textComment.getText()
510
-
511
- def apply(self):
512
- self.obj.setObjLabel(self.getLabel())
513
- self.obj.setObjComment(self.getComment())
514
-
515
- if self.obj.hasObjId():
516
- self.mapper.store(self.obj)
517
- self.mapper.commit()
518
-
519
- def buttonbox(self, btnFrame):
520
- # Cancel the binding of <Return> key
521
- Dialog.buttonbox(self, btnFrame)
522
- # self.bind("<Return>", self._noReturn)
523
- self.unbind("<Return>")
524
-
525
- def _noReturn(self, e):
526
- pass
527
-
528
-
529
- """ Functions to display dialogs """
530
-
531
-
532
- def askYesNo(title, msg, parent):
533
- d = YesNoDialog(parent, title, msg)
534
- return d.resultYes()
535
-
536
-
537
- def askYesNoCancel(title, msg, parent):
538
- d = YesNoDialog(parent, title, msg, showCancel=True)
539
- return d.result
540
-
541
-
542
- def askSingleAllCancel(title, msg, parent):
543
- d = GenericDialog(parent, title, msg,
544
- Icon.ALERT,
545
- buttons=[('Single', RESULT_RUN_SINGLE),
546
- ('All', RESULT_RUN_ALL),
547
- ('Cancel', RESULT_CANCEL)],
548
- default='Single',
549
- icons={RESULT_CANCEL: Icon.BUTTON_CANCEL,
550
- RESULT_RUN_SINGLE: Icon.BUTTON_SELECT,
551
- RESULT_RUN_ALL: Icon.ACTION_EXECUTE})
552
-
553
- return d.result
554
-
555
-
556
- def showInfo(title, msg, parent):
557
- MessageDialog(parent, title, msg, Icon.INFO)
558
-
559
-
560
- def showWarning(title, msg, parent):
561
- MessageDialog(parent, title, msg, Icon.ALERT)
562
-
563
-
564
- def showError(title, msg, parent, exception=None):
565
- ExceptionDialog(parent, title, msg, Icon.ERROR, exception=exception)
566
-
567
-
568
- def askString(title, label, parent, entryWidth=20, defaultValue='', headerLabel=None):
569
- d = EntryDialog(parent, title, label, entryWidth, defaultValue, headerLabel)
570
- return d.value
571
-
572
-
573
- def askColor(parent, defaultColor='black'):
574
- (rgbcolor, hexcolor) = _askColor(defaultColor, parent=parent)
575
- return hexcolor
576
-
577
-
578
- def askPath(title=None, msg="Select a file of a folder", path=".", onlyFolders=False, master=None, returnBaseName=False):
579
- from pyworkflow.gui.browser import FileBrowserWindow
580
-
581
- if title is None:
582
- title = "Select a folder" if onlyFolders else "Select a file"
583
- browserW = FileBrowserWindow(title, master=master, path=path, onlyFolders=onlyFolders)
584
- browserW.show(modal=True)
585
-
586
- result = browserW.getLastSelection()
587
- if returnBaseName:
588
- result=os.path.basename(result)
589
-
590
- return result
591
-
592
- class ListDialog(Dialog):
593
- """
594
- Dialog to select an element from a list.
595
- It is implemented using the Tree widget.
596
- """
597
-
598
- def __init__(self, parent, title, provider, message=None, **kwargs):
599
- """ From kwargs:
600
-
601
- :param message: message tooltip to show when browsing.
602
- :param validateSelectionCallback: a callback function to validate selected items.
603
- :param previewCallback: method to be called on item click to fill the callback frame.
604
- :param selectmode: 'extended' by default. Selection mode of the tk.Tree
605
- :param selectOnDoubleClick: (False). If True, double click will trigger "Select" button click
606
- :param allowsEmptySelection: (False). Allows empty selection
607
- :param allowSelect: if set to False, the 'Select' button will not be shown.
608
- :param allowsEmptySelection: if set to True, it will not validate that at least one element was selected.
609
- """
610
- self.values = []
611
- self.provider = provider
612
- self.message = message
613
- self.validateSelectionCallback = kwargs.get('validateSelectionCallback', None)
614
- self.previewCallBack = kwargs.get('previewCallback', None)
615
-
616
- self._selectmode = kwargs.get('selectmode', 'extended')
617
- self._selectOnDoubleClick = kwargs.get('selectOnDoubleClick', False)
618
- self._allowsEmptySelection = kwargs.get('allowsEmptySelection', False)
619
-
620
- if "buttons" not in kwargs:
621
- buttons=[]
622
- if kwargs.get('allowSelect', True):
623
- buttons.append(('Select', RESULT_YES))
624
- if kwargs.get('cancelButton', False):
625
- buttons.append(('Close', RESULT_CLOSE))
626
- else:
627
- buttons.append(('Cancel', RESULT_CANCEL))
628
- kwargs['buttons'] = buttons
629
- Dialog.__init__(self, parent, title, **kwargs)
630
-
631
- def body(self, bodyFrame):
632
- bodyFrame.config()
633
- gui.configureWeigths(bodyFrame)
634
- dialogFrame = tk.Frame(bodyFrame)
635
- dialogFrame.grid(row=0, column=0, sticky='news', padx=5, pady=5)
636
- dialogFrame.config()
637
- gui.configureWeigths(dialogFrame, row=1)
638
- self._createFilterBox(dialogFrame)
639
- self._createTree(dialogFrame)
640
- if self.previewCallBack:
641
- self._createPreviewPanel(dialogFrame)
642
-
643
- if self.message:
644
- label = tk.Label(bodyFrame, text=self.message, compound=tk.LEFT,
645
- image=self.getImage(Icon.LIGHTBULB))
646
- label.grid(row=2, column=0, sticky='nw', padx=5, pady=5)
647
- self.initial_focus = self.tree
648
-
649
- def _createTree(self, parent):
650
- self.tree = BoundTree(parent, self.provider, selectmode=self._selectmode, style=LIST_TREEVIEW)
651
- if self._selectOnDoubleClick:
652
- self.tree.itemDoubleClick = lambda obj: self._handleResult(RESULT_YES)
653
-
654
- if self.previewCallBack:
655
- self.tree.itemClick = self._itemClick
656
-
657
- self.tree.grid(row=1, column=0)
658
-
659
- def _itemClick(self, obj):
660
- self.previewCallBack(obj, self.previewFrame)
661
-
662
- def _createPreviewPanel(self, parent):
663
- self.previewFrame = tk.Frame(parent)
664
- self.previewFrame.grid(row=1, column=1)
665
-
666
- def _createFilterBox(self, content):
667
- """ Create the Frame with Filter widgets """
668
-
669
- self.searchBoxframe = tk.Frame(content)
670
- label = tk.Label(self.searchBoxframe, text="Filter")
671
- label.grid(row=0, column=0, sticky='nw')
672
- self._searchVar = tk.StringVar(value='')
673
- self.entry = tk.Entry(self.searchBoxframe, bg=Config.SCIPION_BG_COLOR,
674
- textvariable=self._searchVar, width=40,
675
- font=gui.getDefaultFont())
676
-
677
- self.entry.bind('<KeyRelease>', self._onSearch)
678
- self.entry.focus_set()
679
- self.entry.grid(row=0, column=1, sticky='news')
680
- self.searchBoxframe.grid(row=0, column=0, sticky='news', padx=5,
681
- pady=(10, 5))
682
-
683
- def refresh(self):
684
- """ Refreshes the list taking into account the filter"""
685
- self._onSearch()
686
-
687
- def _onSearch(self, e=None):
688
-
689
- def comparison():
690
- pattern = self._searchVar.get().lower()
691
- return [w[0] for w in self.lista.items()
692
- if pattern in self.lista.get(w[0]).lower()]
693
-
694
- self.tree.update()
695
- self.lista = {}
696
-
697
- for item in self.tree.get_children():
698
-
699
- itemStr = self.tree.item(item)['text']
700
- for value in self.tree.item(item)['values']:
701
- if isinstance(value, int):
702
- itemStr = itemStr + ' ' + str(value)
703
- else:
704
- itemStr = itemStr + ' ' + value
705
-
706
- self.lista[item] = itemStr
707
-
708
- if self._searchVar.get() != '':
709
- matchs = comparison()
710
- if matchs:
711
- for item in self.tree.get_children():
712
- if item not in matchs:
713
- self.tree.delete(item)
714
- else:
715
- self.tree.delete(*self.tree.get_children())
716
-
717
- def apply(self):
718
- self.values = self.tree.getSelectedObjects()
719
-
720
- def validate(self):
721
- self.apply() # load self.values with selected items
722
- err = ''
723
-
724
- if self.values:
725
- if self.validateSelectionCallback:
726
- err = self.validateSelectionCallback(self.values)
727
- else:
728
- if not self._allowsEmptySelection:
729
- err = "Please select an element"
730
-
731
- if err:
732
- showError("Validation error", err, self)
733
- return False
734
-
735
- return True
736
-
737
-
738
- class ToolbarButton:
739
- """
740
- Store information about the buttons that will be added to the toolbar.
741
- """
742
-
743
- def __init__(self, text, command, icon=None, tooltip=None, shortcut=None):
744
- self.text = text
745
- self.command = command
746
- self.icon = icon
747
- self.tooltip = tooltip
748
- self.shortcut = shortcut
749
-
750
-
751
- class ToolbarListDialog(ListDialog):
752
- """
753
- This class extend from ListDialog to allow an
754
- extra toolbar to handle operations over the elements
755
- in the list (e.g. Edit, New, Delete).
756
- """
757
-
758
- def __init__(self, parent, title, provider,
759
- message=None, toolbarButtons=None, **kwargs):
760
- """ From kwargs:
761
- message: message tooltip to show when browsing.
762
- selected: the item that should be selected.
763
- validateSelectionCallback:
764
- a callback function to validate selected items.
765
- allowSelect: if set to False, the 'Select' button will not
766
- be shown.
767
- """
768
- self.toolbarButtons = toolbarButtons
769
- self._itemDoubleClick = kwargs.get('itemDoubleClick', None)
770
- self._itemOnClick = kwargs.get('itemOnClick', None)
771
- ListDialog.__init__(self, parent, title, provider, message, **kwargs)
772
-
773
- def body(self, bodyFrame):
774
- gui.configureWeigths(bodyFrame, 1, 0)
775
-
776
- # Add an extra frame to insert the Toolbar
777
- # and another one for the ListDialog's body
778
- self.toolbarFrame = tk.Frame(bodyFrame)
779
- self.toolbarFrame.grid(row=0, column=0, sticky='new')
780
-
781
- subBody = tk.Frame(bodyFrame)
782
- subBody.grid(row=1, column=0, sticky='news', padx=5, pady=5)
783
- ListDialog.body(self, subBody)
784
-
785
- if self.toolbarButtons:
786
- for i, b in enumerate(self.toolbarButtons):
787
- self.addButton(b, i)
788
-
789
- if self._itemDoubleClick:
790
- self.tree.itemDoubleClick = self._itemDoubleClick
791
-
792
- if self._itemOnClick:
793
- self.tree.itemOnClick = self._itemOnClick
794
-
795
- def addButton(self, button, col):
796
-
797
- self._addButton(self.toolbarFrame, button.command, text=button.text, icon=button.icon, col=col, tooltip=button.tooltip, shortcut=button.shortcut)
798
-
799
-
800
- class FlashMessage:
801
- def __init__(self, master, msg, delay=5, relief='solid', func=None):
802
- self.root = tk.Toplevel(master=master)
803
- # hides until know geometry
804
- self.root.withdraw()
805
- self.root.wm_overrideredirect(1)
806
- tk.Label(self.root, text=" %s " % msg,
807
- bd=1, bg='DodgerBlue4', fg='white').pack()
808
- gui.centerWindows(self.root, refWindows=master)
809
- self.root.deiconify()
810
- self.root.grab_set()
811
- self.msg = msg
812
-
813
- if func:
814
- self.root.update_idletasks()
815
- self.root.after(10, self.process, func)
816
- else:
817
- self.root.after(int(delay * 1000), self.close)
818
- self.root.wait_window(self.root)
819
-
820
- def process(self, func):
821
- func()
822
- self.root.destroy()
823
-
824
- def close(self):
825
- self.root.destroy()
826
-
827
-
828
- class FloatingMessage:
829
- def __init__(self, master, msg, xPos=None, yPos=None, textWidth=280,
830
- font='Helvetica', size=12, bd=1, bg=Config.SCIPION_MAIN_COLOR, fg='white'):
831
- if xPos is None:
832
- xPos = (master.winfo_width() - textWidth) / 2
833
- yPos = master.winfo_height() / 2
834
-
835
- self.floatingMessage = tk.Label(master, text=" %s " % msg,
836
- bd=bd, bg=bg, fg=fg)
837
- self.floatingMessage.place(x=xPos, y=yPos, width=textWidth)
838
- self.floatingMessage.config(font=(font, size))
839
-
840
- def setMessage(self, msg):
841
- self.floatingMessage.config(text=msg)
842
-
843
- def show(self):
844
- self.floatingMessage.update_idletasks()
845
-
846
- def close(self):
847
- self.floatingMessage.destroy()
848
-
849
-
850
-
851
- class SearchBaseWindow(Window):
852
- """ Base window for searching in a list
853
- You are going to implement several elements:
854
-
855
- columnsConfig: a dictionary with elements with this structure:
856
- <column-key>: (<title>,{kwargs for tree.column method}, weight, <casting_method>(optional, otherwise str))
857
-
858
- Example:
859
-
860
- columnConfig = {
861
- '#0': ('Status', {'width': 50, 'minwidth': 50, 'stretch': tk.NO}, 3),
862
- 'protocol': ('Protocol', {'width': 300, 'stretch': tk.FALSE}), 5,
863
- 'streaming': ('Streamified', {'width': 100, 'stretch': tk.FALSE}, 3),
864
- 'installed': ('Installation', {'width': 110, 'stretch': tk.FALSE}, 3),
865
- 'help': ('Help', {'minwidth': 300, 'stretch': tk.YES}, 3),
866
- 'score': ('Score', {'width': 50, 'stretch': tk.FALSE}, 3, int),
867
- }
868
-
869
- _createResultsTree method
870
- _onSearchClick method
871
-
872
- See SearchProtocolWindow as an example
873
-
874
- """
875
- COLUMN_TEXT_INDEX = 0
876
- COLUMN_KWARGS_INDEX = 1
877
- WEIGHT_INDEX = 2
878
- CASTING_INDEX = 3
879
- columnConfig = {} # Columns configuration
880
-
881
- def __init__(self, parentWindow, title="Search element", onClick=None, onDoubleClick=None, **kwargs):
882
- super().__init__(title=title,
883
- masterWindow=parentWindow)
884
-
885
- self.onClick = self._click if onClick is None else onClick
886
- self.onDoubleClick = self._double_click if onDoubleClick is None else onDoubleClick
887
-
888
- content = tk.Frame(self.root, bg=Config.SCIPION_BG_COLOR)
889
- self._createContent(content)
890
- content.grid(row=0, column=0, sticky='news')
891
- content.columnconfigure(0, weight=1)
892
- content.rowconfigure(1, weight=1)
893
-
894
- def getColumnKeys(self):
895
- return self.columnConfig.keys()
896
-
897
- def _createContent(self, content):
898
- self._createSearchBox(content)
899
- self._createResultsBox(content)
900
-
901
- def _createSearchBox(self, content):
902
- """ Create the Frame with Search widgets """
903
- frame = tk.Frame(content, bg=Config.SCIPION_BG_COLOR)
904
-
905
- label = tk.Label(frame, text="Search", bg=Config.SCIPION_BG_COLOR)
906
- label.grid(row=0, column=0, sticky='nw')
907
- self._searchVar = tk.StringVar()
908
- entry = tk.Entry(frame, bg='white', textvariable=self._searchVar, font=gui.getDefaultFont())
909
- entry.bind(TK.RETURN, self._onSearchClick)
910
- entry.bind(TK.ENTER, self._onSearchClick)
911
- entry.focus_set()
912
- entry.grid(row=0, column=1, sticky='nw')
913
- btn = widgets.IconButton(frame, "Search",
914
- imagePath=Icon.ACTION_SEARCH,
915
- command=self._onSearchClick)
916
- btn.grid(row=0, column=2, sticky='nw')
917
-
918
- frame.grid(row=0, column=0, sticky='new', padx=5, pady=(10, 5))
919
-
920
- def _createResultsBox(self, content):
921
- frame = tk.Frame(content, bg=Color.ALT_COLOR, padx=5, pady=5)
922
- configureWeigths(frame)
923
- self._resultsTree = self._createResultsTree(frame,
924
- show=None,
925
- columns=list(self.getColumnKeys())[1:])
926
- self._configureTreeColumns()
927
- self._resultsTree.grid(row=0, column=0, sticky='news')
928
- frame.grid(row=1, column=0, sticky='news', padx=5, pady=5)
929
-
930
- def _createResultsTree(self, frame, show, columns):
931
-
932
- t = Tree(frame, show=show, columns=columns, style=LIST_TREEVIEW)
933
- t.column('#0', minwidth=100)
934
- t.bind("<Button-1>", self.onClick)
935
- t.bind("<Double-1>", self.onDoubleClick)
936
- return t
937
-
938
- def _click(self, event):
939
- """ To be implemented, triggered on tree-view click """
940
- pass
941
-
942
- def _double_click(self, event):
943
- """ To be implemented, triggered on tree-view double click """
944
- pass
945
-
946
- def addSearchWeight(self, line2Search, searchtext):
947
- # Adds a weight value for the search
948
- weight = 0
949
-
950
- linelower = [str(v).lower() for v in line2Search]
951
-
952
- for index, column in enumerate(self.columnConfig.values()):
953
-
954
- if searchtext in linelower[index]:
955
- # prioritize findings in label
956
- weight += column[self.WEIGHT_INDEX] * 2
957
-
958
- elif " " in searchtext:
959
- for word in searchtext.split():
960
- if word in linelower[index]:
961
- weight += column[self.WEIGHT_INDEX]
962
-
963
- return line2Search + (weight,)
964
-
965
- def _configureTreeColumns(self):
966
-
967
- for key, columnConf in self.columnConfig.items():
968
- casting = str if len(columnConf) <= self.CASTING_INDEX else columnConf[self.CASTING_INDEX]
969
- self._resultsTree.column(key, **columnConf[self.COLUMN_KWARGS_INDEX])
970
- self._resultsTree.heading(key,
971
- text=columnConf[self.COLUMN_TEXT_INDEX],
972
- command=lambda bound_key=key, bound_casting=casting:
973
- self._resultsTree.sortByColumn(bound_key, False, casting=bound_casting))
974
-
975
- def _onSearchClick(self, e=None):
976
- """ To be implemented, triggered on search button click"""
977
- pass