PrEditor 2.1.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.
- preditor/__init__.py +315 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +165 -0
- preditor/cli.py +192 -0
- preditor/config.py +318 -0
- preditor/constants.py +13 -0
- preditor/contexts.py +210 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/.hab.json +10 -0
- preditor/dccs/maya/PrEditor_maya.mod +1 -0
- preditor/dccs/maya/README.md +22 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
- preditor/dccs/studiomax/PackageContents.xml +32 -0
- preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
- preditor/dccs/studiomax/README.md +17 -0
- preditor/dccs/studiomax/preditor.ms +16 -0
- preditor/dccs/studiomax/preditor_menu.mnx +7 -0
- preditor/debug.py +149 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +165 -0
- preditor/gui/__init__.py +56 -0
- preditor/gui/app.py +163 -0
- preditor/gui/codehighlighter.py +289 -0
- preditor/gui/completer.py +237 -0
- preditor/gui/console.py +605 -0
- preditor/gui/console_base.py +911 -0
- preditor/gui/dialog.py +181 -0
- preditor/gui/drag_tab_bar.py +625 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +69 -0
- preditor/gui/find_files.py +137 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
- preditor/gui/group_tab_widget/__init__.py +0 -0
- preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
- preditor/gui/level_buttons.py +358 -0
- preditor/gui/logger_window_handler.py +77 -0
- preditor/gui/logger_window_plugin.py +35 -0
- preditor/gui/loggerwindow.py +2405 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/output_console.py +11 -0
- preditor/gui/qtdesigner/__init__.py +21 -0
- preditor/gui/qtdesigner/_log_plugin.py +29 -0
- preditor/gui/qtdesigner/console_base_plugin.py +48 -0
- preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
- preditor/gui/set_text_editor_path_dialog.py +61 -0
- preditor/gui/status_label.py +99 -0
- preditor/gui/suggest_path_quotes_dialog.py +50 -0
- preditor/gui/ui/editor_chooser.ui +93 -0
- preditor/gui/ui/errordialog.ui +74 -0
- preditor/gui/ui/find_files.ui +140 -0
- preditor/gui/ui/loggerwindow.ui +1909 -0
- preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
- preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
- preditor/gui/window.py +161 -0
- preditor/gui/workbox_mixin.py +1139 -0
- preditor/gui/workbox_text_edit.py +136 -0
- preditor/gui/workboxwidget.py +315 -0
- preditor/logging_config.py +55 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +381 -0
- preditor/resource/environment_variables.html +26 -0
- preditor/resource/error_mail.html +85 -0
- preditor/resource/error_mail_inline.html +41 -0
- preditor/resource/img/README.md +17 -0
- preditor/resource/img/arrow_forward.png +0 -0
- preditor/resource/img/check-bold.png +0 -0
- preditor/resource/img/chevron-down.png +0 -0
- preditor/resource/img/chevron-up.png +0 -0
- preditor/resource/img/close-thick.png +0 -0
- preditor/resource/img/comment-edit.png +0 -0
- preditor/resource/img/content-copy.png +0 -0
- preditor/resource/img/content-cut.png +0 -0
- preditor/resource/img/content-duplicate.png +0 -0
- preditor/resource/img/content-paste.png +0 -0
- preditor/resource/img/content-save.png +0 -0
- preditor/resource/img/debug_disabled.png +0 -0
- preditor/resource/img/eye-check.png +0 -0
- preditor/resource/img/file-plus.png +0 -0
- preditor/resource/img/file-remove.png +0 -0
- preditor/resource/img/format-align-left.png +0 -0
- preditor/resource/img/format-letter-case-lower.png +0 -0
- preditor/resource/img/format-letter-case-upper.png +0 -0
- preditor/resource/img/format-letter-case.svg +1 -0
- preditor/resource/img/information.png +0 -0
- preditor/resource/img/logging_critical.png +0 -0
- preditor/resource/img/logging_custom.png +0 -0
- preditor/resource/img/logging_debug.png +0 -0
- preditor/resource/img/logging_error.png +0 -0
- preditor/resource/img/logging_info.png +0 -0
- preditor/resource/img/logging_not_set.png +0 -0
- preditor/resource/img/logging_warning.png +0 -0
- preditor/resource/img/marker.png +0 -0
- preditor/resource/img/play.png +0 -0
- preditor/resource/img/playlist-play.png +0 -0
- preditor/resource/img/plus-minus-variant.png +0 -0
- preditor/resource/img/preditor.ico +0 -0
- preditor/resource/img/preditor.png +0 -0
- preditor/resource/img/preditor.psd +0 -0
- preditor/resource/img/preditor.svg +44 -0
- preditor/resource/img/regex.svg +1 -0
- preditor/resource/img/restart.svg +1 -0
- preditor/resource/img/skip-forward-outline.png +0 -0
- preditor/resource/img/skip-next-outline.png +0 -0
- preditor/resource/img/skip-next.png +0 -0
- preditor/resource/img/skip-previous.png +0 -0
- preditor/resource/img/subdirectory-arrow-right.png +0 -0
- preditor/resource/img/text-search-variant.png +0 -0
- preditor/resource/img/warning-big.png +0 -0
- preditor/resource/lang/python.json +30 -0
- preditor/resource/pref_updates/pref_updates.json +17 -0
- preditor/resource/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +76 -0
- preditor/resource/stylesheet/Dark.css +210 -0
- preditor/scintilla/__init__.py +40 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +97 -0
- preditor/scintilla/delayables/spell_check.py +174 -0
- preditor/scintilla/documenteditor.py +1924 -0
- preditor/scintilla/finddialog.py +68 -0
- preditor/scintilla/lang/__init__.py +80 -0
- preditor/scintilla/lang/config/bash.ini +15 -0
- preditor/scintilla/lang/config/batch.ini +14 -0
- preditor/scintilla/lang/config/cpp.ini +19 -0
- preditor/scintilla/lang/config/css.ini +19 -0
- preditor/scintilla/lang/config/eyeonscript.ini +17 -0
- preditor/scintilla/lang/config/html.ini +21 -0
- preditor/scintilla/lang/config/javascript.ini +24 -0
- preditor/scintilla/lang/config/lua.ini +16 -0
- preditor/scintilla/lang/config/maxscript.ini +20 -0
- preditor/scintilla/lang/config/mel.ini +18 -0
- preditor/scintilla/lang/config/mu.ini +22 -0
- preditor/scintilla/lang/config/nsi.ini +19 -0
- preditor/scintilla/lang/config/perl.ini +19 -0
- preditor/scintilla/lang/config/puppet.ini +19 -0
- preditor/scintilla/lang/config/python.ini +28 -0
- preditor/scintilla/lang/config/ruby.ini +19 -0
- preditor/scintilla/lang/config/sql.ini +7 -0
- preditor/scintilla/lang/config/xml.ini +21 -0
- preditor/scintilla/lang/config/yaml.ini +18 -0
- preditor/scintilla/lang/language.py +240 -0
- preditor/scintilla/lexers/__init__.py +0 -0
- preditor/scintilla/lexers/cpplexer.py +22 -0
- preditor/scintilla/lexers/javascriptlexer.py +27 -0
- preditor/scintilla/lexers/maxscriptlexer.py +235 -0
- preditor/scintilla/lexers/mellexer.py +369 -0
- preditor/scintilla/lexers/mulexer.py +33 -0
- preditor/scintilla/lexers/pythonlexer.py +42 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +72 -0
- preditor/stream/console_handler.py +169 -0
- preditor/stream/director.py +144 -0
- preditor/stream/manager.py +97 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +191 -0
- preditor/utils/call_stack.py +86 -0
- preditor/utils/cute.py +106 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +338 -0
- preditor/version.py +34 -0
- preditor/weakref.py +363 -0
- preditor-2.1.0.dist-info/METADATA +308 -0
- preditor-2.1.0.dist-info/RECORD +179 -0
- preditor-2.1.0.dist-info/WHEEL +5 -0
- preditor-2.1.0.dist-info/entry_points.txt +19 -0
- preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
- preditor-2.1.0.dist-info/top_level.txt +3 -0
- tests/encodings/test_ecoding.py +33 -0
- tests/find_files/test_find_files.py +74 -0
- tests/ide/test_delayable_engine.py +171 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Json:
|
|
10
|
+
"""Load a json file with a better tracebacks if something goes wrong.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
filename (pathlib.Path): The path to the file being loaded/parsed. Unless
|
|
14
|
+
`json_str` is also provided load will use `json.load` to parse the
|
|
15
|
+
contents of this json file.
|
|
16
|
+
json_str (str, optional): If provided then uses `json.loads` to parse
|
|
17
|
+
this value. `filename` must be provided and will be included in any
|
|
18
|
+
exceptions that are raised parsing this text.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, filename, json_str=None):
|
|
22
|
+
if isinstance(filename, str):
|
|
23
|
+
filename = Path(filename)
|
|
24
|
+
self.filename = filename
|
|
25
|
+
self.json_str = json_str
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _load_json(cls, source, load_funct, *args, **kwargs):
|
|
29
|
+
"""Work function that parses json and ensures any errors report the source.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
source (os.PathLike or str): The source of the json data. This is
|
|
33
|
+
reported in any raised exceptions.
|
|
34
|
+
load_funct (callable): A function called to parse the json data.
|
|
35
|
+
Normally this is `json.load` or `json.loads`.
|
|
36
|
+
*args: Arguments passed to `load_funct`.
|
|
37
|
+
*kwargs: Keyword arguments passed to `load_funct`.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
FileNotFoundError: If filename is not pointing to a file that
|
|
41
|
+
actually exists.
|
|
42
|
+
ValueError: The error raised due to invalid json.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
return load_funct(*args, **kwargs)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
# Using python's native json parser
|
|
48
|
+
msg = f'{e} Source("{source}")'
|
|
49
|
+
raise type(e)(msg, e.doc, e.pos).with_traceback(sys.exc_info()[2]) from None
|
|
50
|
+
|
|
51
|
+
def load(self):
|
|
52
|
+
"""Parse and return self.json_str if defined otherwise self.filename."""
|
|
53
|
+
if self.json_str:
|
|
54
|
+
return self.loads_json(self.json_str, self.filename)
|
|
55
|
+
return self.load_json_file(self.filename)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def load_json_file(cls, filename):
|
|
59
|
+
"""Open and parse a json file. If a parsing error happens the file path
|
|
60
|
+
is added to the exception to allow for easier debugging.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
filename (pathlib.Path): A existing file path.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The data stored in the json file.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
FileNotFoundError: If filename is not pointing to a file that
|
|
70
|
+
actually exists.
|
|
71
|
+
ValueError: The error raised due to invalid json.
|
|
72
|
+
"""
|
|
73
|
+
if not filename.is_file():
|
|
74
|
+
raise FileNotFoundError(
|
|
75
|
+
errno.ENOENT, os.strerror(errno.ENOENT), str(filename)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with filename.open() as fle:
|
|
79
|
+
data = cls._load_json(filename, json.load, fle)
|
|
80
|
+
return data
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def loads_json(cls, json_str, source):
|
|
84
|
+
"""Open and parse a json string. If a parsing error happens the source
|
|
85
|
+
file path is added to the exception to allow for easier debugging.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
json_str (str): The json data to parse.
|
|
89
|
+
source (pathlib.Path): The location json_str was pulled from.
|
|
90
|
+
This is reported if any parsing errors happen.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The data stored in the json file.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
FileNotFoundError: If filename is not pointing to a file that
|
|
97
|
+
actually exists.
|
|
98
|
+
ValueError: The error raised due to invalid json.
|
|
99
|
+
"""
|
|
100
|
+
return cls._load_json(source, json.loads, json_str)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ShellPrint:
|
|
104
|
+
"""Utilities to print to sys.__stdout__/__stderr__ bypassing the PrEditor streams.
|
|
105
|
+
|
|
106
|
+
This allows you to write to the host console instead of PrEditor's interface.
|
|
107
|
+
This helps when developing for the stream or console classes if you cause an
|
|
108
|
+
exception you might get no traceback printed to debug otherwise.
|
|
109
|
+
|
|
110
|
+
On windows if using gui mode app(pythonw.exe) that doesn't have a console
|
|
111
|
+
the methods will not print anything and just return False. If possible switch
|
|
112
|
+
to using python.exe or install another file stream like `preditor.debug.FileLogger`,
|
|
113
|
+
but they will need to be installed on `sys.__stdout__/__stderr__`.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, error=False):
|
|
117
|
+
self.error = error
|
|
118
|
+
|
|
119
|
+
def print(self, *args, **kwargs):
|
|
120
|
+
"""Prints to the shell."""
|
|
121
|
+
if "file" in kwargs:
|
|
122
|
+
raise KeyError(
|
|
123
|
+
"file can not be passed to `ShellPrint.print`. Instead use error."
|
|
124
|
+
)
|
|
125
|
+
# Check for pythonw.exe's lack of streams and exit
|
|
126
|
+
kwargs["file"] = self.stream
|
|
127
|
+
if kwargs["file"] is None:
|
|
128
|
+
"""Note: This protects against errors like this when using pythonw
|
|
129
|
+
File "preditor/stream/director.py", line 124, in write
|
|
130
|
+
super().write(msg)
|
|
131
|
+
RuntimeError: reentrant call inside <_io.BufferedWriter name='nul'>
|
|
132
|
+
"""
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Print to the host stream
|
|
136
|
+
print(*args, **kwargs)
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def print_exc(self, msg, limit=None, chain=True, width=79):
|
|
140
|
+
"""Prints a header line, the current exception and a footer line to shell.
|
|
141
|
+
|
|
142
|
+
This must be called from inside of a try/except statement.
|
|
143
|
+
"""
|
|
144
|
+
stream = self.stream
|
|
145
|
+
if stream is None:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
print(f" {msg} ".center(width, "-"), file=sys.__stderr__)
|
|
149
|
+
traceback.print_exc(limit=limit, file=stream, chain=chain)
|
|
150
|
+
print(f" {msg} ".center(width, "-"), file=sys.__stderr__)
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def stream(self):
|
|
155
|
+
if self.error:
|
|
156
|
+
return sys.__stderr__
|
|
157
|
+
return sys.__stdout__
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Truncate:
|
|
161
|
+
def __init__(self, text, sep='...'):
|
|
162
|
+
self.text = text
|
|
163
|
+
self.sep = sep
|
|
164
|
+
self.sep_spaces = f' {sep} '
|
|
165
|
+
|
|
166
|
+
def middle(self, n=100):
|
|
167
|
+
"""Truncates the provided text to a fixed length, putting the sep in the middle.
|
|
168
|
+
https://www.xormedia.com/string-truncate-middle-with-ellipsis/
|
|
169
|
+
"""
|
|
170
|
+
if len(self.text) <= n:
|
|
171
|
+
# string is already short-enough
|
|
172
|
+
return self.text
|
|
173
|
+
# half of the size, minus the seperator
|
|
174
|
+
n_2 = int(n) // 2 - len(self.sep_spaces)
|
|
175
|
+
# whatever's left
|
|
176
|
+
n_1 = n - n_2 - len(self.sep_spaces)
|
|
177
|
+
return '{0}{1}{2}'.format(self.text[:n_1], self.sep_spaces, self.text[-n_2:])
|
|
178
|
+
|
|
179
|
+
def lines(self, max_lines=20):
|
|
180
|
+
"""Truncates the provided text to a maximum number of lines with a separator
|
|
181
|
+
at the end if required.
|
|
182
|
+
"""
|
|
183
|
+
lines = self.text.split("\n")
|
|
184
|
+
orig_len = len(lines)
|
|
185
|
+
lines = lines[:max_lines]
|
|
186
|
+
trim_len = len(lines)
|
|
187
|
+
if orig_len != trim_len:
|
|
188
|
+
lines.append("...")
|
|
189
|
+
truncated = "\n".join(lines)
|
|
190
|
+
|
|
191
|
+
return truncated
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
_logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CallStack:
|
|
10
|
+
"""Decorator that logs the inputs and return of the decorated function.
|
|
11
|
+
|
|
12
|
+
For most cases use `@preditor.utils.call_stack.log_calls`, but if you want
|
|
13
|
+
to create a custom configured version you can create your own instance of
|
|
14
|
+
`CallStack` to customize the output.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
logger: A python logging instance to write log messages to.
|
|
18
|
+
level: Write to logger using this debug level.
|
|
19
|
+
print: If set to True use the print function instead of python logging.
|
|
20
|
+
input_prefix: Text shown before the function name.
|
|
21
|
+
return_prefix: Text shown before the return data.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, logger=None, level=logging.DEBUG, print=True):
|
|
25
|
+
self._call_depth = threading.local()
|
|
26
|
+
self.logger = _logger if logger is None else logger
|
|
27
|
+
self.level = level
|
|
28
|
+
self.print = print
|
|
29
|
+
self.input_prefix = "\u2192"
|
|
30
|
+
self.return_prefix = "\u2190"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def indent(self):
|
|
34
|
+
return getattr(self._call_depth, "indent", 0)
|
|
35
|
+
|
|
36
|
+
@indent.setter
|
|
37
|
+
def indent(self, indent):
|
|
38
|
+
self._call_depth.indent = indent
|
|
39
|
+
|
|
40
|
+
def log(self, msg):
|
|
41
|
+
if self.print:
|
|
42
|
+
print(msg)
|
|
43
|
+
else:
|
|
44
|
+
self.logger.log(self.level, msg, stacklevel=2)
|
|
45
|
+
|
|
46
|
+
def log_calls(self, func):
|
|
47
|
+
"""Decorator that writes function input and return value.
|
|
48
|
+
If another decorated function call is made during the first call it's
|
|
49
|
+
output will be indented to reflect that.
|
|
50
|
+
"""
|
|
51
|
+
# Check for and remove self and cls arguments once during decoration
|
|
52
|
+
sig = inspect.signature(func)
|
|
53
|
+
params = list(sig.parameters.values())
|
|
54
|
+
slice_index = 1 if params and params[0].name in ("self", "cls") else 0
|
|
55
|
+
|
|
56
|
+
@functools.wraps(func)
|
|
57
|
+
def wrapper(*args, **kwargs):
|
|
58
|
+
# remove 'self' or 'cls' from positional display (but do NOT remove kwargs)
|
|
59
|
+
# display_args = args[1:] if slice_index and args else args
|
|
60
|
+
display_args = args[slice_index:]
|
|
61
|
+
|
|
62
|
+
# Generate repr of the calling arguments
|
|
63
|
+
parts = [repr(a) for a in display_args]
|
|
64
|
+
parts += [f"{k}={v!r}" for k, v in kwargs.items()]
|
|
65
|
+
arg_str = ", ".join(parts)
|
|
66
|
+
|
|
67
|
+
indent = " " * self.indent
|
|
68
|
+
self.log(f"{indent}{self.input_prefix} {func.__qualname__}({arg_str})")
|
|
69
|
+
|
|
70
|
+
self.indent += 1
|
|
71
|
+
try:
|
|
72
|
+
result = func(*args, **kwargs)
|
|
73
|
+
finally:
|
|
74
|
+
self.indent -= 1
|
|
75
|
+
|
|
76
|
+
self.log(f"{indent}{self.return_prefix} {result!r}")
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
return wrapper
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
call_stack = CallStack()
|
|
83
|
+
"""An shared instance for ease of use and configuration"""
|
|
84
|
+
|
|
85
|
+
log_calls = call_stack.log_calls
|
|
86
|
+
"""Use `from preditor.utils.call_stack import log_calls` as a shared decorator."""
|
preditor/utils/cute.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
__all__ = ["ensureWindowIsVisible"]
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
# NOTE: Only import QtWidgets and QtGui inside functions not at the module level
|
|
5
|
+
# to preserve headless environment support.
|
|
6
|
+
from Qt.QtCore import Property
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def ensureWindowIsVisible(widget):
|
|
10
|
+
"""
|
|
11
|
+
Checks the widget's geometry against all of the system's screens. If it does
|
|
12
|
+
not intersect, it will reposition it to the top left corner of the highest
|
|
13
|
+
numbered screen. Returns a boolean indicating if it had to move the widget.
|
|
14
|
+
"""
|
|
15
|
+
from Qt.QtWidgets import QApplication
|
|
16
|
+
|
|
17
|
+
screens = QApplication.screens()
|
|
18
|
+
geo = widget.geometry()
|
|
19
|
+
|
|
20
|
+
for screen in screens:
|
|
21
|
+
if screen.geometry().intersects(geo):
|
|
22
|
+
break
|
|
23
|
+
else:
|
|
24
|
+
monGeo = screens[-1].geometry() # Use the last screen available
|
|
25
|
+
geo.moveTo(monGeo.x() + 7, monGeo.y() + 30)
|
|
26
|
+
|
|
27
|
+
# Setting the geometry may trigger a second check if setGeometry is overridden
|
|
28
|
+
disable = hasattr(widget, 'checkScreenGeo') and widget.checkScreenGeo
|
|
29
|
+
if disable:
|
|
30
|
+
widget.checkScreenGeo = False
|
|
31
|
+
widget.setGeometry(geo)
|
|
32
|
+
if disable:
|
|
33
|
+
widget.checkScreenGeo = True
|
|
34
|
+
return True
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def QtPropertyInit(name, default, callback=None, typ=None):
|
|
39
|
+
"""Initializes a default Property value with a usable getter and setter.
|
|
40
|
+
|
|
41
|
+
You can optionally pass a function that will get called any time the property
|
|
42
|
+
is set. If using the same callback for multiple properties, you may want to
|
|
43
|
+
use the preditor.decorators.singleShot decorator to prevent your function getting
|
|
44
|
+
called multiple times at once. This callback must accept the attribute name and
|
|
45
|
+
value being set.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
class TestClass(QWidget):
|
|
49
|
+
def __init__(self, *args, **kwargs):
|
|
50
|
+
super(TestClass, self).__init__(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
stdoutColor = QtPropertyInit('_stdoutColor', QColor(0, 0, 255))
|
|
53
|
+
pyForegroundColor = QtPropertyInit('_pyForegroundColor', QColor(0, 0, 255))
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name(str): The name of internal attribute to store to and lookup from.
|
|
57
|
+
default: The property's default value. This will also define the Property type
|
|
58
|
+
if typ is not set. To define a property containing a list, dict or set,
|
|
59
|
+
pass the list, dict, or set class not an instance of the class. Ie pass
|
|
60
|
+
`list` not `[]`. See flake8-bugbear rule B006 for more info.
|
|
61
|
+
callback(callable): If provided this function is called when the property is
|
|
62
|
+
set.
|
|
63
|
+
typ (class, optional): If not None this value is used to specify the type of
|
|
64
|
+
the Property. This is useful when you need to specify a property as python's
|
|
65
|
+
object but pass a default value of a given class.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Property
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# Prevent all instances of class sharing a mutable data structure. If the
|
|
72
|
+
# default is one of these classes and not a instance of them, replace them
|
|
73
|
+
# with an instance of the default class.
|
|
74
|
+
# See flake8-bugbear B006: Do not use mutable data structures for argument
|
|
75
|
+
# defaults. They are created during function definition time. All calls to
|
|
76
|
+
# the function reuse this one instance of that data structure, persisting
|
|
77
|
+
# changes between them.
|
|
78
|
+
is_mutable = default in (list, dict, set)
|
|
79
|
+
|
|
80
|
+
def _getattrDefault(default, is_mutable, self, attrName):
|
|
81
|
+
try:
|
|
82
|
+
value = getattr(self, attrName)
|
|
83
|
+
except AttributeError:
|
|
84
|
+
# Create a unique instance of the default mutable class for self.
|
|
85
|
+
if is_mutable:
|
|
86
|
+
default = default()
|
|
87
|
+
|
|
88
|
+
setattr(self, attrName, default)
|
|
89
|
+
return default
|
|
90
|
+
return value
|
|
91
|
+
|
|
92
|
+
def _setattrCallback(callback, attrName, self, value):
|
|
93
|
+
setattr(self, attrName, value)
|
|
94
|
+
if callback:
|
|
95
|
+
callback(self, attrName, value)
|
|
96
|
+
|
|
97
|
+
ga = partial(_getattrDefault, default, is_mutable)
|
|
98
|
+
sa = partial(_setattrCallback, callback, name)
|
|
99
|
+
# Use the default value's class if typ is not provided.
|
|
100
|
+
if typ is None:
|
|
101
|
+
if is_mutable:
|
|
102
|
+
# In this case the type is the same as the default
|
|
103
|
+
typ = default
|
|
104
|
+
else:
|
|
105
|
+
typ = default.__class__
|
|
106
|
+
return Property(typ, fget=(lambda s: ga(s, name)), fset=(lambda s, v: sa(s, v)))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_stylesheet(stylesheet='', path=None):
|
|
8
|
+
"""Returns the contents of the requested stylesheet.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
|
|
12
|
+
stylesheet (str): the name of the stylesheet. Attempt to load stylesheet.css
|
|
13
|
+
shipped with preditor. Ignored if path is provided.
|
|
14
|
+
|
|
15
|
+
path (str): Return the contents of this file path.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The contents of stylesheet or blank if stylesheet was not found.
|
|
19
|
+
valid: A stylesheet was found and loaded.
|
|
20
|
+
"""
|
|
21
|
+
if path is None:
|
|
22
|
+
path = os.path.join(
|
|
23
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
24
|
+
'resource',
|
|
25
|
+
'stylesheet',
|
|
26
|
+
'{}.css'.format(stylesheet),
|
|
27
|
+
)
|
|
28
|
+
if os.path.isfile(path):
|
|
29
|
+
with open(path) as f:
|
|
30
|
+
return f.read(), True
|
|
31
|
+
return '', False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def stylesheets(subFolder=None):
|
|
35
|
+
"""Returns a list of installed stylesheet names.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
subFolder (str or None, optional): Use this to access sub-folders of
|
|
39
|
+
the stylesheet resource directory.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
list: A list .css file paths in the target directory.
|
|
43
|
+
"""
|
|
44
|
+
components = [
|
|
45
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
46
|
+
'resource',
|
|
47
|
+
'stylesheet',
|
|
48
|
+
]
|
|
49
|
+
if subFolder is not None:
|
|
50
|
+
components.append(subFolder)
|
|
51
|
+
cssdir = os.path.join(*components)
|
|
52
|
+
cssfiles = sorted(glob.glob(os.path.join(cssdir, '*.css')))
|
|
53
|
+
# Only return the filename without the .css extension
|
|
54
|
+
return [os.path.splitext(os.path.basename(fp))[0] for fp in cssfiles]
|