diff --git a/MANIFEST.in b/MANIFEST.in
index 239e346f..79543a81 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
graft preditor/resource
graft preditor/dccs
recursive-include preditor *.ui
+recursive-exclude tests *
diff --git a/README.md b/README.md
index 974f4e12..3e59abad 100644
--- a/README.md
+++ b/README.md
@@ -97,13 +97,13 @@ a better `parent_callback`.
## Installing Qt
PrEditor is built on Qt, but uses [Qt.py](https://github.com/mottosso/Qt.py) so
-you can choose to use PySide2 or PyQt5. We have elected to not directly depend
-on either of these packages as if you want to use PrEditor inside of a an existing
-application like Maya or Houdini, they already come with PySide2 installed. If
-you are using it externally, add them to your pip install command.
+you can choose to use PySide6, PySide2, PyQt6 or PyQt5. We have elected to not
+directly depend on either of these packages so that you can use PrEditor inside
+of existing applications like Maya or Houdini that already come with PySide
+installed. If you are using it externally add them to your pip install command.
-- PySide2: `pip install preditor PySide2`
-- PyQt5: `pip install preditor PyQt5`
+- PySide6: `pip install preditor PySide6`
+- PyQt6: `pip install preditor PyQt6`
## Cli
@@ -125,35 +125,68 @@ this is only useful for windows.
## QScintilla workbox
The more mature QScintilla workbox requires a few extra dependencies that must
-be passed manually. It hasn't been added to `extras_require` because we plan to
-split it into its own pip module due to it requiring PyQt5 which is a little hard
-to get working inside of DCC's that ship with PySide2 by default. Here is the
-python 3 pip install command.
+be passed manually. We have added it as pip `optional-dependencies`. QScintilla
+only works with PyQt5/6 and it is a little hard to get PyQt working inside of
+DCC's that ship with PySide2/6 by default. Here is the python 3 pip install command.
-- `pip install preditor PyQt5, QScintilla>=2.11.4 aspell-python-py3`
+- PyQt6: `pip install preditor[qsci6] PyQt6, aspell-python-py3`
+- PyQt5: `pip install preditor[qsci5] PyQt5, aspell-python-py3`
The aspell-python-py3 requirement is optional to enable spell check.
+You may need to set the `QT_PREFERRED_BINDING` or `QT_PREFERRED_BINDING_JSON`
+[environment variable](https://github.com/mottosso/Qt.py?tab=readme-ov-file#override-preferred-choice) to ensure that PrEditor can use PyQt5/PyQt6.
# DCC Integration
-## Maya
-
-PrEditor is pre-setup to use as a Maya module. To use it, create a virtualenv
-with the same python as maya, or install it using mayapy.
-
-```
-virtualenv venv_preditor
-venv_preditor\Scripts\activate
-pip install PrEditor
-set MAYA_MODULE_PATH=c:\path\to\venv_preditor\Lib\site-packages\preditor\dccs
-```
-Note: Due to how maya .mod files works if you are using development installs you
-can't use pip editable installs. This is due to the relative path used
-`PYTHONPATH +:= ../..` in `PrEditor_maya.mod`. You can modify that to use a hard
-coded file path for testing, or add a second .mod file to add the virtualenv's
-`site-packages` file path as a hard coded file path.
-
+Here are several example integrations for DCC's included in PrEditor. These
+require some setup to manage installing all pip requirements. These will require
+you to follow the [Setup](#setup) instructions below.
+
+- [Maya](/preditor/dccs/maya/README.md)
+- [3ds Max](/preditor/dccs/studiomax/README.md)
+
+If you are using hab, you can simply add the path to the [preditor](/preditor) folder to your site's `distro_paths`. [See .hab.json](/preditor/dccs/.hab.json)
+
+## Setup
+
+PrEditor has many python pip requirements. The easiest way to get access to all
+of them inside an DCC is to create a virtualenv and pip install the requirements.
+You can possibly use the python included with DCC(mayapy), but this guide covers
+using a system install of python.
+
+1. Identify the minor version of python that the dcc is using. Running `sys.version_info[:2]` in the DCC returns the major and minor version of python.
+2. Download and install the required version of python. Note, you likely only need to match the major and minor version of python(3.11 not 3.11.12). It's recommended that you don't use the windows store to install python as it has had issues when used to create virtualenvs.
+3. Create a virtualenv using that version of python. On windows you can use `py.exe -3.11` or call the correct python.exe file. Change `-3.11` to match the major and minor version returned by step 1. Note that you should create separate venvs for a given python minor version and potentially for minor versions of Qt if you are using PyQt.
+ ```batch
+ cd c:\path\to\venv\parent
+ py -3.11 -m virtualenv preditor_311
+ ```
+4. Use the newly created pip exe to install PrEditor and its dependencies.
+ * This example shows using PySide and the simple TextEdit workbox in a minimal configuration.
+ ```batch
+ c:\path\to\venv\parent\preditor_311\Scripts\pip install PrEditor
+ ```
+ * This example shows using QScintilla in PyQt6 for a better editing experience. Note that you need to match the PyQt version used by the DCC, This may require matching the exact version of PyQt.
+ ```batch
+ c:\path\to\venv\parent\preditor_311\Scripts\pip install PrEditor[qsci6] PyQt6==6.5.3
+ ```
+
+### Editable install
+
+You should skip this section unless you want to develop PrEditor's code from an git repo using python's editable pip install.
+
+Due to how editable installs work you will need to set an environment variable
+specifying the site-packages directory of the virtualenv you created in the
+previous step. On windows this should be the `lib\site-packages` folder inside
+of the venv you just created. Store this in the `PREDITOR_SITE`, this can be done
+permanently or temporarily(via `set "PREDITOR_SITE=c:\path\to\venv\parent\preditor_311\lib\site-packages"`).
+
+This is required because you are going to use the path to your git repo's preditor
+folder in the module/plugin loading methods for the the DCC you are using, but
+there is no way to automatically find the virtualenv that your random git repo
+is installed in. In fact, you may have have your git repo installed into multiple
+virtualenvs at once.
# Plugins
diff --git a/preditor/__init__.py b/preditor/__init__.py
index 75f6a86a..f7083083 100644
--- a/preditor/__init__.py
+++ b/preditor/__init__.py
@@ -184,7 +184,10 @@ def launch(run_workbox=False, app_id=None, name=None, standalone=False):
# it regain focus.
widget.activateWindow()
widget.raise_()
- widget.setWindowState(widget.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
+ widget.setWindowState(
+ widget.windowState() & ~Qt.WindowState.WindowMinimized
+ | Qt.WindowState.WindowActive
+ )
widget.console().setFocus()
app.start()
diff --git a/preditor/about_module.py b/preditor/about_module.py
index 8223211c..99c27975 100644
--- a/preditor/about_module.py
+++ b/preditor/about_module.py
@@ -111,10 +111,14 @@ def text(self):
pass
# Add info for all Qt5 bindings that have been imported
- if 'PyQt5.QtCore' in sys.modules:
- ret.append('PyQt5: {}'.format(sys.modules['PyQt5.QtCore'].PYQT_VERSION_STR))
+ if 'PySide6.QtCore' in sys.modules:
+ ret.append('PySide6: {}'.format(sys.modules['PySide6.QtCore'].qVersion()))
+ if 'PyQt6.QtCore' in sys.modules:
+ ret.append('PyQt6: {}'.format(sys.modules['PyQt6.QtCore'].PYQT_VERSION_STR))
if 'PySide2.QtCore' in sys.modules:
ret.append('PySide2: {}'.format(sys.modules['PySide2.QtCore'].qVersion()))
+ if 'PyQt5.QtCore' in sys.modules:
+ ret.append('PyQt5: {}'.format(sys.modules['PyQt5.QtCore'].PYQT_VERSION_STR))
# Add qt library paths for plugin debugging
for i, path in enumerate(QtCore.QCoreApplication.libraryPaths()):
diff --git a/preditor/dccs/.hab.json b/preditor/dccs/.hab.json
new file mode 100644
index 00000000..4113ff3a
--- /dev/null
+++ b/preditor/dccs/.hab.json
@@ -0,0 +1,10 @@
+{
+ "name": "preditor",
+ "version": "0.0.dev0",
+ "environment": {
+ "append": {
+ "MAYA_MODULE_PATH": "{relative_root}/maya",
+ "ADSK_APPLICATION_PLUGINS": "{relative_root}/studiomax"
+ }
+ }
+}
diff --git a/preditor/dccs/maya/PrEditor_maya.mod b/preditor/dccs/maya/PrEditor_maya.mod
index 8a8016d4..187c05f8 100644
--- a/preditor/dccs/maya/PrEditor_maya.mod
+++ b/preditor/dccs/maya/PrEditor_maya.mod
@@ -1,2 +1 @@
+ PrEditor DEVELOPMENT .
-PYTHONPATH +:= ../..
diff --git a/preditor/dccs/maya/README.md b/preditor/dccs/maya/README.md
new file mode 100644
index 00000000..d1ab8a36
--- /dev/null
+++ b/preditor/dccs/maya/README.md
@@ -0,0 +1,22 @@
+# Maya Integration
+
+This is an example of using an Maya module to add PrEditor into Maya. This adds
+a PrEditor menu with a PrEditor action in Maya's menu bar letting you open PrEditor. It
+adds the excepthook so if a python exception is raised it will prompt the user
+to show PrEditor. PrEditor will show all python stdout/stderr output generated
+after the plugin is loaded.
+
+# Setup
+
+Make sure to follow these [setup instructions](/preditor/README.md#Setup) first to create the virtualenv.
+
+Alternatively you can use [myapy's](https://help.autodesk.com/view/MAYAUL/2026/ENU/?guid=GUID-72A245EC-CDB4-46AB-BEE0-4BBBF9791627) pip to install the requirements, but a
+separate virtualenv is recommended. This method should not require setting the
+`PREDITOR_SITE` environment variable even if you use an editable install.
+
+# Use
+
+The [preditor/dccs/maya](/preditor/dccs/maya) directory is setup as a Maya Module. To load it in
+maya add the full path to that directory to the `MAYA_MODULE_PATH` environment
+variable. You can use `;` on windows and `:` on linux to join multiple paths together.
+You will need to enable auto load for the `PrEditor_maya.py` plugin.
diff --git a/preditor/dccs/maya/plug-ins/PrEditor_maya.py b/preditor/dccs/maya/plug-ins/PrEditor_maya.py
index ec993855..702417a5 100644
--- a/preditor/dccs/maya/plug-ins/PrEditor_maya.py
+++ b/preditor/dccs/maya/plug-ins/PrEditor_maya.py
@@ -1,5 +1,9 @@
from __future__ import absolute_import
+import os
+import site
+from pathlib import Path
+
import maya.mel
from maya import OpenMayaUI, cmds
@@ -35,12 +39,39 @@ def launch(ignored):
return widget
+def update_site():
+ """Adds a site dir to python. This makes its contents importable to python.
+
+ This includes making any editable installs located in that site-packages folder
+ accessible to this python instance. This does not activate the virtualenv.
+
+ If the env var `PREDITOR_SITE` is set, this path is used. Otherwise the
+ parent directory of preditor is used.
+
+ - `PREDITOR_SITE` is useful if you want to use an editable install of preditor
+ for development. This should point to a virtualenv's site-packages folder.
+ - Otherwise if the virtualenv has a regular pip install of preditor you can
+ skip setting the env var.
+ """
+ venv_path = os.getenv("PREDITOR_SITE")
+ # If the env var is not defined then use the parent dir of this preditor package.
+ if venv_path is None:
+ venv_path = cmds.moduleInfo(moduleName="PrEditor", path=True)
+ venv_path = Path(venv_path).parent.parent.parent
+ venv_path = str(venv_path)
+
+ print(f"Preditor is adding python site: {venv_path}")
+ site.addsitedir(venv_path)
+
+
def initializePlugin(mobject): # noqa: N802
"""Initialize the script plug-in"""
global preditor_menu
# If running headless, there is no need to build a gui and create the python logger
if not headless():
+ update_site()
+
from Qt.QtWidgets import QApplication
import preditor
@@ -67,7 +98,7 @@ def initializePlugin(mobject): # noqa: N802
gmainwindow = maya.mel.eval("$temp1=$gMainWindow")
preditor_menu = cmds.menu(label="PrEditor", parent=gmainwindow, tearOff=True)
cmds.menuItem(
- label="Show",
+ label="PrEditor",
command=launch,
sourceType="python",
image=preditor.resourcePath('img/preditor.png'),
diff --git a/preditor/dccs/studiomax/PackageContents.xml b/preditor/dccs/studiomax/PackageContents.xml
new file mode 100644
index 00000000..3e29c439
--- /dev/null
+++ b/preditor/dccs/studiomax/PackageContents.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr b/preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr
new file mode 100644
index 00000000..3e8705eb
--- /dev/null
+++ b/preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr
@@ -0,0 +1,8 @@
+macroScript PrEditor_Show
+category:"PrEditor"
+tooltip:"PrEditor..."
+IconName:"preditor.ico"
+(
+ local preditor = python.import "preditor"
+ preditor.launch()
+)
diff --git a/preditor/dccs/studiomax/README.md b/preditor/dccs/studiomax/README.md
new file mode 100644
index 00000000..5b0fe7e2
--- /dev/null
+++ b/preditor/dccs/studiomax/README.md
@@ -0,0 +1,17 @@
+# 3ds Max Integration
+
+This is an example of using an Autodesk Application Package to load PrEditor into
+3ds Max. This adds a PrEditor item to the Scripting menu in 3ds Max's menu bar
+to show PrEditor. It adds the excepthook so if a python exception is raised
+it will prompt the user to show PrEditor. PrEditor will show all python stdout/stderr
+output generated after the plugin is loaded.
+
+# Setup
+
+Make sure to follow these [setup instructions](/preditor/README.md#Setup) first to create the virtualenv.
+
+# Use
+
+The [preditor/dccs/studiomax](/preditor/dccs/studiomax) directory is setup as a 3ds Max Application Plugin.
+To load it in 3ds Max add the full path to that directory to the `ADSK_APPLICATION_PLUGINS` environment
+variable. You can use `;` on windows and `:` on linux to join multiple paths together.
diff --git a/preditor/dccs/studiomax/preditor.ms b/preditor/dccs/studiomax/preditor.ms
new file mode 100644
index 00000000..3c5cb731
--- /dev/null
+++ b/preditor/dccs/studiomax/preditor.ms
@@ -0,0 +1,16 @@
+
+function configure_preditor = (
+ local pysite = python.import "site"
+ local WinEnv = dotNetClass "System.Environment"
+ -- If the env var PREDITOR_SITE is set, add its packages to python
+ local venv_path = WinEnv.GetEnvironmentVariable "PREDITOR_SITE"
+ if venv_path != undefined do (
+ print("Preditor is adding python site: " + venv_path)
+ pysite.addsitedir venv_path
+ )
+ -- Configure preditor, adding excepthook etc.
+ local preditor = python.import "preditor"
+ preditor.configure "studiomax"
+)
+
+configure_preditor()
diff --git a/preditor/dccs/studiomax/preditor_menu.mnx b/preditor/dccs/studiomax/preditor_menu.mnx
new file mode 100644
index 00000000..5c5f588f
--- /dev/null
+++ b/preditor/dccs/studiomax/preditor_menu.mnx
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/preditor/debug.py b/preditor/debug.py
index 39854c76..63dbdbc6 100644
--- a/preditor/debug.py
+++ b/preditor/debug.py
@@ -21,11 +21,15 @@ def clear(self, stamp=False):
"""Removes the contents of the log file."""
open(self._logfile, 'w').close()
if stamp:
- msg = '--------- Date: {today} Version: {version} ---------'
- print(msg.format(today=datetime.datetime.today(), version=sys.version))
+ print(self.stamp())
def flush(self):
- self._stdhandle.flush()
+ if self._stdhandle:
+ self._stdhandle.flush()
+
+ def stamp(self):
+ msg = '--------- Date: {today} Version: {version} ---------'
+ return msg.format(today=datetime.datetime.today(), version=sys.version)
def write(self, msg):
f = open(self._logfile, 'a')
diff --git a/preditor/excepthooks.py b/preditor/excepthooks.py
index 1b166e2a..dc7b3ff2 100644
--- a/preditor/excepthooks.py
+++ b/preditor/excepthooks.py
@@ -15,7 +15,7 @@ class PreditorExceptHook(object):
This calls each callable in the `preditor.config.excepthooks` list any time
`sys.excepthook` is called due to an raised exception.
- If `config.excepthook` is empty when installing this class, it will
+ If `config.excepthooks` is empty when installing this class, it will
automatically add `default_excepthooks`. You can disable this by adding `None`
to the list before this class is initialized.
"""
diff --git a/preditor/gui/app.py b/preditor/gui/app.py
index c96dbef2..afa938d8 100644
--- a/preditor/gui/app.py
+++ b/preditor/gui/app.py
@@ -93,7 +93,7 @@ def dpi_awareness_args(cls):
Returns:
args: Extend the arguments used to intialize the QApplication.
"""
- if settings.OS_TYPE == "Windows" and Qt.IsPyQt5:
+ if settings.OS_TYPE == "Windows" and (Qt.IsPyQt6 or Qt.IsPyQt5):
# Make Qt automatically scale based on the monitor the window is
# currently located.
return ["--platform", "windows:dpiawareness=0"]
@@ -157,4 +157,4 @@ def start(self):
"""Exec's the QApplication if it hasn't already been started."""
if self.app_created and self.app and not self.app_has_exec:
self.app_has_exec = True
- self.app.exec_()
+ Qt.QtCompat.QApplication.exec_()
diff --git a/preditor/gui/codehighlighter.py b/preditor/gui/codehighlighter.py
index 0f509947..f0000794 100644
--- a/preditor/gui/codehighlighter.py
+++ b/preditor/gui/codehighlighter.py
@@ -4,7 +4,6 @@
import os
import re
-from Qt.QtCore import QRegExp
from Qt.QtGui import QColor, QSyntaxHighlighter, QTextCharFormat
from .. import resourcePath
@@ -68,59 +67,46 @@ def highlightBlock(self, text):
if parent and hasattr(parent, 'outputPrompt'):
self.highlightText(
text,
- QRegExp('%s[^\\n]*' % re.escape(parent.outputPrompt())),
+ re.compile('%s[^\\n]*' % re.escape(parent.outputPrompt())),
format,
)
# format the keywords
format = self.keywordFormat()
for kwd in self._keywords:
- self.highlightText(text, QRegExp(r'\b%s\b' % kwd), format)
+ self.highlightText(text, re.compile(r'\b%s\b' % kwd), format)
# format the strings
format = self.stringFormat()
+
for string in self._strings:
self.highlightText(
text,
- QRegExp('%s[^%s]*' % (string, string)),
+ re.compile('{s}[^{s}]*{s}'.format(s=string)),
format,
- includeLast=True,
)
# format the comments
format = self.commentFormat()
for comment in self._comments:
- self.highlightText(text, QRegExp(comment), format)
+ self.highlightText(text, re.compile(comment), format)
def highlightText(self, text, expr, format, offset=0, includeLast=False):
"""Highlights a text group with an expression and format
Args:
text (str): text to highlight
- expr (QRegExp): search parameter
+ expr (QRegularExpression): search parameter
format (QTextCharFormat): formatting rule
- offset (int): number of characters to offset by when highlighting
includeLast (bool): whether or not the last character should be highlighted
"""
- pos = expr.indexIn(text, 0)
-
# highlight all the given matches to the expression in the text
- while pos != -1:
- pos = expr.pos(offset)
- length = len(expr.cap(offset))
-
- # use the last character if desired
+ for match in expr.finditer(text):
+ start, end = match.span()
+ length = end - start
if includeLast:
length += 1
-
- # set the formatting
- self.setFormat(pos, length, format)
-
- matched = expr.matchedLength()
- if includeLast:
- matched += 1
-
- pos = expr.indexIn(text, pos + matched)
+ self.setFormat(start, length, format)
def keywordColor(self):
# pull the color from the parent if possible because this doesn't support
diff --git a/preditor/gui/completer.py b/preditor/gui/completer.py
index fcb742c9..343e3c51 100644
--- a/preditor/gui/completer.py
+++ b/preditor/gui/completer.py
@@ -5,6 +5,7 @@
import sys
from enum import Enum
+import Qt as Qt_py
from Qt.QtCore import QRegExp, QSortFilterProxyModel, QStringListModel, Qt
from Qt.QtGui import QCursor, QTextCursor
from Qt.QtWidgets import QCompleter, QToolTip
@@ -63,12 +64,16 @@ def __init__(self, widget):
def setCaseSensitive(self, caseSensitive=True):
"""Set case sensitivity for completions"""
- self._sensitivity = Qt.CaseSensitive if caseSensitive else Qt.CaseInsensitive
+ self._sensitivity = (
+ Qt.CaseSensitivity.CaseSensitive
+ if caseSensitive
+ else Qt.CaseSensitivity.CaseInsensitive
+ )
self.buildCompleter()
def caseSensitive(self):
"""Return current case sensitivity state for completions"""
- caseSensitive = self._sensitivity == Qt.CaseSensitive
+ caseSensitive = self._sensitivity == Qt.CaseSensitivity.CaseSensitive
return caseSensitive
def setCompleterMode(self, completerMode=CompleterMode.STARTS_WITH):
@@ -89,7 +94,7 @@ def buildCompleter(self):
self.filterModel.setSourceModel(model)
self.filterModel.setFilterCaseSensitivity(self._sensitivity)
self.setModel(self.filterModel)
- self.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
+ self.setCompletionMode(QCompleter.CompletionMode.UnfilteredPopupCompletion)
def currentObject(self, scope=None, docMode=False):
if self._enabled:
@@ -155,8 +160,14 @@ def refreshList(self, scope=None):
if self._completerMode == CompleterMode.FULL_FUZZY:
regExStr = ".*".join(prefix)
- regexp = QRegExp(regExStr, self._sensitivity)
- self.filterModel.setFilterRegExp(regexp)
+ if Qt_py.IsPyQt6 or Qt_py.IsPySide6:
+ regexp = QRegExp(regExStr)
+ if self._sensitivity:
+ regexp.setPatternOptions(QRegExp.PatternOption.CaseInsensitiveOption)
+ self.filterModel.setFilterRegularExpression(regexp)
+ else:
+ regexp = QRegExp(regExStr, self._sensitivity)
+ self.filterModel.setFilterRegExp(regexp)
def clear(self):
self.popup().hide()
@@ -193,7 +204,7 @@ def textUnderCursor(self, useParens=False):
"""pulls out the text underneath the cursor of this items widget"""
cursor = self.widget().textCursor()
- cursor.select(QTextCursor.WordUnderCursor)
+ cursor.select(QTextCursor.SelectionType.WordUnderCursor)
# grab the selected word
word = cursor.selectedText()
diff --git a/preditor/gui/console.py b/preditor/gui/console.py
index 1e28d0c7..1bf7b358 100644
--- a/preditor/gui/console.py
+++ b/preditor/gui/console.py
@@ -15,7 +15,14 @@
import __main__
from Qt import QtCompat
from Qt.QtCore import QPoint, Qt, QTimer
-from Qt.QtGui import QColor, QFontMetrics, QTextCharFormat, QTextCursor, QTextDocument
+from Qt.QtGui import (
+ QColor,
+ QFontMetrics,
+ QKeySequence,
+ QTextCharFormat,
+ QTextCursor,
+ QTextDocument,
+)
from Qt.QtWidgets import QAbstractItemView, QAction, QApplication, QTextEdit
from .. import settings, stream
@@ -32,7 +39,7 @@ class ConsolePrEdit(QTextEdit):
# These Qt Properties can be customized using style sheets.
commentColor = QtPropertyInit('_commentColor', QColor(0, 206, 52))
- errorMessageColor = QtPropertyInit('_errorMessageColor', QColor(Qt.red))
+ errorMessageColor = QtPropertyInit('_errorMessageColor', QColor(Qt.GlobalColor.red))
keywordColor = QtPropertyInit('_keywordColor', QColor(17, 154, 255))
resultColor = QtPropertyInit('_resultColor', QColor(128, 128, 128))
stdoutColor = QtPropertyInit('_stdoutColor', QColor(17, 154, 255))
@@ -99,7 +106,9 @@ def __init__(self, parent):
self.uiClearToLastPromptACT = QAction('Clear to Last', self)
self.uiClearToLastPromptACT.triggered.connect(self.clearToLastPrompt)
- self.uiClearToLastPromptACT.setShortcut(Qt.CTRL | Qt.SHIFT | Qt.Key_Backspace)
+ self.uiClearToLastPromptACT.setShortcut(
+ QKeySequence(Qt.Modifier.CTRL | Qt.Modifier.SHIFT | Qt.Key.Key_Backspace)
+ )
self.addAction(self.uiClearToLastPromptACT)
self.x = 0
@@ -155,8 +164,8 @@ def setConsoleFont(self, font):
workbox = self.window().current_workbox()
if workbox:
tab_width = workbox.__tab_width__()
- fontPixelWidth = QFontMetrics(font).width(" ")
- self.setTabStopWidth(fontPixelWidth * tab_width)
+ fontPixelWidth = QFontMetrics(font).horizontalAdvance(" ")
+ self.setTabStopDistance(fontPixelWidth * tab_width)
# Scroll to same relative position where we started
if origPercent is not None:
@@ -170,7 +179,7 @@ def mousePressEvent(self, event):
self.clickPos = event.pos()
self.anchor = self.anchorAt(event.pos())
if self.anchor:
- QApplication.setOverrideCursor(Qt.PointingHandCursor)
+ QApplication.setOverrideCursor(Qt.CursorShape.PointingHandCursor)
return super(ConsolePrEdit, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
@@ -178,7 +187,7 @@ def mouseReleaseEvent(self, event):
click position is the same as release position, if so, call errorHyperlink.
"""
samePos = event.pos() == self.clickPos
- left = event.button() == Qt.LeftButton
+ left = event.button() == Qt.MouseButton.LeftButton
if samePos and left and self.anchor:
self.errorHyperlink()
@@ -192,7 +201,7 @@ def wheelEvent(self, event):
# scrolling. If used in LoggerWindow, use that wheel event
# May not want to import LoggerWindow, so perhaps
# check by str(type())
- ctrlPressed = event.modifiers() == Qt.ControlModifier
+ ctrlPressed = event.modifiers() == Qt.KeyboardModifier.ControlModifier
if ctrlPressed and "LoggerWindow" in str(type(self.window())):
self.window().wheelEvent(event)
else:
@@ -202,7 +211,7 @@ def keyReleaseEvent(self, event):
"""Override of keyReleaseEvent to determine when to end navigation of
previous commands
"""
- if event.key() == Qt.Key_Alt:
+ if event.key() == Qt.Key.Key_Alt:
self._prevCommandIndex = 0
else:
event.ignore()
@@ -319,7 +328,7 @@ def setCommand(self):
prevCommand = self._prevCommands[self._prevCommandIndex]
cursor = self.textCursor()
- cursor.select(QTextCursor.LineUnderCursor)
+ cursor.select(QTextCursor.SelectionType.LineUnderCursor)
if cursor.selectedText().startswith(self._consolePrompt):
prevCommand = "{}{}".format(self._consolePrompt, prevCommand)
cursor.insertText(prevCommand)
@@ -335,7 +344,7 @@ def clearToLastPrompt(self):
currentCursor = self.textCursor()
# move to the end of the document so we can search backwards
cursor = self.textCursor()
- cursor.movePosition(cursor.End)
+ cursor.movePosition(QTextCursor.MoveOperation.End)
self.setTextCursor(cursor)
# Check if the last line is a empty prompt. If so, then preform two finds so we
# find the prompt we are looking for instead of this empty prompt
@@ -343,12 +352,14 @@ def clearToLastPrompt(self):
2 if self.toPlainText()[-len(self.prompt()) :] == self.prompt() else 1
)
for _ in range(findCount):
- self.find(self.prompt(), QTextDocument.FindBackward)
+ self.find(self.prompt(), QTextDocument.FindFlag.FindBackward)
# move to the end of the found line, select the rest of the text and remove it
# preserving history if there is anything to remove.
cursor = self.textCursor()
- cursor.movePosition(cursor.EndOfLine)
- cursor.movePosition(cursor.End, cursor.KeepAnchor)
+ cursor.movePosition(QTextCursor.MoveOperation.EndOfLine)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor
+ )
txt = cursor.selectedText()
if txt:
self.setTextCursor(cursor)
@@ -364,7 +375,7 @@ def executeString(self, commandText, filename='', extraPrint=True
if self.clearExecutionTime is not None:
self.clearExecutionTime()
cursor = self.textCursor()
- cursor.select(QTextCursor.BlockUnderCursor)
+ cursor.select(QTextCursor.SelectionType.BlockUnderCursor)
line = cursor.selectedText()
if line and line[0] not in string.printable:
line = line[1:]
@@ -472,7 +483,7 @@ def insertCompletion(self, completion):
"""inserts the completion text into the editor"""
if self.completer().widget() == self:
cursor = self.textCursor()
- cursor.select(QTextCursor.WordUnderCursor)
+ cursor.select(QTextCursor.SelectionType.WordUnderCursor)
cursor.insertText(completion)
self.setTextCursor(cursor)
@@ -535,21 +546,21 @@ def keyPressEvent(self, event):
# character, or remove it if backspace or delete has just been pressed.
key = event.text()
_, prefix = completer.currentObject(scope=__main__.__dict__)
- isBackspaceOrDel = event.key() in (Qt.Key_Backspace, Qt.Key_Delete)
+ isBackspaceOrDel = event.key() in (Qt.Key.Key_Backspace, Qt.Key.Key_Delete)
if key.isalnum() or key in ("-", "_"):
prefix += str(key)
elif isBackspaceOrDel and prefix:
prefix = prefix[:-1]
if completer and event.key() in (
- Qt.Key_Backspace,
- Qt.Key_Delete,
- Qt.Key_Escape,
+ Qt.Key.Key_Backspace,
+ Qt.Key.Key_Delete,
+ Qt.Key.Key_Escape,
):
completer.hideDocumentation()
# enter || return keys will execute the command
- if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
if completer.popup().isVisible():
completer.clear()
event.ignore()
@@ -557,11 +568,11 @@ def keyPressEvent(self, event):
self.executeCommand()
# home key will move the cursor to home
- elif event.key() == Qt.Key_Home:
+ elif event.key() == Qt.Key.Key_Home:
self.moveToHome()
# otherwise, ignore the event for completion events
- elif event.key() in (Qt.Key_Tab, Qt.Key_Backtab):
+ elif event.key() in (Qt.Key.Key_Tab, Qt.Key.Key_Backtab):
if not completer.popup().isVisible():
# The completer does not get updated if its not visible while typing.
# We are about to complete the text using it so ensure its updated.
@@ -571,19 +582,28 @@ def keyPressEvent(self, event):
)
# Insert the correct text and clear the completion model
index = completer.popup().currentIndex()
- self.insertCompletion(index.data(Qt.DisplayRole))
+ self.insertCompletion(index.data(Qt.ItemDataRole.DisplayRole))
completer.clear()
- elif event.key() == Qt.Key_Escape and completer.popup().isVisible():
+ elif event.key() == Qt.Key.Key_Escape and completer.popup().isVisible():
completer.clear()
# other wise handle the keypress
else:
# define special key sequences
modifiers = QApplication.instance().keyboardModifiers()
- ctrlSpace = event.key() == Qt.Key_Space and modifiers == Qt.ControlModifier
- ctrlM = event.key() == Qt.Key_M and modifiers == Qt.ControlModifier
- ctrlI = event.key() == Qt.Key_I and modifiers == Qt.ControlModifier
+ ctrlSpace = (
+ event.key() == Qt.Key.Key_Space
+ and modifiers == Qt.KeyboardModifier.ControlModifier
+ )
+ ctrlM = (
+ event.key() == Qt.Key.Key_M
+ and modifiers == Qt.KeyboardModifier.ControlModifier
+ )
+ ctrlI = (
+ event.key() == Qt.Key.Key_I
+ and modifiers == Qt.KeyboardModifier.ControlModifier
+ )
# Process all events we do not want to override
if not (ctrlSpace or ctrlM or ctrlI):
@@ -602,20 +622,20 @@ def keyPressEvent(self, event):
# check for particular events for the completion
if completer:
# look for documentation popups
- if event.key() == Qt.Key_ParenLeft:
+ if event.key() == Qt.Key.Key_ParenLeft:
rect = self.cursorRect()
point = self.mapToGlobal(QPoint(rect.x(), rect.y()))
completer.showDocumentation(pos=point, scope=__main__.__dict__)
# hide documentation popups
- elif event.key() == Qt.Key_ParenRight:
+ elif event.key() == Qt.Key.Key_ParenRight:
completer.hideDocumentation()
# determine if we need to show the popup or if it already is visible, we
# need to update it
elif (
- event.key() == Qt.Key_Period
- or event.key() == Qt.Key_Escape
+ event.key() == Qt.Key.Key_Period
+ or event.key() == Qt.Key.Key_Escape
or completer.popup().isVisible()
or ctrlSpace
or ctrlI
@@ -646,7 +666,9 @@ def keyPressEvent(self, event):
completer.setCurrentRow(index.row())
# Make sure that current selection is visible, ie scroll to it
- completer.popup().scrollTo(index, QAbstractItemView.EnsureVisible)
+ completer.popup().scrollTo(
+ index, QAbstractItemView.ScrollHint.EnsureVisible
+ )
# show the completer for the rect
rect = self.cursorRect()
@@ -663,7 +685,7 @@ def keyPressEvent(self, event):
if completer.wasCompleting and not completer.popup().isVisible():
wasCompletingCounterMax = completer.wasCompletingCounterMax
if completer.wasCompletingCounter <= wasCompletingCounterMax:
- if event.key() not in (Qt.Key_Backspace, Qt.Key_Left):
+ if event.key() not in (Qt.Key.Key_Backspace, Qt.Key.Key_Left):
completer.wasCompletingCounter += 1
else:
completer.wasCompletingCounter = 0
@@ -671,20 +693,26 @@ def keyPressEvent(self, event):
def moveToHome(self):
"""moves the cursor to the home location"""
- mode = QTextCursor.MoveAnchor
+ mode = QTextCursor.MoveMode.MoveAnchor
# select the home
- if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
- mode = QTextCursor.KeepAnchor
+ if (
+ QApplication.instance().keyboardModifiers()
+ == Qt.KeyboardModifier.ShiftModifier
+ ):
+ mode = QTextCursor.MoveMode.KeepAnchor
# grab the cursor
cursor = self.textCursor()
- if QApplication.instance().keyboardModifiers() == Qt.ControlModifier:
+ if (
+ QApplication.instance().keyboardModifiers()
+ == Qt.KeyboardModifier.ControlModifier
+ ):
# move to the top of the document if control is pressed
- cursor.movePosition(QTextCursor.Start)
+ cursor.movePosition(QTextCursor.MoveOperation.Start)
else:
# Otherwise just move it to the start of the line
- cursor.movePosition(QTextCursor.StartOfBlock, mode)
+ cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock, mode)
# move the cursor to the end of the prompt.
- cursor.movePosition(QTextCursor.Right, mode, len(self.prompt()))
+ cursor.movePosition(QTextCursor.MoveOperation.Right, mode, len(self.prompt()))
self.setTextCursor(cursor)
def outputPrompt(self):
@@ -721,7 +749,7 @@ def startPrompt(self, prompt):
prompt(str): The prompt to start the line with. If this prompt
is already the only text on the last line this function does nothing.
"""
- self.moveCursor(QTextCursor.End)
+ self.moveCursor(QTextCursor.MoveOperation.End)
# if this is not already a new line
if self.textCursor().block().text() != prompt:
@@ -744,9 +772,11 @@ def startOutputLine(self):
self.startPrompt(self._outputPrompt)
def removeCurrentLine(self):
- self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
- self.moveCursor(QTextCursor.StartOfLine, QTextCursor.MoveAnchor)
- self.moveCursor(QTextCursor.End, QTextCursor.KeepAnchor)
+ self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.MoveAnchor)
+ self.moveCursor(
+ QTextCursor.MoveOperation.StartOfLine, QTextCursor.MoveMode.MoveAnchor
+ )
+ self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor)
self.textCursor().removeSelectedText()
self.textCursor().deletePreviousChar()
self.insertPlainText("\n")
@@ -787,7 +817,7 @@ def write(self, msg, error=False):
hasattr(window, 'uiErrorHyperlinksACT')
and window.uiErrorHyperlinksACT.isChecked()
)
- self.moveCursor(QTextCursor.End)
+ self.moveCursor(QTextCursor.MoveOperation.End)
charFormat = QTextCharFormat()
if not error:
@@ -806,7 +836,7 @@ def write(self, msg, error=False):
info = None
if doHyperlink and msg == '\n':
- cursor.select(QTextCursor.BlockUnderCursor)
+ cursor.select(QTextCursor.SelectionType.BlockUnderCursor)
line = cursor.selectedText()
# Remove possible leading unicode paragraph separator, which really
diff --git a/preditor/gui/dialog.py b/preditor/gui/dialog.py
index 293382fc..6dfed9a8 100644
--- a/preditor/gui/dialog.py
+++ b/preditor/gui/dialog.py
@@ -24,11 +24,14 @@ def instance(cls, parent=None):
if not cls._instance:
cls._instance = cls(parent=parent)
# protect the memory
- cls._instance.setAttribute(Qt.WA_DeleteOnClose, False)
+ cls._instance.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
return cls._instance
def __init__(
- self, parent=None, flags=Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
+ self,
+ parent=None,
+ flags=Qt.WindowType.WindowMinMaxButtonsHint
+ | Qt.WindowType.WindowCloseButtonHint,
):
# if there is no root, create
if not parent:
@@ -68,7 +71,7 @@ def __init__(
# dead dialogs
# set the delete attribute to clean up the window once it is closed
- self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
# set this property to true to properly handle tracking events to control
# keyboard overrides
@@ -109,7 +112,7 @@ def _shouldDisableAccelerators(self, old, now):
def closeEvent(self, event):
# ensure this object gets deleted
wwidget = None
- if self.testAttribute(Qt.WA_DeleteOnClose):
+ if self.testAttribute(Qt.WidgetAttribute.WA_DeleteOnClose):
# collect the win widget to uncache it
if self.parent() and self.parent().inherits('QWinWidget'):
wwidget = self.parent()
@@ -128,10 +131,10 @@ def exec_(self):
# This function properly transfers ownership of the dialog instance back to
# Python anyway
- self.setAttribute(Qt.WA_DeleteOnClose, False)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
# execute the dialog
- return QDialog.exec_(self)
+ return super().exec()
def setGeometry(self, *args):
"""
@@ -158,7 +161,7 @@ def _shutdown(cls, this):
# allow the global instance to be cleared
if this == cls._instance:
cls._instance = None
- this.setAttribute(Qt.WA_DeleteOnClose, True)
+ this.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
try:
this.close()
except RuntimeError:
diff --git a/preditor/gui/drag_tab_bar.py b/preditor/gui/drag_tab_bar.py
index 530654e2..e0945d23 100644
--- a/preditor/gui/drag_tab_bar.py
+++ b/preditor/gui/drag_tab_bar.py
@@ -52,12 +52,12 @@ def mouseMoveEvent(self, event): # noqa: N802
drag.setMimeData(self._mime_data)
drag.setPixmap(self._mime_data.imageData())
drag.setHotSpot(event_pos - pos_in_tab)
- cursor = QCursor(Qt.OpenHandCursor)
- drag.setDragCursor(cursor.pixmap(), Qt.MoveAction)
- action = drag.exec_(Qt.MoveAction)
+ cursor = QCursor(Qt.CursorShape.OpenHandCursor)
+ drag.setDragCursor(cursor.pixmap(), Qt.DropAction.MoveAction)
+ action = drag.exec(Qt.DropAction.MoveAction)
# If the user didn't successfully add this to a new tab widget, restore
# the tab to the original location.
- if action == Qt.IgnoreAction:
+ if action == Qt.DropAction.IgnoreAction:
original_tab_index = self._mime_data.property('original_tab_index')
self.parentWidget().insertTab(
original_tab_index, widget, self._mime_data.text()
@@ -66,7 +66,7 @@ def mouseMoveEvent(self, event): # noqa: N802
self._mime_data = None
def mousePressEvent(self, event): # noqa: N802
- if event.button() == Qt.LeftButton and not self._mime_data:
+ if event.button() == Qt.MouseButton.LeftButton and not self._mime_data:
tab_index = self.tabAt(event.pos())
# While we don't remove the tab on mouse press, capture its tab image
@@ -123,7 +123,7 @@ def dropEvent(self, event): # noqa: N802
if event.source().parentWidget() == self:
return
- event.setDropAction(Qt.MoveAction)
+ event.setDropAction(Qt.DropAction.MoveAction)
event.accept()
counter = self.count()
@@ -184,7 +184,7 @@ def install_tab_widget(cls, tab_widget, mime_type='DragTabBar', menu=True):
tab_widget.setDocumentMode(True)
if menu:
- bar.setContextMenuPolicy(Qt.CustomContextMenu)
+ bar.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
bar.customContextMenuRequested.connect(bar.tab_menu)
return bar
diff --git a/preditor/gui/errordialog.py b/preditor/gui/errordialog.py
index d4de030b..0322066b 100644
--- a/preditor/gui/errordialog.py
+++ b/preditor/gui/errordialog.py
@@ -21,7 +21,7 @@ def __init__(self, parent):
self.parent_ = parent
self.setWindowTitle('Error Occurred')
- self.uiErrorLBL.setTextFormat(Qt.RichText)
+ self.uiErrorLBL.setTextFormat(Qt.TextFormat.RichText)
self.uiIconLBL.setPixmap(
QPixmap(
os.path.join(
@@ -30,7 +30,7 @@ def __init__(self, parent):
'img',
'warning-big.png',
)
- ).scaledToHeight(64, Qt.SmoothTransformation)
+ ).scaledToHeight(64, Qt.TransformationMode.SmoothTransformation)
)
self.uiLoggerBTN.clicked.connect(self.show_logger)
diff --git a/preditor/gui/find_files.py b/preditor/gui/find_files.py
index 4ea2dc29..f677f69b 100644
--- a/preditor/gui/find_files.py
+++ b/preditor/gui/find_files.py
@@ -30,22 +30,24 @@ def __init__(self, parent=None, managers=None, console=None):
# Create shortcuts
self.uiCloseSCT = QShortcut(
- QKeySequence(Qt.Key_Escape), self, context=Qt.WidgetWithChildrenShortcut
+ QKeySequence(Qt.Key.Key_Escape),
+ self,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.uiCloseSCT.activated.connect(self.hide)
self.uiCaseSensitiveSCT = QShortcut(
- QKeySequence(Qt.AltModifier | Qt.Key_C),
+ QKeySequence(Qt.KeyboardModifier.AltModifier | Qt.Key.Key_C),
self,
- context=Qt.WidgetWithChildrenShortcut,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.uiCaseSensitiveSCT.activated.connect(self.uiCaseSensitiveBTN.toggle)
self.uiRegexSCT = QShortcut(
- QKeySequence(Qt.AltModifier | Qt.Key_R),
+ QKeySequence(Qt.KeyboardModifier.AltModifier | Qt.Key.Key_R),
self,
- context=Qt.WidgetWithChildrenShortcut,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.uiRegexSCT.activated.connect(self.uiRegexBTN.toggle)
diff --git a/preditor/gui/fuzzy_search/fuzzy_search.py b/preditor/gui/fuzzy_search/fuzzy_search.py
index f341ac3b..ed7acdcc 100644
--- a/preditor/gui/fuzzy_search/fuzzy_search.py
+++ b/preditor/gui/fuzzy_search/fuzzy_search.py
@@ -22,14 +22,18 @@ def __init__(self, model, parent=None, **kwargs):
self.y_offset = 100
self.setMinimumSize(400, 200)
self.uiCloseSCT = QShortcut(
- Qt.Key_Escape, self, context=Qt.WidgetWithChildrenShortcut
+ Qt.Key.Key_Escape,
+ self,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.uiCloseSCT.activated.connect(self._canceled)
- self.uiUpSCT = QShortcut(Qt.Key_Up, self, context=Qt.WidgetWithChildrenShortcut)
+ self.uiUpSCT = QShortcut(
+ Qt.Key.Key_Up, self, context=Qt.ShortcutContext.WidgetWithChildrenShortcut
+ )
self.uiUpSCT.activated.connect(partial(self.increment_selection, -1))
self.uiDownSCT = QShortcut(
- Qt.Key_Down, self, context=Qt.WidgetWithChildrenShortcut
+ Qt.Key.Key_Down, self, context=Qt.ShortcutContext.WidgetWithChildrenShortcut
)
self.uiDownSCT.activated.connect(partial(self.increment_selection, 1))
@@ -90,4 +94,4 @@ def reposition(self):
def popup(self):
self.show()
self.reposition()
- self.uiLineEDIT.setFocus(Qt.PopupFocusReason)
+ self.uiLineEDIT.setFocus(Qt.FocusReason.PopupFocusReason)
diff --git a/preditor/gui/group_tab_widget/__init__.py b/preditor/gui/group_tab_widget/__init__.py
index 5d06d6a5..23deb79f 100644
--- a/preditor/gui/group_tab_widget/__init__.py
+++ b/preditor/gui/group_tab_widget/__init__.py
@@ -61,13 +61,13 @@ def __init__(self, editor_kwargs=None, core_name=None, *args, **kwargs):
corner.uiMenuBTN = QToolButton(corner)
corner.uiMenuBTN.setIcon(QIcon(resourcePath('img/chevron-down.png')))
corner.uiMenuBTN.setObjectName('group_tab_widget_menu_btn')
- corner.uiMenuBTN.setPopupMode(QToolButton.InstantPopup)
+ corner.uiMenuBTN.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
corner.uiCornerMENU = GroupTabMenu(self, parent=corner.uiMenuBTN)
corner.uiMenuBTN.setMenu(corner.uiCornerMENU)
lyt.addWidget(corner.uiMenuBTN)
self.uiCornerBTN = corner
- self.setCornerWidget(self.uiCornerBTN, Qt.TopRightCorner)
+ self.setCornerWidget(self.uiCornerBTN, Qt.Corner.TopRightCorner)
def add_new_tab(self, group, title="Workbox", group_fmt=None):
"""Adds a new tab to the requested group, creating the group if the group
@@ -148,9 +148,9 @@ def close_tab(self, index):
'Are you sure you want to close all tabs under the "{}" tab?'.format(
self.tabText(self.currentIndex())
),
- QMessageBox.Yes | QMessageBox.Cancel,
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
)
- if ret == QMessageBox.Yes:
+ if ret == QMessageBox.StandardButton.Yes:
# Clean up all temp files created by this group's editors if they
# are not using actual saved files.
tab_widget = self.widget(self.currentIndex())
diff --git a/preditor/gui/group_tab_widget/grouped_tab_models.py b/preditor/gui/group_tab_widget/grouped_tab_models.py
index e7eac9a5..dda635d3 100644
--- a/preditor/gui/group_tab_widget/grouped_tab_models.py
+++ b/preditor/gui/group_tab_widget/grouped_tab_models.py
@@ -7,7 +7,7 @@
class GroupTabItemModel(QStandardItemModel):
- GroupIndexRole = Qt.UserRole + 1
+ GroupIndexRole = Qt.ItemDataRole.UserRole + 1
TabIndexRole = GroupIndexRole + 1
def __init__(self, manager, *args, **kwargs):
@@ -24,7 +24,7 @@ def workbox_indexes_from_model_index(self, index):
def pathFromIndex(self, index):
parts = [""]
while index.isValid():
- parts.append(self.data(index, Qt.DisplayRole))
+ parts.append(self.data(index, Qt.ItemDataRole.DisplayRole))
index = index.parent()
if len(parts) == 1:
return ""
@@ -56,7 +56,7 @@ def process(self):
class GroupTabListItemModel(GroupTabItemModel):
def flags(self, index):
- return Qt.ItemIsEnabled | Qt.ItemIsSelectable
+ return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
def process(self):
root = self.invisibleRootItem()
@@ -101,7 +101,7 @@ def filterAcceptsRow(self, sourceRow, sourceParent):
def pathFromIndex(self, index):
parts = [""]
while index.isValid():
- parts.append(self.data(index, Qt.DisplayRole))
+ parts.append(self.data(index, Qt.ItemDataRole.DisplayRole))
index = index.parent()
if len(parts) == 1:
return ""
diff --git a/preditor/gui/group_tab_widget/grouped_tab_widget.py b/preditor/gui/group_tab_widget/grouped_tab_widget.py
index dd0d9edf..c9902692 100644
--- a/preditor/gui/group_tab_widget/grouped_tab_widget.py
+++ b/preditor/gui/group_tab_widget/grouped_tab_widget.py
@@ -25,7 +25,7 @@ def __init__(self, editor_kwargs, editor_cls=None, core_name=None, *args, **kwar
self.uiCornerBTN.setText('+')
self.uiCornerBTN.setIcon(QIcon(resourcePath('img/file-plus.png')))
self.uiCornerBTN.released.connect(lambda: self.add_new_editor())
- self.setCornerWidget(self.uiCornerBTN, Qt.TopRightCorner)
+ self.setCornerWidget(self.uiCornerBTN, Qt.Corner.TopRightCorner)
def add_new_editor(self, title="Workbox"):
editor, title = self.default_tab(title)
@@ -41,16 +41,18 @@ def addTab(self, *args, **kwargs): # noqa: N802
def close_tab(self, index):
if self.count() == 1:
msg = "You have to leave at least one tab open."
- QMessageBox.critical(self, 'Tab can not be closed.', msg, QMessageBox.Ok)
+ QMessageBox.critical(
+ self, 'Tab can not be closed.', msg, QMessageBox.StandardButton.Ok
+ )
return
ret = QMessageBox.question(
self,
'Donate to the cause?',
"Would you like to donate this tabs contents to the /dev/null fund "
"for wayward code?",
- QMessageBox.Yes | QMessageBox.Cancel,
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
)
- if ret == QMessageBox.Yes:
+ if ret == QMessageBox.StandardButton.Yes:
# If the tab was saved to a temp file, remove it from disk
editor = self.widget(index)
editor.__remove_tempfile__()
diff --git a/preditor/gui/level_buttons.py b/preditor/gui/level_buttons.py
index 7af59948..25fa1dcb 100644
--- a/preditor/gui/level_buttons.py
+++ b/preditor/gui/level_buttons.py
@@ -134,7 +134,7 @@ def __init__(self, parent=None):
parent (QWidget, optional): The parent widget for this button.
"""
super(LoggingLevelButton, self).__init__(parent=parent)
- self.setPopupMode(QToolButton.InstantPopup)
+ self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
# create root logger menu
root = logging.getLogger("")
diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py
index d6364adc..575e7956 100644
--- a/preditor/gui/loggerwindow.py
+++ b/preditor/gui/loggerwindow.py
@@ -12,15 +12,17 @@
from functools import partial
import __main__
+import Qt as Qt_py
from Qt import QtCompat, QtCore, QtWidgets
from Qt.QtCore import QByteArray, Qt, QTimer, Signal, Slot
-from Qt.QtGui import QCursor, QFont, QIcon, QTextCursor
+from Qt.QtGui import QCursor, QFont, QIcon, QKeySequence, QTextCursor
from Qt.QtWidgets import (
QApplication,
QFontDialog,
QInputDialog,
QMessageBox,
QTextBrowser,
+ QTextEdit,
QToolTip,
QVBoxLayout,
)
@@ -198,7 +200,7 @@ def __init__(self, parent, name=None, run_workbox=False, standalone=False):
self.uiCompleterModeMENU.addSeparator()
action = self.uiCompleterModeMENU.addAction('Cycle mode')
action.setObjectName('uiCycleModeACT')
- action.setShortcut(Qt.CTRL | Qt.Key_M)
+ action.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_M))
action.triggered.connect(self.cycleCompleterMode)
self.uiCompleterModeMENU.hovered.connect(self.handleMenuHovered)
@@ -490,7 +492,7 @@ def setup_run_workbox(self):
def openSetPreferredTextEditorDialog(self):
dlg = SetTextEditorPathDialog(parent=self)
- dlg.exec_()
+ dlg.exec()
def focusToConsole(self):
"""Move focus to the console"""
@@ -529,7 +531,7 @@ def copyToWorkbox(self):
cursor = console.textCursor()
if not cursor.hasSelection():
- cursor.select(QTextCursor.LineUnderCursor)
+ cursor.select(QTextCursor.SelectionType.LineUnderCursor)
text = cursor.selectedText()
prompt = console.prompt()
if text.startswith(prompt):
@@ -565,7 +567,7 @@ def getPrevCommand(self):
def wheelEvent(self, event):
"""adjust font size on ctrl+scrollWheel"""
- if event.modifiers() == Qt.ControlModifier:
+ if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
# WheelEvents can be emitted in a cluster, but we only want one at a time
# (ie to change font size by 1, rather than 2 or 3). Let's bail if previous
# font-resize wheel event was within a certain threshhold.
@@ -605,7 +607,10 @@ def handleMenuHovered(self, action):
else:
text = action.toolTip()
- menu = action.parentWidget()
+ if Qt_py.IsPyQt4:
+ menu = action.parentWidget()
+ else:
+ menu = action.parent()
QToolTip.showText(QCursor.pos(), text, menu)
def selectFont(self, monospace=False, proportional=False):
@@ -620,13 +625,16 @@ def selectFont(self, monospace=False, proportional=False):
curFontFamily = origFont.family()
if monospace and proportional:
- options = QFontDialog.MonospacedFonts | QFontDialog.ProportionalFonts
+ options = (
+ QFontDialog.FontDialogOption.MonospacedFonts
+ | QFontDialog.FontDialogOption.ProportionalFonts
+ )
kind = "monospace or proportional "
elif monospace:
- options = QFontDialog.MonospacedFonts
+ options = QFontDialog.FontDialogOption.MonospacedFonts
kind = "monospace "
elif proportional:
- options = QFontDialog.ProportionalFonts
+ options = QFontDialog.FontDialogOption.ProportionalFonts
kind = "proportional "
# Present a QFontDialog for user to choose a font
@@ -686,9 +694,9 @@ def _genPrefName(cls, baseName, index):
def adjustWorkboxOrientation(self, state):
if state:
- self.uiSplitterSPLIT.setOrientation(Qt.Horizontal)
+ self.uiSplitterSPLIT.setOrientation(Qt.Orientation.Horizontal)
else:
- self.uiSplitterSPLIT.setOrientation(Qt.Vertical)
+ self.uiSplitterSPLIT.setOrientation(Qt.Orientation.Vertical)
def backupPreferences(self):
"""Saves a copy of the current preferences to a zip archive."""
@@ -757,7 +765,11 @@ def execSelected(self, truncate=True):
def keyPressEvent(self, event):
# Fix 'Maya : Qt tools lose focus' https://redmine.blur.com/issues/34430
- if event.modifiers() & (Qt.AltModifier | Qt.ControlModifier | Qt.ShiftModifier):
+ if event.modifiers() & (
+ Qt.KeyboardModifier.AltModifier
+ | Qt.KeyboardModifier.ControlModifier
+ | Qt.KeyboardModifier.ShiftModifier
+ ):
pass
else:
super(LoggerWindow, self).keyPressEvent(event)
@@ -781,7 +793,7 @@ def recordPrefs(self, manual=False):
pref.update(
{
'loggergeom': [geo.x(), geo.y(), geo.width(), geo.height()],
- 'windowState': int(self.windowState()),
+ 'windowState': QtCompat.enumValue(self.windowState()),
'SplitterVertical': self.uiEditorVerticalACT.isChecked(),
'SplitterSize': self.uiSplitterSPLIT.sizes(),
'tabIndent': self.uiIndentationsTabsACT.isChecked(),
@@ -863,7 +875,7 @@ def maybeDisplayDialog(self, dialog):
if dialog.objectName() in self.dont_ask_again:
return
- dialog.exec_()
+ dialog.exec()
def restartLogger(self):
"""Closes this PrEditor instance and starts a new process with the same
@@ -911,7 +923,7 @@ def restorePrefs(self):
sizes = pref.get('SplitterSize')
if sizes:
self.uiSplitterSPLIT.setSizes(sizes)
- self.setWindowState(Qt.WindowStates(pref.get('windowState', 0)))
+ self.setWindowState(Qt.WindowState(pref.get('windowState', 0)))
self.uiIndentationsTabsACT.setChecked(pref.get('tabIndent', True))
self.uiCopyTabsToSpacesACT.setChecked(pref.get('copyIndentsAsSpaces', False))
@@ -983,7 +995,7 @@ def restorePrefs(self):
_font = pref.get('consoleFont', None)
if _font:
font = QFont()
- if font.fromString(_font):
+ if QtCompat.QFont.fromString(font, _font):
self.console().setConsoleFont(font)
self.dont_ask_again = pref.get('dont_ask_again', [])
@@ -1166,9 +1178,9 @@ def setFlashWindowInterval(self):
def setWordWrap(self, state):
if state:
- self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.WidgetWidth)
+ self.uiConsoleTXT.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
else:
- self.uiConsoleTXT.setLineWrapMode(self.uiConsoleTXT.NoWrap)
+ self.uiConsoleTXT.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
def show_about(self):
"""Shows `preditor.about_preditor()`'s output in a message box."""
@@ -1248,7 +1260,7 @@ def shutdown(self):
# if this is the global instance, then allow it to be deleted on close
if self == LoggerWindow._instance:
- self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
LoggerWindow._instance = None
# clear out the system
@@ -1339,7 +1351,7 @@ def instance(
)
# protect the memory
- inst.setAttribute(Qt.WA_DeleteOnClose, False)
+ inst.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
# cache the instance
LoggerWindow._instance = inst
diff --git a/preditor/gui/set_text_editor_path_dialog.py b/preditor/gui/set_text_editor_path_dialog.py
index 1d14e868..59df355d 100644
--- a/preditor/gui/set_text_editor_path_dialog.py
+++ b/preditor/gui/set_text_editor_path_dialog.py
@@ -56,4 +56,6 @@ def accept(self):
else:
msg = "That path doesn't exists or isn't an executable file."
label = 'Incorrect Path'
- QMessageBox.warning(self.window(), label, msg, QMessageBox.Ok)
+ QMessageBox.warning(
+ self.window(), label, msg, QMessageBox.StandardButton.Ok
+ )
diff --git a/preditor/gui/window.py b/preditor/gui/window.py
index ac9fee0c..116c2e43 100644
--- a/preditor/gui/window.py
+++ b/preditor/gui/window.py
@@ -25,7 +25,7 @@ def instance(cls, parent=None):
if not cls._instance:
cls._instance = cls(parent=parent)
# protect the memory
- cls._instance.setAttribute(Qt.WA_DeleteOnClose, False)
+ cls._instance.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
return cls._instance
def __init__(self, parent=None, flags=0):
@@ -67,7 +67,7 @@ def __init__(self, parent=None, flags=0):
# dead dialogs
# set the delete attribute to clean up the window once it is closed
- self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
# If this value is set to False calling setGeometry on this window will not
# adjust the geometry to ensure the window is on a valid screen.
self.checkScreenGeo = True
@@ -103,7 +103,7 @@ def _shouldDisableAccelerators(self, old, now):
def closeEvent(self, event):
# ensure this object gets deleted
wwidget = None
- if self.testAttribute(Qt.WA_DeleteOnClose):
+ if self.testAttribute(Qt.WidgetAttribute.WA_DeleteOnClose):
# collect the win widget to uncache it
if self.parent() and self.parent().inherits('QWinWidget'):
wwidget = self.parent()
@@ -141,7 +141,7 @@ def _shutdown(cls, this):
# allow the global instance to be cleared
if this == cls._instance:
cls._instance = None
- this.setAttribute(Qt.WA_DeleteOnClose, True)
+ this.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
try:
this.close()
except RuntimeError:
diff --git a/preditor/gui/workbox_mixin.py b/preditor/gui/workbox_mixin.py
index efa2b63b..a447e67c 100644
--- a/preditor/gui/workbox_mixin.py
+++ b/preditor/gui/workbox_mixin.py
@@ -1,9 +1,11 @@
from __future__ import absolute_import, print_function
+import io
import os
import tempfile
import textwrap
+import chardet
from Qt.QtCore import Qt
from Qt.QtWidgets import QStackedWidget
@@ -317,14 +319,37 @@ def __remove_tempfile__(self):
os.remove(tempfile)
@classmethod
- def __open_file__(cls, filename):
- with open(filename) as fle:
- return fle.read()
- return ""
+ def __open_file__(cls, filename, strict=True):
+ """Open a file and try to detect the text encoding it was saved as.
+
+ Returns:
+ encoding(str): The detected encoding, Defaults to "utf-8" if unable
+ to detect encoding.
+ text(str): The contents of the file decoded to a str.
+ """
+ with open(filename, "rb") as f:
+ text_bytes = f.read()
+
+ # Open file, detect source encoding and convert to utf-8
+ encoding = chardet.detect(text_bytes)['encoding'] or 'utf-8'
+ try:
+ text = text_bytes.decode(encoding)
+ except UnicodeDecodeError as e:
+ if strict:
+ raise UnicodeDecodeError( # noqa: B904
+ e.encoding,
+ e.object,
+ e.start,
+ e.end,
+ f"{e.reason}, Filename: {filename}",
+ )
+ encoding = 'utf-8'
+ text = text_bytes.decode(encoding, errors="ignore")
+ return encoding, text
@classmethod
- def __write_file__(cls, filename, txt):
- with open(filename, 'w') as fle:
+ def __write_file__(cls, filename, txt, encoding=None):
+ with io.open(filename, 'w', newline='\n', encoding=encoding) as fle:
fle.write(txt)
def __show__(self):
@@ -335,7 +360,7 @@ def __show__(self):
if self._filename_pref:
self.__load__(self._filename_pref)
elif self._tempfile:
- txt = self.__open_file__(self.__tempfile__())
+ _, txt = self.__open_file__(self.__tempfile__(), strict=False)
self.__set_text__(txt)
def process_shortcut(self, event, run=True):
@@ -365,10 +390,14 @@ def process_shortcut(self, event, run=True):
modifiers = event.modifiers()
# Determine which relevant combos are pressed
- ret = key == Qt.Key_Return
- enter = key == Qt.Key_Enter
- shift = modifiers == Qt.ShiftModifier
- ctrlShift = modifiers == Qt.ControlModifier | Qt.ShiftModifier
+ ret = key == Qt.Key.Key_Return
+ enter = key == Qt.Key.Key_Enter
+ shift = modifiers == Qt.KeyboardModifier.ShiftModifier
+ ctrlShift = (
+ modifiers
+ == Qt.KeyboardModifier.ControlModifier
+ | Qt.KeyboardModifier.ShiftModifier
+ )
# Determine which actions to take
evalTrunc = enter or (ret and shift)
diff --git a/preditor/gui/workbox_text_edit.py b/preditor/gui/workbox_text_edit.py
index 4bf0a392..476aaf32 100644
--- a/preditor/gui/workbox_text_edit.py
+++ b/preditor/gui/workbox_text_edit.py
@@ -27,6 +27,7 @@ def __init__(
):
super(WorkboxTextEdit, self).__init__(parent=parent, core_name=core_name)
self._filename = None
+ self._encoding = None
self.__set_console__(console)
highlight = CodeHighlighter(self)
highlight.setLanguage('Python')
@@ -64,7 +65,7 @@ def __font__(self):
def __set_font__(self, font):
metrics = QFontMetrics(font)
- self.setTabStopDistance(metrics.width(" ") * 4)
+ self.setTabStopDistance(metrics.horizontalAdvance(" ") * 4)
super(WorkboxTextEdit, self).setFont(font)
def __goto_line__(self, line):
@@ -79,7 +80,8 @@ def __set_indentations_use_tabs__(self, state):
def __load__(self, filename):
self._filename = filename
- txt = self.__open_file__(self._filename)
+ enc, txt = self.__open_file__(self._filename)
+ self._encoding = enc
self.__set_text__(txt)
def __margins_font__(self):
@@ -114,7 +116,7 @@ def __selected_text__(self, start_of_line=False, selectText=False):
selectText = self.window().uiSelectTextACT.isChecked() or selectText
if selectText:
- cursor.select(QTextCursor.LineUnderCursor)
+ cursor.select(QTextCursor.SelectionType.LineUnderCursor)
self.setTextCursor(cursor)
return text, line
diff --git a/preditor/gui/workboxwidget.py b/preditor/gui/workboxwidget.py
index 048898ce..c26a57a8 100644
--- a/preditor/gui/workboxwidget.py
+++ b/preditor/gui/workboxwidget.py
@@ -1,6 +1,5 @@
from __future__ import absolute_import, print_function
-import io
import re
import time
@@ -10,6 +9,7 @@
from .. import core, resourcePath
from ..gui.workbox_mixin import WorkboxMixin
+from ..scintilla import QsciScintilla
from ..scintilla.documenteditor import DocumentEditor, SearchOptions
from ..scintilla.finddialog import FindDialog
@@ -36,15 +36,19 @@ def __init__(
self.initShortcuts()
self.setLanguage('Python')
# Default to unix newlines
- self.setEolMode(self.EolUnix)
+ self.setEolMode(QsciScintilla.EolMode.EolUnix)
if hasattr(self.window(), "setWorkboxFontBasedOnConsole"):
self.window().setWorkboxFontBasedOnConsole()
def __auto_complete_enabled__(self):
- return self.autoCompletionSource() == self.AcsAll
+ return self.autoCompletionSource() == QsciScintilla.AutoCompletionSource.AcsAll
def __set_auto_complete_enabled__(self, state):
- state = self.AcsAll if state else self.AcsNone
+ state = (
+ QsciScintilla.AutoCompletionSource.AcsAll
+ if state
+ else QsciScintilla.AutoCompletionSource.AcsNone
+ )
self.setAutoCompletionSource(state)
def __clear__(self):
@@ -120,7 +124,7 @@ def __marker_add__(self, line):
try:
marker = self._marker
except AttributeError:
- self._marker = self.markerDefine(self.Circle)
+ self._marker = self.markerDefine(QsciScintilla.MarkerSymbol.Circle)
marker = self._marker
self.markerAdd(line, marker)
@@ -197,10 +201,10 @@ def __set_text__(self, txt):
self.setText(txt)
@classmethod
- def __write_file__(cls, filename, txt):
- with io.open(filename, 'w', newline='\n') as fle:
- # Save unix newlines for simplicity
- fle.write(cls.__unix_end_lines__(txt))
+ def __write_file__(cls, filename, txt, encoding=None):
+ # Save unix newlines for simplicity
+ txt = cls.__unix_end_lines__(txt)
+ super(WorkboxWidget, cls).__write_file__(filename, txt, encoding=encoding)
def keyPressEvent(self, event):
"""Check for certain keyboard shortcuts, and handle them as needed,
@@ -216,13 +220,13 @@ def keyPressEvent(self, event):
when Return is pressed), so this combination is not detectable.
"""
if self._software == 'softimage':
- DocumentEditor.keyPressEvent(self, event)
+ super(WorkboxWidget, self).keyPressEvent(event)
else:
if self.process_shortcut(event):
return
else:
# Send regular keystroke
- DocumentEditor.keyPressEvent(self, event)
+ super(WorkboxWidget, self).keyPressEvent(event)
def initShortcuts(self):
"""Use this to set up shortcuts when the DocumentEditor"""
@@ -248,7 +252,7 @@ def initShortcuts(self):
# create the search dialog and connect actions
self._searchDialog = FindDialog(self)
- self._searchDialog.setAttribute(Qt.WA_DeleteOnClose, False)
+ self._searchDialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
self.uiFindACT.triggered.connect(
lambda: self._searchDialog.search(self.searchText())
)
diff --git a/preditor/scintilla/__init__.py b/preditor/scintilla/__init__.py
index 3fb4d133..9a57bf80 100644
--- a/preditor/scintilla/__init__.py
+++ b/preditor/scintilla/__init__.py
@@ -1,5 +1,23 @@
from __future__ import absolute_import
+__all__ = ["delayables", "FindState", "Qsci", "QsciScintilla"]
+
+import Qt
+
+if Qt.IsPyQt6:
+ from PyQt6 import Qsci
+ from PyQt6.Qsci import QsciScintilla
+elif Qt.IsPyQt5:
+ from PyQt5 import Qsci
+ from PyQt5.Qsci import QsciScintilla
+elif Qt.IsPyQt4:
+ from PyQt4 import Qsci
+ from PyQt4.Qsci import QsciScintilla
+else:
+ raise ImportError(
+ "QScintilla library is not supported by {}".format(Qt.__binding__)
+ )
+
class FindState(object):
"""
@@ -19,4 +37,4 @@ def __init__(self):
self.end_pos = None
-from . import delayables # noqa: F401, E402
+from . import delayables # noqa: E402
diff --git a/preditor/scintilla/delayables/smart_highlight.py b/preditor/scintilla/delayables/smart_highlight.py
index f73c8500..1ac17216 100644
--- a/preditor/scintilla/delayables/smart_highlight.py
+++ b/preditor/scintilla/delayables/smart_highlight.py
@@ -1,17 +1,17 @@
from __future__ import absolute_import, print_function
-from PyQt5.Qsci import QsciScintilla
+import Qt
from Qt.QtCore import QSignalMapper
from Qt.QtWidgets import QWidget
from ...delayable_engine.delayables import SearchDelayable
-from .. import FindState
+from .. import FindState, QsciScintilla
class SmartHighlight(SearchDelayable):
key = 'smart_highlight'
indicator_number = 30
- indicator_style = QsciScintilla.StraightBoxIndicator
+ indicator_style = QsciScintilla.IndicatorStyle.StraightBoxIndicator
border_alpha = 255
def __init__(self, engine):
@@ -36,7 +36,10 @@ def add_document(self, document):
)
self.signal_mapper.setMapping(document, document)
- self.signal_mapper.mapped[QWidget].connect(self.update_highlighter)
+ if Qt.IsPyQt4:
+ self.signal_mapper.mapped[QWidget].connect(self.update_highlighter)
+ else:
+ self.signal_mapper.mappedObject.connect(self.update_highlighter)
document.selectionChanged.connect(self.signal_mapper.map)
def clear_markings(self, document):
diff --git a/preditor/scintilla/delayables/spell_check.py b/preditor/scintilla/delayables/spell_check.py
index 1838edbb..75c8349a 100644
--- a/preditor/scintilla/delayables/spell_check.py
+++ b/preditor/scintilla/delayables/spell_check.py
@@ -4,12 +4,11 @@
import re
import string
-from PyQt5.Qsci import QsciScintilla
from Qt.QtCore import Qt
from Qt.QtGui import QColor
from ...delayable_engine.delayables import RangeDelayable
-from .. import lang
+from .. import QsciScintilla, lang
logger = logging.getLogger(__name__)
@@ -49,12 +48,14 @@ def add_document(self, document):
# https://www.scintilla.org/ScintillaDox.html#SCI_INDICSETSTYLE
# https://qscintilla.com/#clickable_text/indicators
document.indicatorDefine(
- QsciScintilla.SquiggleLowIndicator, self.indicator_number
+ QsciScintilla.IndicatorStyle.SquiggleLowIndicator, self.indicator_number
)
document.SendScintilla(
QsciScintilla.SCI_SETINDICATORCURRENT, self.indicator_number
)
- document.setIndicatorForegroundColor(QColor(Qt.red), self.indicator_number)
+ document.setIndicatorForegroundColor(
+ QColor(Qt.GlobalColor.red), self.indicator_number
+ )
document.SCN_MODIFIED.connect(document.onTextModified)
diff --git a/preditor/scintilla/documenteditor.py b/preditor/scintilla/documenteditor.py
index f49e0dda..0f67b3c8 100644
--- a/preditor/scintilla/documenteditor.py
+++ b/preditor/scintilla/documenteditor.py
@@ -19,11 +19,9 @@
from contextlib import contextmanager
from functools import partial
-from PyQt5.Qsci import QsciScintilla
-from PyQt5.QtCore import QTextCodec
-from Qt import QtCompat
-from Qt.QtCore import Property, QFile, QPoint, Qt, Signal
-from Qt.QtGui import QColor, QFont, QFontMetrics, QIcon
+import Qt as Qt_py
+from Qt.QtCore import Property, QEvent, QPoint, Qt, Signal
+from Qt.QtGui import QColor, QFont, QFontMetrics, QIcon, QKeySequence
from Qt.QtWidgets import (
QAction,
QApplication,
@@ -37,7 +35,8 @@
from ..delayable_engine import DelayableEngine
from ..enum import Enum, EnumGroup
from ..gui import QtPropertyInit
-from . import lang
+from ..gui.workbox_mixin import WorkboxMixin
+from . import QsciScintilla, lang
logger = logging.getLogger(__name__)
@@ -90,7 +89,7 @@ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
self.additionalFilenames = []
self._language = ''
self._lastSearch = ''
- self._textCodec = None
+ self._encoding = 'utf-8'
self._fileMonitoringActive = False
self._marginsFont = self._defaultFont
self._lastSearchDirection = SearchDirection.First
@@ -99,21 +98,21 @@ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
self._enableFontResizing = True
# QSci doesnt provide accessors to these values, so store them internally
self._foldMarginBackgroundColor = QColor(224, 224, 224)
- self._foldMarginForegroundColor = QColor(Qt.white)
+ self._foldMarginForegroundColor = QColor(Qt.GlobalColor.white)
self._marginsBackgroundColor = QColor(224, 224, 224)
self._marginsForegroundColor = QColor()
self._matchedBraceBackgroundColor = QColor(224, 224, 224)
self._matchedBraceForegroundColor = QColor()
- self._unmatchedBraceBackgroundColor = QColor(Qt.white)
- self._unmatchedBraceForegroundColor = QColor(Qt.blue)
+ self._unmatchedBraceBackgroundColor = QColor(Qt.GlobalColor.white)
+ self._unmatchedBraceForegroundColor = QColor(Qt.GlobalColor.blue)
self._caretForegroundColor = QColor()
self._caretBackgroundColor = QColor(255, 255, 255, 255)
self._selectionBackgroundColor = QColor(192, 192, 192)
- self._selectionForegroundColor = QColor(Qt.black)
- self._indentationGuidesBackgroundColor = QColor(Qt.white)
- self._indentationGuidesForegroundColor = QColor(Qt.black)
- self._markerBackgroundColor = QColor(Qt.white)
- self._markerForegroundColor = QColor(Qt.black)
+ self._selectionForegroundColor = QColor(Qt.GlobalColor.black)
+ self._indentationGuidesBackgroundColor = QColor(Qt.GlobalColor.white)
+ self._indentationGuidesForegroundColor = QColor(Qt.GlobalColor.black)
+ self._markerBackgroundColor = QColor(Qt.GlobalColor.white)
+ self._markerForegroundColor = QColor(Qt.GlobalColor.black)
# Setup the DelayableEngine and add the document to it
self.delayable_info = OrderedDict()
@@ -134,13 +133,13 @@ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
self.initSettings(first_time=True)
# set one time properties
- self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
- self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setFolding(QsciScintilla.FoldStyle.BoxedTreeFoldStyle)
+ self.setBraceMatching(QsciScintilla.BraceMatch.SloppyBraceMatch)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.setAcceptDrops(False)
# Not supported by older builds of QsciScintilla
if hasattr(self, 'setTabDrawMode'):
- self.setTabDrawMode(QsciScintilla.TabStrikeOut)
+ self.setTabDrawMode(QsciScintilla.TabDrawMode.TabStrikeOut)
# create connections
self.customContextMenuRequested.connect(self.showMenu)
@@ -173,23 +172,55 @@ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
commands = self.standardCommands()
# Remove the Ctrl+/ "Move left one word part" shortcut so it can be used to
# comment
- command = commands.boundTo(Qt.ControlModifier | Qt.Key_Slash)
+ if Qt_py.IsPyQt6:
+ # In Qt6 enums are not longer simple ints. boundTo still requires ints
+ def to_int(shortcut):
+ return shortcut.toCombined()
+
+ else:
+
+ def to_int(shortcut):
+ return shortcut
+
+ command = commands.boundTo(
+ to_int(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_Slash)
+ )
if command is not None:
command.setKey(0)
for command in commands.commands():
if command.description() == 'Move selected lines up one line':
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Up)
+ command.setKey(
+ to_int(
+ Qt.KeyboardModifier.ControlModifier
+ | Qt.KeyboardModifier.ShiftModifier
+ | Qt.Key.Key_Up
+ )
+ )
if command.description() == 'Move selected lines down one line':
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_Down)
+ command.setKey(
+ to_int(
+ Qt.KeyboardModifier.ControlModifier
+ | Qt.KeyboardModifier.ShiftModifier
+ | Qt.Key.Key_Down
+ )
+ )
if command.description() == 'Duplicate selection':
- command.setKey(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_D)
+ command.setKey(
+ to_int(
+ Qt.KeyboardModifier.ControlModifier
+ | Qt.KeyboardModifier.ShiftModifier
+ | Qt.Key.Key_D
+ )
+ )
if command.description() == 'Cut current line':
command.setKey(0)
# Add QShortcuts
self.uiShowAutoCompleteSCT = QShortcut(
- Qt.CTRL | Qt.Key_Space, self, context=Qt.WidgetShortcut
+ QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Space),
+ self,
+ context=Qt.ShortcutContext.WidgetShortcut,
)
self.uiShowAutoCompleteSCT.activated.connect(lambda: self.showAutoComplete())
@@ -227,11 +258,13 @@ def checkForSave(self):
self.window(),
'Save changes to...',
'Do you want to save your changes?',
- QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
+ QMessageBox.StandardButton.Yes
+ | QMessageBox.StandardButton.No
+ | QMessageBox.StandardButton.Cancel,
)
- if result == QMessageBox.Yes:
+ if result == QMessageBox.StandardButton.Yes:
return self.save()
- elif result == QMessageBox.Cancel:
+ elif result == QMessageBox.StandardButton.Cancel:
return False
return True
@@ -508,22 +541,22 @@ def detectEndLine(self, text):
if newlineN != -1 and newlineR != -1:
if newlineN == newlineR + 1:
# CR LF Windows
- return self.EolWindows
+ return QsciScintilla.EolMode.EolWindows
elif newlineR == newlineN + 1:
- # LF CR ACorn and RISC unsuported
+ # LF CR ACorn and RISC unsupported
return self.eolMode()
if newlineN != -1 and newlineR != -1:
if newlineN < newlineR:
# First return is a LF
- return self.EolUnix
+ return QsciScintilla.EolMode.EolUnix
else:
# first return is a CR
- return self.EolMac
+ return QsciScintilla.EolMode.EolMac
if newlineN != -1:
- return self.EolUnix
+ return QsciScintilla.EolMode.EolUnix
if sys.platform == 'win32':
- return self.EolWindows
- return self.EolUnix
+ return QsciScintilla.EolMode.EolWindows
+ return QsciScintilla.EolMode.EolUnix
def editPermaHighlight(self):
text, success = QInputDialog.getText(
@@ -564,7 +597,7 @@ def enableTitleUpdate(self):
self.modificationChanged.connect(self.refreshTitle)
def eventFilter(self, object, event):
- if event.type() == event.Close and not self.checkForSave():
+ if event.type() == QEvent.Type.Close and not self.checkForSave():
event.ignore()
return True
return False
@@ -657,14 +690,8 @@ def lineMarginWidth(self):
def load(self, filename):
filename = str(filename)
if filename and os.path.exists(filename):
- f = QFile(filename)
- f.open(QFile.ReadOnly)
- text = f.readAll()
- self._textCodec = QTextCodec.codecForUtfText(
- text, QTextCodec.codecForName('UTF-8')
- )
- self.setText(self._textCodec.toUnicode(text))
- f.close()
+ self._encoding, text = WorkboxMixin.__open_file__(filename)
+ self.setText(text)
self.updateFilename(filename)
self.enableFileWatching(True)
self.setEolMode(self.detectEndLine(self.text()))
@@ -719,14 +746,14 @@ def find_simple(self, find_state):
if find_state.start_pos == find_state.end_pos:
return -1
- self.SendScintilla(self.SCI_SETTARGETSTART, find_state.start_pos)
- self.SendScintilla(self.SCI_SETTARGETEND, find_state.end_pos)
+ self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART, find_state.start_pos)
+ self.SendScintilla(QsciScintilla.SCI_SETTARGETEND, find_state.end_pos)
# scintilla can't match unicode strings, even in python 3
# In python 3 you have to cast it to a bytes object
expr = bytes(str(find_state.expr).encode("utf-8"))
- return self.SendScintilla(self.SCI_SEARCHINTARGET, len(expr), expr)
+ return self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET, len(expr), expr)
def find_text(self, find_state):
"""Finds text in the document without changing the selection.
@@ -739,10 +766,10 @@ def find_text(self, find_state):
https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
"""
# Set the search flags
- self.SendScintilla(self.SCI_SETSEARCHFLAGS, find_state.flags)
+ self.SendScintilla(QsciScintilla.SCI_SETSEARCHFLAGS, find_state.flags)
# If no end was specified, use the end of the document
if find_state.end_pos is None:
- find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
+ find_state.end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
pos = self.find_simple(find_state)
@@ -751,12 +778,14 @@ def find_text(self, find_state):
if find_state.forward:
find_state.start_pos = 0
if find_state.start_pos_original is None:
- find_state.end_pos = self.SendScintilla(self.SCI_GETLENGTH)
+ find_state.end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
else:
find_state.end_pos = find_state.start_pos_original
else:
if find_state.start_pos_original is None:
- find_state.start_pos = self.SendScintilla(self.SCI_GETLENGTH)
+ find_state.start_pos = self.SendScintilla(
+ QsciScintilla.SCI_GETLENGTH
+ )
else:
find_state.start_pos = find_state.start_pos_original
find_state.end_pos = 0
@@ -769,8 +798,8 @@ def find_text(self, find_state):
return -1, 0
# It was found.
- target_start = self.SendScintilla(self.SCI_GETTARGETSTART)
- target_end = self.SendScintilla(self.SCI_GETTARGETEND)
+ target_start = self.SendScintilla(QsciScintilla.SCI_GETTARGETSTART)
+ target_end = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
# Finally adjust the start position so that we don't find the same one again.
if find_state.forward:
@@ -818,10 +847,12 @@ def findTextNotFound(self, text):
self,
'No Text Found',
msg % (text, line),
- buttons=(QMessageBox.Yes | QMessageBox.No),
- defaultButton=QMessageBox.Yes,
+ buttons=(
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
+ ),
+ defaultButton=QMessageBox.StandardButton.Yes,
)
- if result == QMessageBox.Yes:
+ if result == QMessageBox.StandardButton.Yes:
self.goToLine(line)
except ValueError:
QMessageBox.critical(
@@ -830,20 +861,20 @@ def findTextNotFound(self, text):
def keyPressEvent(self, event):
key = event.key()
- if key == Qt.Key_Backtab:
+ if key == Qt.Key.Key_Backtab:
self.unindentSelection()
- elif key == Qt.Key_Escape:
+ elif key == Qt.Key.Key_Escape:
# Using QShortcut for Escape did not seem to work.
self.showAutoComplete(True)
else:
return QsciScintilla.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
- if event.key() == Qt.Key_Menu:
+ if event.key() == Qt.Key.Key_Menu:
# Calculate the screen coordinates of the text cursor.
position = self.positionFromLineIndex(*self.getCursorPosition())
- x = self.SendScintilla(self.SCI_POINTXFROMPOSITION, 0, position)
- y = self.SendScintilla(self.SCI_POINTYFROMPOSITION, 0, position)
+ x = self.SendScintilla(QsciScintilla.SCI_POINTXFROMPOSITION, 0, position)
+ y = self.SendScintilla(QsciScintilla.SCI_POINTYFROMPOSITION, 0, position)
# When using the menu key, show the right click menu at the text
# cursor, not the mouse cursor, it is not in the correct place.
self.showMenu(QPoint(x, y))
@@ -867,15 +898,20 @@ def initSettings(self, first_time=False):
self.setShowSmartHighlighting(True)
self.setBackspaceUnindents(True)
- self.setEdgeMode(self.EdgeNone)
+ self.setEdgeMode(QsciScintilla.EdgeMode.EdgeNone)
- # set autocompletion settings
- self.setAutoCompletionSource(QsciScintilla.AcsAll)
+ # set auto-completion settings
+ self.setAutoCompletionSource(QsciScintilla.AutoCompletionSource.AcsAll)
self.setAutoCompletionThreshold(3)
self.setFont(self.documentFont)
self.setMarginsFont(self.marginsFont())
- self.setMarginWidth(0, QFontMetrics(self.marginsFont()).width('0000000') + 5)
+ metric = QFontMetrics(self.marginsFont())
+ if Qt_py.IsPyQt4:
+ width = metric.width('0000000')
+ else:
+ width = metric.horizontalAdvance('0000000')
+ self.setMarginWidth(0, width + 5)
def markerNext(self):
line, index = self.getCursorPosition()
@@ -910,15 +946,15 @@ def marginsFont(self):
def multipleSelection(self):
"""Returns if multiple selection is enabled."""
- return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPLESELECTION)
def multipleSelectionAdditionalSelectionTyping(self):
"""Returns if multiple selection allows additional typing."""
- return self.SendScintilla(self.SCI_GETMULTIPLESELECTION)
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPLESELECTION)
def multipleSelectionMultiPaste(self):
"""Paste into all multiple selections."""
- return self.SendScintilla(self.SCI_GETMULTIPASTE)
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPASTE)
def paste(self):
text = QApplication.clipboard().text()
@@ -926,9 +962,9 @@ def paste(self):
return super(DocumentEditor, self).paste()
def repForMode(mode):
- if mode == self.EolWindows:
+ if mode == QsciScintilla.EolMode.EolWindows:
return '\r\n'
- elif mode == self.EolUnix:
+ elif mode == QsciScintilla.EolMode.EolUnix:
return '\n'
else:
return '\r'
@@ -987,11 +1023,11 @@ def reloadChange(self):
self.window(),
'File Removed...',
'File: %s has been deleted.\nKeep file in editor?' % self.filename(),
- QMessageBox.Yes,
- QMessageBox.No,
+ QMessageBox.StandardButton.Yes,
+ QMessageBox.StandardButton.No,
)
self._dialogShown = False
- if result == QMessageBox.No:
+ if result == QMessageBox.StandardButton.No:
logger.debug(
'The file was deleted, removing document from editor',
)
@@ -1013,13 +1049,16 @@ def reloadDialog(self, message, title='Reload File...'):
if not self._dialogShown:
self._dialogShown = True
if self._autoReloadOnChange or not self.isModified():
- result = QMessageBox.Yes
+ result = QMessageBox.StandardButton.Yes
else:
result = QMessageBox.question(
- self.window(), title, message, QMessageBox.Yes | QMessageBox.No
+ self.window(),
+ title,
+ message,
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
self._dialogShown = False
- if result == QMessageBox.Yes:
+ if result == QMessageBox.StandardButton.Yes:
return self.load(self.filename())
return False
@@ -1037,7 +1076,9 @@ def replace(self, text, searchtext=None, all=False):
# replace all of the instances of the text
if all:
- count = self.text().count(searchtext, Qt.CaseInsensitive)
+ count = self.text().count(
+ searchtext, Qt.CaseSensitivity.CaseInsensitive
+ )
found = 0
while self.findFirst(searchtext, False, False, False, True, True):
if found == count:
@@ -1084,32 +1125,28 @@ def saveAs(self, filename='', setFilename=True):
if not filename:
newFile = True
filename = self.filename()
- filename, extFilter = QtCompat.QFileDialog.getSaveFileName(
+ filename, extFilter = Qt_py.QtCompat.QFileDialog.getSaveFileName(
self.window(), 'Save File as...', filename
)
if filename:
self._saveTimer = time.time()
# save the file to disk
- f = QFile(filename)
- f.open(QFile.WriteOnly)
- # make sure the file is writeable
- if f.error() != QFile.NoError:
- logger.debug('An error occured while saving')
+ try:
+ txt = self.text()
+ WorkboxMixin.__write_file__(filename, txt, encoding=self._encoding)
+ with open(filename, "w", encoding=self._encoding) as f:
+ f.write(self.text())
+ except PermissionError as error:
+ logger.debug('An error occurred while saving')
QMessageBox.question(
self.window(),
'Error saving file...',
- 'There was a error saving the file. Error Code: %i' % f.error(),
- QMessageBox.Ok,
+ 'There was a error saving the file. Error: {}'.format(error),
+ QMessageBox.StandardButton.Ok,
)
- f.close()
return False
- # Attempt to save the file using the same codec that it used to display it
- if self._textCodec:
- f.write(self._textCodec.fromUnicode(self.text()))
- else:
- self.write(f)
- f.close()
+
# notify that the document was saved
self.documentSaved.emit(self, filename)
@@ -1165,8 +1202,8 @@ def is_word(self, start, end):
return False
# Get the word at the start of selection, if the selection doesn't match
# its not a word.
- start_pos = self.SendScintilla(self.SCI_WORDSTARTPOSITION, start, True)
- end_pos = self.SendScintilla(self.SCI_WORDENDPOSITION, start, True)
+ start_pos = self.SendScintilla(QsciScintilla.SCI_WORDSTARTPOSITION, start, True)
+ end_pos = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION, start, True)
return start == start_pos and end == end_pos
@@ -1227,7 +1264,9 @@ def setLexer(self, lexer):
# from a wordCharactersOverride lexer to a lexer that doesn't define custom
# wordCharacters.
wordCharacters = self.wordCharacters()
- self.SendScintilla(self.SCI_SETWORDCHARS, wordCharacters.encode('utf8'))
+ self.SendScintilla(
+ QsciScintilla.SCI_SETWORDCHARS, wordCharacters.encode('utf8')
+ )
if lexer:
lexer.setFont(font)
@@ -1250,7 +1289,7 @@ def setMultipleSelection(self, state):
ranges by holding down the Ctrl key while dragging with the
mouse.
"""
- self.SendScintilla(self.SCI_SETMULTIPLESELECTION, state)
+ self.SendScintilla(QsciScintilla.SCI_SETMULTIPLESELECTION, state)
def setMultipleSelectionAdditionalSelectionTyping(self, state):
"""Enables or disables multiple selection allows additional typing.
@@ -1261,7 +1300,7 @@ def setMultipleSelectionAdditionalSelectionTyping(self, state):
simultaneously. Also allows selection and word and line
deletion commands.
"""
- self.SendScintilla(self.SCI_SETADDITIONALSELECTIONTYPING, state)
+ self.SendScintilla(QsciScintilla.SCI_SETADDITIONALSELECTIONTYPING, state)
def setMultipleSelectionMultiPaste(self, state):
"""Enables or disables multiple selection allows additional typing.
@@ -1272,7 +1311,7 @@ def setMultipleSelectionMultiPaste(self, state):
into each selection with self.SC_MULTIPASTE_EACH.
self.SC_MULTIPASTE_ONCE is the default.
"""
- self.SendScintilla(self.SCI_SETMULTIPASTE, state)
+ self.SendScintilla(QsciScintilla.SCI_SETMULTIPASTE, state)
def setSmartHighlightingRegEx(
self, exp=r'[ \t\n\r\.,?;:!()\[\]+\-\*\/#@^%$"\\~&{}|=<>\']'
@@ -1300,9 +1339,9 @@ def setShowSmartHighlighting(self, state):
def setShowWhitespaces(self, state):
if state:
- self.setWhitespaceVisibility(QsciScintilla.WsVisible)
+ self.setWhitespaceVisibility(QsciScintilla.WhitespaceVisibility.WsVisible)
else:
- self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
+ self.setWhitespaceVisibility(QsciScintilla.WhitespaceVisibility.WsInvisible)
def spellCheckEnabled(self):
"""Is spellcheck is enabled for this document."""
@@ -1320,13 +1359,13 @@ def addWordToDict(self, word):
self.__speller__.saveAllwords()
self.spellCheck(0, None)
self.pos += len(word)
- self.SendScintilla(self.SCI_GOTOPOS, self.pos)
+ self.SendScintilla(QsciScintilla.SCI_GOTOPOS, self.pos)
def correctSpelling(self, action):
- self.SendScintilla(self.SCI_GOTOPOS, self.pos)
- self.SendScintilla(self.SCI_SETANCHOR, self.anchor)
+ self.SendScintilla(QsciScintilla.SCI_GOTOPOS, self.pos)
+ self.SendScintilla(QsciScintilla.SCI_SETANCHOR, self.anchor)
with undo_step(self):
- self.SendScintilla(self.SCI_REPLACESEL, action.text())
+ self.SendScintilla(QsciScintilla.SCI_REPLACESEL, action.text())
def spellCheck(self, start_pos, end_pos):
"""Check spelling for some text in the document.
@@ -1360,18 +1399,20 @@ def onTextModified(
or (mtype & self.SC_MOD_DELETETEXT) == self.SC_MOD_DELETETEXT
):
# Only spell-check if text was inserted/deleted
- line = self.SendScintilla(self.SCI_LINEFROMPOSITION, pos)
+ line = self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, pos)
# More than one line could have been inserted.
# If this number is negative it will cause Qt to crash.
lines_to_check = line + max(0, linesAdded)
self.spellCheck(
- self.SendScintilla(self.SCI_POSITIONFROMLINE, line),
- self.SendScintilla(self.SCI_GETLINEENDPOSITION, lines_to_check),
+ self.SendScintilla(QsciScintilla.SCI_POSITIONFROMLINE, line),
+ self.SendScintilla(
+ QsciScintilla.SCI_GETLINEENDPOSITION, lines_to_check
+ ),
)
def showAutoComplete(self, toggle=False):
# if using autoComplete toggle the autoComplete list
- if self.autoCompletionSource() == QsciScintilla.AcsAll:
+ if self.autoCompletionSource() == QsciScintilla.AutoCompletionSource.AcsAll:
if self.isListActive(): # is the autoComplete list visible
if toggle:
self.cancelList() # Close the autoComplete list
@@ -1389,9 +1430,11 @@ def showMenu(self, pos, popup=True):
x = point.x()
y = point.y()
wordUnderMouse = self.wordAtPoint(point)
- positionMouse = self.SendScintilla(self.SCI_POSITIONFROMPOINT, x, y)
+ positionMouse = self.SendScintilla(
+ QsciScintilla.SCI_POSITIONFROMPOINT, x, y
+ )
wordStartPosition = self.SendScintilla(
- self.SCI_WORDSTARTPOSITION, positionMouse, True
+ QsciScintilla.SCI_WORDSTARTPOSITION, positionMouse, True
)
spell_check = self.delayable_engine.delayables['spell_check']
results = spell_check.chunk_re.findall(
@@ -1556,7 +1599,9 @@ def showSmartHighlighting(self):
return self.delayable_engine.delayable_enabled('smart_highlight')
def showWhitespaces(self):
- return self.whitespaceVisibility() == QsciScintilla.WsVisible
+ return (
+ self.whitespaceVisibility() == QsciScintilla.WhitespaceVisibility.WsVisible
+ )
def smartHighlightingRegEx(self):
return self._smartHighlightingRegEx
@@ -1570,7 +1615,10 @@ def toLower(self):
self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
def toggleFolding(self):
- self.foldAll(QApplication.instance().keyboardModifiers() == Qt.ShiftModifier)
+ self.foldAll(
+ QApplication.instance().keyboardModifiers()
+ == Qt.KeyboardModifier.ShiftModifier
+ )
def toUpper(self):
with undo_step(self):
@@ -1710,10 +1758,8 @@ def updateSelectionInfo(self):
epos=epos,
lineCount=eline - sline + 1,
)
- if self._textCodec and self._textCodec.name() != 'System':
- text = 'Encoding: {enc} {text}'.format(
- enc=self._textCodec.name(), text=text
- )
+ if self._encoding:
+ text = 'Encoding: {enc} {text}'.format(enc=self._encoding, text=text)
window.uiCursorInfoLBL.setText(text)
def setAutoReloadOnChange(self, state):
@@ -1754,7 +1800,10 @@ def windowTitle(self):
return title
def wheelEvent(self, event):
- if self._enableFontResizing and event.modifiers() == Qt.ControlModifier:
+ if (
+ self._enableFontResizing
+ and event.modifiers() == Qt.KeyboardModifier.ControlModifier
+ ):
# If used in LoggerWindow, use that wheel event
# May not want to import LoggerWindow, so perhaps
# check by str(type())
diff --git a/preditor/scintilla/finddialog.py b/preditor/scintilla/finddialog.py
index 86a856ea..59686925 100644
--- a/preditor/scintilla/finddialog.py
+++ b/preditor/scintilla/finddialog.py
@@ -35,10 +35,10 @@ def __init__(self, parent):
def eventFilter(self, object, event):
from Qt.QtCore import QEvent, Qt
- if event.type() == QEvent.KeyPress:
+ if event.type() == QEvent.Type.KeyPress:
if (
- event.key() in (Qt.Key_Enter, Qt.Key_Return)
- and not event.modifiers() == Qt.ShiftModifier
+ event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return)
+ and not event.modifiers() == Qt.KeyboardModifier.ShiftModifier
):
self.parent().uiFindNextACT.triggered.emit(True)
self.accept()
diff --git a/preditor/scintilla/lang/language.py b/preditor/scintilla/lang/language.py
index c75d2b34..4b06fe72 100644
--- a/preditor/scintilla/lang/language.py
+++ b/preditor/scintilla/lang/language.py
@@ -8,7 +8,7 @@
except ImportError:
from ConfigParser import ConfigParser
-from PyQt5 import Qsci
+from .. import Qsci
class MethodDescriptor(object):
diff --git a/preditor/scintilla/lexers/cpplexer.py b/preditor/scintilla/lexers/cpplexer.py
index 43bc1046..e08c45b7 100644
--- a/preditor/scintilla/lexers/cpplexer.py
+++ b/preditor/scintilla/lexers/cpplexer.py
@@ -1,10 +1,11 @@
from __future__ import absolute_import
-from PyQt5.Qsci import QsciLexerCPP
from Qt.QtGui import QColor
+from .. import Qsci
-class CppLexer(QsciLexerCPP):
+
+class CppLexer(Qsci.QsciLexerCPP):
# Items in this list will be highlighted using the color for self.KeywordSet2
highlightedKeywords = ''
diff --git a/preditor/scintilla/lexers/javascriptlexer.py b/preditor/scintilla/lexers/javascriptlexer.py
index 20531d05..e0e7ddf9 100644
--- a/preditor/scintilla/lexers/javascriptlexer.py
+++ b/preditor/scintilla/lexers/javascriptlexer.py
@@ -1,11 +1,13 @@
from __future__ import absolute_import
-from PyQt5.Qsci import QsciLexerJavaScript
from Qt.QtGui import QColor
+from .. import Qsci
-class JavaScriptLexer(QsciLexerJavaScript):
- # Items in this list will be highlighted using the color for self.KeywordSet2
+
+class JavaScriptLexer(Qsci.QsciLexerJavaScript):
+ # Items in this list will be highlighted using the color for
+ # `Qsci.QsciLexerJavaScript.KeywordSet2`
highlightedKeywords = ''
def defaultFont(self, index):
@@ -13,7 +15,7 @@ def defaultFont(self, index):
return self.font(0)
def defaultPaper(self, style):
- if style == self.KeywordSet2:
+ if style == Qsci.QsciLexerJavaScript.KeywordSet2:
# Set the highlight color for this lexer
return QColor(155, 255, 155)
return super(JavaScriptLexer, self).defaultPaper(style)
diff --git a/preditor/scintilla/lexers/maxscriptlexer.py b/preditor/scintilla/lexers/maxscriptlexer.py
index 89dc3d63..01c56655 100644
--- a/preditor/scintilla/lexers/maxscriptlexer.py
+++ b/preditor/scintilla/lexers/maxscriptlexer.py
@@ -4,7 +4,8 @@
from builtins import str as text
from future.utils import iteritems
-from PyQt5.Qsci import QsciLexerCustom, QsciScintilla
+
+from .. import Qsci, QsciScintilla
MS_KEYWORDS = """
if then else not and or key collect
@@ -17,12 +18,12 @@
"""
-class MaxscriptLexer(QsciLexerCustom):
+class MaxscriptLexer(Qsci.QsciLexerCustom):
# Items in this list will be highligheded using the color for self.SmartHighlight
highlightedKeywords = ''
def __init__(self, parent=None):
- QsciLexerCustom.__init__(self, parent)
+ super(MaxscriptLexer, self).__init__(parent)
self._styles = {
0: 'Default',
1: 'Comment',
@@ -48,15 +49,15 @@ def defaultColor(self, style):
return QColor(40, 160, 40)
elif style in (self.Keyword, self.Operator):
- return QColor(Qt.blue)
+ return QColor(Qt.GlobalColor.blue)
elif style == self.Number:
- return QColor(Qt.red)
+ return QColor(Qt.GlobalColor.red)
elif style == self.String:
return QColor(180, 140, 30)
- return QsciLexerCustom.defaultColor(self, style)
+ return super(MaxscriptLexer, self).defaultColor(style)
def defaultPaper(self, style):
if style == self.SmartHighlight:
@@ -77,7 +78,7 @@ def keywords(self, style):
return MS_KEYWORDS
if style == self.SmartHighlight:
return self.highlightedKeywords
- return QsciLexerCustom.keywords(self, style)
+ return super(MaxscriptLexer, self).keywords(style)
def processChunk(self, chunk, lastState, keywords):
# process the length of the chunk
diff --git a/preditor/scintilla/lexers/mellexer.py b/preditor/scintilla/lexers/mellexer.py
index 41ec7147..fc16264d 100644
--- a/preditor/scintilla/lexers/mellexer.py
+++ b/preditor/scintilla/lexers/mellexer.py
@@ -2,9 +2,10 @@
import re
-from PyQt5.Qsci import QsciLexerCPP
from Qt.QtGui import QColor
+from .. import Qsci
+
MEL_SYNTAX = """and array as case catch continue do else exit float for from global if
in int local not of off on or proc random return select string then throw to try vector
when where while with true false
@@ -310,7 +311,7 @@
"""
-class MelLexer(QsciLexerCPP):
+class MelLexer(Qsci.QsciLexerCPP):
# Items in this list will be highlighted using the color for self.GlobalClass
_highlightedKeywords = ''
# Mel uses $varName for variables, so we have to allow them in words
diff --git a/preditor/scintilla/lexers/mulexer.py b/preditor/scintilla/lexers/mulexer.py
index 1e8855bb..19fb62d1 100644
--- a/preditor/scintilla/lexers/mulexer.py
+++ b/preditor/scintilla/lexers/mulexer.py
@@ -1,14 +1,15 @@
from __future__ import absolute_import
-from PyQt5.Qsci import QsciLexerCPP
from Qt.QtGui import QColor
+from .. import Qsci
+
MU_KEYWORDS = """
method string Color use require module for_each let global function nil void
"""
-class MuLexer(QsciLexerCPP):
+class MuLexer(Qsci.QsciLexerCPP):
# Items in this list will be highlighted using the color for self.KeywordSet2
highlightedKeywords = ''
diff --git a/preditor/scintilla/lexers/pythonlexer.py b/preditor/scintilla/lexers/pythonlexer.py
index 07754549..991f228d 100644
--- a/preditor/scintilla/lexers/pythonlexer.py
+++ b/preditor/scintilla/lexers/pythonlexer.py
@@ -1,22 +1,23 @@
from __future__ import absolute_import
-from PyQt5.Qsci import QsciLexerPython
from Qt.QtGui import QColor
+from .. import Qsci
-class PythonLexer(QsciLexerPython):
- # Items in this list will be highlighted using the color for
- # self.HighlightedIdentifier
+
+class PythonLexer(Qsci.QsciLexerPython):
+ # Items in this list will be highlighted using the color
+ # for Qsci.QsciLexerPython.HighlightedIdentifier.
highlightedKeywords = ''
def __init__(self, *args):
super(PythonLexer, self).__init__(*args)
# set the indentation warning
- self.setIndentationWarning(self.Inconsistent)
+ self.setIndentationWarning(Qsci.QsciLexerPython.IndentationWarning.Inconsistent)
def defaultPaper(self, style):
- if style == self.HighlightedIdentifier:
+ if style == Qsci.QsciLexerPython.HighlightedIdentifier:
# Set the highlight color for this lexer
return QColor(155, 255, 155)
return super(PythonLexer, self).defaultPaper(style)
diff --git a/preditor/utils/cute.py b/preditor/utils/cute.py
index d6384e3b..0b893514 100644
--- a/preditor/utils/cute.py
+++ b/preditor/utils/cute.py
@@ -7,19 +7,20 @@
def ensureWindowIsVisible(widget):
"""
Checks the widget's geometry against all of the system's screens. If it does
- not intersect it will reposition it to the top left corner of the highest
- numbered desktop. Returns a boolean indicating if it had to move the
- widget.
+ not intersect, it will reposition it to the top left corner of the highest
+ numbered screen. Returns a boolean indicating if it had to move the widget.
"""
- desktop = QApplication.desktop()
+ screens = QApplication.screens()
geo = widget.geometry()
- for screen in range(desktop.screenCount()):
- monGeo = desktop.screenGeometry(screen)
- if monGeo.intersects(geo):
+
+ for screen in screens:
+ if screen.geometry().intersects(geo):
break
else:
+ monGeo = screens[-1].geometry() # Use the last screen available
geo.moveTo(monGeo.x() + 7, monGeo.y() + 30)
- # setting the geometry may trigger a second check if setGeometry is overridden
+
+ # Setting the geometry may trigger a second check if setGeometry is overridden
disable = hasattr(widget, 'checkScreenGeo') and widget.checkScreenGeo
if disable:
widget.checkScreenGeo = False
diff --git a/pyproject.toml b/pyproject.toml
index da171105..77653e75 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,6 @@ license = {text = "LGPL-3.0"}
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
- "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
@@ -22,14 +21,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: PyPy",
]
requires-python = ">=3.7"
-dependencies = [
- "Qt.py",
- "configparser>=4.0.2",
- "future>=0.18.2",
- "signalslot>=0.1.2",
- "importlib-metadata>=4.8.3",
-]
-dynamic = ["version"]
+dynamic = ["dependencies", "optional-dependencies", "version"]
[project.readme]
file = "README.md"
@@ -40,27 +32,6 @@ Homepage = "https://github.com/blurstudio/PrEditor"
Source = "https://github.com/blurstudio/PrEditor"
Tracker = "https://github.com/blurstudio/PrEditor/issues"
-[project.optional-dependencies]
-cli =[
- "click>=7.1.2",
- "click-default-group",
-]
-dev =[
- "black",
- "build",
- "covdefaults",
- "coverage",
- "flake8",
- "flake8-bugbear",
- "Flake8-pyproject",
- "pep8-naming",
- "pytest",
- "tox",
-]
-shortcut =[
- "casement>=0.1.0;platform_system=='Windows'",
-]
-
[project.scripts]
preditor = "preditor.cli:cli"
@@ -80,15 +51,25 @@ QScintilla = "preditor.gui.workboxwidget:WorkboxWidget"
[project.entry-points."preditor.plug.logging_handlers"]
PrEditor = "preditor.gui.logger_window_handler:LoggerWindowHandler"
-
[tool.setuptools]
-include-package-data = true
platforms = ["any"]
license-files = ["LICENSE"]
+[tool.setuptools.dynamic]
+dependencies = {file = ["requirements.txt"]}
+
+[tool.setuptools.dynamic.optional-dependencies]
+cli = {file = ["requirements-cli.txt"]}
+dev = {file = ["requirements-dev.txt"]}
+qsci5 = {file = ["requirements-qsci5.txt"]}
+qsci6 = {file = ["requirements-qsci6.txt"]}
+shortcut = {file = ["requirements-shortcut.txt"]}
+
[tool.setuptools.packages.find]
-exclude = ["tests"]
-namespaces = false
+exclude = [
+ "examples",
+ "tests",
+]
[tool.setuptools_scm]
write_to = "preditor/version.py"
diff --git a/requirements-cli.txt b/requirements-cli.txt
new file mode 100644
index 00000000..d3b66e71
--- /dev/null
+++ b/requirements-cli.txt
@@ -0,0 +1,2 @@
+click>=7.1.2
+click-default-group
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 00000000..d7780b86
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,10 @@
+black
+build
+covdefaults
+coverage
+flake8
+flake8-bugbear
+Flake8-pyproject
+pep8-naming
+pytest
+tox
diff --git a/requirements-qsci5.txt b/requirements-qsci5.txt
new file mode 100644
index 00000000..9bbde904
--- /dev/null
+++ b/requirements-qsci5.txt
@@ -0,0 +1 @@
+QScintilla
diff --git a/requirements-qsci6.txt b/requirements-qsci6.txt
new file mode 100644
index 00000000..9b634eb5
--- /dev/null
+++ b/requirements-qsci6.txt
@@ -0,0 +1 @@
+PyQt6-QScintilla
diff --git a/requirements-shortcut.txt b/requirements-shortcut.txt
new file mode 100644
index 00000000..d2d285fa
--- /dev/null
+++ b/requirements-shortcut.txt
@@ -0,0 +1 @@
+casement>=0.1.0;platform_system=='Windows'
diff --git a/requirements.txt b/requirements.txt
index 86b3ad81..d6085dd3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
-build
+chardet
configparser>=4.0.2
future>=0.18.2
importlib-metadata>=4.8.3
-Qt.py
+Qt.py>=1.4.4
signalslot>=0.1.2