PrEditor 1.0.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.
Potentially problematic release.
This version of PrEditor might be problematic. Click here for more details.
- preditor/__init__.py +322 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +161 -0
- preditor/cli.py +192 -0
- preditor/config.py +302 -0
- preditor/contexts.py +119 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/maya/PrEditor_maya.mod +2 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +110 -0
- preditor/debug.py +144 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +131 -0
- preditor/gui/__init__.py +93 -0
- preditor/gui/app.py +160 -0
- preditor/gui/codehighlighter.py +209 -0
- preditor/gui/completer.py +226 -0
- preditor/gui/console.py +867 -0
- preditor/gui/dialog.py +178 -0
- preditor/gui/drag_tab_bar.py +190 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +68 -0
- preditor/gui/find_files.py +125 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
- preditor/gui/group_tab_widget/__init__.py +325 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +78 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
- preditor/gui/level_buttons.py +343 -0
- preditor/gui/logger_window_handler.py +48 -0
- preditor/gui/logger_window_plugin.py +32 -0
- preditor/gui/loggerwindow.py +1385 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/set_text_editor_path_dialog.py +59 -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 +1105 -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 +389 -0
- preditor/gui/workbox_text_edit.py +137 -0
- preditor/gui/workboxwidget.py +298 -0
- preditor/logging_config.py +52 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +74 -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/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +65 -0
- preditor/resource/stylesheet/Dark.css +199 -0
- preditor/scintilla/__init__.py +22 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +94 -0
- preditor/scintilla/delayables/spell_check.py +173 -0
- preditor/scintilla/documenteditor.py +2038 -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 +21 -0
- preditor/scintilla/lexers/javascriptlexer.py +25 -0
- preditor/scintilla/lexers/maxscriptlexer.py +234 -0
- preditor/scintilla/lexers/mellexer.py +368 -0
- preditor/scintilla/lexers/mulexer.py +32 -0
- preditor/scintilla/lexers/pythonlexer.py +41 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +80 -0
- preditor/stream/director.py +73 -0
- preditor/stream/manager.py +74 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +0 -0
- preditor/utils/cute.py +30 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +342 -0
- preditor/version.py +21 -0
- preditor/weakref.py +363 -0
- preditor-1.0.0.dist-info/METADATA +224 -0
- preditor-1.0.0.dist-info/RECORD +158 -0
- preditor-1.0.0.dist-info/WHEEL +5 -0
- preditor-1.0.0.dist-info/entry_points.txt +18 -0
- preditor-1.0.0.dist-info/licenses/LICENSE +165 -0
- preditor-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1385 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import warnings
|
|
9
|
+
from builtins import bytes
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from functools import partial
|
|
12
|
+
|
|
13
|
+
import __main__
|
|
14
|
+
from Qt import QtCompat, QtCore, QtWidgets
|
|
15
|
+
from Qt.QtCore import QByteArray, Qt, QTimer, Signal, Slot
|
|
16
|
+
from Qt.QtGui import QCursor, QFont, QIcon, QTextCursor
|
|
17
|
+
from Qt.QtWidgets import (
|
|
18
|
+
QApplication,
|
|
19
|
+
QFontDialog,
|
|
20
|
+
QInputDialog,
|
|
21
|
+
QMessageBox,
|
|
22
|
+
QTextBrowser,
|
|
23
|
+
QToolTip,
|
|
24
|
+
QVBoxLayout,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .. import (
|
|
28
|
+
DEFAULT_CORE_NAME,
|
|
29
|
+
about_preditor,
|
|
30
|
+
core,
|
|
31
|
+
debug,
|
|
32
|
+
get_core_name,
|
|
33
|
+
osystem,
|
|
34
|
+
plugins,
|
|
35
|
+
prefs,
|
|
36
|
+
resourcePath,
|
|
37
|
+
)
|
|
38
|
+
from ..delayable_engine import DelayableEngine
|
|
39
|
+
from ..gui import Dialog, Window, loadUi, tab_widget_for_tab
|
|
40
|
+
from ..gui.fuzzy_search.fuzzy_search import FuzzySearch
|
|
41
|
+
from ..gui.group_tab_widget.grouped_tab_models import GroupTabListItemModel
|
|
42
|
+
from ..logging_config import LoggingConfig
|
|
43
|
+
from ..utils import stylesheets
|
|
44
|
+
from .completer import CompleterMode
|
|
45
|
+
from .level_buttons import LoggingLevelButton
|
|
46
|
+
from .set_text_editor_path_dialog import SetTextEditorPathDialog
|
|
47
|
+
from .status_label import StatusLabel
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class WorkboxPages:
|
|
51
|
+
"""Nice names for the uiWorkboxSTACK indexes."""
|
|
52
|
+
|
|
53
|
+
Options = 0
|
|
54
|
+
Workboxes = 1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class WorkboxName(str):
|
|
58
|
+
"""The joined name of a workbox `group/workbox` with access to its parts.
|
|
59
|
+
|
|
60
|
+
This subclass provides properties for the group and workbox values separately.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __new__(cls, group, workbox):
|
|
64
|
+
txt = "/".join((group, workbox))
|
|
65
|
+
ret = super().__new__(cls, txt)
|
|
66
|
+
# Preserve the imitable nature of str's by using properties without setters.
|
|
67
|
+
ret._group = group
|
|
68
|
+
ret._workbox = workbox
|
|
69
|
+
return ret
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def group(self):
|
|
73
|
+
"""The tab name of the group tab that contains the workbox."""
|
|
74
|
+
return self._group
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def workbox(self):
|
|
78
|
+
"""The workbox of the tab for this workbox inside of the group."""
|
|
79
|
+
return self._workbox
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class LoggerWindow(Window):
|
|
83
|
+
_instance = None
|
|
84
|
+
styleSheetChanged = Signal(str)
|
|
85
|
+
|
|
86
|
+
def __init__(self, parent, name=None, run_workbox=False, standalone=False):
|
|
87
|
+
super(LoggerWindow, self).__init__(parent=parent)
|
|
88
|
+
self.name = name if name else get_core_name()
|
|
89
|
+
self._stylesheet = 'Bright'
|
|
90
|
+
|
|
91
|
+
# Create timer to autohide status messages
|
|
92
|
+
self.statusTimer = QTimer()
|
|
93
|
+
self.statusTimer.setSingleShot(True)
|
|
94
|
+
|
|
95
|
+
# Store the previous time a font-resize wheel event was triggered to prevent
|
|
96
|
+
# rapid-fire WheelEvents. Initialize to the current time.
|
|
97
|
+
self.previousFontResizeTime = datetime.now()
|
|
98
|
+
|
|
99
|
+
self.setWindowIcon(QIcon(resourcePath('img/preditor.png')))
|
|
100
|
+
loadUi(__file__, self)
|
|
101
|
+
|
|
102
|
+
self.uiConsoleTXT.flash_window = self
|
|
103
|
+
self.uiConsoleTXT.clearExecutionTime = self.clearExecutionTime
|
|
104
|
+
self.uiConsoleTXT.reportExecutionTime = self.reportExecutionTime
|
|
105
|
+
self.uiClearToLastPromptACT.triggered.connect(
|
|
106
|
+
self.uiConsoleTXT.clearToLastPrompt
|
|
107
|
+
)
|
|
108
|
+
# If we don't disable this shortcut Qt won't respond to this classes or
|
|
109
|
+
# the ConsolePrEdit's
|
|
110
|
+
self.uiConsoleTXT.uiClearToLastPromptACT.setShortcut('')
|
|
111
|
+
|
|
112
|
+
# create the status reporting label
|
|
113
|
+
self.uiStatusLBL = StatusLabel(self)
|
|
114
|
+
self.uiMenuBar.setCornerWidget(self.uiStatusLBL)
|
|
115
|
+
|
|
116
|
+
# create the workbox tabs
|
|
117
|
+
self._currentTab = -1
|
|
118
|
+
self._reloadRequested = set()
|
|
119
|
+
# Setup delayable system
|
|
120
|
+
self.delayable_engine = DelayableEngine.instance('logger', self)
|
|
121
|
+
|
|
122
|
+
self.uiWorkboxTAB.editor_kwargs = dict(
|
|
123
|
+
console=self.uiConsoleTXT, delayable_engine=self.delayable_engine.name
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Create additional buttons in toolbar.
|
|
127
|
+
self.uiLoggingLevelBTN = LoggingLevelButton(self)
|
|
128
|
+
self.uiConsoleTOOLBAR.insertWidget(
|
|
129
|
+
self.uiRunSelectedACT,
|
|
130
|
+
self.uiLoggingLevelBTN,
|
|
131
|
+
)
|
|
132
|
+
self.uiConsoleTOOLBAR.insertSeparator(self.uiRunSelectedACT)
|
|
133
|
+
|
|
134
|
+
# Configure Find in Workboxes
|
|
135
|
+
self.uiFindInWorkboxesWGT.hide()
|
|
136
|
+
self.uiFindInWorkboxesWGT.managers.append(self.uiWorkboxTAB)
|
|
137
|
+
self.uiFindInWorkboxesWGT.console = self.console()
|
|
138
|
+
|
|
139
|
+
# Initial configuration of the logToFile feature
|
|
140
|
+
self._logToFilePath = None
|
|
141
|
+
self._stds = None
|
|
142
|
+
self.uiLogToFileClearACT.setVisible(False)
|
|
143
|
+
|
|
144
|
+
self.uiRestartACT.triggered.connect(self.restartLogger)
|
|
145
|
+
self.uiCloseLoggerACT.triggered.connect(self.closeLogger)
|
|
146
|
+
|
|
147
|
+
self.uiRunAllACT.triggered.connect(self.execAll)
|
|
148
|
+
# Even though the RunSelected actions (with shortcuts) are connected
|
|
149
|
+
# here, this only affects if the action is chosen from the menu. The
|
|
150
|
+
# shortcuts are always intercepted by the workbox document editor. To
|
|
151
|
+
# handle this, the workbox.keyPressEvent method will perceive the
|
|
152
|
+
# shortcut press, and call .execSelected, which will then ultimately call
|
|
153
|
+
# workbox.__exec_selected__
|
|
154
|
+
self.uiRunSelectedACT.triggered.connect(
|
|
155
|
+
partial(self.execSelected, truncate=True)
|
|
156
|
+
)
|
|
157
|
+
self.uiRunSelectedDontTruncateACT.triggered.connect(
|
|
158
|
+
partial(self.execSelected, truncate=False)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.uiConsoleAutoCompleteEnabledACT.toggled.connect(
|
|
162
|
+
partial(self.setAutoCompleteEnabled, console=True)
|
|
163
|
+
)
|
|
164
|
+
self.uiWorkboxAutoCompleteEnabledACT.toggled.connect(
|
|
165
|
+
partial(self.setAutoCompleteEnabled, console=False)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
self.uiAutoCompleteCaseSensitiveACT.toggled.connect(self.setCaseSensitive)
|
|
169
|
+
|
|
170
|
+
self.uiSelectMonospaceFontACT.triggered.connect(
|
|
171
|
+
partial(self.selectFont, monospace=True)
|
|
172
|
+
)
|
|
173
|
+
self.uiSelectProportionalFontACT.triggered.connect(
|
|
174
|
+
partial(self.selectFont, proportional=True)
|
|
175
|
+
)
|
|
176
|
+
self.uiSelectAllFontACT.triggered.connect(
|
|
177
|
+
partial(self.selectFont, monospace=True, proportional=True)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Setup ability to cycle completer mode, and create action for each mode
|
|
181
|
+
self.completerModeCycle = itertools.cycle(CompleterMode)
|
|
182
|
+
# create CompleterMode submenu
|
|
183
|
+
defaultMode = next(self.completerModeCycle)
|
|
184
|
+
for mode in CompleterMode:
|
|
185
|
+
modeName = mode.displayName()
|
|
186
|
+
action = self.uiCompleterModeMENU.addAction(modeName)
|
|
187
|
+
action.setObjectName('ui{}ModeACT'.format(modeName))
|
|
188
|
+
action.setData(mode)
|
|
189
|
+
action.setCheckable(True)
|
|
190
|
+
action.setChecked(mode == defaultMode)
|
|
191
|
+
completerMode = CompleterMode(mode)
|
|
192
|
+
action.setToolTip(completerMode.toolTip())
|
|
193
|
+
action.triggered.connect(partial(self.selectCompleterMode, action))
|
|
194
|
+
|
|
195
|
+
self.uiCompleterModeMENU.addSeparator()
|
|
196
|
+
action = self.uiCompleterModeMENU.addAction('Cycle mode')
|
|
197
|
+
action.setObjectName('uiCycleModeACT')
|
|
198
|
+
action.setShortcut(Qt.CTRL | Qt.Key_M)
|
|
199
|
+
action.triggered.connect(self.cycleCompleterMode)
|
|
200
|
+
self.uiCompleterModeMENU.hovered.connect(self.handleMenuHovered)
|
|
201
|
+
|
|
202
|
+
# Workbox add/remove
|
|
203
|
+
self.uiNewWorkboxACT.triggered.connect(
|
|
204
|
+
lambda: self.uiWorkboxTAB.add_new_tab(group=True)
|
|
205
|
+
)
|
|
206
|
+
self.uiCloseWorkboxACT.triggered.connect(self.uiWorkboxTAB.close_current_tab)
|
|
207
|
+
|
|
208
|
+
# Browse previous commands
|
|
209
|
+
self.uiGetPrevCmdACT.triggered.connect(self.getPrevCommand)
|
|
210
|
+
self.uiGetNextCmdACT.triggered.connect(self.getNextCommand)
|
|
211
|
+
|
|
212
|
+
# Focus to console or to workbox, optionally copy seleciton or line
|
|
213
|
+
self.uiFocusToConsoleACT.triggered.connect(self.focusToConsole)
|
|
214
|
+
self.uiCopyToConsoleACT.triggered.connect(self.copyToConsole)
|
|
215
|
+
self.uiFocusToWorkboxACT.triggered.connect(self.focusToWorkbox)
|
|
216
|
+
self.uiCopyToWorkboxACT.triggered.connect(self.copyToWorkbox)
|
|
217
|
+
|
|
218
|
+
# Navigate workbox tabs
|
|
219
|
+
self.uiNextTabACT.triggered.connect(self.nextTab)
|
|
220
|
+
self.uiPrevTabACT.triggered.connect(self.prevTab)
|
|
221
|
+
|
|
222
|
+
self.uiTab1ACT.triggered.connect(partial(self.gotoTabByIndex, 1))
|
|
223
|
+
self.uiTab2ACT.triggered.connect(partial(self.gotoTabByIndex, 2))
|
|
224
|
+
self.uiTab3ACT.triggered.connect(partial(self.gotoTabByIndex, 3))
|
|
225
|
+
self.uiTab4ACT.triggered.connect(partial(self.gotoTabByIndex, 4))
|
|
226
|
+
self.uiTab5ACT.triggered.connect(partial(self.gotoTabByIndex, 5))
|
|
227
|
+
self.uiTab6ACT.triggered.connect(partial(self.gotoTabByIndex, 6))
|
|
228
|
+
self.uiTab7ACT.triggered.connect(partial(self.gotoTabByIndex, 7))
|
|
229
|
+
self.uiTab8ACT.triggered.connect(partial(self.gotoTabByIndex, 8))
|
|
230
|
+
self.uiTabLastACT.triggered.connect(partial(self.gotoTabByIndex, -1))
|
|
231
|
+
|
|
232
|
+
self.uiGroup1ACT.triggered.connect(partial(self.gotoGroupByIndex, 1))
|
|
233
|
+
self.uiGroup2ACT.triggered.connect(partial(self.gotoGroupByIndex, 2))
|
|
234
|
+
self.uiGroup3ACT.triggered.connect(partial(self.gotoGroupByIndex, 3))
|
|
235
|
+
self.uiGroup4ACT.triggered.connect(partial(self.gotoGroupByIndex, 4))
|
|
236
|
+
self.uiGroup5ACT.triggered.connect(partial(self.gotoGroupByIndex, 5))
|
|
237
|
+
self.uiGroup6ACT.triggered.connect(partial(self.gotoGroupByIndex, 6))
|
|
238
|
+
self.uiGroup7ACT.triggered.connect(partial(self.gotoGroupByIndex, 7))
|
|
239
|
+
self.uiGroup8ACT.triggered.connect(partial(self.gotoGroupByIndex, 8))
|
|
240
|
+
self.uiGroupLastACT.triggered.connect(partial(self.gotoGroupByIndex, -1))
|
|
241
|
+
|
|
242
|
+
self.uiFocusNameACT.triggered.connect(self.show_focus_name)
|
|
243
|
+
|
|
244
|
+
self.uiCommentToggleACT.triggered.connect(self.comment_toggle)
|
|
245
|
+
|
|
246
|
+
self.uiSpellCheckEnabledACT.toggled.connect(self.setSpellCheckEnabled)
|
|
247
|
+
self.uiIndentationsTabsACT.toggled.connect(self.updateIndentationsUseTabs)
|
|
248
|
+
self.uiCopyTabsToSpacesACT.toggled.connect(self.updateCopyIndentsAsSpaces)
|
|
249
|
+
self.uiWordWrapACT.toggled.connect(self.setWordWrap)
|
|
250
|
+
self.uiResetWarningFiltersACT.triggered.connect(warnings.resetwarnings)
|
|
251
|
+
self.uiLogToFileACT.triggered.connect(self.installLogToFile)
|
|
252
|
+
self.uiLogToFileClearACT.triggered.connect(self.clearLogToFile)
|
|
253
|
+
self.uiClearLogACT.triggered.connect(self.clearLog)
|
|
254
|
+
self.uiSaveConsoleSettingsACT.triggered.connect(
|
|
255
|
+
lambda: self.recordPrefs(manual=True)
|
|
256
|
+
)
|
|
257
|
+
self.uiClearBeforeRunningACT.triggered.connect(self.setClearBeforeRunning)
|
|
258
|
+
self.uiEditorVerticalACT.toggled.connect(self.adjustWorkboxOrientation)
|
|
259
|
+
self.uiEnvironmentVarsACT.triggered.connect(self.showEnvironmentVars)
|
|
260
|
+
self.uiBackupPreferencesACT.triggered.connect(self.backupPreferences)
|
|
261
|
+
self.uiBrowsePreferencesACT.triggered.connect(self.browsePreferences)
|
|
262
|
+
self.uiAboutPreditorACT.triggered.connect(self.show_about)
|
|
263
|
+
self.uiSetFlashWindowIntervalACT.triggered.connect(self.setFlashWindowInterval)
|
|
264
|
+
|
|
265
|
+
self.uiSetPreferredTextEditorPathACT.triggered.connect(
|
|
266
|
+
self.openSetPreferredTextEditorDialog
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Tooltips - Qt4 doesn't have a ToolTipsVisible method, so we fake it
|
|
270
|
+
regEx = ".*"
|
|
271
|
+
menus = self.findChildren(QtWidgets.QMenu, QtCore.QRegExp(regEx))
|
|
272
|
+
for menu in menus:
|
|
273
|
+
menu.hovered.connect(self.handleMenuHovered)
|
|
274
|
+
|
|
275
|
+
self.uiClearLogACT.setIcon(QIcon(resourcePath('img/close-thick.png')))
|
|
276
|
+
self.uiNewWorkboxACT.setIcon(QIcon(resourcePath('img/file-plus.png')))
|
|
277
|
+
self.uiCloseWorkboxACT.setIcon(QIcon(resourcePath('img/file-remove.png')))
|
|
278
|
+
self.uiSaveConsoleSettingsACT.setIcon(
|
|
279
|
+
QIcon(resourcePath('img/content-save.png'))
|
|
280
|
+
)
|
|
281
|
+
self.uiAboutPreditorACT.setIcon(QIcon(resourcePath('img/information.png')))
|
|
282
|
+
self.uiRestartACT.setIcon(QIcon(resourcePath('img/restart.svg')))
|
|
283
|
+
self.uiCloseLoggerACT.setIcon(QIcon(resourcePath('img/close-thick.png')))
|
|
284
|
+
|
|
285
|
+
# Make action shortcuts available anywhere in the Logger
|
|
286
|
+
self.addAction(self.uiClearLogACT)
|
|
287
|
+
|
|
288
|
+
self.dont_ask_again = []
|
|
289
|
+
|
|
290
|
+
# Load any plugins that modify the LoggerWindow
|
|
291
|
+
self.plugins = {}
|
|
292
|
+
for name, plugin in plugins.loggerwindow():
|
|
293
|
+
self.plugins[name] = plugin(self)
|
|
294
|
+
|
|
295
|
+
self.restorePrefs()
|
|
296
|
+
|
|
297
|
+
# add stylesheet menu options.
|
|
298
|
+
for style_name in stylesheets.stylesheets():
|
|
299
|
+
action = self.uiStyleMENU.addAction(style_name)
|
|
300
|
+
action.setObjectName('ui{}ACT'.format(style_name))
|
|
301
|
+
action.setCheckable(True)
|
|
302
|
+
action.setChecked(self._stylesheet == style_name)
|
|
303
|
+
action.triggered.connect(partial(self.setStyleSheet, style_name))
|
|
304
|
+
|
|
305
|
+
self.uiConsoleTOOLBAR.show()
|
|
306
|
+
loggerName = QApplication.instance().translate(
|
|
307
|
+
'PrEditorWindow', DEFAULT_CORE_NAME
|
|
308
|
+
)
|
|
309
|
+
self.setWindowTitle(
|
|
310
|
+
'{} - {} - {} {}-bit'.format(
|
|
311
|
+
loggerName,
|
|
312
|
+
self.name,
|
|
313
|
+
'{}.{}.{}'.format(*sys.version_info[:3]),
|
|
314
|
+
osystem.getPointerSize(),
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
self.setWorkboxFontBasedOnConsole()
|
|
319
|
+
self.setEditorChooserFontBasedOnConsole()
|
|
320
|
+
|
|
321
|
+
self.setup_run_workbox()
|
|
322
|
+
|
|
323
|
+
if not standalone:
|
|
324
|
+
# This action only is valid when running in standalone mode
|
|
325
|
+
self.uiRestartACT.setVisible(False)
|
|
326
|
+
|
|
327
|
+
# Run the current workbox after the LoggerWindow is shown.
|
|
328
|
+
if run_workbox:
|
|
329
|
+
# By using two singleShot timers, we can show and draw the LoggerWindow,
|
|
330
|
+
# then call execAll. This makes it easier to see what code you are running
|
|
331
|
+
# before it has finished running completely.
|
|
332
|
+
# QTimer.singleShot(0, lambda: QTimer.singleShot(0, self.execAll))
|
|
333
|
+
QTimer.singleShot(
|
|
334
|
+
0, lambda: QTimer.singleShot(0, lambda: self.run_workbox(run_workbox))
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
@Slot()
|
|
338
|
+
def apply_options(self):
|
|
339
|
+
"""Apply editor options the user chose on the WorkboxPage.Options page."""
|
|
340
|
+
editor_cls_name, editor_cls = plugins.editor(
|
|
341
|
+
self.uiEditorChooserWGT.editor_name()
|
|
342
|
+
)
|
|
343
|
+
if editor_cls_name is None:
|
|
344
|
+
return
|
|
345
|
+
if editor_cls_name != self.editor_cls_name:
|
|
346
|
+
self.editor_cls_name = editor_cls_name
|
|
347
|
+
self.uiWorkboxTAB.editor_cls = editor_cls
|
|
348
|
+
# We need to change the editor, save all prefs
|
|
349
|
+
self.recordPrefs()
|
|
350
|
+
# Clear the uiWorkboxTAB
|
|
351
|
+
self.uiWorkboxTAB.clear()
|
|
352
|
+
# Restore prefs to populate the tabs
|
|
353
|
+
self.restorePrefs()
|
|
354
|
+
|
|
355
|
+
self.update_workbox_stack()
|
|
356
|
+
|
|
357
|
+
def comment_toggle(self):
|
|
358
|
+
self.current_workbox().__comment_toggle__()
|
|
359
|
+
|
|
360
|
+
def current_workbox(self):
|
|
361
|
+
"""Returns the current workbox for the current tab group."""
|
|
362
|
+
return self.uiWorkboxTAB.current_groups_widget()
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def name_for_workbox(cls, workbox):
|
|
366
|
+
"""Returns the name for a given workbox or None if not valid.
|
|
367
|
+
|
|
368
|
+
The name is a `WorkboxName` object showing the group and name joined by
|
|
369
|
+
a `/`.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
workbox: The workbox to get the name of. If None is passed then it
|
|
373
|
+
will return the name of the current workbox.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
The name of the widget as a `WorkboxName` object showing the group
|
|
377
|
+
and name joined by a `/`. If workbox is not valid for the LoggerWindow
|
|
378
|
+
instance then None is returned.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
if workbox is None:
|
|
382
|
+
# if the workbox was not provided use the current workbox
|
|
383
|
+
logger = cls.instance()
|
|
384
|
+
index = logger.uiWorkboxTAB.currentIndex()
|
|
385
|
+
group = logger.uiWorkboxTAB.tabText(index)
|
|
386
|
+
group_widget = logger.uiWorkboxTAB.currentWidget()
|
|
387
|
+
index = group_widget.currentIndex()
|
|
388
|
+
name = group_widget.tabText(index)
|
|
389
|
+
return WorkboxName(group, name)
|
|
390
|
+
|
|
391
|
+
# Otherwise resolve from the parent widgets.
|
|
392
|
+
# Get the parent QTabWidget of the workbox
|
|
393
|
+
workbox_tab_widget = tab_widget_for_tab(workbox)
|
|
394
|
+
if not workbox_tab_widget:
|
|
395
|
+
return None
|
|
396
|
+
# Get the group QTabWidget of the parent QTabWidget of the workbox
|
|
397
|
+
group_widget = tab_widget_for_tab(workbox_tab_widget)
|
|
398
|
+
if not group_widget:
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
# Get the group name
|
|
402
|
+
index = group_widget.indexOf(workbox_tab_widget)
|
|
403
|
+
group = group_widget.tabText(index)
|
|
404
|
+
|
|
405
|
+
index = workbox_tab_widget.indexOf(workbox)
|
|
406
|
+
name = workbox_tab_widget.tabText(index)
|
|
407
|
+
return WorkboxName(group, name)
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def workbox_for_name(cls, name, show=False, visible=False):
|
|
411
|
+
"""Used to find a workbox for a given name. It accepts a string matching
|
|
412
|
+
the "{group}/{workbox}" format, or if True, the current workbox.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
name(str, boolean): Used to define which workbox to run.
|
|
416
|
+
show (bool, optional): If a workbox is found, call `__show__` on it
|
|
417
|
+
to ensure that it is initialized and its text is loaded.
|
|
418
|
+
visible (bool, optional): Make the this workbox visible if found.
|
|
419
|
+
"""
|
|
420
|
+
logger = cls.instance()
|
|
421
|
+
|
|
422
|
+
workbox = None
|
|
423
|
+
|
|
424
|
+
# If name is True, run the current workbox
|
|
425
|
+
if isinstance(name, bool):
|
|
426
|
+
if name:
|
|
427
|
+
workbox = logger.current_workbox()
|
|
428
|
+
|
|
429
|
+
# If name is a string, find first tab with that name
|
|
430
|
+
elif isinstance(name, str):
|
|
431
|
+
split = name.split('/', 1)
|
|
432
|
+
if len(split) < 2:
|
|
433
|
+
return None
|
|
434
|
+
group, editor = split
|
|
435
|
+
group_index = logger.uiWorkboxTAB.index_for_text(group)
|
|
436
|
+
if group_index != -1:
|
|
437
|
+
tab_widget = logger.uiWorkboxTAB.widget(group_index)
|
|
438
|
+
index = tab_widget.index_for_text(editor)
|
|
439
|
+
if index != -1:
|
|
440
|
+
workbox = tab_widget.widget(index)
|
|
441
|
+
if visible:
|
|
442
|
+
tab_widget.setCurrentIndex(index)
|
|
443
|
+
logger.uiWorkboxTAB.setCurrentIndex(group_index)
|
|
444
|
+
|
|
445
|
+
if show and workbox:
|
|
446
|
+
workbox.__show__()
|
|
447
|
+
|
|
448
|
+
return workbox
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def run_workbox(cls, name):
|
|
452
|
+
"""This is a function which will be added to __main__, and therefore
|
|
453
|
+
available to PythonLogger users. It will accept a string matching the
|
|
454
|
+
"{group}/{workbox}" format, or a boolean that will run the current tab
|
|
455
|
+
to support the command line launching functionality which auto-runs the
|
|
456
|
+
current workbox on launch.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
name(str, boolean): Used to define which workbox to run.
|
|
460
|
+
|
|
461
|
+
Raises:
|
|
462
|
+
Exception: "Cannot call current workbox."
|
|
463
|
+
|
|
464
|
+
Example Usages:
|
|
465
|
+
run_workbox('group_a/test')
|
|
466
|
+
run_workbox('some/stuff.py')
|
|
467
|
+
(from command line): blurdev launch Python_Logger --run_workbox
|
|
468
|
+
"""
|
|
469
|
+
workbox = cls.workbox_for_name(name)
|
|
470
|
+
|
|
471
|
+
if workbox is not None:
|
|
472
|
+
# if name is True, its ok to run the workbox, this option
|
|
473
|
+
# is passed by the cli to run the current tab
|
|
474
|
+
if workbox.hasFocus() and name is not True:
|
|
475
|
+
raise Exception("Cannot call current workbox.")
|
|
476
|
+
else:
|
|
477
|
+
# Make sure the workbox text is loaded as it likely has not
|
|
478
|
+
# been shown yet and each tab is now loaded only on demand.
|
|
479
|
+
workbox.__show__()
|
|
480
|
+
workbox.__exec_all__()
|
|
481
|
+
|
|
482
|
+
def setup_run_workbox(self):
|
|
483
|
+
"""We will bind the runWordbox function on __main__, which makes is available to
|
|
484
|
+
code running within PythonLogger.
|
|
485
|
+
"""
|
|
486
|
+
__main__.run_workbox = self.run_workbox
|
|
487
|
+
|
|
488
|
+
def openSetPreferredTextEditorDialog(self):
|
|
489
|
+
dlg = SetTextEditorPathDialog(parent=self)
|
|
490
|
+
dlg.exec_()
|
|
491
|
+
|
|
492
|
+
def focusToConsole(self):
|
|
493
|
+
"""Move focus to the console"""
|
|
494
|
+
self.console().setFocus()
|
|
495
|
+
|
|
496
|
+
def focusToWorkbox(self):
|
|
497
|
+
"""Move focus to the current workbox"""
|
|
498
|
+
self.current_workbox().setFocus()
|
|
499
|
+
|
|
500
|
+
def copyToConsole(self):
|
|
501
|
+
"""Copy current selection or line from workbox to console"""
|
|
502
|
+
workbox = self.current_workbox()
|
|
503
|
+
if not workbox.hasFocus():
|
|
504
|
+
return
|
|
505
|
+
|
|
506
|
+
text, _line = workbox.__selected_text__(selectText=True)
|
|
507
|
+
if not text:
|
|
508
|
+
line, index = workbox.__cursor_position__()
|
|
509
|
+
text = workbox.__text__(line)
|
|
510
|
+
text = text.rstrip('\r\n')
|
|
511
|
+
if not text:
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
cursor = self.console().textCursor()
|
|
515
|
+
if cursor.hasSelection():
|
|
516
|
+
cursor.removeSelectedText()
|
|
517
|
+
|
|
518
|
+
self.console().insertPlainText(text)
|
|
519
|
+
self.focusToConsole()
|
|
520
|
+
|
|
521
|
+
def copyToWorkbox(self):
|
|
522
|
+
"""Copy current selection or line from console to workbox"""
|
|
523
|
+
console = self.console()
|
|
524
|
+
if not console.hasFocus():
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
cursor = console.textCursor()
|
|
528
|
+
if not cursor.hasSelection():
|
|
529
|
+
cursor.select(QTextCursor.LineUnderCursor)
|
|
530
|
+
text = cursor.selectedText()
|
|
531
|
+
prompt = console.prompt()
|
|
532
|
+
if text.startswith(prompt):
|
|
533
|
+
text = text[len(prompt) :]
|
|
534
|
+
text = text.lstrip()
|
|
535
|
+
|
|
536
|
+
outputPrompt = console.outputPrompt()
|
|
537
|
+
outputPrompt = outputPrompt.rstrip()
|
|
538
|
+
if text.startswith(outputPrompt):
|
|
539
|
+
text = text[len(outputPrompt) :]
|
|
540
|
+
text = text.lstrip()
|
|
541
|
+
|
|
542
|
+
if not text:
|
|
543
|
+
return
|
|
544
|
+
|
|
545
|
+
workbox = self.current_workbox()
|
|
546
|
+
workbox.__remove_selected_text__()
|
|
547
|
+
workbox.__insert_text__(text)
|
|
548
|
+
|
|
549
|
+
line, index = workbox.__cursor_position__()
|
|
550
|
+
index += len(text)
|
|
551
|
+
workbox.__set_cursor_position__(line, index)
|
|
552
|
+
|
|
553
|
+
self.focusToWorkbox()
|
|
554
|
+
|
|
555
|
+
def getNextCommand(self):
|
|
556
|
+
if hasattr(self.console(), 'getNextCommand'):
|
|
557
|
+
self.console().getNextCommand()
|
|
558
|
+
|
|
559
|
+
def getPrevCommand(self):
|
|
560
|
+
if hasattr(self.console(), 'getPrevCommand'):
|
|
561
|
+
self.console().getPrevCommand()
|
|
562
|
+
|
|
563
|
+
def wheelEvent(self, event):
|
|
564
|
+
"""adjust font size on ctrl+scrollWheel"""
|
|
565
|
+
if event.modifiers() == Qt.ControlModifier:
|
|
566
|
+
# WheelEvents can be emitted in a cluster, but we only want one at a time
|
|
567
|
+
# (ie to change font size by 1, rather than 2 or 3). Let's bail if previous
|
|
568
|
+
# font-resize wheel event was within a certain threshhold.
|
|
569
|
+
now = datetime.now()
|
|
570
|
+
elapsed = now - self.previousFontResizeTime
|
|
571
|
+
tolerance = timedelta(microseconds=100000)
|
|
572
|
+
if elapsed < tolerance:
|
|
573
|
+
return
|
|
574
|
+
self.previousFontResizeTime = now
|
|
575
|
+
|
|
576
|
+
# QT4 presents QWheelEvent.delta(), QT5 has QWheelEvent.angleDelta().y()
|
|
577
|
+
if hasattr(event, 'delta'): # Qt4
|
|
578
|
+
delta = event.delta()
|
|
579
|
+
else: # QT5
|
|
580
|
+
delta = event.angleDelta().y()
|
|
581
|
+
|
|
582
|
+
# convert delta to +1 or -1, depending
|
|
583
|
+
delta = delta // abs(delta)
|
|
584
|
+
minSize = 5
|
|
585
|
+
maxSize = 50
|
|
586
|
+
font = self.console().font()
|
|
587
|
+
newSize = font.pointSize() + delta
|
|
588
|
+
newSize = max(min(newSize, maxSize), minSize)
|
|
589
|
+
|
|
590
|
+
self.setFontSize(newSize)
|
|
591
|
+
else:
|
|
592
|
+
Window.wheelEvent(self, event)
|
|
593
|
+
|
|
594
|
+
def handleMenuHovered(self, action):
|
|
595
|
+
"""Qt4 doesn't have a ToolTipsVisible method, so we fake it"""
|
|
596
|
+
# Don't show if it's just the text of the action
|
|
597
|
+
text = re.sub(r"(?<!&)&(?!&)", "", action.text())
|
|
598
|
+
text = text.replace('...', '')
|
|
599
|
+
|
|
600
|
+
if text == action.toolTip():
|
|
601
|
+
text = ''
|
|
602
|
+
else:
|
|
603
|
+
text = action.toolTip()
|
|
604
|
+
|
|
605
|
+
menu = action.parentWidget()
|
|
606
|
+
QToolTip.showText(QCursor.pos(), text, menu)
|
|
607
|
+
|
|
608
|
+
def selectFont(self, monospace=False, proportional=False):
|
|
609
|
+
"""Present a QFontChooser dialog, offering, monospace, proportional, or all
|
|
610
|
+
fonts, based on user choice. If a font is chosen, set it on the console and
|
|
611
|
+
workboxes.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
action (QAction): menu action associated with chosen font
|
|
615
|
+
"""
|
|
616
|
+
origFont = self.console().font()
|
|
617
|
+
curFontFamily = origFont.family()
|
|
618
|
+
|
|
619
|
+
if monospace and proportional:
|
|
620
|
+
options = QFontDialog.MonospacedFonts | QFontDialog.ProportionalFonts
|
|
621
|
+
kind = "monospace or proportional "
|
|
622
|
+
elif monospace:
|
|
623
|
+
options = QFontDialog.MonospacedFonts
|
|
624
|
+
kind = "monospace "
|
|
625
|
+
elif proportional:
|
|
626
|
+
options = QFontDialog.ProportionalFonts
|
|
627
|
+
kind = "proportional "
|
|
628
|
+
|
|
629
|
+
# Present a QFontDialog for user to choose a font
|
|
630
|
+
title = "Pick a {} font. Current font is: {}".format(kind, curFontFamily)
|
|
631
|
+
newFont, okClicked = QFontDialog.getFont(origFont, self, title, options=options)
|
|
632
|
+
|
|
633
|
+
if okClicked:
|
|
634
|
+
self.console().setConsoleFont(newFont)
|
|
635
|
+
self.setWorkboxFontBasedOnConsole()
|
|
636
|
+
self.setEditorChooserFontBasedOnConsole()
|
|
637
|
+
|
|
638
|
+
def setFontSize(self, newSize):
|
|
639
|
+
"""Update the font size in the console and current workbox.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
newSize (int): The new size to set the font
|
|
643
|
+
"""
|
|
644
|
+
font = self.console().font()
|
|
645
|
+
font.setPointSize(newSize)
|
|
646
|
+
self.console().setConsoleFont(font)
|
|
647
|
+
|
|
648
|
+
self.setWorkboxFontBasedOnConsole()
|
|
649
|
+
self.setEditorChooserFontBasedOnConsole()
|
|
650
|
+
|
|
651
|
+
def setWorkboxFontBasedOnConsole(self):
|
|
652
|
+
"""If the current workbox's font is different to the console's font, set it to
|
|
653
|
+
match.
|
|
654
|
+
"""
|
|
655
|
+
font = self.console().font()
|
|
656
|
+
|
|
657
|
+
workboxGroup = self.uiWorkboxTAB.currentWidget()
|
|
658
|
+
if workboxGroup is None:
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
workbox = workboxGroup.currentWidget()
|
|
662
|
+
if workbox is None:
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
if workbox.__font__() != font:
|
|
666
|
+
workbox.__set_margins_font__(font)
|
|
667
|
+
workbox.__set_font__(font)
|
|
668
|
+
|
|
669
|
+
def setEditorChooserFontBasedOnConsole(self):
|
|
670
|
+
"""Set the EditorChooser font to match console. This helps with legibility when
|
|
671
|
+
using EditorChooser.
|
|
672
|
+
"""
|
|
673
|
+
font = self.console().font()
|
|
674
|
+
for child in self.uiEditorChooserWGT.children():
|
|
675
|
+
if hasattr(child, "font"):
|
|
676
|
+
child.setFont(font)
|
|
677
|
+
|
|
678
|
+
@classmethod
|
|
679
|
+
def _genPrefName(cls, baseName, index):
|
|
680
|
+
if index:
|
|
681
|
+
baseName = '{name}{index}'.format(name=baseName, index=index)
|
|
682
|
+
return baseName
|
|
683
|
+
|
|
684
|
+
def adjustWorkboxOrientation(self, state):
|
|
685
|
+
if state:
|
|
686
|
+
self.uiSplitterSPLIT.setOrientation(Qt.Horizontal)
|
|
687
|
+
else:
|
|
688
|
+
self.uiSplitterSPLIT.setOrientation(Qt.Vertical)
|
|
689
|
+
|
|
690
|
+
def backupPreferences(self):
|
|
691
|
+
"""Saves a copy of the current preferences to a zip archive."""
|
|
692
|
+
zip_path = prefs.backup()
|
|
693
|
+
print('PrEditor Preferences backed up to "{}"'.format(zip_path))
|
|
694
|
+
return zip_path
|
|
695
|
+
|
|
696
|
+
def browsePreferences(self):
|
|
697
|
+
prefs.browse(core_name=self.name)
|
|
698
|
+
|
|
699
|
+
def console(self):
|
|
700
|
+
return self.uiConsoleTXT
|
|
701
|
+
|
|
702
|
+
def clearLog(self):
|
|
703
|
+
self.uiConsoleTXT.clear()
|
|
704
|
+
|
|
705
|
+
def clearLogToFile(self):
|
|
706
|
+
"""If installLogToFile has been called, clear the stdout."""
|
|
707
|
+
if self._stds:
|
|
708
|
+
self._stds[0].clear(stamp=True)
|
|
709
|
+
|
|
710
|
+
def closeEvent(self, event):
|
|
711
|
+
self.recordPrefs()
|
|
712
|
+
# Save the logger configuration
|
|
713
|
+
lcfg = LoggingConfig(core_name=self.name)
|
|
714
|
+
lcfg.build()
|
|
715
|
+
lcfg.save()
|
|
716
|
+
|
|
717
|
+
super(LoggerWindow, self).closeEvent(event)
|
|
718
|
+
if self.uiConsoleTOOLBAR.isFloating():
|
|
719
|
+
self.uiConsoleTOOLBAR.hide()
|
|
720
|
+
|
|
721
|
+
# Handle any cleanup each workbox tab may need to do before closing
|
|
722
|
+
for editor, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
|
|
723
|
+
editor.__close__()
|
|
724
|
+
|
|
725
|
+
def closeLogger(self):
|
|
726
|
+
self.close()
|
|
727
|
+
|
|
728
|
+
def execAll(self):
|
|
729
|
+
"""Clears the console before executing all workbox code"""
|
|
730
|
+
if self.uiClearBeforeRunningACT.isChecked():
|
|
731
|
+
self.clearLog()
|
|
732
|
+
self.current_workbox().__exec_all__()
|
|
733
|
+
|
|
734
|
+
if self.uiAutoPromptACT.isChecked():
|
|
735
|
+
console = self.console()
|
|
736
|
+
prompt = console.prompt()
|
|
737
|
+
console.startPrompt(prompt)
|
|
738
|
+
|
|
739
|
+
def execSelected(self, truncate=True):
|
|
740
|
+
"""Clears the console before executing selected workbox code.
|
|
741
|
+
|
|
742
|
+
NOTE! This method is not called when the uiRunSelectedACT is triggered,
|
|
743
|
+
because the workbox will always intercept it. So instead, the workbox's
|
|
744
|
+
keyPressEvent will notice the shortcut and call this method.
|
|
745
|
+
"""
|
|
746
|
+
|
|
747
|
+
if self.uiClearBeforeRunningACT.isChecked():
|
|
748
|
+
self.clearLog()
|
|
749
|
+
|
|
750
|
+
self.current_workbox().__exec_selected__(truncate=truncate)
|
|
751
|
+
|
|
752
|
+
if self.uiAutoPromptACT.isChecked():
|
|
753
|
+
self.console().startInputLine()
|
|
754
|
+
|
|
755
|
+
def keyPressEvent(self, event):
|
|
756
|
+
# Fix 'Maya : Qt tools lose focus' https://redmine.blur.com/issues/34430
|
|
757
|
+
if event.modifiers() & (Qt.AltModifier | Qt.ControlModifier | Qt.ShiftModifier):
|
|
758
|
+
pass
|
|
759
|
+
else:
|
|
760
|
+
super(LoggerWindow, self).keyPressEvent(event)
|
|
761
|
+
|
|
762
|
+
def clearExecutionTime(self):
|
|
763
|
+
"""Update status text with hyphens to indicate execution has begun."""
|
|
764
|
+
self.setStatusText('Exec: -.- Seconds')
|
|
765
|
+
QApplication.instance().processEvents()
|
|
766
|
+
|
|
767
|
+
def reportExecutionTime(self, seconds):
|
|
768
|
+
"""Update status text with seconds passed in."""
|
|
769
|
+
self.uiStatusLBL.showSeconds(seconds)
|
|
770
|
+
self.uiMenuBar.adjustSize()
|
|
771
|
+
|
|
772
|
+
def recordPrefs(self, manual=False):
|
|
773
|
+
if not manual and not self.uiAutoSaveSettingssACT.isChecked():
|
|
774
|
+
return
|
|
775
|
+
|
|
776
|
+
pref = self.load_prefs()
|
|
777
|
+
geo = self.geometry()
|
|
778
|
+
pref.update(
|
|
779
|
+
{
|
|
780
|
+
'loggergeom': [geo.x(), geo.y(), geo.width(), geo.height()],
|
|
781
|
+
'windowState': int(self.windowState()),
|
|
782
|
+
'SplitterVertical': self.uiEditorVerticalACT.isChecked(),
|
|
783
|
+
'SplitterSize': self.uiSplitterSPLIT.sizes(),
|
|
784
|
+
'tabIndent': self.uiIndentationsTabsACT.isChecked(),
|
|
785
|
+
'copyIndentsAsSpaces': self.uiCopyTabsToSpacesACT.isChecked(),
|
|
786
|
+
'hintingEnabled': self.uiConsoleAutoCompleteEnabledACT.isChecked(),
|
|
787
|
+
'workboxHintingEnabled': (
|
|
788
|
+
self.uiWorkboxAutoCompleteEnabledACT.isChecked()
|
|
789
|
+
),
|
|
790
|
+
'spellCheckEnabled': self.uiSpellCheckEnabledACT.isChecked(),
|
|
791
|
+
'wordWrap': self.uiWordWrapACT.isChecked(),
|
|
792
|
+
'clearBeforeRunning': self.uiClearBeforeRunningACT.isChecked(),
|
|
793
|
+
'uiSelectTextACT': self.uiSelectTextACT.isChecked(),
|
|
794
|
+
'toolbarStates': str(self.saveState().toHex(), 'utf-8'),
|
|
795
|
+
'consoleFont': self.console().font().toString(),
|
|
796
|
+
'uiAutoSaveSettingssACT': self.uiAutoSaveSettingssACT.isChecked(),
|
|
797
|
+
'uiAutoPromptACT': self.uiAutoPromptACT.isChecked(),
|
|
798
|
+
'uiLinesInNewWorkboxACT': self.uiLinesInNewWorkboxACT.isChecked(),
|
|
799
|
+
'uiErrorHyperlinksACT': self.uiErrorHyperlinksACT.isChecked(),
|
|
800
|
+
'uiStatusLbl_limit': self.uiStatusLBL.limit(),
|
|
801
|
+
'textEditorPath': self.textEditorPath,
|
|
802
|
+
'textEditorCmdTempl': self.textEditorCmdTempl,
|
|
803
|
+
'currentStyleSheet': self._stylesheet,
|
|
804
|
+
'flash_time': self.uiConsoleTXT.flash_time,
|
|
805
|
+
'find_files_regex': self.uiFindInWorkboxesWGT.uiRegexBTN.isChecked(),
|
|
806
|
+
'find_files_cs': (
|
|
807
|
+
self.uiFindInWorkboxesWGT.uiCaseSensitiveBTN.isChecked()
|
|
808
|
+
),
|
|
809
|
+
'find_files_context': self.uiFindInWorkboxesWGT.uiContextSPN.value(),
|
|
810
|
+
'find_files_text': self.uiFindInWorkboxesWGT.uiFindTXT.text(),
|
|
811
|
+
'uiHighlightExactCompletionACT': (
|
|
812
|
+
self.uiHighlightExactCompletionACT.isChecked()
|
|
813
|
+
),
|
|
814
|
+
'dont_ask_again': self.dont_ask_again,
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
# completer settings
|
|
819
|
+
completer = self.console().completer()
|
|
820
|
+
pref["caseSensitive"] = completer.caseSensitive()
|
|
821
|
+
pref["completerMode"] = completer.completerMode().value
|
|
822
|
+
|
|
823
|
+
if self._stylesheet == 'Custom':
|
|
824
|
+
pref['styleSheet'] = self.styleSheet()
|
|
825
|
+
|
|
826
|
+
workbox_prefs = self.uiWorkboxTAB.save_prefs()
|
|
827
|
+
pref['workbox_prefs'] = workbox_prefs
|
|
828
|
+
|
|
829
|
+
pref['editor_cls'] = self.editor_cls_name
|
|
830
|
+
|
|
831
|
+
# Allow any plugins to add their own preferences dictionary
|
|
832
|
+
pref["plugins"] = {}
|
|
833
|
+
for name, plugin in self.plugins.items():
|
|
834
|
+
plugin_pref = plugin.record_prefs(name)
|
|
835
|
+
if plugin_pref:
|
|
836
|
+
pref["plugins"][name] = plugin_pref
|
|
837
|
+
|
|
838
|
+
self.save_prefs(pref)
|
|
839
|
+
|
|
840
|
+
def load_prefs(self):
|
|
841
|
+
filename = prefs.prefs_path('preditor_pref.json', core_name=self.name)
|
|
842
|
+
if os.path.exists(filename):
|
|
843
|
+
with open(filename) as fp:
|
|
844
|
+
return json.load(fp)
|
|
845
|
+
return {}
|
|
846
|
+
|
|
847
|
+
def save_prefs(self, pref):
|
|
848
|
+
# Save preferences to disk
|
|
849
|
+
filename = prefs.prefs_path('preditor_pref.json', core_name=self.name)
|
|
850
|
+
dirname = os.path.dirname(filename)
|
|
851
|
+
if not os.path.exists(dirname):
|
|
852
|
+
os.makedirs(dirname)
|
|
853
|
+
with open(filename, 'w') as fp:
|
|
854
|
+
json.dump(pref, fp, indent=4)
|
|
855
|
+
|
|
856
|
+
def maybeDisplayDialog(self, dialog):
|
|
857
|
+
"""If user hasn't previously opted to not show this particular dialog again,
|
|
858
|
+
show it.
|
|
859
|
+
"""
|
|
860
|
+
if dialog.objectName() in self.dont_ask_again:
|
|
861
|
+
return
|
|
862
|
+
|
|
863
|
+
dialog.exec_()
|
|
864
|
+
|
|
865
|
+
def restartLogger(self):
|
|
866
|
+
"""Closes this PrEditor instance and starts a new process with the same
|
|
867
|
+
cli arguments.
|
|
868
|
+
|
|
869
|
+
Note: This only works if PrEditor is running in standalone mode. It doesn't
|
|
870
|
+
quit the QApplication or other host process. It simply closes this instance
|
|
871
|
+
of PrEditor, saving its preferences, which should allow Qt to exit if no
|
|
872
|
+
other windows are open.
|
|
873
|
+
"""
|
|
874
|
+
self.close()
|
|
875
|
+
|
|
876
|
+
# Get the current command and launch it as a new process. This handles
|
|
877
|
+
# use of the preditor/preditor executable launchers.
|
|
878
|
+
cmd = sys.argv[0]
|
|
879
|
+
args = sys.argv[1:]
|
|
880
|
+
|
|
881
|
+
if os.path.basename(cmd) == "__main__.py":
|
|
882
|
+
# Handles using `python -m preditor` style launch.
|
|
883
|
+
cmd = sys.executable
|
|
884
|
+
args = ["-m", "preditor"] + args
|
|
885
|
+
QtCore.QProcess.startDetached(cmd, args)
|
|
886
|
+
|
|
887
|
+
def restorePrefs(self):
|
|
888
|
+
pref = self.load_prefs()
|
|
889
|
+
|
|
890
|
+
# Editor selection
|
|
891
|
+
self.editor_cls_name = pref.get('editor_cls')
|
|
892
|
+
if self.editor_cls_name:
|
|
893
|
+
self.editor_cls_name, editor_cls = plugins.editor(self.editor_cls_name)
|
|
894
|
+
self.uiWorkboxTAB.editor_cls = editor_cls
|
|
895
|
+
else:
|
|
896
|
+
self.uiWorkboxTAB.editor_cls = None
|
|
897
|
+
# Set the workbox core_name so it reads/writes its tabs content into the
|
|
898
|
+
# same core_name preference folder.
|
|
899
|
+
self.uiWorkboxTAB.core_name = self.name
|
|
900
|
+
self.uiEditorChooserWGT.set_editor_name(self.editor_cls_name)
|
|
901
|
+
|
|
902
|
+
# Geometry
|
|
903
|
+
if 'loggergeom' in pref:
|
|
904
|
+
self.setGeometry(*pref['loggergeom'])
|
|
905
|
+
self.uiEditorVerticalACT.setChecked(pref.get('SplitterVertical', False))
|
|
906
|
+
self.adjustWorkboxOrientation(self.uiEditorVerticalACT.isChecked())
|
|
907
|
+
|
|
908
|
+
sizes = pref.get('SplitterSize')
|
|
909
|
+
if sizes:
|
|
910
|
+
self.uiSplitterSPLIT.setSizes(sizes)
|
|
911
|
+
self.setWindowState(Qt.WindowStates(pref.get('windowState', 0)))
|
|
912
|
+
self.uiIndentationsTabsACT.setChecked(pref.get('tabIndent', True))
|
|
913
|
+
self.uiCopyTabsToSpacesACT.setChecked(pref.get('copyIndentsAsSpaces', False))
|
|
914
|
+
|
|
915
|
+
# completer settings
|
|
916
|
+
self.setCaseSensitive(pref.get('caseSensitive', True))
|
|
917
|
+
completerMode = CompleterMode(pref.get('completerMode', 0))
|
|
918
|
+
self.cycleToCompleterMode(completerMode)
|
|
919
|
+
self.setCompleterMode(completerMode)
|
|
920
|
+
self.uiHighlightExactCompletionACT.setChecked(
|
|
921
|
+
pref.get('uiHighlightExactCompletionACT', False)
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
self.setSpellCheckEnabled(self.uiSpellCheckEnabledACT.isChecked())
|
|
925
|
+
self.uiSpellCheckEnabledACT.setChecked(pref.get('spellCheckEnabled', False))
|
|
926
|
+
self.uiSpellCheckEnabledACT.setDisabled(False)
|
|
927
|
+
|
|
928
|
+
self.uiAutoSaveSettingssACT.setChecked(pref.get('uiAutoSaveSettingssACT', True))
|
|
929
|
+
|
|
930
|
+
self.uiAutoPromptACT.setChecked(pref.get('uiAutoPromptACT', False))
|
|
931
|
+
self.uiLinesInNewWorkboxACT.setChecked(
|
|
932
|
+
pref.get('uiLinesInNewWorkboxACT', False)
|
|
933
|
+
)
|
|
934
|
+
self.uiErrorHyperlinksACT.setChecked(pref.get('uiErrorHyperlinksACT', True))
|
|
935
|
+
self.uiStatusLBL.setLimit(pref.get('uiStatusLbl_limit', 5))
|
|
936
|
+
|
|
937
|
+
# Find Files settings
|
|
938
|
+
self.uiFindInWorkboxesWGT.uiRegexBTN.setChecked(
|
|
939
|
+
pref.get('find_files_regex', False)
|
|
940
|
+
)
|
|
941
|
+
self.uiFindInWorkboxesWGT.uiCaseSensitiveBTN.setChecked(
|
|
942
|
+
pref.get('find_files_cs', False)
|
|
943
|
+
)
|
|
944
|
+
self.uiFindInWorkboxesWGT.uiContextSPN.setValue(
|
|
945
|
+
pref.get('find_files_context', 3)
|
|
946
|
+
)
|
|
947
|
+
self.uiFindInWorkboxesWGT.uiFindTXT.setText(pref.get('find_files_text', ''))
|
|
948
|
+
|
|
949
|
+
# External text editor filepath and command template
|
|
950
|
+
defaultExePath = r"C:\Program Files\Sublime Text 3\sublime_text.exe"
|
|
951
|
+
defaultCmd = r'"{exePath}" "{modulePath}":{lineNum}'
|
|
952
|
+
self.textEditorPath = pref.get('textEditorPath', defaultExePath)
|
|
953
|
+
self.textEditorCmdTempl = pref.get('textEditorCmdTempl', defaultCmd)
|
|
954
|
+
|
|
955
|
+
self.uiWordWrapACT.setChecked(pref.get('wordWrap', True))
|
|
956
|
+
self.setWordWrap(self.uiWordWrapACT.isChecked())
|
|
957
|
+
self.uiClearBeforeRunningACT.setChecked(pref.get('clearBeforeRunning', False))
|
|
958
|
+
self.setClearBeforeRunning(self.uiClearBeforeRunningACT.isChecked())
|
|
959
|
+
self.uiSelectTextACT.setChecked(pref.get('uiSelectTextACT', True))
|
|
960
|
+
|
|
961
|
+
self._stylesheet = pref.get('currentStyleSheet', 'Bright')
|
|
962
|
+
if self._stylesheet == 'Custom':
|
|
963
|
+
self.setStyleSheet(pref.get('styleSheet', ''))
|
|
964
|
+
else:
|
|
965
|
+
self.setStyleSheet(self._stylesheet)
|
|
966
|
+
self.uiConsoleTXT.flash_time = pref.get('flash_time', 1.0)
|
|
967
|
+
|
|
968
|
+
self.uiWorkboxTAB.restore_prefs(pref.get('workbox_prefs', {}))
|
|
969
|
+
|
|
970
|
+
hintingEnabled = pref.get('hintingEnabled', True)
|
|
971
|
+
self.uiConsoleAutoCompleteEnabledACT.setChecked(hintingEnabled)
|
|
972
|
+
self.setAutoCompleteEnabled(hintingEnabled, console=True)
|
|
973
|
+
workboxHintingEnabled = pref.get('workboxHintingEnabled', True)
|
|
974
|
+
self.uiWorkboxAutoCompleteEnabledACT.setChecked(workboxHintingEnabled)
|
|
975
|
+
self.setAutoCompleteEnabled(workboxHintingEnabled, console=False)
|
|
976
|
+
|
|
977
|
+
# Ensure the correct workbox stack page is shown
|
|
978
|
+
self.update_workbox_stack()
|
|
979
|
+
|
|
980
|
+
_font = pref.get('consoleFont', None)
|
|
981
|
+
if _font:
|
|
982
|
+
font = QFont()
|
|
983
|
+
if font.fromString(_font):
|
|
984
|
+
self.console().setConsoleFont(font)
|
|
985
|
+
|
|
986
|
+
self.dont_ask_again = pref.get('dont_ask_again', [])
|
|
987
|
+
|
|
988
|
+
# Allow any plugins to restore their own preferences
|
|
989
|
+
for name, plugin in self.plugins.items():
|
|
990
|
+
plugin.restore_prefs(name, pref.get("plugins", {}).get(name))
|
|
991
|
+
|
|
992
|
+
def restoreToolbars(self, pref=None):
|
|
993
|
+
if pref is None:
|
|
994
|
+
pref = self.load_prefs()
|
|
995
|
+
|
|
996
|
+
state = pref.get('toolbarStates', None)
|
|
997
|
+
if state:
|
|
998
|
+
state = QByteArray.fromHex(bytes(state, 'utf-8'))
|
|
999
|
+
self.restoreState(state)
|
|
1000
|
+
|
|
1001
|
+
def setAutoCompleteEnabled(self, state, console=True):
|
|
1002
|
+
if console:
|
|
1003
|
+
self.uiConsoleTXT.completer().setEnabled(state)
|
|
1004
|
+
else:
|
|
1005
|
+
for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
|
|
1006
|
+
workbox.__set_auto_complete_enabled__(state)
|
|
1007
|
+
|
|
1008
|
+
def setSpellCheckEnabled(self, state):
|
|
1009
|
+
try:
|
|
1010
|
+
self.delayable_engine.set_delayable_enabled('spell_check', state)
|
|
1011
|
+
except KeyError:
|
|
1012
|
+
# Spell check can not be enabled
|
|
1013
|
+
if self.isVisible():
|
|
1014
|
+
# Only show warning if Logger is visible and also disable the action
|
|
1015
|
+
self.uiSpellCheckEnabledACT.setDisabled(True)
|
|
1016
|
+
QMessageBox.warning(
|
|
1017
|
+
self, "Spell-Check", 'Unable to activate spell check.'
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
def setStatusText(self, txt):
|
|
1021
|
+
"""Set the text shown in the menu corner of the menu bar.
|
|
1022
|
+
|
|
1023
|
+
Args:
|
|
1024
|
+
txt (str): The text to show in the status text label.
|
|
1025
|
+
"""
|
|
1026
|
+
self.uiStatusLBL.setText(txt)
|
|
1027
|
+
self.uiMenuBar.adjustSize()
|
|
1028
|
+
|
|
1029
|
+
def clearStatusText(self):
|
|
1030
|
+
"""Clear any displayed status text"""
|
|
1031
|
+
self.uiStatusLBL.clear()
|
|
1032
|
+
self.uiMenuBar.adjustSize()
|
|
1033
|
+
|
|
1034
|
+
def autoHideStatusText(self):
|
|
1035
|
+
"""Set timer to automatically clear status text"""
|
|
1036
|
+
if self.statusTimer.isActive():
|
|
1037
|
+
self.statusTimer.stop()
|
|
1038
|
+
self.statusTimer.singleShot(2000, self.clearStatusText)
|
|
1039
|
+
self.statusTimer.start()
|
|
1040
|
+
|
|
1041
|
+
def setStyleSheet(self, stylesheet, recordPrefs=True):
|
|
1042
|
+
"""Accepts the name of a stylesheet included with blurdev, or a full
|
|
1043
|
+
path to any stylesheet. If given None, it will default to Bright.
|
|
1044
|
+
"""
|
|
1045
|
+
sheet = None
|
|
1046
|
+
if stylesheet is None:
|
|
1047
|
+
stylesheet = 'Bright'
|
|
1048
|
+
if os.path.isfile(stylesheet):
|
|
1049
|
+
# A path to a stylesheet was passed in
|
|
1050
|
+
with open(stylesheet) as f:
|
|
1051
|
+
sheet = f.read()
|
|
1052
|
+
self._stylesheet = stylesheet
|
|
1053
|
+
else:
|
|
1054
|
+
# Try to find an installed stylesheet with the given name
|
|
1055
|
+
sheet, valid = stylesheets.read_stylesheet(stylesheet)
|
|
1056
|
+
if valid:
|
|
1057
|
+
self._stylesheet = stylesheet
|
|
1058
|
+
else:
|
|
1059
|
+
# Assume the user passed the text of the stylesheet directly
|
|
1060
|
+
sheet = stylesheet
|
|
1061
|
+
self._stylesheet = 'Custom'
|
|
1062
|
+
|
|
1063
|
+
# Load the stylesheet
|
|
1064
|
+
if sheet is not None:
|
|
1065
|
+
super(LoggerWindow, self).setStyleSheet(sheet)
|
|
1066
|
+
|
|
1067
|
+
# Update the style menu
|
|
1068
|
+
for act in self.uiStyleMENU.actions():
|
|
1069
|
+
name = act.objectName()
|
|
1070
|
+
isCurrent = name == 'ui{}ACT'.format(self._stylesheet)
|
|
1071
|
+
act.setChecked(isCurrent)
|
|
1072
|
+
|
|
1073
|
+
# Notify widgets that the styleSheet has changed
|
|
1074
|
+
self.styleSheetChanged.emit(stylesheet)
|
|
1075
|
+
|
|
1076
|
+
def setCaseSensitive(self, state):
|
|
1077
|
+
"""Set completer case-sensivity"""
|
|
1078
|
+
completer = self.console().completer()
|
|
1079
|
+
completer.setCaseSensitive(state)
|
|
1080
|
+
self.uiAutoCompleteCaseSensitiveACT.setChecked(state)
|
|
1081
|
+
self.reportCaseChange(state)
|
|
1082
|
+
completer.refreshList()
|
|
1083
|
+
|
|
1084
|
+
def toggleCaseSensitive(self):
|
|
1085
|
+
"""Toggle completer case-sensitivity"""
|
|
1086
|
+
state = self.console().completer().caseSensitive()
|
|
1087
|
+
self.reportCaseChange(state)
|
|
1088
|
+
self.setCaseSensitive(not state)
|
|
1089
|
+
|
|
1090
|
+
# Completer Modes
|
|
1091
|
+
def cycleCompleterMode(self):
|
|
1092
|
+
"""Cycle comleter mode"""
|
|
1093
|
+
completerMode = next(self.completerModeCycle)
|
|
1094
|
+
self.setCompleterMode(completerMode)
|
|
1095
|
+
self.reportCompleterModeChange(completerMode)
|
|
1096
|
+
|
|
1097
|
+
def cycleToCompleterMode(self, completerMode):
|
|
1098
|
+
"""
|
|
1099
|
+
Syncs the completerModeCycle iterator to currently chosen completerMode
|
|
1100
|
+
Args:
|
|
1101
|
+
completerMode: Chosen CompleterMode ENUM member
|
|
1102
|
+
"""
|
|
1103
|
+
for _ in range(len(CompleterMode)):
|
|
1104
|
+
tempMode = next(self.completerModeCycle)
|
|
1105
|
+
if tempMode == completerMode:
|
|
1106
|
+
break
|
|
1107
|
+
|
|
1108
|
+
def setCompleterMode(self, completerMode):
|
|
1109
|
+
"""
|
|
1110
|
+
Set the completer mode to chosen mode
|
|
1111
|
+
Args:
|
|
1112
|
+
completerMode: Chosen CompleterMode ENUM member
|
|
1113
|
+
"""
|
|
1114
|
+
completer = self.console().completer()
|
|
1115
|
+
|
|
1116
|
+
completer.setCompleterMode(completerMode)
|
|
1117
|
+
completer.buildCompleter()
|
|
1118
|
+
|
|
1119
|
+
for action in self.uiCompleterModeMENU.actions():
|
|
1120
|
+
action.setChecked(action.data() == completerMode)
|
|
1121
|
+
|
|
1122
|
+
def selectCompleterMode(self, action):
|
|
1123
|
+
if not action.isChecked():
|
|
1124
|
+
action.setChecked(True)
|
|
1125
|
+
return
|
|
1126
|
+
"""
|
|
1127
|
+
Handle when completer mode is chosen via menu
|
|
1128
|
+
Will sync mode iterator and set the completion mode
|
|
1129
|
+
Args:
|
|
1130
|
+
action: the menu action associated with the chosen mode
|
|
1131
|
+
"""
|
|
1132
|
+
|
|
1133
|
+
# update cycleToCompleterMode to current Mode
|
|
1134
|
+
mode = action.data()
|
|
1135
|
+
self.cycleToCompleterMode(mode)
|
|
1136
|
+
self.setCompleterMode(mode)
|
|
1137
|
+
|
|
1138
|
+
def reportCaseChange(self, state):
|
|
1139
|
+
"""Update status text with current Case Sensitivity Mode"""
|
|
1140
|
+
text = "Case Sensitive " if state else "Case Insensitive "
|
|
1141
|
+
self.setStatusText(text)
|
|
1142
|
+
self.autoHideStatusText()
|
|
1143
|
+
|
|
1144
|
+
def reportCompleterModeChange(self, mode):
|
|
1145
|
+
"""Update status text with current Completer Mode"""
|
|
1146
|
+
self.setStatusText('Completer Mode: {} '.format(mode.displayName()))
|
|
1147
|
+
self.autoHideStatusText()
|
|
1148
|
+
|
|
1149
|
+
def setClearBeforeRunning(self, state):
|
|
1150
|
+
self.uiRunSelectedACT.setIcon(QIcon(resourcePath('img/playlist-play.png')))
|
|
1151
|
+
self.uiRunAllACT.setIcon(QIcon(resourcePath('img/play.png')))
|
|
1152
|
+
|
|
1153
|
+
def setFlashWindowInterval(self):
|
|
1154
|
+
value = self.uiConsoleTXT.flash_time
|
|
1155
|
+
msg = (
|
|
1156
|
+
'If running code in the logger takes X seconds or longer,\n'
|
|
1157
|
+
'the window will flash if it is not in focus.\n'
|
|
1158
|
+
'Setting the value to zero will disable flashing.'
|
|
1159
|
+
)
|
|
1160
|
+
value, success = QInputDialog.getDouble(self, 'Set flash window', msg, value)
|
|
1161
|
+
if success:
|
|
1162
|
+
self.uiConsoleTXT.flash_time = value
|
|
1163
|
+
|
|
1164
|
+
def setWordWrap(self, state):
|
|
1165
|
+
if state:
|
|
1166
|
+
self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.WidgetWidth)
|
|
1167
|
+
else:
|
|
1168
|
+
self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.NoWrap)
|
|
1169
|
+
|
|
1170
|
+
def show_about(self):
|
|
1171
|
+
"""Shows `preditor.about_preditor()`'s output in a message box."""
|
|
1172
|
+
msg = about_preditor(instance=self)
|
|
1173
|
+
QMessageBox.information(self, 'About PrEditor', '<pre>{}</pre>'.format(msg))
|
|
1174
|
+
|
|
1175
|
+
def showEnvironmentVars(self):
|
|
1176
|
+
dlg = Dialog(self)
|
|
1177
|
+
lyt = QVBoxLayout(dlg)
|
|
1178
|
+
lbl = QTextBrowser(dlg)
|
|
1179
|
+
lyt.addWidget(lbl)
|
|
1180
|
+
dlg.setWindowTitle('Blurdev Environment Variable Help')
|
|
1181
|
+
with open(resourcePath('environment_variables.html')) as f:
|
|
1182
|
+
lbl.setText(f.read().replace('\n', ''))
|
|
1183
|
+
dlg.setMinimumSize(600, 400)
|
|
1184
|
+
dlg.show()
|
|
1185
|
+
|
|
1186
|
+
def showEvent(self, event):
|
|
1187
|
+
super(LoggerWindow, self).showEvent(event)
|
|
1188
|
+
self.restoreToolbars()
|
|
1189
|
+
self.updateIndentationsUseTabs()
|
|
1190
|
+
self.updateCopyIndentsAsSpaces()
|
|
1191
|
+
|
|
1192
|
+
# Adjust the minimum height of the label so it's text is the same as
|
|
1193
|
+
# the action menu text
|
|
1194
|
+
height = self.uiMenuBar.actionGeometry(self.uiFileMENU.menuAction()).height()
|
|
1195
|
+
self.uiStatusLBL.setMinimumHeight(height)
|
|
1196
|
+
|
|
1197
|
+
@Slot()
|
|
1198
|
+
def show_workbox_options(self):
|
|
1199
|
+
self.uiWorkboxSTACK.setCurrentIndex(WorkboxPages.Options)
|
|
1200
|
+
|
|
1201
|
+
@Slot()
|
|
1202
|
+
def show_find_in_workboxes(self):
|
|
1203
|
+
"""Ensure the find workboxes widget is visible and has focus."""
|
|
1204
|
+
self.uiFindInWorkboxesWGT.activate()
|
|
1205
|
+
|
|
1206
|
+
@Slot()
|
|
1207
|
+
def show_focus_name(self):
|
|
1208
|
+
model = GroupTabListItemModel(manager=self.uiWorkboxTAB)
|
|
1209
|
+
model.process()
|
|
1210
|
+
|
|
1211
|
+
def update_tab(index):
|
|
1212
|
+
group, tab = model.workbox_indexes_from_model_index(index)
|
|
1213
|
+
if group is not None:
|
|
1214
|
+
self.uiWorkboxTAB.set_current_groups_from_index(group, tab)
|
|
1215
|
+
|
|
1216
|
+
w = FuzzySearch(model, parent=self)
|
|
1217
|
+
w.selected.connect(update_tab)
|
|
1218
|
+
w.canceled.connect(update_tab)
|
|
1219
|
+
w.highlighted.connect(update_tab)
|
|
1220
|
+
w.popup()
|
|
1221
|
+
|
|
1222
|
+
def updateCopyIndentsAsSpaces(self):
|
|
1223
|
+
for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
|
|
1224
|
+
workbox.__set_copy_indents_as_spaces__(
|
|
1225
|
+
self.uiCopyTabsToSpacesACT.isChecked()
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
def updateIndentationsUseTabs(self):
|
|
1229
|
+
for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
|
|
1230
|
+
workbox.__set_indentations_use_tabs__(
|
|
1231
|
+
self.uiIndentationsTabsACT.isChecked()
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
@Slot()
|
|
1235
|
+
def update_workbox_stack(self):
|
|
1236
|
+
if self.uiWorkboxTAB.editor_cls:
|
|
1237
|
+
index = WorkboxPages.Workboxes
|
|
1238
|
+
else:
|
|
1239
|
+
index = WorkboxPages.Options
|
|
1240
|
+
|
|
1241
|
+
self.uiWorkboxSTACK.setCurrentIndex(index)
|
|
1242
|
+
|
|
1243
|
+
def shutdown(self):
|
|
1244
|
+
# close out of the ide system
|
|
1245
|
+
|
|
1246
|
+
# if this is the global instance, then allow it to be deleted on close
|
|
1247
|
+
if self == LoggerWindow._instance:
|
|
1248
|
+
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
|
1249
|
+
LoggerWindow._instance = None
|
|
1250
|
+
|
|
1251
|
+
# clear out the system
|
|
1252
|
+
self.close()
|
|
1253
|
+
|
|
1254
|
+
def nextTab(self):
|
|
1255
|
+
"""Move focus to next workbox tab"""
|
|
1256
|
+
tabWidget = self.uiWorkboxTAB.currentWidget()
|
|
1257
|
+
if not tabWidget.currentWidget().hasFocus():
|
|
1258
|
+
tabWidget.currentWidget().setFocus()
|
|
1259
|
+
|
|
1260
|
+
index = tabWidget.currentIndex()
|
|
1261
|
+
if index == tabWidget.count() - 1:
|
|
1262
|
+
tabWidget.setCurrentIndex(0)
|
|
1263
|
+
else:
|
|
1264
|
+
tabWidget.setCurrentIndex(index + 1)
|
|
1265
|
+
|
|
1266
|
+
def prevTab(self):
|
|
1267
|
+
"""Move focus to previous workbox tab"""
|
|
1268
|
+
tabWidget = self.uiWorkboxTAB.currentWidget()
|
|
1269
|
+
if not tabWidget.currentWidget().hasFocus():
|
|
1270
|
+
tabWidget.currentWidget().setFocus()
|
|
1271
|
+
|
|
1272
|
+
index = tabWidget.currentIndex()
|
|
1273
|
+
if index == 0:
|
|
1274
|
+
tabWidget.setCurrentIndex(tabWidget.count() - 1)
|
|
1275
|
+
else:
|
|
1276
|
+
tabWidget.setCurrentIndex(index - 1)
|
|
1277
|
+
|
|
1278
|
+
def gotoGroupByIndex(self, index):
|
|
1279
|
+
"""Generally to be used in conjunction with the Ctrl+Alt+<num> keyboard
|
|
1280
|
+
shortcuts, which allow user to jump directly to another tab, mimicking
|
|
1281
|
+
web browser functionality.
|
|
1282
|
+
"""
|
|
1283
|
+
if index == -1:
|
|
1284
|
+
index = self.uiWorkboxTAB.count() - 1
|
|
1285
|
+
else:
|
|
1286
|
+
count = self.uiWorkboxTAB.count()
|
|
1287
|
+
index = min(index, count)
|
|
1288
|
+
index -= 1
|
|
1289
|
+
|
|
1290
|
+
self.uiWorkboxTAB.setCurrentIndex(index)
|
|
1291
|
+
|
|
1292
|
+
def gotoTabByIndex(self, index):
|
|
1293
|
+
"""Generally to be used in conjunction with the Ctrl+<num> keyboard
|
|
1294
|
+
shortcuts, which allow user to jump directly to another tab, mimicking
|
|
1295
|
+
web browser functionality.
|
|
1296
|
+
"""
|
|
1297
|
+
group_tab = self.uiWorkboxTAB.currentWidget()
|
|
1298
|
+
if index == -1:
|
|
1299
|
+
index = group_tab.count() - 1
|
|
1300
|
+
else:
|
|
1301
|
+
count = group_tab.count()
|
|
1302
|
+
index = min(index, count)
|
|
1303
|
+
index -= 1
|
|
1304
|
+
|
|
1305
|
+
group_tab.setCurrentIndex(index)
|
|
1306
|
+
|
|
1307
|
+
@staticmethod
|
|
1308
|
+
def instance(
|
|
1309
|
+
parent=None, name=None, run_workbox=False, create=True, standalone=False
|
|
1310
|
+
):
|
|
1311
|
+
"""Returns the existing instance of the PrEditor gui creating it on first call.
|
|
1312
|
+
|
|
1313
|
+
Args:
|
|
1314
|
+
parent (QWidget, optional): If the instance hasn't been created yet, create
|
|
1315
|
+
it and parent it to this object.
|
|
1316
|
+
run_workbox (bool, optional): If the instance hasn't been created yet, this
|
|
1317
|
+
will execute the active workbox's code once fully initialized.
|
|
1318
|
+
create (bool, optional): Returns None if the instance has not been created.
|
|
1319
|
+
standalone (bool, optional): Launch PrEditor in standalone mode. This
|
|
1320
|
+
enables extra options that only make sense when it is running as
|
|
1321
|
+
its own app, not inside of another app.
|
|
1322
|
+
|
|
1323
|
+
Returns:
|
|
1324
|
+
Returns a fully initialized instance of the PrEditor gui. If called more
|
|
1325
|
+
than once, the same instance will be returned. If create is False, it may
|
|
1326
|
+
return None.
|
|
1327
|
+
"""
|
|
1328
|
+
# create the instance for the logger
|
|
1329
|
+
if not LoggerWindow._instance:
|
|
1330
|
+
if not create:
|
|
1331
|
+
return None
|
|
1332
|
+
|
|
1333
|
+
# create the logger instance
|
|
1334
|
+
inst = LoggerWindow(
|
|
1335
|
+
parent, name=name, run_workbox=run_workbox, standalone=standalone
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1338
|
+
# RV has a Unique window structure. It makes more sense to not parent a
|
|
1339
|
+
# singleton window than to parent it to a specific top level window.
|
|
1340
|
+
if core.objectName() == 'rv':
|
|
1341
|
+
inst.setParent(None)
|
|
1342
|
+
inst.setAttribute(Qt.WA_QuitOnClose, False)
|
|
1343
|
+
|
|
1344
|
+
# protect the memory
|
|
1345
|
+
inst.setAttribute(Qt.WA_DeleteOnClose, False)
|
|
1346
|
+
|
|
1347
|
+
# cache the instance
|
|
1348
|
+
LoggerWindow._instance = inst
|
|
1349
|
+
|
|
1350
|
+
return LoggerWindow._instance
|
|
1351
|
+
|
|
1352
|
+
def installLogToFile(self):
|
|
1353
|
+
"""All stdout/stderr output is also appended to this file.
|
|
1354
|
+
|
|
1355
|
+
This uses preditor.debug.logToFile(path, useOldStd=True).
|
|
1356
|
+
"""
|
|
1357
|
+
if self._logToFilePath is None:
|
|
1358
|
+
path = osystem.defaultLogFile()
|
|
1359
|
+
path, _ = QtCompat.QFileDialog.getSaveFileName(
|
|
1360
|
+
self, "Log Output to File", path
|
|
1361
|
+
)
|
|
1362
|
+
if not path:
|
|
1363
|
+
return
|
|
1364
|
+
path = os.path.normpath(path)
|
|
1365
|
+
print('Output logged to: "{}"'.format(path))
|
|
1366
|
+
debug.logToFile(path, useOldStd=True)
|
|
1367
|
+
# Store the std's so we can clear them later
|
|
1368
|
+
self._stds = (sys.stdout, sys.stderr)
|
|
1369
|
+
self.uiLogToFileACT.setText('Output Logged to File')
|
|
1370
|
+
self.uiLogToFileClearACT.setVisible(True)
|
|
1371
|
+
self._logToFilePath = path
|
|
1372
|
+
else:
|
|
1373
|
+
print('Output logged to: "{}"'.format(self._logToFilePath))
|
|
1374
|
+
|
|
1375
|
+
@classmethod
|
|
1376
|
+
def instance_shutdown(cls):
|
|
1377
|
+
"""Call shutdown the LoggerWindow instance only if it was instantiated.
|
|
1378
|
+
|
|
1379
|
+
Returns:
|
|
1380
|
+
bool: If a shutdown was required
|
|
1381
|
+
"""
|
|
1382
|
+
if cls._instance:
|
|
1383
|
+
cls._instance.shutdown()
|
|
1384
|
+
return True
|
|
1385
|
+
return False
|