1
0
Fork 0
mirror of synced 2024-06-23 08:40:45 +12:00

IndicatorLineEdit: Run edit callback function asynchronously

Execute the edit callback function in a thread. By executing it in a thread
we don't have to wait for longer validation procedures to finish to
continue updating the UI. This is most notable in the MoveGamePopUp
which is heavy on disk IO.

Because we cannot use special text formatting in a thread, the
indicator messages have been reworked while also becoming extensible.

A dictionary of extended reasons can be specified through the
`IndicatorLineEdit.extend_reasons()` method.

The dictionary has to follow the following format
```
python
{
	MyIndicatorReasons.REASON: self.tr("Reason message")
	MyIndicatorReasons.OTHER_REASON: self.tr("Other reason message")
}
```

In the above example `MyIndicatorReasons` is a subclass of `IndicatorReasons`
which should be specified as follows

```
python
MyIndicatorReasons(IndicatorReasons):
	REASON = auto()
	OTHER_REASON = auto()
```
This commit is contained in:
loathingKernel 2023-02-18 14:33:42 +02:00
parent 7e4ded70b5
commit ecc1bd8d5c
12 changed files with 204 additions and 104 deletions

View file

@ -15,7 +15,7 @@ from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils import config_helper
from rare.utils.extra_widgets import PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
from rare.utils.misc import get_size
from rare.widgets.collapsible_widget import CollapsibleFrame
@ -253,11 +253,11 @@ class InstallDialog(QDialog):
self.get_options()
self.get_download_info()
def option_changed(self, path) -> Tuple[bool, str, str]:
def option_changed(self, path) -> Tuple[bool, str, int]:
self.options_changed = True
self.ui.install_button.setEnabled(False)
self.ui.verify_button.setEnabled(not self.worker_running)
return True, path, ""
return True, path, IndicatorReasonsCommon.VALID
def non_reload_option_changed(self, option: str):
if option == "download_only":

View file

@ -9,7 +9,7 @@ from legendary.core import LegendaryCore
from legendary.utils import webview_login
from rare.ui.components.dialogs.login.browser_login import Ui_BrowserLogin
from rare.utils.extra_widgets import IndicatorLineEdit
from rare.utils.extra_widgets import IndicatorLineEdit, IndicatorReasonsCommon
from rare.utils.misc import icon
logger = getLogger("BrowserLogin")
@ -49,19 +49,19 @@ class BrowserLogin(QFrame):
return self.sid_edit.is_valid
@staticmethod
def text_changed(text) -> Tuple[bool, str, str]:
def text_changed(text) -> Tuple[bool, str, int]:
if text:
text = text.strip()
if text.startswith("{") and text.endswith("}"):
try:
text = json.loads(text).get("authorizationCode")
except json.JSONDecodeError:
return False, text, IndicatorLineEdit.reasons.wrong_format
return False, text, IndicatorReasonsCommon.WRONG_FORMAT
elif '"' in text:
text = text.strip('"')
return len(text) == 32, text, IndicatorLineEdit.reasons.wrong_format
return len(text) == 32, text, IndicatorReasonsCommon.WRONG_FORMAT
else:
return False, text, ""
return False, text, IndicatorReasonsCommon.VALID
def do_login(self):
self.ui.status_label.setText(self.tr("Logging in..."))

View file

@ -11,7 +11,7 @@ from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings
from rare.models.game import RareGame
from rare.shared.workers.wine_resolver import WineResolver
from rare.utils import config_helper
from rare.utils.extra_widgets import PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
from rare.utils.misc import icon, get_raw_save_path
logger = getLogger("GameSettings")
@ -32,9 +32,9 @@ class GameSettings(DefaultGameSettings):
"",
file_type=QFileDialog.DirectoryOnly,
placeholder=self.tr("Cloud save path"),
edit_func=lambda text: (True, text, None)
edit_func=lambda text: (True, text, IndicatorReasonsCommon.VALID)
if os.path.exists(text)
else (False, text, PathEdit.reasons.dir_not_exist),
else (False, text, IndicatorReasonsCommon.DIR_NOT_EXISTS),
save_func=self.save_save_path,
)
self.cloud_layout.addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit)

