diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9432dbb01..a770dae66 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -554,6 +554,9 @@ jobs:
- name: Setup pip options
run: setup_pip_options
+ - name: Install system dependencies
+ run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev
+
- name: Setup Python environment
run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/dist_extra_gui_qt.txt -r reqs/test.txt
- name: Build UI
@@ -667,7 +670,7 @@ jobs:
- name: List cache contents
run: list_cache
-
+
outputs:
version: ${{ steps.set_version.outputs.version }}
# }}}
@@ -710,7 +713,7 @@ jobs:
run: setup_pip_options
- name: Install system dependencies
- run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev
+ run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev
- name: Setup Python environment
run: setup_python_env -c reqs/constraints.txt -r reqs/build.txt -r reqs/setup.txt
@@ -958,4 +961,4 @@ jobs:
run: publish_github_release
# }}}
-# vim: foldmethod=marker foldlevel=0
\ No newline at end of file
+# vim: foldmethod=marker foldlevel=0
diff --git a/.github/workflows/ci/workflow_template.yml b/.github/workflows/ci/workflow_template.yml
index f5234ca70..95fb190f4 100644
--- a/.github/workflows/ci/workflow_template.yml
+++ b/.github/workflows/ci/workflow_template.yml
@@ -127,9 +127,9 @@ jobs:
run: setup_osx_python '<@ j.python @>'
<% endif %>
- <% if j.type == 'build' and j.os == 'Linux' %>
+ <% if j.type in ['build', 'test_gui_qt'] and j.os == 'Linux' %>
- name: Install system dependencies
- run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev
+ run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev
<% endif %>
- name: Setup Python environment
@@ -256,7 +256,7 @@ jobs:
- name: List cache contents
run: list_cache
<% if j.type == 'test_packaging' %>
-
+
outputs:
version: ${{ steps.set_version.outputs.version }}
<% endif %>
diff --git a/MANIFEST.in b/MANIFEST.in
index e2095bb7a..c0bc6501a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -28,10 +28,5 @@ include test/*.py
include test/gui_qt/*.py
include tox.ini
include windows/*
-# Exclude: CI/Git/GitHub specific files,
-# as well as generated Python files (UI).
-exclude .gitignore
-exclude plover/gui_qt/*_rc.py
+
exclude plover/gui_qt/*_ui.py
-exclude plover/gui_qt/.gitignore
-prune .github
diff --git a/linux/appimage/blacklist.txt b/linux/appimage/blacklist.txt
index c89a7376a..14b7a9dfe 100644
--- a/linux/appimage/blacklist.txt
+++ b/linux/appimage/blacklist.txt
@@ -31,16 +31,14 @@
# Plover.
:usr/lib/python${pyversion}/site-packages/plover
gui_qt/*.ui
- gui_qt/resources
messages/**/*.po
messages/plover.pot
-# PyQt5.
+# PyQt6.
:usr/bin
- pylupdate5
- pyrcc5
- pyuic5
-:usr/lib/python${pyversion}/site-packages/PyQt5
+ pylupdate6
+ pyuic6
+:usr/lib/python${pyversion}/site-packages/PyQt6
**/*Designer*
**/*[Hh]elp*
**/*[Qq]ml*
@@ -49,19 +47,18 @@
**/*[Ww]ayland*
**/*[Ww]eb[Ee]ngine*
bindings
- Qt5/plugins/egldeviceintegrations
- Qt5/plugins/geoservices
- Qt5/plugins/platforms/libqeglfs.so
- Qt5/plugins/platforms/libqlinuxfb.so
- Qt5/plugins/platforms/libqminimal.so
- Qt5/plugins/platforms/libqminimalegl.so
- Qt5/plugins/platforms/libqoffscreen.so
- Qt5/plugins/platforms/libqvnc.so
- Qt5/plugins/platforms/libqwebgl.so
- Qt5/plugins/sceneparsers
- Qt5/plugins/webview
+ Qt6/plugins/egldeviceintegrations
+ Qt6/plugins/geoservices
+ Qt6/plugins/platforms/libqeglfs.so
+ Qt6/plugins/platforms/libqlinuxfb.so
+ Qt6/plugins/platforms/libqminimal.so
+ Qt6/plugins/platforms/libqminimalegl.so
+ Qt6/plugins/platforms/libqoffscreen.so
+ Qt6/plugins/platforms/libqvnc.so
+ Qt6/plugins/platforms/libqwebgl.so
+ Qt6/plugins/sceneparsers
+ Qt6/plugins/webview
pylupdate*
- pyrcc*
uic
# vim: ft=config
diff --git a/news.d/api/1601.break.md b/news.d/api/1601.break.md
new file mode 100644
index 000000000..cc2b2ac57
--- /dev/null
+++ b/news.d/api/1601.break.md
@@ -0,0 +1 @@
+Update UI to PyQt6 from PyQt5
diff --git a/osx/app_resources/dist_blacklist.txt b/osx/app_resources/dist_blacklist.txt
index c96eb6fe0..5d47ced4d 100644
--- a/osx/app_resources/dist_blacklist.txt
+++ b/osx/app_resources/dist_blacklist.txt
@@ -23,8 +23,8 @@
turtle*
**/*.exe
*/test*
-# PyQt5.
-:lib/python$python_base_version/site-packages/PyQt5
+# PyQt6.
+:lib/python$python_base_version/site-packages/PyQt6
**/*AxContainer*
**/*Bluetooth*
**/*CLucene*
@@ -35,34 +35,32 @@
**/*Serial*
**/*Sql*
**/*Test*
- Qt5/plugins/audio
- Qt5/plugins/bearer
- Qt5/plugins/generic
- Qt5/plugins/geoservices
- Qt5/plugins/mediaservice
- Qt5/plugins/playlistformats
- Qt5/plugins/position
- Qt5/plugins/printsupport
- Qt5/plugins/sceneparsers
- Qt5/plugins/sensor*
- Qt5/plugins/sqldrivers
- Qt5/qml
- Qt5/resources
- Qt5/translations/qt_help_*
- Qt5/translations/qtconnectivity_*
- Qt5/translations/qtdeclarative_*
- Qt5/translations/qtlocation_*
- Qt5/translations/qtmultimedia_*
- Qt5/translations/qtquick*
- Qt5/translations/qtserialport_*
- Qt5/translations/qtwebsockets_*
+ Qt6/plugins/audio
+ Qt6/plugins/bearer
+ Qt6/plugins/generic
+ Qt6/plugins/geoservices
+ Qt6/plugins/mediaservice
+ Qt6/plugins/playlistformats
+ Qt6/plugins/position
+ Qt6/plugins/printsupport
+ Qt6/plugins/sceneparsers
+ Qt6/plugins/sensor*
+ Qt6/plugins/sqldrivers
+ Qt6/qml
+ Qt6/resources
+ Qt6/translations/qt_help_*
+ Qt6/translations/qtconnectivity_*
+ Qt6/translations/qtdeclarative_*
+ Qt6/translations/qtlocation_*
+ Qt6/translations/qtmultimedia_*
+ Qt6/translations/qtquick*
+ Qt6/translations/qtserialport_*
+ Qt6/translations/qtwebsockets_*
pylupdate*
- pyrcc*
uic
# Plover.
:lib/python$python_base_version/site-packages/plover
gui_qt/*.ui
- gui_qt/resources
messages/**/*.po
messages/plover.pot
diff --git a/plover/gui_qt/about_dialog.py b/plover/gui_qt/about_dialog.py
index c1fabe1b4..93ace6eba 100644
--- a/plover/gui_qt/about_dialog.py
+++ b/plover/gui_qt/about_dialog.py
@@ -1,7 +1,7 @@
import re
-from PyQt5.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog
import plover
diff --git a/plover/gui_qt/add_translation_dialog.py b/plover/gui_qt/add_translation_dialog.py
index 1973664c2..5ab15d4cd 100644
--- a/plover/gui_qt/add_translation_dialog.py
+++ b/plover/gui_qt/add_translation_dialog.py
@@ -1,4 +1,4 @@
-from PyQt5.QtWidgets import QDialogButtonBox
+from PyQt6.QtWidgets import QDialogButtonBox
from plover import _
@@ -29,7 +29,7 @@ def __init__(self, engine, dictionary_path=None):
self.finished.connect(self.save_state)
def on_mapping_valid(self, valid):
- self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(valid)
+ self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(valid)
def on_config_changed(self, config_update):
if 'translation_frame_opacity' in config_update:
diff --git a/plover/gui_qt/add_translation_widget.py b/plover/gui_qt/add_translation_widget.py
index 1d3643f76..144cbed7a 100644
--- a/plover/gui_qt/add_translation_widget.py
+++ b/plover/gui_qt/add_translation_widget.py
@@ -2,8 +2,8 @@
from html import escape as html_escape
from os.path import split as os_path_split
-from PyQt5.QtCore import QEvent, pyqtSignal
-from PyQt5.QtWidgets import QApplication, QWidget
+from PyQt6.QtCore import QEvent, pyqtSignal
+from PyQt6.QtWidgets import QApplication, QWidget
from plover import _
from plover.misc import shorten_path
@@ -104,12 +104,12 @@ def select_dictionary(self, dictionary_path):
self._update_items()
def eventFilter(self, watched, event):
- if event.type() == QEvent.FocusIn:
+ if event.type() == QEvent.Type.FocusIn:
if watched == self.strokes:
self._focus_strokes()
elif watched == self.translation:
self._focus_translation()
- elif event.type() == QEvent.FocusOut:
+ elif event.type() == QEvent.Type.FocusOut:
if watched in (self.strokes, self.translation):
self._unfocus()
return False
diff --git a/plover/gui_qt/config_serial_widget.ui b/plover/gui_qt/config_serial_widget.ui
index 3419b3565..39cb08711 100644
--- a/plover/gui_qt/config_serial_widget.ui
+++ b/plover/gui_qt/config_serial_widget.ui
@@ -341,7 +341,7 @@
baudrate
- activated(QString)
+ textActivated(QString)
SerialWidget
on_baudrate_changed(QString)
@@ -357,7 +357,7 @@
bytesize
- activated(QString)
+ textActivated(QString)
SerialWidget
on_bytesize_changed(QString)
@@ -373,7 +373,7 @@
parity
- activated(QString)
+ textActivated(QString)
SerialWidget
on_parity_changed(QString)
@@ -405,7 +405,7 @@
stopbits
- activated(QString)
+ textActivated(QString)
SerialWidget
on_stopbits_changed(QString)
diff --git a/plover/gui_qt/config_window.py b/plover/gui_qt/config_window.py
index 7cc4b6948..5abbfc00d 100644
--- a/plover/gui_qt/config_window.py
+++ b/plover/gui_qt/config_window.py
@@ -3,12 +3,12 @@
from copy import copy
from functools import partial
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
Qt,
QVariant,
pyqtSignal,
)
-from PyQt5.QtWidgets import (
+from PyQt6.QtWidgets import (
QCheckBox,
QComboBox,
QDialog,
@@ -123,9 +123,9 @@ class TableOption(QTableWidget):
def __init__(self):
super().__init__()
self.horizontalHeader().setStretchLastSection(True)
- self.setSelectionMode(self.SingleSelection)
+ self.setSelectionMode(self.SelectionMode.SingleSelection)
self.setTabKeyNavigation(False)
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.verticalHeader().hide()
self.currentItemChanged.connect(self._on_current_item_changed)
@@ -191,7 +191,7 @@ def setValue(self, value):
row += 1
self.insertRow(row)
item = QTableWidgetItem(key)
- item.setFlags(item.flags() & ~Qt.ItemIsEditable)
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
self.setItem(row, 0, item)
item = QTableWidgetItem(action)
self.setItem(row, 1, item)
@@ -203,8 +203,8 @@ def setValue(self, value):
def _on_cell_changed(self, row, column):
if self._updating:
return
- key = self.item(row, 0).data(Qt.DisplayRole)
- action = self.item(row, 1).data(Qt.DisplayRole)
+ key = self.item(row, 0).data(Qt.ItemDataRole.DisplayRole)
+ action = self.item(row, 1).data(Qt.ItemDataRole.DisplayRole)
bindings = self._value.get_bindings()
if action:
bindings[key] = action
@@ -257,11 +257,11 @@ def setValue(self, value):
row += 1
self.insertRow(row)
item = QTableWidgetItem(self._choices[choice])
- item.setFlags(item.flags() & ~Qt.ItemIsEditable)
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
self.setItem(row, 0, item)
item = QTableWidgetItem()
- item.setFlags((item.flags() & ~Qt.ItemIsEditable) | Qt.ItemIsUserCheckable)
- item.setCheckState(Qt.Checked if choice in value else Qt.Unchecked)
+ item.setFlags((item.flags() & ~Qt.ItemFlag.ItemIsEditable) | Qt.ItemFlag.ItemIsUserCheckable)
+ item.setCheckState(Qt.CheckState.Checked if choice in value else Qt.CheckState.Unchecked)
self.setItem(row, 1, item)
self.resizeColumnsToContents()
self.setMinimumSize(self.viewportSizeHint())
@@ -271,7 +271,7 @@ def _on_cell_changed(self, row, column):
if self._updating:
return
assert column == 1
- choice = self._reversed_choices[self.item(row, 0).data(Qt.DisplayRole)]
+ choice = self._reversed_choices[self.item(row, 0).data(Qt.ItemDataRole.DisplayRole)]
if self.item(row, 1).checkState():
self._value.add(choice)
else:
@@ -437,8 +437,8 @@ def __init__(self, engine):
(option_by_name[option_name], update_fn)
for option_name, update_fn in option.dependents
]
- self.buttons.button(QDialogButtonBox.Ok).clicked.connect(self.on_apply)
- self.buttons.button(QDialogButtonBox.Apply).clicked.connect(self.on_apply)
+ self.buttons.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(self.on_apply)
+ self.buttons.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(self.on_apply)
self.tabs.currentWidget().setFocus()
self.restore_state()
self.finished.connect(self.save_state)
@@ -491,7 +491,7 @@ def _create_option_widget(self, option):
def keyPressEvent(self, event):
# Disable Enter/Return key to trigger "OK".
- if event.key() in (Qt.Key_Enter, Qt.Key_Return):
+ if event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return):
return
super().keyPressEvent(event)
diff --git a/plover/gui_qt/dictionaries_widget.py b/plover/gui_qt/dictionaries_widget.py
index b1937cd03..f45a67c92 100644
--- a/plover/gui_qt/dictionaries_widget.py
+++ b/plover/gui_qt/dictionaries_widget.py
@@ -1,14 +1,14 @@
from contextlib import contextmanager
import os
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QAbstractListModel,
QModelIndex,
Qt,
pyqtSignal,
)
-from PyQt5.QtGui import QCursor, QIcon
-from PyQt5.QtWidgets import (
+from PyQt6.QtGui import QCursor
+from PyQt6.QtWidgets import (
QFileDialog,
QGroupBox,
QMenu,
@@ -24,7 +24,7 @@
from plover.gui_qt.dictionaries_widget_ui import Ui_DictionariesWidget
from plover.gui_qt.dictionary_editor import DictionaryEditor
-from plover.gui_qt.utils import ToolBar
+from plover.gui_qt.utils import ToolBar, Icon
def _dictionary_formats(include_readonly=True):
@@ -96,11 +96,13 @@ def is_loaded(self):
return self.state not in {'loading', 'error'}
SUPPORTED_ROLES = {
- Qt.AccessibleTextRole, Qt.CheckStateRole,
- Qt.DecorationRole, Qt.DisplayRole, Qt.ToolTipRole
+ Qt.ItemDataRole.AccessibleTextRole, Qt.ItemDataRole.CheckStateRole,
+ Qt.ItemDataRole.DecorationRole, Qt.ItemDataRole.DisplayRole,
+ Qt.ItemDataRole.ToolTipRole
}
- FLAGS = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
+ FLAGS = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable \
+ | Qt.ItemFlag.ItemIsUserCheckable
has_undo_changed = pyqtSignal(bool)
@@ -356,17 +358,17 @@ def rowCount(self, parent=QModelIndex()):
@classmethod
def flags(cls, index):
- return cls.FLAGS if index.isValid() else Qt.NoItemFlags
+ return cls.FLAGS if index.isValid() else Qt.ItemFlag.NoItemFlags
def data(self, index, role):
if not index.isValid() or role not in self.SUPPORTED_ROLES:
return None
d = self._from_row[index.row()]
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
return d.short_path
- if role == Qt.CheckStateRole:
- return Qt.Checked if d.enabled else Qt.Unchecked
- if role == Qt.AccessibleTextRole:
+ if role == Qt.ItemDataRole.CheckStateRole:
+ return Qt.CheckState.Checked if d.enabled else Qt.CheckState.Unchecked
+ if role == Qt.ItemDataRole.AccessibleTextRole:
accessible_text = [d.short_path]
if not d.enabled:
# i18n: Widget: “DictionariesWidget”, accessible text.
@@ -385,9 +387,9 @@ def data(self, index, role):
# i18n: Widget: “DictionariesWidget”, accessible text.
accessible_text.append(_('read-only'))
return ', '.join(accessible_text)
- if role == Qt.DecorationRole:
+ if role == Qt.ItemDataRole.DecorationRole:
return self._icons.get('favorite' if d is self._favorite else d.state)
- if role == Qt.ToolTipRole:
+ if role == Qt.ItemDataRole.ToolTipRole:
# i18n: Widget: “DictionariesWidget”, tooltip.
tooltip = [_('Full path: {path}.').format(path=d.config.path)]
if d is self._favorite:
@@ -407,11 +409,11 @@ def data(self, index, role):
return None
def setData(self, index, value, role):
- if not index.isValid() or role != Qt.CheckStateRole:
+ if not index.isValid() or role != Qt.ItemDataRole.CheckStateRole:
return False
- if value == Qt.Checked:
+ if value == Qt.CheckState.Checked:
enabled = True
- elif value == Qt.Unchecked:
+ elif value == Qt.CheckState.Unchecked:
enabled = False
else:
return False
@@ -487,16 +489,16 @@ def __init__(self, *args, **kwargs):
edit_menu.addSeparator()
edit_menu.addAction(self.action_MoveDictionariesUp)
edit_menu.addAction(self.action_MoveDictionariesDown)
- self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.view.customContextMenuRequested.connect(
- lambda p: edit_menu.exec_(self.view.mapToGlobal(p)))
+ lambda p: edit_menu.exec(self.view.mapToGlobal(p)))
self.edit_menu = edit_menu
def setup(self, engine):
assert not self._setup
self._engine = engine
self._model = DictionariesModel(engine, {
- name: QIcon(':/dictionary_%s.svg' % name)
+ name: Icon(':/dictionary_%s.svg' % name)
for name in 'favorite loading error readonly normal'.split()
})
self._model.has_undo_changed.connect(self.on_has_undo)
@@ -578,7 +580,7 @@ def _edit_dictionaries(self, index_list):
if not path_list:
return
editor = DictionaryEditor(self._engine, path_list)
- editor.exec_()
+ editor.exec()
def _copy_dictionaries(self, dictionaries):
need_reload = False
@@ -661,7 +663,7 @@ def on_activate_dictionary(self, index):
self._edit_dictionaries([index])
def on_add_dictionaries(self):
- self.menu_AddDictionaries.exec_(QCursor.pos())
+ self.menu_AddDictionaries.exec(QCursor.pos())
def on_add_translation(self):
dictionary = next(self._model.iter_loaded([self.view.currentIndex()]), None)
diff --git a/plover/gui_qt/dictionary_editor.py b/plover/gui_qt/dictionary_editor.py
index a32a7bfe0..4c5c19b2c 100644
--- a/plover/gui_qt/dictionary_editor.py
+++ b/plover/gui_qt/dictionary_editor.py
@@ -3,13 +3,12 @@
from collections import namedtuple
from itertools import chain
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QAbstractTableModel,
QModelIndex,
Qt,
)
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import (
+from PyQt6.QtWidgets import (
QComboBox,
QDialog,
QStyledItemDelegate,
@@ -22,7 +21,7 @@
from plover.gui_qt.dictionary_editor_ui import Ui_DictionaryEditor
from plover.gui_qt.steno_validator import StenoValidator
-from plover.gui_qt.utils import ToolBar, WindowState
+from plover.gui_qt.utils import ToolBar, WindowState, Icon
_COL_STENO, _COL_TRANS, _COL_DICT, _COL_COUNT = range(3 + 1)
@@ -65,7 +64,7 @@ class DictionaryItemModel(QAbstractTableModel):
def __init__(self, dictionary_list, sort_column, sort_order):
super().__init__()
- self._error_icon = QIcon(':/dictionary_error.svg')
+ self._error_icon = Icon(':/dictionary_error.svg')
self._dictionary_list = dictionary_list
self._operations = []
self._entries = []
@@ -168,7 +167,7 @@ def columnCount(self, parent):
return _COL_COUNT
def headerData(self, section, orientation, role):
- if orientation != Qt.Horizontal or role != Qt.DisplayRole:
+ if orientation != Qt.Orientation.Horizontal or role != Qt.ItemDataRole.DisplayRole:
return None
if section == _COL_STENO:
# i18n: Widget: “DictionaryEditor”.
@@ -181,11 +180,15 @@ def headerData(self, section, orientation, role):
return _('Dictionary')
def data(self, index, role):
- if not index.isValid() or role not in (Qt.EditRole, Qt.DisplayRole, Qt.DecorationRole):
+ if not index.isValid() or role not in (
+ Qt.ItemDataRole.EditRole,
+ Qt.ItemDataRole.DisplayRole,
+ Qt.ItemDataRole.DecorationRole
+ ):
return None
item = self._entries[index.row()]
column = index.column()
- if role == Qt.DecorationRole:
+ if role == Qt.ItemDataRole.DecorationRole:
if column == _COL_STENO:
try:
normalize_steno(item.steno)
@@ -202,10 +205,10 @@ def data(self, index, role):
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags
- f = Qt.ItemIsEnabled | Qt.ItemIsSelectable
+ f = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
item = self._entries[index.row()]
if not item.dictionary.readonly:
- f |= Qt.ItemIsEditable
+ f |= Qt.ItemFlag.ItemIsEditable
return f
def filter(self, strokes_filter=None, translation_filter=None):
@@ -226,13 +229,13 @@ def sort(self, column, order):
else:
key = itemgetter(column)
self._entries.sort(key=key,
- reverse=(order == Qt.DescendingOrder))
+ reverse=(order == Qt.SortOrder.DescendingOrder))
self._sort_column = column
self._sort_order = order
self.layoutChanged.emit()
- def setData(self, index, value, role=Qt.EditRole, record=True):
- assert role == Qt.EditRole
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole, record=True):
+ assert role == Qt.ItemDataRole.EditRole
row = index.row()
column = index.column()
old_item = self._entries[row]
@@ -315,7 +318,7 @@ def __init__(self, engine, dictionary_paths):
for dictionary in engine.dictionaries.dicts
if dictionary.path in dictionary_paths
]
- sort_column, sort_order = _COL_STENO, Qt.AscendingOrder
+ sort_column, sort_order = _COL_STENO, Qt.SortOrder.AscendingOrder
self._model = DictionaryItemModel(dictionary_list,
sort_column,
sort_order)
diff --git a/plover/gui_qt/engine.py b/plover/gui_qt/engine.py
index 1c2d338c5..d564429df 100644
--- a/plover/gui_qt/engine.py
+++ b/plover/gui_qt/engine.py
@@ -1,4 +1,4 @@
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QThread,
QVariant,
pyqtSignal,
diff --git a/plover/gui_qt/log_qt.py b/plover/gui_qt/log_qt.py
index ec3eaf01e..531c0897e 100644
--- a/plover/gui_qt/log_qt.py
+++ b/plover/gui_qt/log_qt.py
@@ -1,7 +1,7 @@
import logging
-from PyQt5.QtCore import QObject, pyqtSignal
+from PyQt6.QtCore import QObject, pyqtSignal
from plover import log
diff --git a/plover/gui_qt/lookup_dialog.py b/plover/gui_qt/lookup_dialog.py
index fd868fba5..ab4268536 100644
--- a/plover/gui_qt/lookup_dialog.py
+++ b/plover/gui_qt/lookup_dialog.py
@@ -1,5 +1,5 @@
-from PyQt5.QtCore import QEvent, Qt
+from PyQt6.QtCore import QEvent, Qt
from plover import _
from plover.translation import unescape_translation
@@ -28,8 +28,8 @@ def __init__(self, engine):
self.finished.connect(self.save_state)
def eventFilter(self, watched, event):
- if event.type() == QEvent.KeyPress and \
- event.key() in (Qt.Key_Enter, Qt.Key_Return):
+ if event.type() == QEvent.Type.KeyPress and \
+ event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return):
return True
return False
@@ -44,6 +44,6 @@ def on_lookup(self, pattern):
def changeEvent(self, event):
super().changeEvent(event)
- if event.type() == QEvent.ActivationChange and self.isActiveWindow():
+ if event.type() == QEvent.Type.ActivationChange and self.isActiveWindow():
self.pattern.setFocus()
self.pattern.selectAll()
diff --git a/plover/gui_qt/machine_options.py b/plover/gui_qt/machine_options.py
index 3c6b69349..dcdf49024 100644
--- a/plover/gui_qt/machine_options.py
+++ b/plover/gui_qt/machine_options.py
@@ -1,15 +1,15 @@
from copy import copy
from pathlib import Path
-from PyQt5.QtCore import Qt, QVariant, pyqtSignal
-from PyQt5.QtGui import (
+from PyQt6.QtCore import Qt, QVariant, pyqtSignal
+from PyQt6.QtGui import (
QTextCharFormat,
QTextFrameFormat,
QTextListFormat,
QTextCursor,
QTextDocument,
)
-from PyQt5.QtWidgets import (
+from PyQt6.QtWidgets import (
QGroupBox,
QStyledItemDelegate,
QStyle,
@@ -68,19 +68,19 @@ def __init__(self):
self._details_frame_format.setForeground(foreground)
self._details_frame_format.setTopMargin(doc_margin)
self._details_frame_format.setBottomMargin(-3 * doc_margin)
- self._details_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
+ self._details_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle.BorderStyle_Solid)
self._details_frame_format.setBorder(doc_margin / 2)
self._details_frame_format.setPadding(doc_margin)
self._details_list_format = QTextListFormat()
- self._details_list_format.setStyle(QTextListFormat.ListSquare)
+ self._details_list_format.setStyle(QTextListFormat.Style.ListSquare)
def _format_port(self, index):
self._doc.clear()
cursor = QTextCursor(self._doc)
cursor.setCharFormat(self._device_format)
- port_info = index.data(Qt.UserRole)
+ port_info = index.data(Qt.ItemDataRole.UserRole)
if port_info is None:
- cursor.insertText(index.data(Qt.DisplayRole))
+ cursor.insertText(index.data(Qt.ItemDataRole.DisplayRole))
return
cursor.insertText(port_info.device)
details = serial_port_details(port_info)
@@ -95,7 +95,7 @@ def _format_port(self, index):
def paint(self, painter, option, index):
painter.save()
- if option.state & QStyle.State_Selected:
+ if option.state & QStyle.StateFlag.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
text_color = option.palette.highlightedText()
else:
diff --git a/plover/gui_qt/main.py b/plover/gui_qt/main.py
index 49444c2fb..185fa2451 100644
--- a/plover/gui_qt/main.py
+++ b/plover/gui_qt/main.py
@@ -2,19 +2,17 @@
import signal
import sys
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QCoreApplication,
QLibraryInfo,
QTimer,
QTranslator,
Qt,
- QtDebugMsg,
- QtInfoMsg,
- QtWarningMsg,
+ QtMsgType,
pyqtRemoveInputHook,
qInstallMessageHandler,
)
-from PyQt5.QtWidgets import QApplication, QMessageBox
+from PyQt6.QtWidgets import QApplication, QMessageBox
from plover import _, __name__ as __software_name__, __version__, log
from plover.oslayer.config import CONFIG_DIR
@@ -47,7 +45,6 @@ def __init__(self, config, controller, use_qt_notifications):
QCoreApplication.setOrganizationDomain('openstenoproject.org')
self._app = QApplication([sys.argv[0], '-name', 'plover'])
- self._app.setAttribute(Qt.AA_UseHighDpiPixmaps)
# Apply custom stylesheet if present.
stylesheet_path = Path(CONFIG_DIR) / 'plover.qss'
@@ -58,7 +55,7 @@ def __init__(self, config, controller, use_qt_notifications):
# Enable localization of standard Qt controls.
log.info('setting language to: %s', _.lang)
self._translator = QTranslator()
- translations_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
+ translations_dir = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
self._translator.load('qtbase_' + _.lang, translations_dir)
self._app.installTranslator(self._translator)
@@ -86,7 +83,7 @@ def __del__(self):
del self._translator
def run(self):
- self._app.exec_()
+ self._app.exec()
return self._engine.join()
@@ -102,9 +99,9 @@ def default_excepthook(*exc_info):
def default_message_handler(msg_type, msg_log_context, msg_string):
log_fn = {
- QtDebugMsg: log.debug,
- QtInfoMsg: log.info,
- QtWarningMsg: log.warning,
+ QtMsgType.QtDebugMsg: log.debug,
+ QtMsgType.QtInfoMsg: log.info,
+ QtMsgType.QtWarningMsg: log.warning,
}.get(msg_type, log.error)
details = []
if msg_log_context.file is not None:
diff --git a/plover/gui_qt/main_window.py b/plover/gui_qt/main_window.py
index cc6de0df4..52b2f7924 100644
--- a/plover/gui_qt/main_window.py
+++ b/plover/gui_qt/main_window.py
@@ -4,9 +4,9 @@
import os
import subprocess
-from PyQt5.QtCore import QCoreApplication, Qt
-from PyQt5.QtGui import QCursor, QIcon, QKeySequence
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QCoreApplication, Qt
+from PyQt6.QtGui import QCursor, QKeySequence
+from PyQt6.QtWidgets import (
QMainWindow,
QMenu,
)
@@ -22,7 +22,7 @@
from plover.gui_qt.config_window import ConfigWindow
from plover.gui_qt.about_dialog import AboutDialog
from plover.gui_qt.trayicon import TrayIcon
-from plover.gui_qt.utils import WindowState, find_menu_actions
+from plover.gui_qt.utils import Icon, WindowState, find_menu_actions
class MainWindow(QMainWindow, Ui_MainWindow, WindowState):
@@ -81,7 +81,7 @@ def __init__(self, engine, use_qt_notifications):
self.action_Quit.triggered.connect(engine.quit)
# Toolbar popup menu for selecting which tools are shown.
self.toolbar_menu = QMenu()
- self.toolbar.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.toolbar.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.toolbar.customContextMenuRequested.connect(
lambda: self.toolbar_menu.popup(QCursor.pos())
)
@@ -93,11 +93,7 @@ def __init__(self, engine, use_qt_notifications):
if tool.SHORTCUT is not None:
menu_action.setShortcut(QKeySequence.fromString(tool.SHORTCUT))
if tool.ICON is not None:
- icon = tool.ICON
- # Internal QT resources start with a `:`.
- if not icon.startswith(':'):
- icon = resource_filename(icon)
- menu_action.setIcon(QIcon(icon))
+ menu_action.setIcon(Icon(tool.ICON))
menu_action.triggered.connect(partial(self._activate_dialog, tool_plugin.name, args=()))
toolbar_action = self.toolbar.addAction(menu_action.icon(), menu_action.text())
if tool.__doc__ is not None:
diff --git a/plover/gui_qt/paper_tape.py b/plover/gui_qt/paper_tape.py
index 62b180676..13a0e23e3 100644
--- a/plover/gui_qt/paper_tape.py
+++ b/plover/gui_qt/paper_tape.py
@@ -1,13 +1,13 @@
import time
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QAbstractListModel,
QMimeData,
QModelIndex,
Qt,
)
-from PyQt5.QtGui import QFont
-from PyQt5.QtWidgets import (
+from PyQt6.QtGui import QFont
+from PyQt6.QtWidgets import (
QFileDialog,
QFontDialog,
QMessageBox,
@@ -72,12 +72,12 @@ def data(self, index, role):
if not index.isValid():
return None
stroke = self._stroke_list[index.row()]
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
if self._style == STYLE_PAPER:
return self._paper_format(stroke)
if self._style == STYLE_RAW:
return self._raw_format(stroke)
- if role == Qt.AccessibleTextRole:
+ if role == Qt.ItemDataRole.AccessibleTextRole:
return stroke.rtfcre
return None
@@ -105,7 +105,7 @@ def mimeTypes(self):
def mimeData(self, indexes):
data = QMimeData()
data.setText('\n'.join(filter(None, (
- self.data(index, Qt.DisplayRole)
+ self.data(index, Qt.ItemDataRole.DisplayRole)
for index in indexes
))))
return data
@@ -128,7 +128,7 @@ def __init__(self, engine):
self.header.setContentsMargins(4, 0, 0, 0)
self.styles.addItems(TAPE_STYLES)
self.tape.setModel(self._model)
- self.tape.setSelectionMode(self.tape.ExtendedSelection)
+ self.tape.setSelectionMode(self.tape.SelectionMode.ExtendedSelection)
self._copy_action = ActionCopyViewSelectionToClipboard(self.tape)
self.tape.addAction(self._copy_action)
# Toolbar.
@@ -167,7 +167,7 @@ def _restore_state(self, settings):
def _save_state(self, settings):
settings.setValue('style', TAPE_STYLES.index(self._style))
settings.setValue('font', self.tape.font().toString())
- ontop = bool(self.windowFlags() & Qt.WindowStaysOnTopHint)
+ ontop = bool(self.windowFlags() & Qt.WindowType.WindowStaysOnTopHint)
settings.setValue('ontop', ontop)
def on_config_changed(self, config):
@@ -202,7 +202,7 @@ def on_style_changed(self, style):
def on_select_font(self):
font, ok = QFontDialog.getFont(self.tape.font(), self, '',
- QFontDialog.MonospacedFonts)
+ QFontDialog.FontDialogOption.MonospacedFonts)
if ok:
self.header.setFont(font)
self.tape.setFont(font)
@@ -210,9 +210,9 @@ def on_select_font(self):
def on_toggle_ontop(self, ontop):
flags = self.windowFlags()
if ontop:
- flags |= Qt.WindowStaysOnTopHint
+ flags |= Qt.WindowType.WindowStaysOnTopHint
else:
- flags &= ~Qt.WindowStaysOnTopHint
+ flags &= ~Qt.WindowType.WindowStaysOnTopHint
self.setWindowFlags(flags)
self.show()
@@ -222,8 +222,8 @@ def on_clear(self):
msgbox.setText(_('Do you want to clear the paper tape?'))
msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
# Make sure the message box ends up above the paper tape!
- msgbox.setWindowFlags(msgbox.windowFlags() | (flags & Qt.WindowStaysOnTopHint))
- if QMessageBox.Yes != msgbox.exec_():
+ msgbox.setWindowFlags(msgbox.windowFlags() | (flags & Qt.WindowType.WindowStaysOnTopHint))
+ if QMessageBox.Yes != msgbox.exec():
return
self._strokes = []
self.action_Clear.setEnabled(False)
@@ -241,4 +241,4 @@ def on_save(self):
return
with open(filename, 'w') as fp:
for row in range(self._model.rowCount(self._model.index(-1, -1))):
- print(self._model.data(self._model.index(row, 0), Qt.DisplayRole), file=fp)
+ print(self._model.data(self._model.index(row, 0), Qt.ItemDataRole.DisplayRole), file=fp)
diff --git a/plover/gui_qt/paper_tape.ui b/plover/gui_qt/paper_tape.ui
index 43b9f9fc5..13ddf0e1a 100644
--- a/plover/gui_qt/paper_tape.ui
+++ b/plover/gui_qt/paper_tape.ui
@@ -155,7 +155,7 @@
styles
- activated(QString)
+ textActivated(QString)
PaperTape
on_style_changed()
diff --git a/plover/gui_qt/resources/__init__.py b/plover/gui_qt/resources/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/plover/gui_qt/resources/resources.qrc b/plover/gui_qt/resources/resources.qrc
index 17bf3904a..ddc074bfa 100644
--- a/plover/gui_qt/resources/resources.qrc
+++ b/plover/gui_qt/resources/resources.qrc
@@ -1,5 +1,5 @@
-
+
folder.svg
up.svg
down.svg
diff --git a/plover/gui_qt/steno_validator.py b/plover/gui_qt/steno_validator.py
index 7b620178f..2d76424dc 100644
--- a/plover/gui_qt/steno_validator.py
+++ b/plover/gui_qt/steno_validator.py
@@ -1,4 +1,4 @@
-from PyQt5.QtGui import QValidator
+from PyQt6.QtGui import QValidator
from plover.steno import normalize_steno
@@ -7,17 +7,17 @@ class StenoValidator(QValidator):
def validate(self, text, pos):
if not text.strip('-/'):
- state = QValidator.Intermediate
+ state = QValidator.State.Intermediate
else:
prefix = text.rstrip('-/')
if text == prefix:
- state = QValidator.Acceptable
+ state = QValidator.State.Acceptable
steno = text
else:
- state = QValidator.Intermediate
+ state = QValidator.State.Intermediate
steno = prefix
try:
normalize_steno(steno)
except ValueError:
- state = QValidator.Invalid
+ state = QValidator.State.Invalid
return state, text, pos
diff --git a/plover/gui_qt/suggestions_dialog.py b/plover/gui_qt/suggestions_dialog.py
index 018bc15f1..ea4792389 100644
--- a/plover/gui_qt/suggestions_dialog.py
+++ b/plover/gui_qt/suggestions_dialog.py
@@ -1,13 +1,13 @@
import re
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import (
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import (
+ QAction,
QCursor,
QFont,
)
-from PyQt5.QtWidgets import (
- QAction,
+from PyQt6.QtWidgets import (
QFontDialog,
QMenu,
)
@@ -95,7 +95,7 @@ def _save_state(self, settings):
font = self._get_font(name)
font_string = font.toString()
settings.setValue(name, font_string)
- ontop = bool(self.windowFlags() & Qt.WindowStaysOnTopHint)
+ ontop = bool(self.windowFlags() & Qt.WindowType.WindowStaysOnTopHint)
settings.setValue('ontop', ontop)
def _show_suggestions(self, suggestion_list):
@@ -143,7 +143,7 @@ def on_translation(self, old, new):
self._show_suggestions(suggestion_list)
def on_select_font(self):
- action = self._font_menu.exec_(QCursor.pos())
+ action = self._font_menu.exec(QCursor.pos())
if action is None:
return
if action == self._font_menu_text:
@@ -151,7 +151,7 @@ def on_select_font(self):
font_options = ()
elif action == self._font_menu_strokes:
name = 'strokes_font'
- font_options = (QFontDialog.MonospacedFonts,)
+ font_options = (QFontDialog.FontDialogOption.MonospacedFonts,)
font = self._get_font(name)
font, ok = QFontDialog.getFont(font, self, '', *font_options)
if ok:
@@ -160,9 +160,9 @@ def on_select_font(self):
def on_toggle_ontop(self, ontop):
flags = self.windowFlags()
if ontop:
- flags |= Qt.WindowStaysOnTopHint
+ flags |= Qt.WindowType.WindowStaysOnTopHint
else:
- flags &= ~Qt.WindowStaysOnTopHint
+ flags &= ~Qt.WindowType.WindowStaysOnTopHint
self.setWindowFlags(flags)
self.show()
diff --git a/plover/gui_qt/suggestions_widget.py b/plover/gui_qt/suggestions_widget.py
index c6755a25c..2a0c33d60 100644
--- a/plover/gui_qt/suggestions_widget.py
+++ b/plover/gui_qt/suggestions_widget.py
@@ -1,17 +1,17 @@
-from PyQt5.QtCore import (
+from PyQt6.QtCore import (
QAbstractListModel,
QMimeData,
QModelIndex,
Qt,
)
-from PyQt5.QtGui import (
+from PyQt6.QtGui import (
QFont,
QFontMetrics,
QTextCharFormat,
QTextCursor,
QTextDocument,
)
-from PyQt5.QtWidgets import (
+from PyQt6.QtWidgets import (
QListView,
QStyle,
QStyledItemDelegate,
@@ -35,7 +35,7 @@ def __init__(self, parent=None):
self._doc = QTextDocument()
self._translation_char_format = QTextCharFormat()
self._strokes_char_format = QTextCharFormat()
- self._strokes_char_format.font().setStyleHint(QFont.Monospace)
+ self._strokes_char_format.font().setStyleHint(QFont.StyleHint.Monospace)
self._size_hint_cache = {}
def clear_size_hint_cache(self):
@@ -60,7 +60,7 @@ def strokes_font(self, font):
self.clear_size_hint_cache()
def _format_suggestion(self, index):
- suggestion = index.data(Qt.DisplayRole)
+ suggestion = index.data(Qt.ItemDataRole.DisplayRole)
translation = escape_translation(suggestion.text) + ':'
if not suggestion.steno_list:
translation += ' ' + NO_SUGGESTIONS_STRING
@@ -79,7 +79,7 @@ def _suggestion_size_hint(self, index):
def paint(self, painter, option, index):
painter.save()
- if option.state & QStyle.State_Selected:
+ if option.state & QStyle.StateFlag.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
text_color = option.palette.highlightedText()
else:
@@ -118,9 +118,9 @@ def data(self, index, role):
if not index.isValid():
return None
suggestion = self._suggestion_list[index.row()]
- if role == Qt.DisplayRole:
+ if role == Qt.ItemDataRole.DisplayRole:
return suggestion
- if role == Qt.AccessibleTextRole:
+ if role == Qt.ItemDataRole.AccessibleTextRole:
translation = escape_translation(suggestion.text)
if suggestion.steno_list:
steno = ', '.join('/'.join(strokes_list) for strokes_list in
@@ -147,7 +147,7 @@ def mimeTypes(self):
def mimeData(self, indexes):
data = QMimeData()
data.setText('\n'.join(filter(None, (
- self.data(index, Qt.AccessibleTextRole)
+ self.data(index, Qt.ItemDataRole.AccessibleTextRole)
for index in indexes
))))
return data
@@ -157,8 +157,8 @@ class SuggestionsWidget(QListView):
def __init__(self, parent=None):
super().__init__(parent=parent)
- self.setResizeMode(self.Adjust)
- self.setSelectionMode(self.ExtendedSelection)
+ self.setResizeMode(self.ResizeMode.Adjust)
+ self.setSelectionMode(self.SelectionMode.ExtendedSelection)
self._copy_action = ActionCopyViewSelectionToClipboard(self)
self.addAction(self._copy_action)
self._model = SuggestionsModel()
diff --git a/plover/gui_qt/tool.py b/plover/gui_qt/tool.py
index efc2a6c81..e9a5be692 100644
--- a/plover/gui_qt/tool.py
+++ b/plover/gui_qt/tool.py
@@ -1,5 +1,5 @@
-from PyQt5.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog
from plover.gui_qt.utils import WindowState
diff --git a/plover/gui_qt/trayicon.py b/plover/gui_qt/trayicon.py
index 9dc4451ae..de2b885a3 100644
--- a/plover/gui_qt/trayicon.py
+++ b/plover/gui_qt/trayicon.py
@@ -1,6 +1,5 @@
-from PyQt5.QtCore import QObject, pyqtSignal
-from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QMessageBox, QSystemTrayIcon
+from PyQt6.QtCore import QObject, pyqtSignal
+from PyQt6.QtWidgets import QMessageBox, QSystemTrayIcon
from plover import _, __name__ as __software_name__
from plover import log
@@ -11,6 +10,7 @@
STATE_RUNNING,
STATE_ERROR,
)
+from plover.gui_qt.utils import Icon
class TrayIcon(QObject):
@@ -27,7 +27,7 @@ def __init__(self):
'disabled',
'enabled',
):
- icon = QIcon(':/state-%s.svg' % state)
+ icon = Icon(':/state-%s.svg' % state)
if hasattr(icon, 'setIsMask'):
icon.setIsMask(True)
self._state_icons[state] = icon
@@ -42,7 +42,7 @@ def set_menu(self, menu):
self._trayicon.setContextMenu(menu)
def show_message(self, message,
- icon=QSystemTrayIcon.Information,
+ icon=QSystemTrayIcon.MessageIcon.Information,
timeout=10000):
self._trayicon.showMessage(__software_name__.capitalize(),
message, icon, timeout)
@@ -50,13 +50,13 @@ def show_message(self, message,
def log(self, level, message):
if self._enabled:
if level <= log.INFO:
- icon = QSystemTrayIcon.Information
+ icon = QSystemTrayIcon.MessageIcon.Information
timeout = 10
elif level <= log.WARNING:
- icon = QSystemTrayIcon.Warning
+ icon = QSystemTrayIcon.MessageIcon.Warning
timeout = 15
else:
- icon = QSystemTrayIcon.Critical
+ icon = QSystemTrayIcon.MessageIcon.Critical
timeout = 25
self.show_message(message, icon, timeout * 1000)
else:
@@ -69,7 +69,7 @@ def log(self, level, message):
msgbox = QMessageBox()
msgbox.setText(message)
msgbox.setIcon(icon)
- msgbox.exec_()
+ msgbox.exec()
def is_supported(self):
return self._supported
@@ -134,5 +134,5 @@ def _update_state(self):
)
def _on_activated(self, reason):
- if reason == QSystemTrayIcon.Trigger:
+ if reason == QSystemTrayIcon.ActivationReason.Trigger:
self.clicked.emit()
diff --git a/plover/gui_qt/utils.py b/plover/gui_qt/utils.py
index 3549d596a..763c6b4f0 100644
--- a/plover/gui_qt/utils.py
+++ b/plover/gui_qt/utils.py
@@ -1,12 +1,18 @@
-from PyQt5.QtCore import QSettings
-from PyQt5.QtGui import QGuiApplication, QKeySequence
-from PyQt5.QtWidgets import (
+from PyQt6.QtCore import QSettings
+from PyQt6.QtGui import (
QAction,
+ QGuiApplication,
+ QIcon,
+ QKeySequence,
+ QPixmap
+)
+from PyQt6.QtWidgets import (
QMainWindow,
QToolBar,
QToolButton,
QWidget,
)
+import importlib.resources
from plover import _
@@ -17,7 +23,7 @@ def copy_selection_to_clipboard():
data = view.model().mimeData(indexes)
QGuiApplication.clipboard().setMimeData(data)
action = QAction(_('Copy selection to clipboard'))
- action.setShortcut(QKeySequence(QKeySequence.Copy))
+ action.setShortcut(QKeySequence(QKeySequence.StandardKey.Copy))
action.triggered.connect(copy_selection_to_clipboard)
return action
@@ -38,6 +44,24 @@ def ToolBar(*action_list):
return toolbar
+def Icon(resource):
+ icon = QIcon()
+ package = "plover.gui_qt.resources"
+
+ if type(resource) is tuple:
+ package = resource[0]
+ resource = resource[1]
+
+ if type(resource) is str:
+ if resource.startswith(":/"):
+ resource = resource[2:]
+
+ with importlib.resources.path(package, resource) as f_path:
+ icon.addPixmap(QPixmap(str(f_path)))
+
+ return icon
+
+
class WindowState(QWidget):
ROLE = None
diff --git a/plover_build_utils/setup.py b/plover_build_utils/setup.py
index 0be32e26e..63c35e2ff 100644
--- a/plover_build_utils/setup.py
+++ b/plover_build_utils/setup.py
@@ -100,17 +100,20 @@ def finalize_options(self):
pass
def _build_ui(self, src):
+ from pyqt6rc import convert_tools
dst = os.path.splitext(src)[0] + '_ui.py'
if not self.force and os.path.exists(dst) and \
os.path.getmtime(dst) >= os.path.getmtime(src):
return
- cmd = (
- sys.executable, '-m', 'PyQt5.uic.pyuic',
- '--from-import', src,
- )
if self.verbose:
print('generating', dst)
- contents = subprocess.check_output(cmd).decode('utf-8')
+
+ resources = {}
+ resources_found = convert_tools.update_resources(src, resources)
+ contents = os.popen(f"python -m PyQt6.uic.pyuic {src}").read()
+ if resources_found is not None:
+ contents = convert_tools.modify_py(contents, resources)
+
for hook in self.hooks:
mod_name, attr_name = hook.split(':')
mod = importlib.import_module(mod_name)
@@ -119,19 +122,6 @@ def _build_ui(self, src):
with open(dst, 'w') as fp:
fp.write(contents)
- def _build_resources(self, src):
- dst = os.path.join(
- os.path.dirname(os.path.dirname(src)),
- os.path.splitext(os.path.basename(src))[0]
- ) + '_rc.py'
- cmd = (
- sys.executable, '-m', 'PyQt5.pyrcc_main',
- src, '-o', dst,
- )
- if self.verbose:
- print('generating', dst)
- subprocess.check_call(cmd)
-
def run(self):
self.run_command('egg_info')
std_hook_prefix = __package__ + '.pyqt:'
@@ -143,8 +133,6 @@ def run(self):
print('generating UI using hooks:', ', '.join(hooks_info))
ei_cmd = self.get_finalized_command('egg_info')
for src in ei_cmd.filelist.files:
- if src.endswith('.qrc'):
- self._build_resources(src)
if src.endswith('.ui'):
self._build_ui(src)
diff --git a/pyproject.toml b/pyproject.toml
index b6c489996..0a096030c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,8 @@
[build-system]
requires = [
"Babel",
- "PyQt5>=5.8.2",
+ "PyQt6>=6.4.2",
+ "pyqt6rc>=0.5.2",
"setuptools>=38.2.4",
"wheel",
]
diff --git a/pytest.ini b/pytest.ini
index 59abe85c3..2cf5b7375 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -2,7 +2,7 @@
addopts = -ra
markers =
gui_qt: GUI specific tests.
-qt_api = pyqt5
+qt_api = pyqt6
testpaths =
test
diff --git a/reqs/constraints.txt b/reqs/constraints.txt
index 928d34af0..a5d00c62c 100644
--- a/reqs/constraints.txt
+++ b/reqs/constraints.txt
@@ -12,7 +12,7 @@ charset-normalizer==2.0.7
check-manifest==0.47
click==8.0.3
click-default-group==1.2.2
-cmarkgfm==0.6.0
+cmarkgfm>=1.2.0
colorama==0.4.4
cryptography==35.0.0
dmgbuild==1.5.2
@@ -33,27 +33,27 @@ packaging==21.0
pep517==0.12.0
pip==21.3.1
pkginfo==1.7.1
-plover-plugins-manager==0.7.0
+plover-plugins-manager==0.7.1
plover-stroke==1.1.0
plover-treal==1.0.1
pluggy==1.0.0
py==1.10.0
pycparser==2.20
Pygments==2.10.0
-pyobjc-core==7.3
-pyobjc-framework-Cocoa==7.3
-pyobjc-framework-Quartz==7.3
+pyobjc-core==9.0
+pyobjc-framework-Cocoa==9.0
+pyobjc-framework-Quartz==9.0
pyparsing==3.0.3
-PyQt5==5.15.6
-PyQt5-Qt5==5.15.2
-PyQt5-sip==12.9.0
+PyQt6==6.4.2
+PyQt6-Qt6==6.4.3
+pyqt6rc==0.5.2
pyserial==3.5
pytest==6.2.5
pytest-qt==4.0.2
python-xlib==0.31
pytz==2021.3
PyYAML==6.0
-readme-renderer==30.0
+readme-renderer==37.3
requests==2.26.0
requests-cache==0.9.1
requests-futures==1.0.0
diff --git a/reqs/dist.txt b/reqs/dist.txt
index 43102086a..fc1ec31b5 100644
--- a/reqs/dist.txt
+++ b/reqs/dist.txt
@@ -1,9 +1,9 @@
appdirs>=1.3.0
appnope>=0.1.0; "darwin" in sys_platform
plover-stroke>=1.1.0
-pyobjc-core>=4.0; "darwin" in sys_platform
-pyobjc-framework-Cocoa>=4.0; "darwin" in sys_platform
-pyobjc-framework-Quartz>=4.0; "darwin" in sys_platform
+pyobjc-core>=9.0; "darwin" in sys_platform
+pyobjc-framework-Cocoa>=9.0; "darwin" in sys_platform
+pyobjc-framework-Quartz>=9.0; "darwin" in sys_platform
pyserial>=2.7
python-xlib>=0.16; ("linux" in sys_platform or "bsd" in sys_platform) and python_version < "3.9"
python-xlib>=0.29; ("linux" in sys_platform or "bsd" in sys_platform) and python_version >= "3.9"
diff --git a/reqs/dist_extra_gui_qt.txt b/reqs/dist_extra_gui_qt.txt
index c7854c32a..294e4a5f0 100644
--- a/reqs/dist_extra_gui_qt.txt
+++ b/reqs/dist_extra_gui_qt.txt
@@ -1,3 +1,4 @@
-PyQt5>=5.5
+PyQt6>=6.4
+pyqt6rc>=0.5.2
# vim: ft=cfg commentstring=#\ %s list
diff --git a/reqs/dist_plugins.txt b/reqs/dist_plugins.txt
index 47e7ee15f..2540fc70b 100644
--- a/reqs/dist_plugins.txt
+++ b/reqs/dist_plugins.txt
@@ -1,4 +1,4 @@
-plover-plugins-manager
+plover-plugins-manager @ https://github.com/greghope667/plover_plugins_manager/archive/pyqt6-migration.zip
plover-treal
# vim: ft=cfg commentstring=#\ %s list
diff --git a/reqs/setup.txt b/reqs/setup.txt
index ec915a75a..6417b6ccb 100644
--- a/reqs/setup.txt
+++ b/reqs/setup.txt
@@ -1,5 +1,6 @@
Babel
-PyQt5>=5.8.2
+PyQt6>=6.4.2
+pyqt6rc>=0.5.2
setuptools>=38.2.4
wheel
diff --git a/setup.cfg b/setup.cfg
index 8a1ef9c3e..52a68e4a5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -40,6 +40,7 @@ packages =
plover.dictionary
plover.gui_none
plover.gui_qt
+ plover.gui_qt.resources
plover.machine
plover.machine.keyboard_capture
plover.macro
@@ -116,6 +117,5 @@ plover =
messages/plover.pot
plover.gui_qt =
*.ui
- resources/*
# vim: commentstring=#\ %s list
diff --git a/test/gui_qt/test_dictionaries_widget.py b/test/gui_qt/test_dictionaries_widget.py
index 7f059ee9a..fdc7541e9 100644
--- a/test/gui_qt/test_dictionaries_widget.py
+++ b/test/gui_qt/test_dictionaries_widget.py
@@ -4,7 +4,7 @@
from types import SimpleNamespace
import operator
-from PyQt5.QtCore import QModelIndex, QPersistentModelIndex, Qt
+from PyQt6.QtCore import QModelIndex, QPersistentModelIndex, Qt
import pytest
@@ -37,12 +37,13 @@
ENABLED_FROM_CHAR = {c: e for e, c in ENABLED_TO_CHAR.items()}
CHECKED_TO_BOOL = {
- Qt.Checked: True,
- Qt.Unchecked: False,
+ Qt.CheckState.Checked: True,
+ Qt.CheckState.Unchecked: False,
}
-MODEL_ROLES = sorted([Qt.AccessibleTextRole, Qt.CheckStateRole,
- Qt.DecorationRole, Qt.DisplayRole, Qt.ToolTipRole])
+MODEL_ROLES = sorted([Qt.ItemDataRole.AccessibleTextRole, Qt.ItemDataRole.CheckStateRole,
+ Qt.ItemDataRole.DecorationRole, Qt.ItemDataRole.DisplayRole,
+ Qt.ItemDataRole.ToolTipRole])
def parse_state(state_str):
@@ -119,9 +120,9 @@ def check(self, expected,
actual_state = []
for row in range(self.model.rowCount()):
index = self.model.index(row)
- enabled = CHECKED_TO_BOOL[index.data(Qt.CheckStateRole)]
- icon = index.data(Qt.DecorationRole)
- path = index.data(Qt.DisplayRole)
+ enabled = CHECKED_TO_BOOL[index.data(Qt.ItemDataRole.CheckStateRole)]
+ icon = index.data(Qt.ItemDataRole.DecorationRole)
+ path = index.data(Qt.ItemDataRole.DisplayRole)
actual_state.append('%s %s %s' % (
ENABLED_TO_CHAR.get(enabled, '?'),
ICON_TO_CHAR.get(icon, '?'),
@@ -150,8 +151,10 @@ def check(self, expected,
call.args[2].sort()
assert call == mock.call.dataChanged(index, index, MODEL_ROLES)
if layout_change:
- assert signal_calls[0:2] == [mock.call.layoutAboutToBeChanged([], self.model.NoLayoutChangeHint),
- mock.call.layoutChanged([], self.model.NoLayoutChangeHint)]
+ assert signal_calls[0:2] == [mock.call.layoutAboutToBeChanged(
+ [], self.model.LayoutChangeHint.NoLayoutChangeHint),
+ mock.call.layoutChanged(
+ [], self.model.LayoutChangeHint.NoLayoutChangeHint)]
del signal_calls[0:2]
assert not signal_calls
self.signals.reset_mock()
@@ -224,7 +227,7 @@ def test_model_accessible_text_1(model_test):
'commands.json, loading',
'asset:plover:assets/main.json, disabled, loading',
)):
- assert model_test.model.index(n).data(Qt.AccessibleTextRole) == expected
+ assert model_test.model.index(n).data(Qt.ItemDataRole.AccessibleTextRole) == expected
def test_model_accessible_text_2(model_test):
'''
@@ -239,21 +242,21 @@ def test_model_accessible_text_2(model_test):
'commands.json',
'asset:plover:assets/main.json, disabled, read-only',
)):
- assert model_test.model.index(n).data(Qt.AccessibleTextRole) == expected
+ assert model_test.model.index(n).data(Qt.ItemDataRole.AccessibleTextRole) == expected
def test_model_accessible_text_3(model_test):
'''
☑ ! invalid.bad
'''
expected = 'invalid.bad, errored: %s.' % INVALID_EXCEPTION
- assert model_test.model.index(0).data(Qt.AccessibleTextRole) == expected
+ assert model_test.model.index(0).data(Qt.ItemDataRole.AccessibleTextRole) == expected
def test_model_accessible_text_4(model_test):
'''
☐ ! invalid.bad
'''
expected = 'invalid.bad, disabled, errored: %s.' % INVALID_EXCEPTION
- assert model_test.model.index(0).data(Qt.AccessibleTextRole) == expected
+ assert model_test.model.index(0).data(Qt.ItemDataRole.AccessibleTextRole) == expected
def test_model_add_existing(model_test):
'''
@@ -424,7 +427,7 @@ def test_model_favorite(model_test):
☐ 🛇 asset:plover:assets/main.json
'''
# New favorite.
- model_test.model.setData(model_test.model.index(1), Qt.Unchecked, Qt.CheckStateRole)
+ model_test.model.setData(model_test.model.index(1), Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
model_test.check(
'''
☑ 🛇 read-only.ro
@@ -438,7 +441,7 @@ def test_model_favorite(model_test):
undo_change=True,
)
# No favorite.
- model_test.model.setData(model_test.model.index(3), Qt.Unchecked, Qt.CheckStateRole)
+ model_test.model.setData(model_test.model.index(3), Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
model_test.check(
'''
☑ 🛇 read-only.ro
@@ -614,19 +617,19 @@ def test_model_persistent_index(model_test):
'''
persistent_index = QPersistentModelIndex(model_test.model.index(1))
assert persistent_index.row() == 1
- assert persistent_index.data(Qt.CheckStateRole) == Qt.Checked
- assert persistent_index.data(Qt.DecorationRole) == 'favorite'
- assert persistent_index.data(Qt.DisplayRole) == 'user.json'
+ assert persistent_index.data(Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Checked
+ assert persistent_index.data(Qt.ItemDataRole.DecorationRole) == 'favorite'
+ assert persistent_index.data(Qt.ItemDataRole.DisplayRole) == 'user.json'
model_test.configure(classic_dictionaries_display_order=True)
assert persistent_index.row() == 2
- assert persistent_index.data(Qt.CheckStateRole) == Qt.Checked
- assert persistent_index.data(Qt.DecorationRole) == 'favorite'
- assert persistent_index.data(Qt.DisplayRole) == 'user.json'
- model_test.model.setData(persistent_index, Qt.Unchecked, Qt.CheckStateRole)
+ assert persistent_index.data(Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Checked
+ assert persistent_index.data(Qt.ItemDataRole.DecorationRole) == 'favorite'
+ assert persistent_index.data(Qt.ItemDataRole.DisplayRole) == 'user.json'
+ model_test.model.setData(persistent_index, Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
assert persistent_index.row() == 2
- assert persistent_index.data(Qt.CheckStateRole) == Qt.Unchecked
- assert persistent_index.data(Qt.DecorationRole) == 'normal'
- assert persistent_index.data(Qt.DisplayRole) == 'user.json'
+ assert persistent_index.data(Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Unchecked
+ assert persistent_index.data(Qt.ItemDataRole.DecorationRole) == 'normal'
+ assert persistent_index.data(Qt.ItemDataRole.DisplayRole) == 'user.json'
def test_model_qtmodeltester(model_test, qtmodeltester):
'''
@@ -686,18 +689,18 @@ def test_model_set_checked(model_test):
first_index = model_test.model.index(0)
for index, value, ret, state in (
# Invalid index.
- (QModelIndex(), Qt.Unchecked, False, on_state),
+ (QModelIndex(), Qt.CheckState.Unchecked, False, on_state),
# Invalid values.
(first_index, 'pouet', False, on_state),
- (first_index, Qt.PartiallyChecked, False, on_state),
+ (first_index, Qt.CheckState.PartiallyChecked, False, on_state),
# Already checked.
- (first_index, Qt.Checked, False, on_state),
+ (first_index, Qt.CheckState.Checked, False, on_state),
# Uncheck.
- (first_index, Qt.Unchecked, True, off_state),
+ (first_index, Qt.CheckState.Unchecked, True, off_state),
# Recheck.
- (first_index, Qt.Checked, True, on_state),
+ (first_index, Qt.CheckState.Checked, True, on_state),
):
- assert model_test.model.setData(index, value, Qt.CheckStateRole) == ret
+ assert model_test.model.setData(index, value, Qt.ItemDataRole.CheckStateRole) == ret
model_test.check(state, config_change='update' if ret else None,
data_change=[index.row()] if ret else None)
@@ -727,7 +730,7 @@ def test_model_undo_1(model_test):
state = state.split('\n')
state[n] = '☑' + state[n][1:]
state = '\n'.join(state)
- model_test.model.setData(model_test.model.index(n), Qt.Checked, Qt.CheckStateRole)
+ model_test.model.setData(model_test.model.index(n), Qt.CheckState.Checked, Qt.ItemDataRole.CheckStateRole)
model_test.check(state, config_change='update', data_change=[n],
undo_change=(True if n == 0 else None))
for n in range(5):
@@ -766,12 +769,12 @@ class WidgetTest(namedtuple('WidgetTest', '''
def select(self, selection):
sm = self.widget.view.selectionModel()
for row in selection:
- sm.select(self.model.index(row), sm.Select)
+ sm.select(self.model.index(row), sm.SelectionFlag.Select)
def unselect(self, selection):
sm = self.widget.view.selectionModel()
for row in selection:
- sm.select(self.model.index(row), sm.Deselect)
+ sm.select(self.model.index(row), sm.SelectionFlag.Deselect)
def __getattr__(self, name):
return getattr(self.model_test, name)
diff --git a/test/gui_qt/test_steno_validator.py b/test/gui_qt/test_steno_validator.py
index 28359dc0c..1ab166b09 100644
--- a/test/gui_qt/test_steno_validator.py
+++ b/test/gui_qt/test_steno_validator.py
@@ -1,4 +1,4 @@
-from PyQt5.QtGui import QValidator
+from PyQt6.QtGui import QValidator
import pytest
@@ -7,21 +7,21 @@
@pytest.mark.parametrize(('text', 'state'), (
# Acceptable.
- ('ST', QValidator.Acceptable),
- ('TEFT', QValidator.Acceptable),
- ('TEFT/-G', QValidator.Acceptable),
- ('/ST', QValidator.Acceptable),
- ('-F', QValidator.Acceptable),
+ ('ST', QValidator.State.Acceptable),
+ ('TEFT', QValidator.State.Acceptable),
+ ('TEFT/-G', QValidator.State.Acceptable),
+ ('/ST', QValidator.State.Acceptable),
+ ('-F', QValidator.State.Acceptable),
# Intermediate.
- ('-', QValidator.Intermediate),
- ('/', QValidator.Intermediate),
- ('/-', QValidator.Intermediate),
- ('ST/', QValidator.Intermediate),
- ('ST/-', QValidator.Intermediate),
- ('ST//', QValidator.Intermediate),
+ ('-', QValidator.State.Intermediate),
+ ('/', QValidator.State.Intermediate),
+ ('/-', QValidator.State.Intermediate),
+ ('ST/', QValidator.State.Intermediate),
+ ('ST/-', QValidator.State.Intermediate),
+ ('ST//', QValidator.State.Intermediate),
# Invalid.
- ('WK', QValidator.Invalid),
- ('PLOVER', QValidator.Invalid),
+ ('WK', QValidator.State.Invalid),
+ ('PLOVER', QValidator.State.Invalid),
))
def test_steno_validator_validate(text, state):
validator = StenoValidator()
diff --git a/windows/dist_blacklist.txt b/windows/dist_blacklist.txt
index 6faaf7da8..3d7c601df 100644
--- a/windows/dist_blacklist.txt
+++ b/windows/dist_blacklist.txt
@@ -1,7 +1,7 @@
# Python.
Scripts
-# PyQt5.
-:Lib/site-packages/PyQt5
+# PyQt6.
+:Lib/site-packages/PyQt6
**/*Designer*
**/*[Hh]elp*
**/*Test*
@@ -9,21 +9,18 @@
**/*[Qq]uick*
**/*[Ww]eb[Ee]ngine*
bindings
- Qt5/bin/libeay32.dll
- Qt5/bin/ssleay32.dll
- Qt5/plugins/platforms/qminimal.dll
- Qt5/plugins/platforms/qoffscreen.dll
- Qt5/plugins/platforms/qwebgl.dll
- Qt5/plugins/sceneparsers
- Qt5/qml
+ Qt6/bin/libeay32.dll
+ Qt6/bin/ssleay32.dll
+ Qt6/plugins/platforms/qminimal.dll
+ Qt6/plugins/platforms/qoffscreen.dll
+ Qt6/plugins/platforms/qwebgl.dll
+ Qt6/plugins/sceneparsers
+ Qt6/qml
pylupdate*
- pyrcc*
uic
# Plover.
:Lib/site-packages/plover
gui_qt/*.ui
- gui_qt/*.ui
- gui_qt/resources
messages/**/*.po
messages/plover.pot