scipion-pyworkflow 3.10.6__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.
- pyworkflow/config.py +131 -67
- pyworkflow/constants.py +2 -1
- pyworkflow/plugin.py +93 -44
- pyworkflow/resources/showj/arrowDown.png +0 -0
- pyworkflow/resources/showj/arrowUp.png +0 -0
- pyworkflow/resources/showj/background_section.png +0 -0
- pyworkflow/resources/showj/colRowModeOff.png +0 -0
- pyworkflow/resources/showj/colRowModeOn.png +0 -0
- pyworkflow/resources/showj/delete.png +0 -0
- pyworkflow/resources/showj/doc_icon.png +0 -0
- pyworkflow/resources/showj/download_icon.png +0 -0
- pyworkflow/resources/showj/enabled_gallery.png +0 -0
- pyworkflow/resources/showj/galleryViewOff.png +0 -0
- pyworkflow/resources/showj/galleryViewOn.png +0 -0
- pyworkflow/resources/showj/goto.png +0 -0
- pyworkflow/resources/showj/menu.png +0 -0
- pyworkflow/resources/showj/separator.png +0 -0
- pyworkflow/resources/showj/tableViewOff.png +0 -0
- pyworkflow/resources/showj/tableViewOn.png +0 -0
- pyworkflow/resources/showj/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- pyworkflow/resources/showj/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- pyworkflow/resources/showj/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- pyworkflow/resources/showj/volumeOff.png +0 -0
- pyworkflow/resources/showj/volumeOn.png +0 -0
- pyworkflow/viewer.py +23 -1
- pyworkflowtests/protocols.py +1 -3
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +13 -27
- scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
- pyworkflow/apps/__init__.py +0 -29
- pyworkflow/apps/pw_manager.py +0 -37
- pyworkflow/apps/pw_plot.py +0 -51
- pyworkflow/apps/pw_project.py +0 -113
- pyworkflow/apps/pw_protocol_list.py +0 -143
- pyworkflow/apps/pw_protocol_run.py +0 -51
- pyworkflow/apps/pw_run_tests.py +0 -267
- pyworkflow/apps/pw_schedule_run.py +0 -322
- pyworkflow/apps/pw_sleep.py +0 -37
- pyworkflow/apps/pw_sync_data.py +0 -439
- pyworkflow/apps/pw_viewer.py +0 -78
- pyworkflow/gui/__init__.py +0 -36
- pyworkflow/gui/browser.py +0 -726
- pyworkflow/gui/canvas.py +0 -1190
- pyworkflow/gui/dialog.py +0 -977
- pyworkflow/gui/form.py +0 -2637
- pyworkflow/gui/graph.py +0 -247
- pyworkflow/gui/graph_layout.py +0 -271
- pyworkflow/gui/gui.py +0 -566
- pyworkflow/gui/matplotlib_image.py +0 -233
- pyworkflow/gui/plotter.py +0 -247
- pyworkflow/gui/project/__init__.py +0 -25
- pyworkflow/gui/project/base.py +0 -192
- pyworkflow/gui/project/constants.py +0 -139
- pyworkflow/gui/project/labels.py +0 -205
- pyworkflow/gui/project/project.py +0 -492
- pyworkflow/gui/project/searchprotocol.py +0 -154
- pyworkflow/gui/project/searchrun.py +0 -181
- pyworkflow/gui/project/steps.py +0 -171
- pyworkflow/gui/project/utils.py +0 -332
- pyworkflow/gui/project/variables.py +0 -179
- pyworkflow/gui/project/viewdata.py +0 -472
- pyworkflow/gui/project/viewprojects.py +0 -510
- pyworkflow/gui/project/viewprotocols.py +0 -2093
- pyworkflow/gui/project/viewprotocols_extra.py +0 -559
- pyworkflow/gui/text.py +0 -771
- pyworkflow/gui/tooltip.py +0 -185
- pyworkflow/gui/tree.py +0 -684
- pyworkflow/gui/widgets.py +0 -307
- pyworkflow/mapper/__init__.py +0 -26
- pyworkflow/mapper/mapper.py +0 -222
- pyworkflow/mapper/sqlite.py +0 -1581
- pyworkflow/mapper/sqlite_db.py +0 -145
- pyworkflow/project/__init__.py +0 -31
- pyworkflow/project/config.py +0 -454
- pyworkflow/project/manager.py +0 -180
- pyworkflow/project/project.py +0 -2007
- pyworkflow/protocol/__init__.py +0 -38
- pyworkflow/protocol/bibtex.py +0 -48
- pyworkflow/protocol/constants.py +0 -87
- pyworkflow/protocol/executor.py +0 -471
- pyworkflow/protocol/hosts.py +0 -314
- pyworkflow/protocol/launch.py +0 -270
- pyworkflow/protocol/package.py +0 -42
- pyworkflow/protocol/params.py +0 -741
- pyworkflow/protocol/protocol.py +0 -2641
- pyworkflow/tests/__init__.py +0 -29
- pyworkflow/tests/test_utils.py +0 -25
- pyworkflow/tests/tests.py +0 -341
- pyworkflow/utils/__init__.py +0 -38
- pyworkflow/utils/dataset.py +0 -414
- pyworkflow/utils/echo.py +0 -104
- pyworkflow/utils/graph.py +0 -169
- pyworkflow/utils/log.py +0 -284
- pyworkflow/utils/path.py +0 -528
- pyworkflow/utils/process.py +0 -153
- pyworkflow/utils/profiler.py +0 -92
- pyworkflow/utils/progressbar.py +0 -154
- pyworkflow/utils/properties.py +0 -631
- pyworkflow/utils/reflection.py +0 -129
- pyworkflow/utils/utils.py +0 -879
- pyworkflow/utils/which.py +0 -229
- pyworkflow/webservices/__init__.py +0 -8
- pyworkflow/webservices/config.py +0 -11
- pyworkflow/webservices/notifier.py +0 -162
- pyworkflow/webservices/repository.py +0 -59
- pyworkflow/webservices/workflowhub.py +0 -74
- pyworkflowtests/tests/__init__.py +0 -0
- pyworkflowtests/tests/test_canvas.py +0 -72
- pyworkflowtests/tests/test_domain.py +0 -45
- pyworkflowtests/tests/test_logs.py +0 -74
- pyworkflowtests/tests/test_mappers.py +0 -392
- pyworkflowtests/tests/test_object.py +0 -507
- pyworkflowtests/tests/test_project.py +0 -42
- pyworkflowtests/tests/test_protocol_execution.py +0 -142
- pyworkflowtests/tests/test_protocol_export.py +0 -78
- pyworkflowtests/tests/test_protocol_output.py +0 -158
- pyworkflowtests/tests/test_streaming.py +0 -47
- pyworkflowtests/tests/test_utils.py +0 -210
- scipion_pyworkflow-3.10.6.dist-info/RECORD +0 -140
- scipion_pyworkflow-3.10.6.dist-info/dependency_links.txt +0 -1
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/entry_points.txt +0 -0
- {scipion_pyworkflow-3.10.6.dist-info → scipion_pyworkflow-3.11.0.dist-info}/licenses/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.10.6.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
|