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.
- pyworkflow/config.py +131 -67
- pyworkflow/constants.py +12 -2
- pyworkflow/object.py +3 -2
- pyworkflow/plugin.py +93 -44
- pyworkflow/project/scripts/fix_links.py +4 -1
- 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/objects.py +2 -2
- pyworkflowtests/protocols.py +1 -3
- {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/METADATA +21 -25
- scipion_pyworkflow-3.11.0.dist-info/RECORD +71 -0
- {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/WHEEL +1 -1
- scipion_pyworkflow-3.11.0.dist-info/entry_points.txt +2 -0
- 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 -560
- 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 -1578
- 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 -2010
- pyworkflow/protocol/__init__.py +0 -38
- pyworkflow/protocol/bibtex.py +0 -48
- pyworkflow/protocol/constants.py +0 -87
- pyworkflow/protocol/executor.py +0 -455
- pyworkflow/protocol/hosts.py +0 -313
- pyworkflow/protocol/launch.py +0 -270
- pyworkflow/protocol/package.py +0 -42
- pyworkflow/protocol/params.py +0 -741
- pyworkflow/protocol/protocol.py +0 -2582
- 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 -132
- 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 -135
- 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.5.dist-info/RECORD +0 -140
- scipion_pyworkflow-3.10.5.dist-info/dependency_links.txt +0 -1
- scipion_pyworkflow-3.10.5.dist-info/entry_points.txt +0 -5
- {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info/licenses}/LICENSE.txt +0 -0
- {scipion_pyworkflow-3.10.5.dist-info → scipion_pyworkflow-3.11.0.dist-info}/top_level.txt +0 -0
pyworkflow/gui/text.py
DELETED
@@ -1,771 +0,0 @@
|
|
1
|
-
# **************************************************************************
|
2
|
-
# *
|
3
|
-
# * Authors: J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
|
4
|
-
# *
|
5
|
-
# * [1] SciLifeLab, Stockholm University
|
6
|
-
# *
|
7
|
-
# * This program is free software: you can redistribute it and/or modify
|
8
|
-
# * it under the terms of the GNU General Public License as published by
|
9
|
-
# * the Free Software Foundation, either version 3 of the License, or
|
10
|
-
# * (at your option) any later version.
|
11
|
-
# *
|
12
|
-
# * This program is distributed in the hope that it will be useful,
|
13
|
-
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
-
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
-
# * GNU General Public License for more details.
|
16
|
-
# *
|
17
|
-
# * You should have received a copy of the GNU General Public License
|
18
|
-
# * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
19
|
-
# *
|
20
|
-
# * All comments concerning this program package may be sent to the
|
21
|
-
# * e-mail address 'scipion@cnb.csic.es'
|
22
|
-
# *
|
23
|
-
# **************************************************************************
|
24
|
-
"""
|
25
|
-
Text based widgets.
|
26
|
-
"""
|
27
|
-
|
28
|
-
|
29
|
-
import os
|
30
|
-
import sys
|
31
|
-
import time
|
32
|
-
import webbrowser
|
33
|
-
import subprocess
|
34
|
-
import tkinter.ttk as ttk
|
35
|
-
import tkinter as tk
|
36
|
-
import tkinter.messagebox as tkMessageBox
|
37
|
-
|
38
|
-
import pyworkflow as pw
|
39
|
-
from pyworkflow import ASCII_COLOR_2_TKINTER
|
40
|
-
from pyworkflow.utils import (HYPER_BOLD, HYPER_ITALIC, HYPER_LINK1, HYPER_LINK2,
|
41
|
-
parseHyperText, renderLine, renderTextFile,
|
42
|
-
which, envVarOn, expandPattern)
|
43
|
-
from pyworkflow.utils.properties import Message, Color, Icon
|
44
|
-
from . import gui
|
45
|
-
from .widgets import Scrollable, IconButton
|
46
|
-
from .tooltip import ToolTip
|
47
|
-
|
48
|
-
|
49
|
-
# Define a function to open files cleanly in a system-dependent way
|
50
|
-
if sys.platform.startswith('darwin'): # macs use the "open" command
|
51
|
-
def _open_cmd(path, tkParent=None):
|
52
|
-
subprocess.Popen(['open', path])
|
53
|
-
elif os.name == 'nt': # there is a function os.startfile for windows
|
54
|
-
def _open_cmd(path, tkParent=None):
|
55
|
-
os.startfile(path)
|
56
|
-
elif os.name == 'posix': # linux systems and so on
|
57
|
-
def find_prog(*args):
|
58
|
-
"""Return the first argument that is a program in PATH"""
|
59
|
-
for command in args:
|
60
|
-
if which(command):
|
61
|
-
return command
|
62
|
-
return None
|
63
|
-
|
64
|
-
x_open = find_prog('xdg-open', 'gnome-open', 'kde-open', 'gvfs-open')
|
65
|
-
editor = pw.Config.SCIPION_TEXT_EDITOR
|
66
|
-
if not editor:
|
67
|
-
editor = find_prog('pluma', 'gedit', 'kwrite', 'geany', 'kate',
|
68
|
-
'emacs', 'nedit', 'mousepad', 'code')
|
69
|
-
|
70
|
-
def _open_cmd(path, tkParent=None):
|
71
|
-
# If it is an url, open with browser.
|
72
|
-
if path.startswith('http://') or path.startswith('https://') or path.endswith('.html'):
|
73
|
-
try:
|
74
|
-
webbrowser.open_new_tab(path)
|
75
|
-
return
|
76
|
-
except:
|
77
|
-
pass
|
78
|
-
# OK, it is a file. Check if it does exist
|
79
|
-
# and notify if it does not
|
80
|
-
if not os.path.isfile(path):
|
81
|
-
try:
|
82
|
-
# if tkRoot is null the error message may be behind
|
83
|
-
# other windows
|
84
|
-
tkMessageBox.showerror("File Error", # bar title
|
85
|
-
"File not found\n(%s)" % path, # message
|
86
|
-
parent=tkParent)
|
87
|
-
return
|
88
|
-
except:
|
89
|
-
return
|
90
|
-
|
91
|
-
if x_open: # standard way to open
|
92
|
-
proc = subprocess.Popen([x_open, path])
|
93
|
-
time.sleep(1)
|
94
|
-
if proc.poll() in [None, 0]:
|
95
|
-
return # yay! that's the way to do it!
|
96
|
-
if editor: # last card: try to open it in an editor
|
97
|
-
proc = subprocess.Popen([editor, path])
|
98
|
-
time.sleep(1)
|
99
|
-
if proc.poll() in [None, 0]:
|
100
|
-
return # hope we found your fav editor :)
|
101
|
-
print('WARNING: Cannot open %s' % path) # nothing worked! :(
|
102
|
-
else:
|
103
|
-
def _open_cmd(path, tkParent=None):
|
104
|
-
try:
|
105
|
-
tkMessageBox.showerror("Unknown System", # bar title
|
106
|
-
'Unknown system, so cannot open %s' % path, # message
|
107
|
-
parent=tkParent)
|
108
|
-
return
|
109
|
-
except:
|
110
|
-
pass
|
111
|
-
|
112
|
-
|
113
|
-
class HyperlinkManager:
|
114
|
-
""" Tkinter Text Widget Hyperlink Manager, taken from:
|
115
|
-
http://effbot.org/zone/tkinter-text-hyperlink.htm """
|
116
|
-
def __init__(self, text):
|
117
|
-
self.text = text
|
118
|
-
self.text.tag_config("hyper", foreground=pw.Config.SCIPION_MAIN_COLOR, underline=1)
|
119
|
-
self.text.tag_bind("hyper", "<Enter>", self._enter)
|
120
|
-
self.text.tag_bind("hyper", "<Leave>", self._leave)
|
121
|
-
self.text.tag_bind("hyper", "<Button-1>", self._click)
|
122
|
-
self.reset()
|
123
|
-
|
124
|
-
def reset(self):
|
125
|
-
self.links = {}
|
126
|
-
|
127
|
-
def add(self, action):
|
128
|
-
# add an action to the manager. returns tags to use in
|
129
|
-
# associated text widget
|
130
|
-
tag = "hyper-%d" % len(self.links)
|
131
|
-
self.links[tag] = action
|
132
|
-
return "hyper", tag
|
133
|
-
|
134
|
-
def _enter(self, event):
|
135
|
-
self.text.config(cursor="hand2")
|
136
|
-
|
137
|
-
def _leave(self, event):
|
138
|
-
self.text.config(cursor="")
|
139
|
-
|
140
|
-
def _click(self, event):
|
141
|
-
for tag in self.text.tag_names(tk.CURRENT):
|
142
|
-
if tag[:6] == "hyper-":
|
143
|
-
self.links[tag]()
|
144
|
-
return
|
145
|
-
|
146
|
-
|
147
|
-
class Text(tk.Text, Scrollable):
|
148
|
-
""" Base Text widget with some functionality
|
149
|
-
that will be used by children classes.
|
150
|
-
"""
|
151
|
-
def __init__(self, master, **opts):
|
152
|
-
if 'handlers' in opts:
|
153
|
-
self.handlers = opts.pop('handlers')
|
154
|
-
else:
|
155
|
-
self.handlers = {}
|
156
|
-
opts['font'] = gui.fontNormal
|
157
|
-
defaults = self.getDefaults()
|
158
|
-
defaults.update(opts)
|
159
|
-
Scrollable.__init__(self, master, tk.Text, wrap=tk.WORD, **opts)
|
160
|
-
self._createWidgets(master, **defaults)
|
161
|
-
self.configureTags()
|
162
|
-
|
163
|
-
def _createWidgets(self, master, **opts):
|
164
|
-
"""This is an internal function to create the Text, the Scrollbar and the Frame"""
|
165
|
-
|
166
|
-
# create a popup menu
|
167
|
-
self.menu = tk.Menu(master, tearoff=0, postcommand=self.updateMenu)
|
168
|
-
self.menu.add_command(label="Copy to clipboard", command=self.copyToClipboard)
|
169
|
-
self.menu.add_command(label="Open path", command=self.openFile)
|
170
|
-
# Associate with right click
|
171
|
-
self.bind("<Button-1>", self.onClick)
|
172
|
-
self.bind("<Button-3>", self.onRightClick)
|
173
|
-
|
174
|
-
def getDefaults(self):
|
175
|
-
"""This should be implemented in subclasses to provide defaults"""
|
176
|
-
return {}
|
177
|
-
|
178
|
-
def configureTags(self):
|
179
|
-
"""This should be implemented to create specific tags"""
|
180
|
-
pass
|
181
|
-
|
182
|
-
def addLine(self, line):
|
183
|
-
"""Should be implemented to add a line """
|
184
|
-
self.insert(tk.END, line + '\n')
|
185
|
-
|
186
|
-
def addNewline(self):
|
187
|
-
self.insert(tk.END, '\n')
|
188
|
-
|
189
|
-
def goBegin(self):
|
190
|
-
self.see(0.0)
|
191
|
-
|
192
|
-
def goEnd(self):
|
193
|
-
self.see(tk.END)
|
194
|
-
|
195
|
-
def isAtEnd(self):
|
196
|
-
return self.scrollbar.get() == 1.0
|
197
|
-
|
198
|
-
def clear(self):
|
199
|
-
self.delete(0.0, tk.END)
|
200
|
-
|
201
|
-
def getText(self):
|
202
|
-
|
203
|
-
textWithNewLine = self.get(0.0, tk.END)
|
204
|
-
|
205
|
-
# Remove the last new line
|
206
|
-
return textWithNewLine.rstrip('\n')
|
207
|
-
|
208
|
-
def setText(self, text):
|
209
|
-
""" Replace the current text with new one. """
|
210
|
-
self.clear()
|
211
|
-
self.addText(text)
|
212
|
-
|
213
|
-
def addText(self, text):
|
214
|
-
""" Add some text to the current state. """
|
215
|
-
if isinstance(text, list):
|
216
|
-
for line in text:
|
217
|
-
self.addLine(line)
|
218
|
-
else:
|
219
|
-
for line in text.splitlines():
|
220
|
-
self.addLine(line)
|
221
|
-
|
222
|
-
def onClick(self, e=None):
|
223
|
-
self.selection = None
|
224
|
-
self.selection_clear()
|
225
|
-
self.menu.unpost()
|
226
|
-
|
227
|
-
def onRightClick(self, e):
|
228
|
-
try:
|
229
|
-
self.selection = self.selection_get().strip()
|
230
|
-
self.menu.post(e.x_root, e.y_root)
|
231
|
-
except tk.TclError as e:
|
232
|
-
pass
|
233
|
-
|
234
|
-
def copyToClipboard(self, e=None):
|
235
|
-
self.clipboard_clear()
|
236
|
-
self.clipboard_append(self.selection)
|
237
|
-
|
238
|
-
def openFile(self):
|
239
|
-
# What happens when you right-click and select "Open path"
|
240
|
-
self.openPath(self.selection)
|
241
|
-
|
242
|
-
def openPath(self, path):
|
243
|
-
"""Try to open the selected path"""
|
244
|
-
path = expandPattern(path)
|
245
|
-
|
246
|
-
# If the path is a dir, open it with scipion browser dir <path>
|
247
|
-
if os.path.isdir(path):
|
248
|
-
dpath = (path if os.path.isabs(path)
|
249
|
-
else os.path.join(os.getcwd(), path))
|
250
|
-
subprocess.Popen([pw.PYTHON, pw.getViewerScript(), dpath])
|
251
|
-
return
|
252
|
-
|
253
|
-
# If it is a file, interpret it correctly and open it with DataView
|
254
|
-
dirname = os.path.dirname(path)
|
255
|
-
fname = os.path.basename(path)
|
256
|
-
if '@' in fname:
|
257
|
-
path = os.path.join(dirname, fname.split('@', 1)[-1])
|
258
|
-
else:
|
259
|
-
path = os.path.join(dirname, fname)
|
260
|
-
|
261
|
-
if os.path.exists(path) or path.startswith("http"):
|
262
|
-
from pwem import emlib
|
263
|
-
fn = emlib.FileName(path)
|
264
|
-
if fn is not None and (fn.isImage() or fn.isMetaData()):
|
265
|
-
# fn is None if xmippLib is the xmippLib ghost library
|
266
|
-
from pwem.viewers import DataView
|
267
|
-
DataView(path).show()
|
268
|
-
else:
|
269
|
-
_open_cmd(path)
|
270
|
-
else:
|
271
|
-
# This is probably one special reference, like sci-open:... that
|
272
|
-
# can be interpreted with our handlers.
|
273
|
-
tag = path.split(':', 1)[0] if ':' in path else None
|
274
|
-
if tag in self.handlers:
|
275
|
-
self.handlers[tag](path.split(':', 1)[-1])
|
276
|
-
else:
|
277
|
-
print("Can't find %s" % path)
|
278
|
-
|
279
|
-
def updateMenu(self, e=None):
|
280
|
-
state = 'normal'
|
281
|
-
# if not xmippExists(self.selection):
|
282
|
-
# state = 'disabled'#self.menu.entryconfig(1, background="green")
|
283
|
-
self.menu.entryconfig(1, state=state)
|
284
|
-
|
285
|
-
def setReadOnly(self, value):
|
286
|
-
state = tk.NORMAL
|
287
|
-
if value:
|
288
|
-
state = tk.DISABLED
|
289
|
-
self.config(state=state)
|
290
|
-
|
291
|
-
def highlight(self, pattern, tag, start="1.0", end="end", regexp=False):
|
292
|
-
""" Apply the given tag to all text that matches the given pattern
|
293
|
-
|
294
|
-
If 'regexp' is set to True, pattern will be treated as a regular expression
|
295
|
-
Taken from:
|
296
|
-
http://stackoverflow.com/questions/3781670/tkinter-text-highlighting-in-python
|
297
|
-
"""
|
298
|
-
start = self.index(start)
|
299
|
-
end = self.index(end)
|
300
|
-
self.mark_set("matchStart", start)
|
301
|
-
self.mark_set("matchEnd", start)
|
302
|
-
self.mark_set("searchLimit", end)
|
303
|
-
|
304
|
-
count = tk.IntVar()
|
305
|
-
while True:
|
306
|
-
index = self.search(pattern, "matchEnd", "searchLimit",
|
307
|
-
count=count, regexp=regexp)
|
308
|
-
if index == "":
|
309
|
-
break
|
310
|
-
self.mark_set("matchStart", index)
|
311
|
-
self.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
|
312
|
-
self.tag_add(tag, "matchStart", "matchEnd")
|
313
|
-
|
314
|
-
|
315
|
-
def configureColorTags(text):
|
316
|
-
""" Create tags in text (of type tk.Text) for all the supported colors. """
|
317
|
-
try:
|
318
|
-
for color in ASCII_COLOR_2_TKINTER.values():
|
319
|
-
text.tag_config(color, foreground=color)
|
320
|
-
return True
|
321
|
-
except Exception as e:
|
322
|
-
print("Colors still not available (%s)" % e)
|
323
|
-
return False
|
324
|
-
|
325
|
-
|
326
|
-
class TaggedText(Text):
|
327
|
-
"""
|
328
|
-
Implement a Text that will recognize some basic tags
|
329
|
-
*some_text* will display some_text in bold
|
330
|
-
_some_text_ will display some_text in italic
|
331
|
-
some_link or [[some_link][some_label]] will display some_link
|
332
|
-
as hyperlink or some_label as hyperlink to some_link
|
333
|
-
also colors are recognized if set option colors=True
|
334
|
-
"""
|
335
|
-
def __init__(self, master, colors=True, **opts):
|
336
|
-
self.colors = colors
|
337
|
-
Text.__init__(self, master, **opts)
|
338
|
-
self.hm = HyperlinkManager(self)
|
339
|
-
|
340
|
-
def getDefaults(self):
|
341
|
-
return {'bg': pw.Config.SCIPION_BG_COLOR, 'bd': 0}
|
342
|
-
# It used to have also 'font': gui.fontNormal but that stops
|
343
|
-
# this file from running. Apparently there is no fontNormal in gui.
|
344
|
-
|
345
|
-
def configureTags(self):
|
346
|
-
self.tag_config('normal', justify=tk.LEFT, font=gui.fontNormal)
|
347
|
-
self.tag_config(HYPER_BOLD, justify=tk.LEFT, font=gui.fontBold)
|
348
|
-
self.tag_config(HYPER_ITALIC, justify=tk.LEFT, font=gui.fontItalic)
|
349
|
-
if self.colors:
|
350
|
-
self.colors = configureColorTags(self)
|
351
|
-
# Color can be unavailable, so disable use of colors
|
352
|
-
|
353
|
-
@staticmethod
|
354
|
-
def openLink(link):
|
355
|
-
webbrowser.open_new_tab(link) # Open in the same browser, new tab
|
356
|
-
|
357
|
-
@staticmethod
|
358
|
-
def mailTo(email):
|
359
|
-
webbrowser.open("mailto:" + email)
|
360
|
-
|
361
|
-
def matchHyperText(self, match, tag):
|
362
|
-
""" Process when a match a found and store indexes inside string."""
|
363
|
-
self.insert(tk.END, self.line[self.lastIndex:match.start()])
|
364
|
-
g1 = match.group(tag)
|
365
|
-
|
366
|
-
if tag == HYPER_BOLD or tag == HYPER_ITALIC:
|
367
|
-
self.insert(tk.END, ' ' + g1, tag)
|
368
|
-
elif tag == HYPER_LINK1:
|
369
|
-
self.insert(tk.END, g1, self.hm.add(lambda: self.openLink(g1)))
|
370
|
-
elif tag == HYPER_LINK2:
|
371
|
-
label = match.group('link2_label')
|
372
|
-
if g1.startswith('http'):
|
373
|
-
self.insert(tk.END, label, self.hm.add(lambda: self.openLink(g1)))
|
374
|
-
elif g1.startswith('mailto:'):
|
375
|
-
self.insert(tk.END, label, self.hm.add(lambda: self.mailTo(g1)))
|
376
|
-
else:
|
377
|
-
self.insert(tk.END, label, self.hm.add(lambda: self.openPath(g1)))
|
378
|
-
self.lastIndex = match.end()
|
379
|
-
|
380
|
-
return g1
|
381
|
-
|
382
|
-
def addLine(self, line):
|
383
|
-
self.line = line
|
384
|
-
self.lastIndex = 0
|
385
|
-
if line is not None:
|
386
|
-
parseHyperText(line, self.matchHyperText)
|
387
|
-
Text.addLine(self, line[self.lastIndex:])
|
388
|
-
|
389
|
-
|
390
|
-
class OutputText(Text):
|
391
|
-
"""
|
392
|
-
Implement a Text that will show file content
|
393
|
-
and handle console metacharacter for colored output
|
394
|
-
"""
|
395
|
-
def __init__(self, master, filename, colors=True, t_refresh=0, maxSize=400, **opts):
|
396
|
-
""" colors flag indicate if try to parse color meta-characters
|
397
|
-
t_refresh is the refresh time in seconds, 0 means no refresh
|
398
|
-
"""
|
399
|
-
self.filename = filename
|
400
|
-
self.colors = colors
|
401
|
-
self.t_refresh = t_refresh
|
402
|
-
self.maxSize = maxSize
|
403
|
-
|
404
|
-
self.refreshAlarm = None # Identifier returned by after()
|
405
|
-
self.lineNo = 0
|
406
|
-
self.offset = 0
|
407
|
-
self.lastLine = ''
|
408
|
-
Text.__init__(self, master, **opts)
|
409
|
-
self.hm = HyperlinkManager(self)
|
410
|
-
self.doRefresh()
|
411
|
-
|
412
|
-
def getDefaults(self):
|
413
|
-
return {'bg': "black", 'fg': 'white', 'bd': 0,
|
414
|
-
'height': 30, 'width': 100}
|
415
|
-
# It used to have also 'font': gui.fontNormal but that stops this
|
416
|
-
# file from running. Apparently there is no fontNormal in gui.
|
417
|
-
|
418
|
-
def configureTags(self):
|
419
|
-
if self.colors:
|
420
|
-
configureColorTags(self)
|
421
|
-
|
422
|
-
def _removeLastLine(self):
|
423
|
-
line = int(self.index(tk.END).split('.')[0])
|
424
|
-
if line > 0:
|
425
|
-
line -= 1
|
426
|
-
self.delete('%d.0' % line, tk.END)
|
427
|
-
|
428
|
-
def addLine(self, line):
|
429
|
-
renderLine(line, self._addChunk, self.lineNo)
|
430
|
-
|
431
|
-
def _addChunk(self, txt, fmt=None):
|
432
|
-
"""
|
433
|
-
Add text txt to the widget, with format fmt.
|
434
|
-
fmt can be a color (like 'red') or a link that looks like 'link:url'.
|
435
|
-
"""
|
436
|
-
if self.colors and fmt is not None:
|
437
|
-
if fmt.startswith('link:'):
|
438
|
-
fname = fmt.split(':', 1)[-1]
|
439
|
-
self.insert(tk.END, txt, self.hm.add(lambda: openTextFileEditor(fname)))
|
440
|
-
else:
|
441
|
-
self.insert(tk.END, txt, fmt)
|
442
|
-
else:
|
443
|
-
self.insert(tk.END, txt)
|
444
|
-
|
445
|
-
def _notifyLine(self, line):
|
446
|
-
if '\r' in self.lastLine and '\r' in line:
|
447
|
-
self._removeLastLine()
|
448
|
-
self.addNewline()
|
449
|
-
|
450
|
-
self.lastLine = line
|
451
|
-
|
452
|
-
def readFile(self, clear=False):
|
453
|
-
self.setReadOnly(False)
|
454
|
-
|
455
|
-
if clear:
|
456
|
-
self.offset = 0
|
457
|
-
self.lineNo = 0
|
458
|
-
self.clear()
|
459
|
-
|
460
|
-
if os.path.exists(self.filename):
|
461
|
-
self.offset, self.lineNo = renderTextFile(self.filename,
|
462
|
-
self._addChunk,
|
463
|
-
offset=self.offset,
|
464
|
-
lineNo=self.lineNo,
|
465
|
-
maxSize=self.maxSize,
|
466
|
-
notifyLine=self._notifyLine,
|
467
|
-
errors='replace')
|
468
|
-
|
469
|
-
# I'm cancelling this message. If file does not exist ... text is empty.
|
470
|
-
# else:
|
471
|
-
# self.insert(tk.END, "File '%s' doesn't exist" % self.filename)
|
472
|
-
|
473
|
-
self.setReadOnly(True)
|
474
|
-
# self.goEnd()
|
475
|
-
|
476
|
-
def doRefresh(self):
|
477
|
-
# First stop pending refreshes
|
478
|
-
if self.refreshAlarm:
|
479
|
-
self.after_cancel(self.refreshAlarm)
|
480
|
-
self.refreshAlarm = None
|
481
|
-
|
482
|
-
self.readFile()
|
483
|
-
|
484
|
-
if self.t_refresh > 0:
|
485
|
-
self.refreshAlarm = self.after(self.t_refresh*1000, self.doRefresh)
|
486
|
-
|
487
|
-
|
488
|
-
class TextFileViewer(tk.Frame):
|
489
|
-
""" Implementation of a simple text file viewer """
|
490
|
-
|
491
|
-
# Not used? --> LabelBgColor = "white"
|
492
|
-
|
493
|
-
def __init__(self, master, fileList=[],
|
494
|
-
allowSearch=True, allowRefresh=True, allowOpen=False,
|
495
|
-
font=None, maxSize=400, width=100, height=30):
|
496
|
-
tk.Frame.__init__(self, master)
|
497
|
-
self.searchList = None
|
498
|
-
self.lastSearch = None
|
499
|
-
self.refreshAlarm = None
|
500
|
-
self._lastTabIndex = None
|
501
|
-
self.fileList = [] # Files being visualized
|
502
|
-
self.taList = [] # Text areas (OutputText, a scrollable TkText)
|
503
|
-
self.fontDict = {}
|
504
|
-
self._allowSearch = allowSearch
|
505
|
-
self._allowRefresh = allowRefresh
|
506
|
-
self._allowOpen = allowOpen
|
507
|
-
self._font = font # allow a font to be passed as argument to be used
|
508
|
-
self.maxSize = maxSize
|
509
|
-
self.width = width
|
510
|
-
self.height = height
|
511
|
-
|
512
|
-
self.createWidgets(fileList)
|
513
|
-
self.master = master
|
514
|
-
self.addBinding()
|
515
|
-
|
516
|
-
def addFile(self, filename):
|
517
|
-
self.fileList.append(filename)
|
518
|
-
self._addFileTab(filename)
|
519
|
-
|
520
|
-
def clear(self):
|
521
|
-
""" Remove all added files. """
|
522
|
-
self.fileList = []
|
523
|
-
for _ in self.taList:
|
524
|
-
self.notebook.forget(0)
|
525
|
-
self.taList = []
|
526
|
-
self._lastTabIndex = None
|
527
|
-
|
528
|
-
def _addFileTab(self, filename):
|
529
|
-
tab = tk.Frame(self.notebook)
|
530
|
-
tab.rowconfigure(0, weight=1)
|
531
|
-
tab.columnconfigure(0, weight=1)
|
532
|
-
kwargs = {'bg': 'black',
|
533
|
-
'fg': 'white'}
|
534
|
-
|
535
|
-
if self._font is not None:
|
536
|
-
kwargs['font'] = self._font
|
537
|
-
|
538
|
-
t = OutputText(tab, filename, width=self.width, height=self.height,
|
539
|
-
maxSize=self.maxSize, **kwargs)
|
540
|
-
t.frame.grid(column=0, row=0, padx=5, pady=5, sticky='nsew')
|
541
|
-
self.taList.append(t)
|
542
|
-
tabText = " %s " % os.path.basename(filename)
|
543
|
-
self.notebook.add(tab, text=tabText)
|
544
|
-
|
545
|
-
def createWidgets(self, fileList):
|
546
|
-
# registerCommonFonts()
|
547
|
-
self.columnconfigure(0, weight=1)
|
548
|
-
self.rowconfigure(1, weight=1)
|
549
|
-
|
550
|
-
# Create toolbar frame
|
551
|
-
toolbarFrame = tk.Frame(self)
|
552
|
-
toolbarFrame.grid(column=0, row=0, padx=5, sticky='new')
|
553
|
-
gui.configureWeigths(toolbarFrame)
|
554
|
-
# Add the search box
|
555
|
-
right = tk.Frame(toolbarFrame)
|
556
|
-
right.grid(column=1, row=0, sticky='ne')
|
557
|
-
self.searchVar = tk.StringVar()
|
558
|
-
if self._allowSearch:
|
559
|
-
tk.Label(right, text='Search:').grid(row=0, column=3, padx=5)
|
560
|
-
self.searchEntry = tk.Entry(right, textvariable=self.searchVar,
|
561
|
-
font=self._font)
|
562
|
-
self.searchEntry.grid(row=0, column=4, sticky='ew', padx=5)
|
563
|
-
|
564
|
-
# self.searchEntry.bind('<Return>', self.findText)
|
565
|
-
# self.searchEntry.bind('<KP_Enter>', self.findText)
|
566
|
-
# # btn = IconButton(right, "Search", Icon.ACTION_SEARCH,
|
567
|
-
# tooltip=Message.TOOLTIP_SEARCH,
|
568
|
-
# command=self.findText, bg=None)
|
569
|
-
# btn.grid(row=0, column=5, padx=(0, 5))
|
570
|
-
|
571
|
-
btn = IconButton(right, "Next", Icon.ACTION_FIND_NEXT,
|
572
|
-
tooltip=Message.TOOLTIP_SEARCH_NEXT,
|
573
|
-
command=self.findText, bg=None)
|
574
|
-
btn.grid(row=0, column=5, padx=(0, 5))
|
575
|
-
|
576
|
-
btn = IconButton(right, "Previous", Icon.ACTION_FIND_PREVIOUS,
|
577
|
-
tooltip=Message.TOOLTIP_SEARCH_PREVIOUS,
|
578
|
-
command=self.findPrevText, bg=None)
|
579
|
-
btn.grid(row=0, column=6, padx=(0, 5))
|
580
|
-
|
581
|
-
if self._allowRefresh:
|
582
|
-
btn = IconButton(right, "Refresh", Icon.ACTION_REFRESH,
|
583
|
-
tooltip=Message.TOOLTIP_REFRESH,
|
584
|
-
command=self._onRefresh, bg=None)
|
585
|
-
btn.grid(row=0, column=7, padx=(0, 5), pady=2)
|
586
|
-
if self._allowOpen:
|
587
|
-
btn = IconButton(right, "Open external", Icon.ACTION_REFERENCES,
|
588
|
-
tooltip=Message.TOOLTIP_EXTERNAL,
|
589
|
-
command=self._openExternal, bg=None)
|
590
|
-
btn.grid(row=0, column=8, padx=(0, 5), pady=2)
|
591
|
-
|
592
|
-
# Create tabs frame
|
593
|
-
tabsFrame = tk.Frame(self)
|
594
|
-
tabsFrame.grid(column=0, row=1, padx=5, pady=(0, 5), sticky="nsew")
|
595
|
-
tabsFrame.columnconfigure(0, weight=1)
|
596
|
-
tabsFrame.rowconfigure(0, weight=1)
|
597
|
-
self.notebook = ttk.Notebook(tabsFrame)
|
598
|
-
self.notebook.rowconfigure(0, weight=1)
|
599
|
-
self.notebook.columnconfigure(0, weight=1)
|
600
|
-
for f in fileList:
|
601
|
-
self._addFileTab(f)
|
602
|
-
self.notebook.grid(column=0, row=0, sticky='nsew', padx=5, pady=5)
|
603
|
-
self.notebook.bind('<<NotebookTabChanged>>', self._tabChanged)
|
604
|
-
|
605
|
-
def _tabChanged(self, e=None):
|
606
|
-
self._lastTabIndex = self.notebook.select()
|
607
|
-
# reset the search
|
608
|
-
self.lastSearch = None
|
609
|
-
# Setting the focus, captures it when selecting protocols and
|
610
|
-
# therefore "deleting" using keys or other future shortcut for the canvas
|
611
|
-
# will not work.
|
612
|
-
# self.searchEntry.focus_set()
|
613
|
-
|
614
|
-
def addBinding(self):
|
615
|
-
|
616
|
-
shortcutDefinitions = [(lambda e: self.findText(), "Trigger the search", ['<Return>']),
|
617
|
-
(lambda e: self.findText(), "Trigger the search", ['<KP_Enter>']),
|
618
|
-
(lambda e: self.findText(matchCase=True), "Trigger a case sensitive search", ['<Shift-Return>']),
|
619
|
-
(lambda e: self.findText(), "Move to the next highlighted item", ["<Down>", '<F3>']),
|
620
|
-
(lambda e: self.findText(-1), "Move to the previous highlighted item", ["<Up>", '<Shift-F3>']),
|
621
|
-
(lambda e: self.modifyFontSize(pw.Config.SCIPION_FONT_SIZE + 2), "Increase the font size",["<Control-KP_Add>"]),
|
622
|
-
(lambda e: self.modifyFontSize(pw.Config.SCIPION_FONT_SIZE), "Increase the font size",["<Control-KP_Subtract>"]),
|
623
|
-
]
|
624
|
-
tooltip = "Shortcuts:"
|
625
|
-
|
626
|
-
for callback, help, keys in shortcutDefinitions:
|
627
|
-
tooltip += "\n" + help + ": "
|
628
|
-
for key in keys:
|
629
|
-
self.searchEntry.bind(key, callback)
|
630
|
-
tooltip += key
|
631
|
-
|
632
|
-
# Add a tooltip
|
633
|
-
ToolTip(self.searchEntry, tooltip, 800)
|
634
|
-
|
635
|
-
def getIndex(self):
|
636
|
-
""" Return the index of the selected tab. """
|
637
|
-
selected = self.notebook.select()
|
638
|
-
if selected:
|
639
|
-
return self.notebook.index(selected)
|
640
|
-
return -1
|
641
|
-
|
642
|
-
def setIndex(self, index):
|
643
|
-
""" Select the tab with the given index. """
|
644
|
-
if index != -1:
|
645
|
-
self.notebook.select(self.notebook.tabs()[index])
|
646
|
-
|
647
|
-
def selectedText(self):
|
648
|
-
index = self.getIndex()
|
649
|
-
if index != -1:
|
650
|
-
return self.taList[index]
|
651
|
-
return None
|
652
|
-
|
653
|
-
def changeFont(self, event=""):
|
654
|
-
for font in self.fontDict.values():
|
655
|
-
gui.changeFontSize(font, event)
|
656
|
-
|
657
|
-
def refreshAll(self, clear=False, goEnd=False):
|
658
|
-
""" Refresh all output textareas. """
|
659
|
-
for ta in self.taList:
|
660
|
-
ta.readFile(clear)
|
661
|
-
if goEnd:
|
662
|
-
ta.goEnd()
|
663
|
-
if self._lastTabIndex is not None:
|
664
|
-
self.notebook.select(self._lastTabIndex)
|
665
|
-
|
666
|
-
def _onRefresh(self, e=None):
|
667
|
-
""" Action triggered when the 'Refresh' icon is clicked. """
|
668
|
-
self.refreshAll(clear=False, goEnd=True)
|
669
|
-
|
670
|
-
def refreshOutput(self, e=None):
|
671
|
-
if self.refreshAlarm:
|
672
|
-
self.after_cancel(self.refreshAlarm)
|
673
|
-
self.refreshAlarm = None
|
674
|
-
text = self.selectedText()
|
675
|
-
if text:
|
676
|
-
text.readFile()
|
677
|
-
|
678
|
-
def changePosition(self, index):
|
679
|
-
self.selectedText().see(index)
|
680
|
-
|
681
|
-
def findPrevText(self):
|
682
|
-
self.findText(-1)
|
683
|
-
|
684
|
-
def modifyFontSize(self, newSize):
|
685
|
-
text = self.selectedText()
|
686
|
-
text['font']=(None, newSize)
|
687
|
-
|
688
|
-
def findText(self, direction=1, matchCase=0):
|
689
|
-
text = self.selectedText()
|
690
|
-
str = self.searchVar.get()
|
691
|
-
if text:
|
692
|
-
if str is None or str != self.lastSearch:
|
693
|
-
self.buildSearchList(text, str, matchCase=matchCase)
|
694
|
-
self.lastSearch = str
|
695
|
-
|
696
|
-
else:
|
697
|
-
self.nextSearchIndex(text, direction)
|
698
|
-
self.searchEntry.focus_set()
|
699
|
-
|
700
|
-
def buildSearchList(self, text, str, matchCase=0):
|
701
|
-
text.tag_remove('found', '1.0', tk.END)
|
702
|
-
list = []
|
703
|
-
if str:
|
704
|
-
idx = '1.0'
|
705
|
-
while True:
|
706
|
-
idx = text.search(str, idx, nocase=not matchCase, stopindex=tk.END)
|
707
|
-
if not idx:
|
708
|
-
break
|
709
|
-
lastidx = '%s+%dc' % (idx, len(str))
|
710
|
-
text.tag_add('found', idx, lastidx)
|
711
|
-
list.append((idx, lastidx))
|
712
|
-
idx = lastidx
|
713
|
-
text.tag_config('found', foreground='white', background='blue')
|
714
|
-
# Set class variables
|
715
|
-
self.searchList = list
|
716
|
-
self.currentIndex = -1
|
717
|
-
self.nextSearchIndex(text) # select first element
|
718
|
-
|
719
|
-
def nextSearchIndex(self, text, direction=1):
|
720
|
-
# use direction=-1 to go backward
|
721
|
-
text.tag_remove('found_current', '1.0', tk.END)
|
722
|
-
if len(self.searchList) == 0:
|
723
|
-
return
|
724
|
-
self.currentIndex = (self.currentIndex + direction) % len(self.searchList)
|
725
|
-
idx, lastidx = self.searchList[self.currentIndex]
|
726
|
-
text.tag_config('found_current', foreground='yellow', background='red')
|
727
|
-
text.tag_add('found_current', idx, lastidx)
|
728
|
-
text.see(idx)
|
729
|
-
|
730
|
-
def _openExternal(self):
|
731
|
-
""" Open a new window with an external viewer. """
|
732
|
-
if envVarOn('SCIPION_EXTERNAL_VIEWER'):
|
733
|
-
if not self.taList:
|
734
|
-
return
|
735
|
-
openTextFileEditor(self.taList[max(self.getIndex(), 0)].filename)
|
736
|
-
else:
|
737
|
-
showTextFileViewer("File viewer", self.fileList, self.windows)
|
738
|
-
|
739
|
-
|
740
|
-
def openTextFile(filename):
|
741
|
-
""" Open a text file with an external or default viewer. """
|
742
|
-
if envVarOn('SCIPION_EXTERNAL_VIEWER'):
|
743
|
-
openTextFileEditor(filename)
|
744
|
-
else:
|
745
|
-
showTextFileViewer("File viewer", [filename])
|
746
|
-
|
747
|
-
|
748
|
-
def openTextFileEditor(filename, tkParent=None):
|
749
|
-
try:
|
750
|
-
_open_cmd(filename, tkParent)
|
751
|
-
except:
|
752
|
-
showTextFileViewer("File viewer", [filename])
|
753
|
-
|
754
|
-
|
755
|
-
def showTextFileViewer(title, filelist, parent=None, main=False):
|
756
|
-
w = gui.Window(title, parent, minsize=(600, 400))
|
757
|
-
viewer = TextFileViewer(w.root, filelist, maxSize=-1, font=w.font)
|
758
|
-
viewer.grid(row=0, column=0, sticky='news')
|
759
|
-
gui.configureWeigths(w.root)
|
760
|
-
w.show()
|
761
|
-
|
762
|
-
|
763
|
-
if __name__ == '__main__':
|
764
|
-
root = tk.Tk()
|
765
|
-
root.withdraw()
|
766
|
-
root.title("View files")
|
767
|
-
l = TextFileViewer(root, fileList=sys.argv[1:])
|
768
|
-
l.pack(side=tk.TOP, fill=tk.BOTH)
|
769
|
-
gui.centerWindows(root)
|
770
|
-
root.deiconify()
|
771
|
-
root.mainloop()
|