View file

@ -1,5 +1,6 @@
import os
import shutil
from enum import auto
from logging import getLogger
from typing import Tuple, Optional
@ -7,24 +8,42 @@ from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
from rare.models.game import RareGame
from rare.shared import LegendaryCoreSingleton
from rare.utils.extra_widgets import PathEdit
from rare.shared import RareCore
from rare.utils.extra_widgets import PathEdit, IndicatorReasons, IndicatorReasonsCommon
from rare.utils.misc import get_size
from rare.widgets.elide_label import ElideLabel
logger = getLogger("MoveGame")
class MovePathEditReasons(IndicatorReasons):
DST_MISSING = auto()
NO_WRITE_PERM = auto()
SAME_DIR = auto()
DST_IN_SRC = auto()
NESTED_DIR = auto()
NO_SPACE = auto()
class MoveGamePopUp(QWidget):
move_clicked = pyqtSignal(str)
browse_done = pyqtSignal()
def __init__(self, parent=None):
super(MoveGamePopUp, self).__init__(parent=parent)
self.core = LegendaryCoreSingleton()
self.rcore = RareCore.instance()
self.core = RareCore.instance().core()
self.rgame: Optional[RareGame] = None
self.path_edit = PathEdit("", QFileDialog.Directory, edit_func=self.path_edit_cb)
self.path_edit = PathEdit("", QFileDialog.Directory, edit_func=self.path_edit_callback)
self.path_edit.extend_reasons({
MovePathEditReasons.DST_MISSING: self.tr("You need to provide a directory."),
MovePathEditReasons.NO_WRITE_PERM: self.tr("No write permission on destination."),
MovePathEditReasons.SAME_DIR: self.tr("Same directory or subdirectory selected."),
MovePathEditReasons.DST_IN_SRC: self.tr("Destination is inside source directory"),
MovePathEditReasons.NESTED_DIR: self.tr("Game install directories cannot be nested."),
MovePathEditReasons.NO_SPACE: self.tr("Not enough space available on drive."),
})
self.path_edit.path_select.clicked.connect(self.browse_done)
self.button = QPushButton(self.tr("Move"))
@ -64,23 +83,23 @@ class MoveGamePopUp(QWidget):
self.path_edit.setText(str())
self.path_edit.setText(text)
def path_edit_cb(self, path: str):
def path_edit_callback(self, path: str) -> Tuple[bool, str, int]:
self.button.setEnabled(True)
self.warn_label.setHidden(False)
def helper_func(reason: str) -> Tuple[bool, str, str]:
def helper_func(reason: int) -> Tuple[bool, str, int]:
self.button.setEnabled(False)
return False, path, self.tr(reason)
return False, path, reason
if not self.rgame.install_path or not path:
return helper_func("You need to provide a directory.")
return helper_func(MovePathEditReasons.DST_MISSING)
src_path = os.path.realpath(self.rgame.install_path)
dst_path = os.path.realpath(path)
dst_install_path = os.path.realpath(os.path.join(dst_path, os.path.basename(src_path)))
if not os.path.isdir(dst_path):
return helper_func("Directory doesn't exist or file selected.")
return helper_func(IndicatorReasonsCommon.DIR_NOT_EXISTS)
# Get free space on drive and size of game folder
_, _, free_space = shutil.disk_usage(dst_path)
@ -97,20 +116,20 @@ class MoveGamePopUp(QWidget):
self.avail_space.setText(get_size(free_space))
if not os.access(path, os.W_OK) or not os.access(self.rgame.install_path, os.W_OK):
return helper_func("No write permission on destination path/current install path.")
return helper_func(MovePathEditReasons.NO_WRITE_PERM)
if src_path == dst_path or src_path == dst_install_path:
return helper_func("Same directory or parent directory selected.")
return helper_func(MovePathEditReasons.SAME_DIR)
if str(src_path) in str(dst_path):
return helper_func("You can't select a directory that is inside the current install path.")
return helper_func(MovePathEditReasons.DST_IN_SRC)
if str(dst_install_path) in str(src_path):
return helper_func("You can't select a directory which contains the game installation.")
return helper_func(MovePathEditReasons.DST_IN_SRC)
for game in self.core.get_installed_list():
if game.install_path in path:
return helper_func("Game installations cannot be nested due to unintended sideeffects.")
for rgame in self.rcore.installed_games:
if not rgame.is_non_asset and rgame.install_path in path:
return helper_func(MovePathEditReasons.NESTED_DIR)
is_existing_dir = is_game_dir(src_path, dst_install_path)
@ -123,16 +142,16 @@ class MoveGamePopUp(QWidget):
self.warn_label.setHidden(False)
if free_space <= source_size and not is_existing_dir:
return helper_func("Not enough space available on drive.")
return helper_func(MovePathEditReasons.NO_SPACE)
# Fallback
self.button.setEnabled(True)
return True, path, str()
return True, path, IndicatorReasonsCommon.VALID
@pyqtSlot()
def __update_widget(self):
""" React to state updates from RareGame """
if not self.rgame.is_installed and not self.rgame.is_non_asset:
if not self.rgame.is_installed or self.rgame.is_non_asset:
self.setDisabled(True)
return
# FIXME: Make edit_func lighter instead of blocking signals

View file

@ -15,7 +15,7 @@ from rare.shared import RareCore
from rare.shared.workers.wine_resolver import WineResolver
from rare.ui.components.tabs.games.integrations.egl_sync_group import Ui_EGLSyncGroup
from rare.ui.components.tabs.games.integrations.egl_sync_list_group import Ui_EGLSyncListGroup
from rare.utils.extra_widgets import PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
logger = getLogger("EGLSync")
@ -92,9 +92,9 @@ class EGLSyncGroup(QGroupBox):
self.egl_path_edit.setText(path)
@staticmethod
def egl_path_edit_edit_cb(path) -> Tuple[bool, str, str]:
def egl_path_edit_edit_cb(path) -> Tuple[bool, str, int]:
if not path:
return True, path, ""
return True, path, IndicatorReasonsCommon.VALID
if os.path.exists(os.path.join(path, "system.reg")) and os.path.exists(
os.path.join(path, "dosdevices/c:")
):
@ -108,10 +108,10 @@ class EGLSyncGroup(QGroupBox):
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
):
# lower() might or might not be needed in the check
return False, path, PathEdit.reasons.wrong_path
return False, path, IndicatorReasonsCommon.WRONG_FORMAT
if os.path.exists(path):
return True, path, ""
return False, path, PathEdit.reasons.dir_not_exist
return True, path, IndicatorReasonsCommon.VALID
return False, path, IndicatorReasonsCommon.DIR_NOT_EXISTS
def egl_path_edit_save_cb(self, path):
if not path or not os.path.exists(path):

View file

@ -17,7 +17,7 @@ from rare.lgndr.glue.arguments import LgndrImportGameArgs
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
from rare.shared import RareCore
from rare.ui.components.tabs.games.integrations.import_group import Ui_ImportGroup
from rare.utils.extra_widgets import IndicatorLineEdit, PathEdit
from rare.utils.extra_widgets import IndicatorLineEdit, IndicatorReasonsCommon, PathEdit
from rare.widgets.elide_label import ElideLabel
logger = getLogger("Import")
@ -212,15 +212,15 @@ class ImportGroup(QGroupBox):
self.threadpool = QThreadPool.globalInstance()
def path_edit_callback(self, path) -> Tuple[bool, str, str]:
def path_edit_callback(self, path) -> Tuple[bool, str, int]:
if os.path.exists(path):
if os.path.exists(os.path.join(path, ".egstore")):
return True, path, ""
return True, path, IndicatorReasonsCommon.VALID
elif os.path.basename(path) in self.install_dir_list:
return True, path, ""
return True, path, IndicatorReasonsCommon.VALID
else:
return False, path, PathEdit.reasons.dir_not_exist
return False, path, ""
return False, path, IndicatorReasonsCommon.DIR_NOT_EXISTS
return False, path, IndicatorReasonsCommon.UNDEFINED
@pyqtSlot(str)
def path_changed(self, path: str):
@ -231,13 +231,13 @@ class ImportGroup(QGroupBox):
else:
self.app_name_edit.setText("")
def app_name_edit_callback(self, text) -> Tuple[bool, str, str]:
def app_name_edit_callback(self, text) -> Tuple[bool, str, int]:
if not text:
return False, text, ""
return False, text, IndicatorReasonsCommon.UNDEFINED
if text in self.app_name_list:
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
else:
return False, text, IndicatorLineEdit.reasons.game_not_installed
return False, text, IndicatorReasonsCommon.NOT_INSTALLED
@pyqtSlot(str)
def app_name_changed(self, app_name: str):

View file

@ -3,12 +3,12 @@ import re
from logging import getLogger
from typing import Tuple
from PyQt5.QtCore import Qt, QRunnable, QObject, pyqtSignal, QThreadPool, QSettings
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, QSettings
from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox
from rare.shared import LegendaryCoreSingleton
from rare.ui.components.tabs.settings.legendary import Ui_LegendarySettings
from rare.utils.extra_widgets import PathEdit, IndicatorLineEdit
from rare.utils.extra_widgets import PathEdit, IndicatorLineEdit, IndicatorReasonsCommon
from rare.utils.misc import get_size
logger = getLogger("LegendarySettings")
@ -102,18 +102,17 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
QThreadPool.globalInstance().start(worker)
@staticmethod
def locale_edit_cb(text: str) -> Tuple[bool, str, str]:
def locale_edit_cb(text: str) -> Tuple[bool, str, int]:
if text:
if re.match("^[a-zA-Z]{2,3}[-_][a-zA-Z]{2,3}$", text):
language, country = text.replace("_", "-").split("-")
text = "-".join([language.lower(), country.upper()])
if bool(re.match("^[a-z]{2,3}-[A-Z]{2,3}$", text)):
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
else:
return False, text, IndicatorLineEdit.reasons.wrong_format
return False, text, IndicatorReasonsCommon.WRONG_FORMAT
else:
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
def locale_save_cb(self, text: str):
if text:

View file

@ -9,7 +9,7 @@ from rare.components.tabs.settings.widgets.mangohud import MangoHudSettings
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
from rare.ui.components.tabs.settings.linux import Ui_LinuxSettings
from rare.utils.extra_widgets import PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
logger = getLogger("LinuxSettings")
@ -28,7 +28,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.wine_prefix = PathEdit(
self.load_prefix(),
file_type=QFileDialog.DirectoryOnly,
edit_func=lambda path: (os.path.isdir(path) or not path, path, PathEdit.reasons.dir_not_exist),
edit_func=lambda path: (os.path.isdir(path) or not path, path, IndicatorReasonsCommon.DIR_NOT_EXISTS),
save_func=self.save_prefix,
)
self.prefix_layout.addWidget(self.wine_prefix)
@ -38,7 +38,7 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.load_setting(self.name, "wine_executable"),
file_type=QFileDialog.ExistingFile,
name_filter="Wine executable (wine wine64)",
edit_func=lambda text: (os.path.exists(text) or not text, text, PathEdit.reasons.dir_not_exist),
edit_func=lambda text: (os.path.exists(text) or not text, text, IndicatorReasonsCommon.DIR_NOT_EXISTS),
save_func=lambda text: self.save_setting(
text, section=self.name, setting="wine_executable"
),

View file

@ -6,7 +6,7 @@ from PyQt5.QtWidgets import QHBoxLayout, QCheckBox, QFileDialog
from rare.shared import LegendaryCoreSingleton
from rare.utils import config_helper
from rare.utils.extra_widgets import IndicatorLineEdit, PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
class PreLaunchSettings(QHBoxLayout):
@ -28,14 +28,14 @@ class PreLaunchSettings(QHBoxLayout):
self.layout().addWidget(self.wait_check)
self.wait_check.stateChanged.connect(self.save_wait_finish)
def edit_command(self, text: str) -> Tuple[bool, str, str]:
def edit_command(self, text: str) -> Tuple[bool, str, int]:
if not text.strip():
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
if not os.path.isfile(text.split()[0]) and not shutil.which(text.split()[0]):
return False, text, IndicatorLineEdit.reasons.file_not_exist
return False, text, IndicatorReasonsCommon.FILE_NOT_EXISTS
else:
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
def save_pre_launch_command(self, text):
if text:

View file

@ -9,7 +9,7 @@ from rare.components.tabs.settings import LinuxSettings
from .wrapper import WrapperSettings
from rare.ui.components.tabs.settings.proton import Ui_ProtonSettings
from rare.utils import config_helper
from rare.utils.extra_widgets import PathEdit
from rare.utils.extra_widgets import PathEdit, IndicatorReasonsCommon
from rare.shared import LegendaryCoreSingleton
logger = getLogger("Proton")
@ -103,12 +103,12 @@ class ProtonSettings(QGroupBox, Ui_ProtonSettings):
config_helper.save_config()
def proton_prefix_edit(self, text: str) -> Tuple[bool, str, str]:
def proton_prefix_edit(self, text: str) -> Tuple[bool, str, int]:
if not text:
text = os.path.expanduser("~/.proton")
return True, text, ""
return True, text, IndicatorReasonsCommon.VALID
parent_dir = os.path.dirname(text)
return os.path.exists(parent_dir), text, PathEdit.reasons.dir_not_exist
return os.path.exists(parent_dir), text, IndicatorReasonsCommon.DIR_NOT_EXISTS
def proton_prefix_save(self, text: str):
if not self.changeable:

View file

@ -1,6 +1,7 @@
import os
from enum import IntEnum
from logging import getLogger
from typing import Callable, Tuple, Optional
from typing import Callable, Tuple, Optional, Dict
from PyQt5.QtCore import (
Qt,
@ -10,6 +11,10 @@ from PyQt5.QtCore import (
QPoint,
pyqtSignal,
QFileInfo,
QRunnable,
QObject,
QThreadPool,
pyqtSlot,
)
from PyQt5.QtGui import QMovie, QPixmap, QFontMetrics, QImage
from PyQt5.QtWidgets import (
@ -34,34 +39,94 @@ from PyQt5.QtWidgets import (
QScrollArea,
)
from rare.utils.misc import icon as qta_icon
from rare.utils.paths import tmp_dir
from rare.utils.qt_requests import QtRequestManager
from rare.utils.misc import icon as qta_icon
logger = getLogger("ExtraWidgets")
class IndicatorReasons:
dir_not_empty = QCoreApplication.translate("IndicatorReasons", "Directory is not empty")
wrong_format = QCoreApplication.translate("IndicatorReasons", "Given text has wrong format")
game_not_installed = QCoreApplication.translate(
"IndicatorReasons", "Game is not installed or does not exist"
)
dir_not_exist = QCoreApplication.translate("IndicatorReasons", "Directory does not exist")
file_not_exist = QCoreApplication.translate("IndicatorReasons", "File does not exist")
wrong_path = QCoreApplication.translate("IndicatorReasons", "Wrong Directory")
class IndicatorReasons(IntEnum):
"""
Empty enumeration with auto-generated enumeration values.
Extend this class per-case to implement dedicated message types.
Types should be assigned using `auto()` from enum
example:
MyReasons(IndicatorReasons):
MY_REASON = auto()
"""
@staticmethod
def _generate_next_value_(name, start, count, last_values):
"""generate consecutive automatic numbers starting from zero"""
start = len(IndicatorReasonsCommon)
return IntEnum._generate_next_value_(name, start, count, last_values)
class IndicatorReasonsCommon(IndicatorReasons):
VALID = 0
UNDEFINED = 1
WRONG_FORMAT = 2
WRONG_PATH = 3
DIR_NOT_EMPTY = 4
DIR_NOT_EXISTS = 5
FILE_NOT_EXISTS = 6
NOT_INSTALLED = 7
class IndicatorReasonsText(QObject):
def __init__(self, parent=None):
super(IndicatorReasonsText, self).__init__(parent=parent)
self.__text = {
IndicatorReasonsCommon.VALID: self.tr("Ok!"),
IndicatorReasonsCommon.UNDEFINED: self.tr("Unknown error occured"),
IndicatorReasonsCommon.WRONG_FORMAT: self.tr("Wrong format"),
IndicatorReasonsCommon.WRONG_PATH: self.tr("Wrong directory"),
IndicatorReasonsCommon.DIR_NOT_EMPTY: self.tr("Directory is not empty"),
IndicatorReasonsCommon.DIR_NOT_EXISTS: self.tr("Directory does not exist"),
IndicatorReasonsCommon.FILE_NOT_EXISTS: self.tr("File does not exist"),
IndicatorReasonsCommon.NOT_INSTALLED: self.tr("Game is not installed or does not exist"),
}
def __getitem__(self, item: int) -> str:
return self.__text[item]
def __setitem__(self, key: int, value: str):
self.__text[key] = value
def extend(self, reasons: Dict):
for k in self.__text.keys():
if k in reasons.keys():
raise RuntimeError(f"{reasons} contains existing values")
self.__text.update(reasons)
class EditFuncRunnable(QRunnable):
class Signals(QObject):
result = pyqtSignal(bool, str, int)
def __init__(self, func: Callable[[str], Tuple[bool, str, int]], args: str):
super(EditFuncRunnable, self).__init__()
self.setAutoDelete(True)
self.signals = EditFuncRunnable.Signals()
self.func = func
self.args = args
def run(self):
o0, o1, o2 = self.func(self.args)
self.signals.result.emit(o0, o1, o2)
self.signals.deleteLater()
class IndicatorLineEdit(QWidget):
textChanged = pyqtSignal(str)
reasons = IndicatorReasons()
def __init__(
self,
text: str = "",
placeholder: str = "",
completer: QCompleter = None,
edit_func: Callable[[str], Tuple[bool, str, str]] = None,
edit_func: Callable[[str], Tuple[bool, str, int]] = None,
save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy.Policy = QSizePolicy.Expanding,
parent=None,
@ -99,6 +164,12 @@ class IndicatorLineEdit(QWidget):
_translate = QCoreApplication.instance().translate
self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default"))
self.__reasons = IndicatorReasonsText(self)
self.__threadpool = QThreadPool(self)
self.__threadpool.setMaxThreadCount(1)
self.__thread: Optional[EditFuncRunnable] = None
self.is_valid = False
self.edit_func = edit_func
self.save_func = save_func
@ -125,26 +196,37 @@ class IndicatorLineEdit(QWidget):
self.hint_label.setFrameRect(self.line_edit.rect())
self.hint_label.setText(text)
def __indicator(self, res, reason=None):
color = "green" if res else "red"
def extend_reasons(self, reasons: Dict):
self.__reasons.extend(reasons)
def __indicator(self, valid, reason: int = 0):
color = "green" if valid else "red"
self.indicator_label.setPixmap(qta_icon("ei.info-circle", color=color).pixmap(16, 16))
if reason:
self.indicator_label.setToolTip(reason)
if not valid:
self.indicator_label.setToolTip(self.__reasons[reason])
else:
self.indicator_label.setToolTip("")
self.indicator_label.setToolTip(self.__reasons[IndicatorReasonsCommon.VALID])
@pyqtSlot(bool, str, int)
def __edit_handler(self, is_valid: bool, text: str, reason: int):
self.__thread = None
self.line_edit.blockSignals(True)
if text != self.line_edit.text():
self.line_edit.setText(text)
self.line_edit.blockSignals(False)
self.__indicator(is_valid, reason)
if is_valid:
self.__save(text)
self.is_valid = is_valid
self.textChanged.emit(text)
def __edit(self, text):
if self.edit_func is not None:
self.line_edit.blockSignals(True)
self.is_valid, text, reason = self.edit_func(text)
if text != self.line_edit.text():
self.line_edit.setText(text)
self.line_edit.blockSignals(False)
self.__indicator(self.is_valid, reason)
if self.is_valid:
self.__save(text)
self.textChanged.emit(text)
if self.__thread is not None:
self.__thread.signals.result.disconnect(self.__edit_handler)
self.__thread = EditFuncRunnable(self.edit_func, text)
self.__thread.signals.result.connect(self.__edit_handler)
self.__threadpool.start(self.__thread)
def __save(self, text):
if self.save_func is not None:
@ -166,8 +248,8 @@ class PathEditIconProvider(QFileIconProvider):
def __init__(self):
super(PathEditIconProvider, self).__init__()
self.icon_types = dict()
for idx, (icn, fallback) in enumerate(self.icons):
self.icon_types = {}
for idx, (icn, fallback) in enumerate(PathEditIconProvider.icons):
self.icon_types.update({idx - 1: qta_icon(icn, fallback, color="#eeeeee")})
def icon(self, info_type):
@ -192,7 +274,7 @@ class PathEdit(IndicatorLineEdit):
type_filter: str = "",
name_filter: str = "",
placeholder: str = "",
edit_func: Callable[[str], Tuple[bool, str, str]] = None,
edit_func: Callable[[str], Tuple[bool, str, int]] = None,
save_func: Callable[[str], None] = None,
horiz_policy: QSizePolicy.Policy = QSizePolicy.Expanding,
parent=None,
@ -230,8 +312,7 @@ class PathEdit(IndicatorLineEdit):
layout = self.layout()
layout.addWidget(self.path_select)
_translate = QCoreApplication.instance().translate
self.path_select.setText(_translate("PathEdit", "Browse..."))
self.path_select.setText(self.tr("Browse..."))
self.type_filter = type_filter
self.name_filter = name_filter
@ -254,11 +335,12 @@ class PathEdit(IndicatorLineEdit):
self.line_edit.setText(names[0])
self.compl_model.setRootPath(names[0])
def __wrap_edit_function(self, edit_function: Callable[[str], Tuple[bool, str, str]]):
if edit_function:
return lambda text: edit_function(os.path.expanduser(text) if text.startswith("~") else text)
@staticmethod
def __wrap_edit_function(func: Callable[[str], Tuple[bool, str, int]]):
if func:
return lambda text: func(os.path.expanduser(text) if text.startswith("~") else text)
else:
return edit_function
return func
class SideTabBar(QTabBar):

View file

@ -144,9 +144,9 @@ def get_latest_version():
def get_size(b: Union[int, float]) -> str:
for s in ["", "K", "M", "G", "T", "P", "E"]:
for s in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"]:
if b < 1024:
return f"{b:.2f} {s}iB"
return f"{b:.2f} {s}B"
b /= 1024