13 Commits

Author SHA1 Message Date
ceb6e2932e chore: update dependencies 2025-06-04 19:10:28 +08:00
035e2157a8 log tag support 2025-06-04 18:55:23 +08:00
e964a38e3d change # fmt: labels to ruff compatible 2025-06-04 18:55:23 +08:00
0e2026ff1c change logging format to % 2025-06-04 18:55:23 +08:00
3ce4c7bed9 core.color 2025-06-04 18:55:23 +08:00
ad09c95b03 disable strict ruff rules 2025-06-04 18:55:23 +08:00
c664ed7e8d refactor: moving ui.extends to core
* Settings and Singletons moved
2025-06-04 18:55:23 +08:00
4e7d54fbef using ruff as formatter & linter 2025-06-04 18:55:23 +08:00
0c6f4f4961 chore: v0.3.9 2024-06-20 00:04:46 +08:00
10fb98d530 chore: upgrade dependencies 2024-06-20 00:03:19 +08:00
bf034d1397 ci: update build actions
* Switch to official Nuitka GitHub Actions

* Adding Linux build support

* Upgrade deprecated actions
2024-06-20 00:00:28 +08:00
4f864611ee fix: B30 table order (#11) 2024-06-19 22:20:31 +08:00
d9c163431c feat: OCR score date source (#9)
* New settings entries

* Choose `birthTime`/`lastModified` for OCR score date source if the image EXIF fails
2024-06-19 22:18:25 +08:00
49 changed files with 839 additions and 504 deletions

View File

@ -1,40 +1,65 @@
name: Build UI from latest `arcaea-offline-*` dependencies
name: Build Executable from latest `arcaea-offline-*` dependencies
run-name: ${{ github.actor }} started a build request.
on:
workflow_dispatch:
permissions:
contents: write
discussions: write
jobs:
build-windows:
runs-on: windows-2022
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
# install dependencies
- run: "pip install -r requirements.txt"
- run: "pip uninstall arcaea-offline arcaea-offline-ocr -y"
- run: "pip install git+https://github.com/283375/arcaea-offline"
- run: "pip install git+https://github.com/283375/arcaea-offline-ocr"
- run: "pip install nuitka imageio"
- run: "pip install imageio"
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
# release builtin files
- run: 'pyside6-lrelease.exe .\ui\resources\lang\en_US.ts .\ui\resources\lang\zh_CN.ts'
- run: "python prebuild.py"
- run: 'pyside6-rcc.exe .\ui\resources\resources.qrc -o .\ui\resources\resources_rc.py'
- name: Release builtin files
run: |
pyside6-lrelease ui/resources/lang/en_US.ts ui/resources/lang/zh_CN.ts
python prebuild.py
pyside6-rcc ui/resources/resources.qrc -o ui/resources/resources_rc.py
# build
- run: "python -m nuitka --plugin-enable=upx --enable-plugin=pyside6 --assume-yes-for-downloads --windows-icon-from-ico=./ui/resources/images/icon.png --standalone --onefile index.py"
- uses: actions/upload-artifact@v3
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
name: build-windows
path: index.exe
nuitka-version: main
script-name: index.py
standalone: true
onefile: true
enable-plugins: pyside6,upx
windows-icon-from-ico: ui/resources/images/icon.png
linux-icon: ui/resources/images/icon.png
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ runner.os }} Build
path: |
build/*.exe
build/*.bin
build/*.app/**/*

View File

@ -1,4 +1,4 @@
name: Build UI
name: Build Executable
run-name: ${{ github.actor }} started a build request.
on:
@ -12,32 +12,59 @@ permissions:
discussions: write
jobs:
build-windows:
runs-on: windows-2022
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
# install dependencies
- run: "pip install -r requirements.txt"
- run: "pip install nuitka imageio"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install imageio
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
# release builtin files
- run: 'pyside6-lrelease.exe .\ui\resources\lang\en_US.ts .\ui\resources\lang\zh_CN.ts'
- run: "python prebuild.py"
- run: 'pyside6-rcc.exe .\ui\resources\resources.qrc -o .\ui\resources\resources_rc.py'
- name: Release builtin files
run: |
pyside6-lrelease ui/resources/lang/en_US.ts ui/resources/lang/zh_CN.ts
python prebuild.py
pyside6-rcc ui/resources/resources.qrc -o ui/resources/resources_rc.py
# build
- run: "python -m nuitka --plugin-enable=upx --enable-plugin=pyside6 --assume-yes-for-downloads --windows-icon-from-ico=./ui/resources/images/icon.png --standalone --onefile index.py"
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: main
script-name: index.py
standalone: true
onefile: true
enable-plugins: pyside6,upx
windows-icon-from-ico: ui/resources/images/icon.png
linux-icon: ui/resources/images/icon.png
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ runner.os }} Build
path: |
build/*.exe
build/*.bin
build/*.app/**/*
- name: Draft a release
uses: softprops/action-gh-release@v2
@ -46,4 +73,6 @@ jobs:
draft: true
generate_release_notes: true
files: |
index.exe
build/*.exe
build/*.bin
build/*.app/**/*

View File

@ -4,11 +4,10 @@ repos:
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 23.1.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- id: ruff
args: ["--fix"]
- id: ruff-format

0
core/__init__.py Normal file
View File

10
core/color.py Normal file
View File

@ -0,0 +1,10 @@
from PySide6.QtGui import QColor
def mixColor(source: QColor, mix: QColor, ratio: float = 0.5):
r = round((mix.red() - source.red()) * ratio + source.red())
g = round((mix.green() - source.green()) * ratio + source.green())
b = round((mix.blue() - source.blue()) * ratio + source.blue())
a = round((mix.alpha() - source.alpha()) * ratio + source.alpha())
return QColor(r, g, b, a)

View File

@ -0,0 +1,5 @@
from .base import Settings, settings
from .keys import SettingsKeys
from .values import SettingsValues
__all__ = ["settings", "Settings", "SettingsKeys", "SettingsValues"]

44
core/settings/base.py Normal file
View File

@ -0,0 +1,44 @@
import sys
from enum import Enum
from typing import Any
from PySide6.QtCore import QFileInfo, QSettings, Signal
from core.singleton import QSingleton
__all__ = ["Settings"]
TSettingsKey = str | Enum
class Settings(QSettings, metaclass=QSingleton):
updated = Signal(str)
def __init__(self, parent=None):
super().__init__(
QFileInfo(sys.argv[0]).dir().absoluteFilePath("arcaea_offline.ini"),
QSettings.Format.IniFormat,
parent,
)
def __settingsKey(self, key: TSettingsKey) -> str:
if isinstance(key, Enum):
return self.__settingsKey(key.value)
if isinstance(key, str):
return key
raise TypeError(f"{key!r} is not a valid key")
def setValue(self, key: TSettingsKey, value: Any) -> None:
_key = self.__settingsKey(key)
super().setValue(_key, value)
self.updated.emit(_key)
def stringValue(self, key: TSettingsKey) -> str | None:
_key = self.__settingsKey(key)
return self.value(_key, None, type=str)
settings = Settings()

26
core/settings/keys.py Normal file
View File

@ -0,0 +1,26 @@
from dataclasses import dataclass
from enum import StrEnum
class _General(StrEnum):
Language = "Language"
DatabaseUrl = "DatabaseUrl"
class _Ocr(StrEnum):
KnnModelFile = "Ocr/KnnModelFile"
B30KnnModelFile = "Ocr/B30KnnModelFile"
PhashDatabaseFile = "Ocr/PHashDatabaseFile"
DateSource = "Ocr/DateSource"
class _Andreal(StrEnum):
Folder = "Andreal/AndrealFolder"
Executable = "Andreal/AndrealExecutable"
@dataclass(frozen=True)
class SettingsKeys:
General = _General
Ocr = _Ocr
Andreal = _Andreal

17
core/settings/values.py Normal file
View File

@ -0,0 +1,17 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class _Ocr_ScoreDateSource:
FileCreated: str = "FileCreated"
FileLastModified: str = "FileLastModified"
@dataclass(frozen=True)
class _Ocr:
DateSource = _Ocr_ScoreDateSource()
@dataclass(frozen=True)
class SettingsValues:
Ocr = _Ocr()

View File

@ -14,5 +14,5 @@ class Singleton(type, Generic[T]):
return cls._instance
class QObjectSingleton(type(QObject), Singleton):
class QSingleton(type(QObject), Singleton):
pass

View File

@ -9,25 +9,21 @@ from PySide6.QtCore import QCoreApplication, QLocale
from PySide6.QtGui import QFontDatabase, QIcon
from PySide6.QtWidgets import QApplication, QDialog, QMessageBox
import ui.resources.resources_rc
import ui.resources.resources_rc # noqa: F401
from core.settings import SettingsKeys, settings
from ui.extends.shared.language import changeAppLanguage
from ui.extends.shared.settings import Settings
from ui.implements.mainwindow import MainWindow
from ui.startup.databaseChecker import DatabaseChecker, DatabaseCheckerResult
rootLogger = logging.getLogger("root")
rootLogger.setLevel(logging.DEBUG)
rootLoggerFormatter = logging.Formatter(
"[{levelname}]{asctime}|{name}: {msg}", "%m-%d %H:%M:%S", "{"
)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
if issubclass(exc_type, KeyboardInterrupt):
return
rootLogger.critical(
"Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
)
@ -46,6 +42,11 @@ if __name__ == "__main__":
ymd = now.strftime("%Y%m%d")
hms = now.strftime("%H%M%S")
rootLoggerFormatter = logging.Formatter(
"[%(asctime)s/%(levelname)s] %(name)s (%(tag)s): %(message)s",
"%m-%d %H:%M:%S",
defaults={"tag": "/"},
)
rootLoggerFileHandler = logging.FileHandler(
str(logFolder / f"arcaea-offline-pyside-ui-{ymd}-{hms}_debug.log"),
encoding="utf-8",
@ -59,16 +60,19 @@ if __name__ == "__main__":
rootLogger.addHandler(rootLoggerStdOutHandler)
app = QApplication(sys.argv)
locale = (
QLocale(Settings().language()) if Settings().language() else QLocale.system()
)
settingsLanguage = settings.stringValue(SettingsKeys.General.Language)
locale = QLocale(settingsLanguage) if settingsLanguage else QLocale.system()
changeAppLanguage(locale)
QFontDatabase.addApplicationFont(":/fonts/GeosansLight.ttf")
databaseChecker = DatabaseChecker()
databaseChecker.setWindowIcon(QIcon(":/images/icon.png"))
databaseCheckResult = databaseChecker.confirmDb() if Settings().databaseUrl() else 0
databaseCheckResult = (
databaseChecker.confirmDb()
if settings.stringValue(SettingsKeys.General.DatabaseUrl)
else 0
)
if not databaseCheckResult & DatabaseCheckerResult.Initted:
result = databaseChecker.exec()

View File

@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project]
name = "arcaea-offline-pyside-ui"
version = "0.3.8"
version = "0.3.9"
authors = [{ name = "283375", email = "log_283375@163.com" }]
description = "No description."
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"arcaea-offline==0.2.2",
"arcaea-offline-ocr==0.0.98",
"arcaea-offline-ocr==0.0.99",
"exif==1.6.0",
"PySide6==6.5.2",
]
@ -24,19 +24,34 @@ classifiers = [
"Homepage" = "https://github.com/ArcaeaOffline/client-pyside6"
"Bug Tracker" = "https://github.com/ArcaeaOffline/client-pyside6/issues"
[tool.black]
force-exclude = '''
(
ui/designer
| .*_ui.py
| .*_rc.py
)
'''
[tool.ruff]
exclude = ["*_ui.py", "*_rc.py"]
[tool.isort]
profile = "black"
extend_skip = ["ui/designer"]
extend_skip_glob = ["*_ui.py", "*_rc.py"]
[tool.ruff.lint]
# Full list: https://docs.astral.sh/ruff/rules
select = [
"E", # pycodestyle (Error)
"W", # pycodestyle (Warning)
"F", # pyflakes
"I", # isort
"PL", # pylint
"N", # pep8-naming
"A", # flake8-builtins
"DTZ", # flake8-datetimez
"LOG", # flake8-logging
"Q", # flake8-quotes
"G", # flake8-logging-format
"PIE", # flake8-pie
"PT", # flake8-pytest-style
]
ignore = [
"E501", # line-too-long
"N802", # invalid-function-name
"N803", # invalid-argument-name
"N806", # non-lowercase-variable-in-function
"N815", # mixed-case-variable-in-class-scope
"N816", # mixed-case-variable-in-global-scope
]
[tool.pyright]
ignore = ["**/__debug*.*"]

View File

@ -1,4 +1,3 @@
black == 23.7.0
isort == 5.12.0
imageio==2.31.4
Nuitka==1.8.4
ruff
imageio
Nuitka~=2.7.6

View File

@ -1,5 +1,5 @@
arcaea-offline==0.2.2
arcaea-offline-ocr==0.0.98
exif==1.6.0
Pillow==10.1.0
PySide6==6.5.2
arcaea-offline-ocr==0.0.99
exif~=1.6.0
Pillow~=10.1.0
PySide6==6.9.1

View File

@ -22,39 +22,6 @@
<string>queue.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>iccOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="iccUseQtRadioButton">
<property name="text">
<string>icc.useQt</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccUsePILRadioButton">
<property name="text">
<string>icc.usePIL</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccTryFixRadioButton">
<property name="text">
<string>icc.tryFix</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_addImageButton">
<property name="text">
@ -95,6 +62,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="optionsDialogButton">
<property name="text">
<string>queue.optionsButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_startButton">
<property name="text">

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OcrQueueOptionsDialog</class>
<widget class="QDialog" name="OcrQueueOptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>331</width>
<height>157</height>
</rect>
</property>
<property name="windowTitle">
<string>OCR Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>iccOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="iccUseQtRadioButton">
<property name="text">
<string>icc.useQt</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccUsePILRadioButton">
<property name="text">
<string>icc.usePIL</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccTryFixRadioButton">
<property name="text">
<string>icc.tryFix</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>dateOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="dateReadFromExifCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>date.readFromExif</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="dateUseCreationDateRadioButton">
<property name="text">
<string>date.useCreationDate</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="dateUseModifyDateRadioButton">
<property name="text">
<string>date.useModifyDate</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OcrQueueOptionsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OcrQueueOptionsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'ocrQueueOptionsDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
QDialogButtonBox, QGroupBox, QHBoxLayout, QRadioButton,
QSizePolicy, QVBoxLayout, QWidget)
class Ui_OcrQueueOptionsDialog(object):
def setupUi(self, OcrQueueOptionsDialog):
if not OcrQueueOptionsDialog.objectName():
OcrQueueOptionsDialog.setObjectName(u"OcrQueueOptionsDialog")
OcrQueueOptionsDialog.resize(331, 157)
self.verticalLayout = QVBoxLayout(OcrQueueOptionsDialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.groupBox = QGroupBox(OcrQueueOptionsDialog)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout_2 = QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.iccUseQtRadioButton = QRadioButton(self.groupBox)
self.iccUseQtRadioButton.setObjectName(u"iccUseQtRadioButton")
self.verticalLayout_2.addWidget(self.iccUseQtRadioButton)
self.iccUsePILRadioButton = QRadioButton(self.groupBox)
self.iccUsePILRadioButton.setObjectName(u"iccUsePILRadioButton")
self.iccUsePILRadioButton.setChecked(True)
self.verticalLayout_2.addWidget(self.iccUsePILRadioButton)
self.iccTryFixRadioButton = QRadioButton(self.groupBox)
self.iccTryFixRadioButton.setObjectName(u"iccTryFixRadioButton")
self.verticalLayout_2.addWidget(self.iccTryFixRadioButton)
self.horizontalLayout.addWidget(self.groupBox)
self.groupBox_2 = QGroupBox(OcrQueueOptionsDialog)
self.groupBox_2.setObjectName(u"groupBox_2")
self.verticalLayout_3 = QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.dateReadFromExifCheckbox = QCheckBox(self.groupBox_2)
self.dateReadFromExifCheckbox.setObjectName(u"dateReadFromExifCheckbox")
self.dateReadFromExifCheckbox.setEnabled(False)
self.dateReadFromExifCheckbox.setChecked(True)
self.verticalLayout_3.addWidget(self.dateReadFromExifCheckbox)
self.dateUseCreationDateRadioButton = QRadioButton(self.groupBox_2)
self.dateUseCreationDateRadioButton.setObjectName(u"dateUseCreationDateRadioButton")
self.dateUseCreationDateRadioButton.setChecked(True)
self.verticalLayout_3.addWidget(self.dateUseCreationDateRadioButton)
self.dateUseModifyDateRadioButton = QRadioButton(self.groupBox_2)
self.dateUseModifyDateRadioButton.setObjectName(u"dateUseModifyDateRadioButton")
self.verticalLayout_3.addWidget(self.dateUseModifyDateRadioButton)
self.horizontalLayout.addWidget(self.groupBox_2)
self.verticalLayout.addLayout(self.horizontalLayout)
self.buttonBox = QDialogButtonBox(OcrQueueOptionsDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(OcrQueueOptionsDialog)
self.buttonBox.accepted.connect(OcrQueueOptionsDialog.accept)
self.buttonBox.rejected.connect(OcrQueueOptionsDialog.reject)
QMetaObject.connectSlotsByName(OcrQueueOptionsDialog)
# setupUi
def retranslateUi(self, OcrQueueOptionsDialog):
OcrQueueOptionsDialog.setWindowTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"OCR Options", None))
self.groupBox.setTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"iccOptionsGroupBox", None))
self.iccUseQtRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.useQt", None))
self.iccUsePILRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.usePIL", None))
self.iccTryFixRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.tryFix", None))
self.groupBox_2.setTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"dateOptionsGroupBox", None))
self.dateReadFromExifCheckbox.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.readFromExif", None))
self.dateUseCreationDateRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.useCreationDate", None))
self.dateUseModifyDateRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.useModifyDate", None))
# retranslateUi

View File

@ -17,8 +17,8 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QGroupBox,
QHBoxLayout, QHeaderView, QLabel, QProgressBar,
QPushButton, QRadioButton, QSizePolicy, QSpacerItem,
QTableView, QVBoxLayout, QWidget)
QPushButton, QSizePolicy, QSpacerItem, QTableView,
QVBoxLayout, QWidget)
class Ui_OcrQueue(object):
def setupUi(self, OcrQueue):
@ -34,29 +34,6 @@ class Ui_OcrQueue(object):
self.groupBox_3.setObjectName(u"groupBox_3")
self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.groupBox = QGroupBox(self.groupBox_3)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout = QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName(u"verticalLayout")
self.iccUseQtRadioButton = QRadioButton(self.groupBox)
self.iccUseQtRadioButton.setObjectName(u"iccUseQtRadioButton")
self.verticalLayout.addWidget(self.iccUseQtRadioButton)
self.iccUsePILRadioButton = QRadioButton(self.groupBox)
self.iccUsePILRadioButton.setObjectName(u"iccUsePILRadioButton")
self.iccUsePILRadioButton.setChecked(True)
self.verticalLayout.addWidget(self.iccUsePILRadioButton)
self.iccTryFixRadioButton = QRadioButton(self.groupBox)
self.iccTryFixRadioButton.setObjectName(u"iccTryFixRadioButton")
self.verticalLayout.addWidget(self.iccTryFixRadioButton)
self.verticalLayout_2.addWidget(self.groupBox)
self.ocr_addImageButton = QPushButton(self.groupBox_3)
self.ocr_addImageButton.setObjectName(u"ocr_addImageButton")
@ -78,6 +55,11 @@ class Ui_OcrQueue(object):
self.verticalLayout_2.addItem(self.verticalSpacer)
self.optionsDialogButton = QPushButton(self.groupBox_3)
self.optionsDialogButton.setObjectName(u"optionsDialogButton")
self.verticalLayout_2.addWidget(self.optionsDialogButton)
self.ocr_startButton = QPushButton(self.groupBox_3)
self.ocr_startButton.setObjectName(u"ocr_startButton")
@ -154,13 +136,10 @@ class Ui_OcrQueue(object):
def retranslateUi(self, OcrQueue):
self.groupBox_3.setTitle(QCoreApplication.translate("OcrQueue", u"queue.title", None))
self.groupBox.setTitle(QCoreApplication.translate("OcrQueue", u"iccOptionsGroupBox", None))
self.iccUseQtRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.useQt", None))
self.iccUsePILRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.usePIL", None))
self.iccTryFixRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.tryFix", None))
self.ocr_addImageButton.setText(QCoreApplication.translate("OcrQueue", u"queue.addImageButton", None))
self.ocr_removeSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeSelected", None))
self.ocr_removeAllButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeAll", None))
self.optionsDialogButton.setText(QCoreApplication.translate("OcrQueue", u"queue.optionsButton", None))
self.ocr_startButton.setText(QCoreApplication.translate("OcrQueue", u"queue.startOcrButton", None))
self.groupBox_5.setTitle(QCoreApplication.translate("OcrQueue", u"results", None))
self.ocr_acceptSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"results.acceptSelectedButton", None))
@ -169,4 +148,3 @@ class Ui_OcrQueue(object):
self.statusLabel.setText("")
pass
# retranslateUi

View File

@ -5,7 +5,6 @@ from typing import Any, Callable, Optional, overload
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.database import Database
from arcaea_offline.models import Chart, Score
from arcaea_offline_ocr.b30.shared import B30OcrResultItem
from arcaea_offline_ocr.device.common import DeviceOcrResult
from PIL import Image
from PIL.ImageQt import ImageQt
@ -140,7 +139,11 @@ class OcrQueueModel(QAbstractListModel):
return True
else:
logger.warning(
f"{repr(self)} setData at row {index.row()} with role {role} and value {value} rejected."
"%r setData at row %d with role %d and value %s rejected!",
self,
index.row(),
role,
value,
)
return False
@ -150,6 +153,7 @@ class OcrQueueModel(QAbstractListModel):
@iccOption.setter
def iccOption(self, opt: IccOption):
logger.debug("ICC option changed to %s", opt)
self.__iccOption = opt
@overload
@ -158,8 +162,7 @@ class OcrQueueModel(QAbstractListModel):
image: str,
runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None,
):
...
): ...
@overload
def addItem(
@ -167,8 +170,7 @@ class OcrQueueModel(QAbstractListModel):
image: QImage,
runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None,
):
...
): ...
def addItem(
self,
@ -178,7 +180,7 @@ class OcrQueueModel(QAbstractListModel):
):
if isinstance(image, str):
if image in self.imagePaths or not QFileInfo(image).exists():
logger.warning(f"Attempting to add an invalid file {image}")
logger.warning("Attempting to add an invalid file %s", image)
return
imagePath = image
if self.iccOption == IccOption.TryFix:
@ -222,7 +224,7 @@ class OcrQueueModel(QAbstractListModel):
index = self.index(row, 0)
imagePath: str = index.data(self.ImagePathRole)
qImage: QImage = index.data(self.ImageQImageRole)
logger.debug(f"update request: {result}@row{row}")
logger.debug("update request: %r@row%d", result, row)
processOcrResultFunc = index.data(self.ProcessOcrResultFuncRole)
chart, scoreInsert = processOcrResultFunc(imagePath, qImage, result)
@ -293,8 +295,8 @@ class OcrQueueModel(QAbstractListModel):
self.__items.pop(row)
self.endRemoveRows()
return
except Exception as e:
logger.exception(f"Error accepting {repr(item)}")
except Exception:
logger.exception("Error accepting %r", item)
return
def acceptItems(self, __rows: list[int], ignoreValidate: bool = False):
@ -343,13 +345,11 @@ class OcrQueueTableProxyModel(QAbstractTableModel):
def retranslateHeaders(self):
self.__horizontalHeaders = [
# fmt: off
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.select"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.imagePreview"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.chart"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.score"),
# fmt: on
]
] # fmt: skip
def sourceModel(self) -> OcrQueueModel:
return self.__sourceModel

View File

@ -1,16 +0,0 @@
from PySide6.QtGui import QColor
def mix_color(source_color: QColor, mix_color: QColor, mix_ratio: float = 0.5):
r = round((mix_color.red() - source_color.red()) * mix_ratio + source_color.red())
g = round(
(mix_color.green() - source_color.green()) * mix_ratio + source_color.green()
)
b = round(
(mix_color.blue() - source_color.blue()) * mix_ratio + source_color.blue()
)
a = round(
(mix_color.alpha() - source_color.alpha()) * mix_ratio + source_color.alpha()
)
return QColor(r, g, b, a)

View File

@ -7,7 +7,7 @@ from typing import Literal, Optional, overload
from arcaea_offline.models import Chart, Difficulty, Song
from PySide6.QtCore import QFile
from .singleton import Singleton
from core.singleton import Singleton
TPartnerModifier = dict[str, Literal[0, 1, 2]]
@ -48,14 +48,12 @@ class Data(metaclass=Singleton):
return self.dataPath / "Arcaea"
@overload
def getJacketPath(self, chart: Chart, /) -> Path | None:
...
def getJacketPath(self, chart: Chart, /) -> Path | None: ...
@overload
def getJacketPath(
self, song: Song, difficulty: Optional[Difficulty] = None, /
) -> Path | None:
...
) -> Path | None: ...
def getJacketPath(self, *args) -> Path | None:
if isinstance(args[0], Chart):

View File

@ -16,13 +16,11 @@ class DbB30TableModel(DbTableModel):
def retranslateHeaders(self):
self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.potential"),
# fmt: on
]
] # fmt: skip
def syncDb(self):
self.beginResetModel()
@ -39,7 +37,7 @@ class DbB30TableModel(DbTableModel):
(ScoreBest.song_id == Chart.song_id)
& (ScoreBest.rating_class == Chart.rating_class),
)
.order_by(ScoreBest.potential)
.order_by(ScoreBest.potential.desc())
.limit(50)
.all()
)

View File

@ -24,13 +24,11 @@ class DbScoreTableModel(DbTableModel):
def retranslateHeaders(self):
self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.potential"),
# fmt: on
]
] # fmt: skip
def syncDb(self):
self.beginResetModel()
@ -154,7 +152,7 @@ class DbScoreTableModel(DbTableModel):
self.syncDb()
return True
except Exception:
logger.exception(f"Table[Score]: Cannot remove row {row}")
logger.exception("Table[Score]: Cannot remove row %s", row)
return False
def removeRow(self, row: int, parent=...):

View File

@ -1,111 +0,0 @@
import sys
from PySide6.QtCore import QFileInfo, QSettings, Signal
from .singleton import QObjectSingleton
__all__ = [
"LANGUAGE",
"DATABASE_URL",
"KNN_MODEL_FILE",
"B30_KNN_MODEL_FILE",
"PHASH_DATABASE_FILE",
"ANDREAL_FOLDER",
"ANDREAL_EXECUTABLE",
"Settings",
]
# a key without slashes will appear in the "General" section
# see https://doc.qt.io/qt-6/qsettings.html#Format-enum for details
LANGUAGE = "Language"
DATABASE_URL = "DatabaseUrl"
KNN_MODEL_FILE = "Ocr/KnnModelFile"
B30_KNN_MODEL_FILE = "Ocr/B30KnnModelFile"
PHASH_DATABASE_FILE = "Ocr/PHashDatabaseFile"
ANDREAL_FOLDER = "Andreal/AndrealFolder"
ANDREAL_EXECUTABLE = "Andreal/AndrealExecutable"
class Settings(QSettings, metaclass=QObjectSingleton):
updated = Signal(str)
def __init__(self, parent=None):
super().__init__(
QFileInfo(sys.argv[0]).dir().absoluteFilePath("arcaea_offline.ini"),
QSettings.Format.IniFormat,
parent,
)
def setValue(self, key: str, value) -> None:
super().setValue(key, value)
self.updated.emit(key)
def _strItem(self, key: str) -> str | None:
return self.value(key, None, str)
def _setStrItem(self, key: str, value: str):
self.setValue(key, value)
self.sync()
def _resetStrItem(self, key: str):
self.setValue(key, None)
self.sync()
def language(self):
return self._strItem(LANGUAGE)
def setLanguage(self, value: str):
self._setStrItem(LANGUAGE, value)
def databaseUrl(self):
return self._strItem(DATABASE_URL)
def setDatabaseUrl(self, value: str):
self._setStrItem(DATABASE_URL, value)
def knnModelFile(self):
return self._strItem(KNN_MODEL_FILE)
def setKnnModelFile(self, value: str):
self._setStrItem(KNN_MODEL_FILE, value)
def resetKnnModelFile(self):
self._resetStrItem(KNN_MODEL_FILE)
def b30KnnModelFile(self):
return self._strItem(B30_KNN_MODEL_FILE)
def setB30KnnModelFile(self, value: str):
self._setStrItem(B30_KNN_MODEL_FILE, value)
def resetB30KnnModelFile(self):
self._resetStrItem(B30_KNN_MODEL_FILE)
def phashDatabaseFile(self):
return self._strItem(PHASH_DATABASE_FILE)
def setPHashDatabaseFile(self, value: str):
self._setStrItem(PHASH_DATABASE_FILE, value)
def resetPHashDatabaseFile(self):
self._resetStrItem(PHASH_DATABASE_FILE)
def andrealFolder(self):
return self._strItem(ANDREAL_FOLDER)
def setAndrealFolder(self, value: str):
self._setStrItem(ANDREAL_FOLDER, value)
def resetAndrealFolder(self):
self._resetStrItem(ANDREAL_FOLDER)
def andrealExecutable(self):
return self._strItem(ANDREAL_EXECUTABLE)
def setAndrealExecutable(self, value: str):
self._setStrItem(ANDREAL_EXECUTABLE, value)
def resetAndrealExecutable(self):
self._resetStrItem(ANDREAL_EXECUTABLE)

View File

@ -6,10 +6,10 @@ from arcaea_offline_ocr.b30.chieri.v4.ocr import ChieriBotV4Ocr
from arcaea_offline_ocr.b30.shared import B30OcrResultItem
from PySide6.QtGui import QImage
logger = logging.getLogger(__name__)
from ui.extends.components.ocrQueue import OcrRunnable
logger = logging.getLogger(__name__)
class ChieriV4OcrRunnable(OcrRunnable):
def __init__(self, ocr: ChieriBotV4Ocr, component):

View File

@ -19,6 +19,7 @@ from arcaea_offline_ocr.phash_db import ImagePhashDatabase
from arcaea_offline_ocr.utils import imread_unicode
from PySide6.QtCore import QDateTime, QFileInfo
from core.settings import SettingsKeys, SettingsValues, settings
from ui.extends.components.ocrQueue import OcrRunnable
from ui.extends.shared.data import Data
@ -67,8 +68,14 @@ def getImageDate(imagePath: str) -> QDateTime:
if exifImage.has_exif and exifImage.get("datetime_original"):
datetimeStr = exifImage.get("datetime_original")
datetime = QDateTime.fromString(datetimeStr, "yyyy:MM:dd hh:mm:ss")
if not isinstance(datetime, QDateTime):
dateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if dateSource == SettingsValues.Ocr.DateSource.FileLastModified:
datetime = QFileInfo(imagePath).lastModified()
else:
datetime = QFileInfo(imagePath).birthTime()
return datetime

View File

@ -41,7 +41,7 @@ class AndrealExecuteRunnable(QRunnable):
self.signals.completed.emit(self.jsonPath, imageBytes)
except Exception as e:
imageBytes = None
logger.exception(f"{self.__class__.__name__} error")
logger.exception("%s error", self.__class__.__name__)
self.signals.error.emit(self.jsonPath, str(e))
finally:
os.unlink(self.jsonPath)
@ -84,7 +84,10 @@ class AndrealHelper(QObject):
def request(self, jsonPath: str, arguments: list[str]):
logger.debug(
f"{self.__class__.__name__} received request {jsonPath=} {arguments=}"
"%s received request jsonPath=%r arguments=%r",
self.__class__.__name__,
jsonPath,
arguments,
)
runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments)
runnable.signals.error.connect(self.error)

View File

@ -2,9 +2,9 @@ from PySide6.QtCore import QDir, QFileInfo, Qt, Signal, Slot
from PySide6.QtGui import QDragEnterEvent, QDragLeaveEvent, QDropEvent
from PySide6.QtWidgets import QFileDialog, QWidget
from core.settings import settings
from ui.designer.components.fileSelector_ui import Ui_FileSelector
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class FileSelector(Ui_FileSelector, QWidget):
@ -122,13 +122,13 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles:
return
if value := Settings().value(self.settingsKey):
if value := settings.value(self.settingsKey):
self.selectFile(value)
Settings().updated.connect(self.settingsUpdated)
settings.updated.connect(self.settingsUpdated)
def disconnectSettings(self):
Settings().updated.disconnect(self.settingsUpdated)
settings.updated.disconnect(self.settingsUpdated)
self.settingsKey = None
def settingsUpdated(self, key: str):
@ -139,4 +139,4 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles:
return
self.selectFile(Settings().value(self.settingsKey))
self.selectFile(settings.value(self.settingsKey))

View File

@ -2,7 +2,7 @@ from typing import Optional
from PySide6.QtCore import Qt, QTimer, Slot
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QButtonGroup, QWidget
from PySide6.QtWidgets import QWidget
from ui.designer.components.ocrQueue_ui import Ui_OcrQueue
from ui.extends.components.ocrQueue import (
@ -13,6 +13,7 @@ from ui.extends.components.ocrQueue import (
OcrScoreDelegate,
)
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.implements.components.ocrQueueOptionsDialog import OcrQueueOptionsDialog
class OcrQueue(Ui_OcrQueue, QWidget):
@ -26,6 +27,9 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.__model: Optional[OcrQueueModel] = None
self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None
self.optionsDialog = OcrQueueOptionsDialog(self)
self.optionsDialog.iccOptionsChanged.connect(self.setIccOption)
self.__firstResizeDone = False
self.resizeTimer = QTimer(self)
self.resizeTimer.timeout.connect(self.tableView.resizeRowsToContents)
@ -41,13 +45,6 @@ class OcrQueue(Ui_OcrQueue, QWidget):
tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor)
self.tableView.setPalette(tableViewPalette)
self.iccOptionButtonGroup = QButtonGroup(self)
self.iccOptionButtonGroup.buttonToggled.connect(self.updateIccOption)
self.iccOptionButtonGroup.addButton(self.iccUseQtRadioButton, 0)
self.iccOptionButtonGroup.addButton(self.iccUsePILRadioButton, 1)
self.iccOptionButtonGroup.addButton(self.iccTryFixRadioButton, 2)
self.updateIccOption()
self.statusLabelClearTimer = QTimer(self)
self.statusLabelClearTimer.setSingleShot(True)
self.statusLabelClearTimer.timeout.connect(self.clearStatusMessage)
@ -93,9 +90,10 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.ocr_acceptAllButton.setEnabled(__bool)
self.ocr_ignoreValidateCheckBox.setEnabled(__bool)
def updateIccOption(self):
@Slot(int)
def setIccOption(self, option):
if self.model():
self.model().iccOption = self.iccOptionButtonGroup.checkedId()
self.model().iccOption = option
def showStatusMessage(self, message: str):
self.statusLabel.setText(message)
@ -131,6 +129,10 @@ class OcrQueue(Ui_OcrQueue, QWidget):
def modelReseted(self):
self.progressBar.setMaximum(0)
@Slot()
def on_optionsDialogButton_clicked(self):
self.optionsDialog.exec()
@Slot()
def on_ocr_removeSelectedButton_clicked(self):
if self.model():

View File

@ -0,0 +1,49 @@
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QButtonGroup, QDialog
from core.settings import SettingsKeys, SettingsValues, settings
from ui.designer.components.ocrQueueOptionsDialog_ui import Ui_OcrQueueOptionsDialog
class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog):
iccOptionsChanged = Signal(int)
def __init__(self, parent=None):
super(OcrQueueOptionsDialog, self).__init__(parent)
self.setupUi(self)
self.iccOptionButtonGroup = QButtonGroup(self)
self.iccOptionButtonGroup.buttonToggled.connect(
lambda: self.iccOptionsChanged.emit(self.iccOptionButtonGroup.checkedId())
)
self.iccOptionButtonGroup.addButton(self.iccUseQtRadioButton, 0)
self.iccOptionButtonGroup.addButton(self.iccUsePILRadioButton, 1)
self.iccOptionButtonGroup.addButton(self.iccTryFixRadioButton, 2)
self.scoreDateSourceButtonGroup = QButtonGroup(self)
self.scoreDateSourceButtonGroup.addButton(
self.dateUseCreationDateRadioButton, 0
)
self.scoreDateSourceButtonGroup.addButton(self.dateUseModifyDateRadioButton, 1)
self.scoreDateSourceButtonGroup.buttonClicked.connect(
self.on_scoreDateSourceButtonGroup_buttonClicked
)
settings.updated.connect(self.syncCheckboxesFromSettings)
self.syncCheckboxesFromSettings()
def syncCheckboxesFromSettings(self):
scoreDateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if scoreDateSource == SettingsValues.Ocr.DateSource.FileLastModified:
self.dateUseModifyDateRadioButton.setChecked(True)
else:
self.dateUseCreationDateRadioButton.setChecked(True)
def on_scoreDateSourceButtonGroup_buttonClicked(self, button):
buttonId = self.scoreDateSourceButtonGroup.id(button)
if buttonId == 1:
value = SettingsValues.Ocr.DateSource.FileLastModified
else:
value = SettingsValues.Ocr.DateSource.FileCreated
settings.setValue(SettingsKeys.Ocr.DateSource, value)

View File

@ -2,7 +2,7 @@ from PySide6.QtCore import Slot
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QGraphicsColorizeEffect, QRadioButton
from ui.extends.shared.color import mix_color
from core.color import mixColor
STYLESHEET = """
QRadioButton {{
@ -40,7 +40,7 @@ class RatingClassRadioButton(QRadioButton):
def setColors(self, dark_color: QColor, text_color: QColor):
self._dark_color = dark_color
self._text_color = text_color
self._mid_color = mix_color(dark_color, text_color, 0.616)
self._mid_color = mixColor(dark_color, text_color, 0.616)
self.updateEffects()
def isColorsSet(self) -> bool:

View File

@ -126,7 +126,7 @@ class RatingClassSelector(QWidget):
elif ratingClass in range(len(self.buttons)):
button = self.buttons[ratingClass]
else:
logger.debug(f"Cannot select {ratingClass=}, condition check failed")
logger.debug("Cannot select ratingClass=%s, condition check failed", ratingClass)
return
if not button.isEnabled():

View File

@ -61,30 +61,22 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
VALIDATION_ITEMS_TEXT = [
[
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.text"),
# fmt: on
],
[
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.text"),
# fmt: on
],
[
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.text"),
# fmt: on
],
[
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.text"),
# fmt: on,
],
]
] # fmt: skip
def __init__(self, parent=None):
super().__init__(parent)
@ -208,20 +200,16 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
if validate & ScoreValidateResult.ChartNotSet:
self.__triggerMessageBox(
"critical",
# fmt: off
QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.title"),
QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.text"),
# fmt: on
)
) # fmt: skip
return False
if validate & ScoreValidateResult.ScoreIncomplete:
self.__triggerMessageBox(
"critical",
# fmt: off
QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.title"),
QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.text"),
# fmt: on
)
) # fmt: skip
return False
# since validate may have multiple results
@ -347,10 +335,8 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
)
if validate & ScoreValidateResult.ScoreIncompleteForValidate:
texts.append(
# fmt: off
QCoreApplication.translate("ScoreEditor", "validate.scoreIncompleteForValidate")
# fmt: on
)
) # fmt: skip
if not texts:
texts.append(

View File

@ -192,7 +192,7 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
self.fillSongIdComboBox()
return True
else:
logger.warning(f'Attempting to select an unknown pack "{packId}"')
logger.warning("Attempting to select an unknown pack [%s]", packId)
return False
def selectSongId(self, songId: str) -> bool:
@ -202,7 +202,8 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
return True
else:
logger.warning(
f'Attempting to select an unknown song "{songId}", maybe try selecting a pack first?'
"Attempting to select an unknown song [%s], maybe try selecting a pack first?",
songId,
)
return False

View File

@ -1,6 +1,7 @@
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton
from core.settings import SettingsKeys, settings
from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -14,8 +15,8 @@ class SettingsAndreal(SettingsBaseWidget):
self.andrealFolderValueWidget.setMode(
self.andrealFolderValueWidget.getExistingDirectory
)
if self.settings.andrealFolder():
self.andrealFolderValueWidget.selectFile(self.settings.andrealFolder())
if andrealFolder := settings.stringValue(SettingsKeys.Andreal.Folder):
self.andrealFolderValueWidget.selectFile(andrealFolder)
self.andrealFolderValueWidget.filesSelected.connect(self.setAndrealFolder)
self.andrealFolderResetButton.clicked.connect(self.resetAndrealFolder)
self.insertItem(
@ -25,10 +26,8 @@ class SettingsAndreal(SettingsBaseWidget):
self.andrealFolderResetButton,
)
if self.settings.andrealExecutable():
self.andrealExecutableValueWidget.selectFile(
self.settings.andrealExecutable()
)
if andrealExecutable := settings.stringValue(SettingsKeys.Andreal.Executable):
self.andrealExecutableValueWidget.selectFile(andrealExecutable)
self.andrealExecutableValueWidget.filesSelected.connect(
self.setAndrealExecutable
)
@ -44,21 +43,21 @@ class SettingsAndreal(SettingsBaseWidget):
selectedFile = self.andrealFolderValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setAndrealFolder(file)
settings.setValue(SettingsKeys.Andreal.Folder, file)
def resetAndrealFolder(self):
self.andrealFolderValueWidget.reset()
self.settings.resetAndrealFolder()
settings.setValue(SettingsKeys.Andreal.Folder, None)
def setAndrealExecutable(self):
selectedFile = self.andrealExecutableValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setAndrealExecutable(file)
settings.setValue(SettingsKeys.Andreal.Executable, file)
def resetAndrealExecutable(self):
self.andrealExecutableValueWidget.reset()
self.settings.resetAndrealExecutable()
settings.setValue(SettingsKeys.Andreal.Executable, None)
def setupUi(self, *args):
self.andrealFolderLabel = QLabel(self)

View File

@ -1,15 +1,15 @@
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLabel, QPushButton, QWidget
from core.settings import settings
from ui.designer.settings.settingsBaseWidget_ui import Ui_SettingsBaseWidget
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class SettingsBaseWidget(Ui_SettingsBaseWidget, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.settings = Settings()
self.settings = settings
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)

View File

@ -1,6 +1,4 @@
import sys
from PySide6.QtCore import QCoreApplication, QDir, QLocale, QProcess
from PySide6.QtCore import QCoreApplication, QDir, QLocale
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
@ -10,8 +8,8 @@ from PySide6.QtWidgets import (
QPushButton,
)
from core.settings import SettingsKeys, settings
from ui.extends.shared.language import changeAppLanguage, localeToCode, localeToFullName
from ui.extends.shared.settings import DATABASE_URL, LANGUAGE
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -33,8 +31,8 @@ class SettingsGeneral(SettingsBaseWidget):
self.languageFollowSystemCheckBox.toggled.connect(
self.changeLanguageFollowSystem
)
if self.settings.language():
locale = QLocale(self.settings.language())
if language := settings.stringValue(SettingsKeys.General.Language):
locale = QLocale(language)
index = self.languageValueWidget.findData(locale)
if index > -1:
self.languageValueWidget.setCurrentIndex(index)
@ -51,7 +49,7 @@ class SettingsGeneral(SettingsBaseWidget):
self.insertItem(
"dbUrl",
self.dbUrlLabel,
QLabel(self.settings.databaseUrl()),
QLabel(settings.stringValue(SettingsKeys.General.DatabaseUrl)),
self.dbUrlResetButton,
)
@ -59,13 +57,13 @@ class SettingsGeneral(SettingsBaseWidget):
locale = self.languageValueWidget.currentData()
if locale:
changeAppLanguage(locale)
self.settings.setLanguage(localeToCode(locale))
settings.setValue(SettingsKeys.General.Language, localeToCode(locale))
def changeLanguageFollowSystem(self):
followSystem = self.languageFollowSystemCheckBox.isChecked()
self.languageValueWidget.setCurrentIndex(-1)
if followSystem:
self.settings.remove(LANGUAGE)
settings.remove(SettingsKeys.General.Language)
changeAppLanguage(QLocale.system())
self.languageValueWidget.setEnabled(False)
else:
@ -80,7 +78,7 @@ class SettingsGeneral(SettingsBaseWidget):
QMessageBox.StandardButton.No,
)
if userConfirm == QMessageBox.StandardButton.Yes:
self.settings.remove(DATABASE_URL)
settings.remove(SettingsKeys.General.DatabaseUrl)
QApplication.instance().quit()
def setupUi(self, *args):

View File

@ -1,6 +1,7 @@
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton
from core.settings import SettingsKeys, settings
from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -11,8 +12,8 @@ class SettingsOcr(SettingsBaseWidget):
self.setupUi(self)
if self.settings.knnModelFile():
self.knnModelFileValueWidget.selectFile(self.settings.knnModelFile())
if knnModelFile := settings.stringValue(SettingsKeys.Ocr.KnnModelFile):
self.knnModelFileValueWidget.selectFile(knnModelFile)
self.knnModelFileValueWidget.filesSelected.connect(self.setKnnModelFile)
self.knnModelFileResetButton.clicked.connect(self.resetKnnModelFile)
self.insertItem(
@ -22,8 +23,8 @@ class SettingsOcr(SettingsBaseWidget):
self.knnModelFileResetButton,
)
if self.settings.b30KnnModelFile():
self.b30KnnModelFileValueWidget.selectFile(self.settings.b30KnnModelFile())
if b30KnnModelFile := settings.stringValue(SettingsKeys.Ocr.B30KnnModelFile):
self.b30KnnModelFileValueWidget.selectFile(b30KnnModelFile)
self.b30KnnModelFileValueWidget.filesSelected.connect(self.setB30KnnModelFile)
self.b30KnnModelFileResetButton.clicked.connect(self.resetB30KnnModelFile)
self.insertItem(
@ -33,10 +34,10 @@ class SettingsOcr(SettingsBaseWidget):
self.b30KnnModelFileResetButton,
)
if self.settings.phashDatabaseFile():
self.phashDatabaseFileValueWidget.selectFile(
self.settings.phashDatabaseFile()
)
if phashDatabaseFile := settings.stringValue(
SettingsKeys.Ocr.PhashDatabaseFile
):
self.phashDatabaseFileValueWidget.selectFile(phashDatabaseFile)
self.phashDatabaseFileValueWidget.filesSelected.connect(
self.setPHashDatabaseFile
)
@ -52,31 +53,31 @@ class SettingsOcr(SettingsBaseWidget):
selectedFile = self.knnModelFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setKnnModelFile(file)
settings.setValue(SettingsKeys.Ocr.KnnModelFile, file)
def resetKnnModelFile(self):
self.knnModelFileValueWidget.reset()
self.settings.resetKnnModelFile()
settings.setValue(SettingsKeys.Ocr.KnnModelFile, None)
def setB30KnnModelFile(self):
selectedFile = self.b30KnnModelFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setB30KnnModelFile(file)
settings.setValue(SettingsKeys.Ocr.B30KnnModelFile, file)
def resetB30KnnModelFile(self):
self.b30KnnModelFileValueWidget.reset()
self.settings.resetB30KnnModelFile()
settings.setValue(SettingsKeys.Ocr.B30KnnModelFile, None)
def setPHashDatabaseFile(self):
selectedFile = self.phashDatabaseFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setPHashDatabaseFile(file)
settings.setValue(SettingsKeys.Ocr.PhashDatabaseFile, file)
def resetPHashDatabaseFile(self):
self.phashDatabaseFileValueWidget.reset()
self.settings.resetPHashDatabaseFile()
settings.setValue(SettingsKeys.Ocr.PhashDatabaseFile, None)
def setupUi(self, *args):
self.knnModelFileLabel = QLabel(self)

View File

@ -163,19 +163,15 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on
)
) # fmt: skip
return
if not self.constantLineEdit.hasAcceptableInput():
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.constantRequired"),
# fmt: on
)
) # fmt: skip
return
constant = int(self.constantLineEdit.text())
@ -202,10 +198,8 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on
)
) # fmt: skip
return
chartInfo = self.db.get_chart_info(chart.song_id, chart.rating_class)
@ -213,12 +207,10 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
result = QMessageBox.warning(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "deleteConfirm"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
) # fmt: skip
if result == QMessageBox.StandardButton.Yes:
with self.db.sessionmaker() as session:
session.delete(chartInfo)

View File

@ -91,7 +91,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
session.commit()
databaseUpdateSignals.songAddOrDelete.emit()
itemNum = len([item for item in parser.parse() if isinstance(item, instance)])
logger.info(f"updated {itemNum} {logName} from {path}")
logger.info("updated %d %s from %s", itemNum, logName, path)
return itemNum
def importPacklist(self, packlistPath):
@ -161,7 +161,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
return
try:
logger.info(f"Importing {apkFile}")
logger.info("Importing %s", apkFile)
with zipfile.ZipFile(apkFile) as zf:
packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist"
@ -193,7 +193,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database()
parser = St3ScoreParser(dbFile)
logger.info(
f"Got {len(parser.parse())} items from {dbFile}, writing into database..."
"Got %d items from %s, writing into database...",
len(parser.parse()),
dbFile,
)
with db.sessionmaker() as session:
parser.write_database(session)
@ -218,7 +220,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database()
parser = ArcaeaOnlineParser(apiResultFile)
logger.info(
f"Got {len(parser.parse())} items from {apiResultFile}, writing into database..."
"Got %d items from %s, writing into database...",
len(parser.parse()),
apiResultFile,
)
with db.sessionmaker() as session:
parser.write_database(session)

View File

@ -154,23 +154,17 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
self.treeView.setItemDelegateForColumn(1, self.treeViewProxyDelegate)
self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.idEarlier"),
# fmt: on
QuickSelectComboBoxValues.ID_EARLIER
)
) # fmt: skip
self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.dateEarlier"),
# fmt: on
QuickSelectComboBoxValues.DATE_EARLIER
)
) # fmt: skip
self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.columnsIntegral"),
# fmt: on
QuickSelectComboBoxValues.COLUMNS_INTEGRAL
)
) # fmt: skip
def getQueryColumns(self):
columns: list[InstrumentedAttribute] = [Score.song_id, Score.rating_class]
@ -291,12 +285,12 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
confirm = QMessageBox.warning(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "deleteSelectionDialog.content {}").format(len(selectedScores)),
# fmt: on
QCoreApplication.translate(
"TabDb_RemoveDuplicateScores", "deleteSelectionDialog.content {}"
).format(len(selectedScores)),
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
) # fmt: skip
if confirm != QMessageBox.StandardButton.Yes:
return
@ -310,12 +304,11 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
@Slot()
def on_scan_scanButton_clicked(self):
if len(self.getQueryColumns()) <= 2:
message = QCoreApplication.translate("TabDb_RemoveDuplicateScores", "scan_noColumnsDialog.content") # fmt: skip
result = QMessageBox.warning(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "scan_noColumnsDialog.content"),
# fmt: on
message,
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)

View File

@ -9,6 +9,7 @@ from PIL import Image
from PySide6.QtCore import Signal, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30
from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.ocr.dependencies import (
@ -16,11 +17,6 @@ from ui.extends.ocr.dependencies import (
getPhashDatabaseStatusText,
)
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import (
B30_KNN_MODEL_FILE,
KNN_MODEL_FILE,
PHASH_DATABASE_FILE,
)
from ui.extends.tabs.tabOcr.tabOcr_B30 import ChieriV4OcrRunnable, b30ResultToScore
logger = logging.getLogger(__name__)
@ -55,9 +51,15 @@ class TabOcr_B30(Ui_TabOcr_B30, QWidget):
self.ocr = None
logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
self.dependencies_b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE)
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
self.dependencies_knnModelSelector.connectSettings(
SettingsKeys.Ocr.KnnModelFile
)
self.dependencies_b30KnnModelSelector.connectSettings(
SettingsKeys.Ocr.B30KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.ocrQueueModel = OcrQueueModel(self)
self.ocrQueue.setModel(self.ocrQueueModel)

View File

@ -11,10 +11,10 @@ from arcaea_offline_ocr.phash_db import ImagePhashDatabase
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabOcr.tabOcr_Device_ui import Ui_TabOcr_Device
from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import KNN_MODEL_FILE, PHASH_DATABASE_FILE
from ui.extends.tabs.tabOcr.tabOcr_Device import ScoreConverter, TabDeviceOcrRunnable
logger = logging.getLogger(__name__)
@ -54,8 +54,12 @@ class TabOcr_Device(Ui_TabOcr_Device, QWidget):
)
logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
self.dependencies_knnModelSelector.connectSettings(
SettingsKeys.Ocr.KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.options_usePresetCheckBox.setChecked(True)
self.options_usePresetCheckBox.setEnabled(False)

View File

@ -50,6 +50,4 @@ class TabOverview(Ui_TabOverview, QWidget):
def retranslateUi(self, *args):
super().retranslateUi(self)
# fmt: off
self.describeFormatString = QCoreApplication.translate("TabOverview", "databaseDescribeLabel {} {} {} {} {} {}")
# fmt: on
self.describeFormatString = QCoreApplication.translate("TabOverview", "databaseDescribeLabel {} {} {} {} {} {}") # fmt: skip

View File

@ -11,9 +11,9 @@ from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, Qt, Slot
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPaintEvent, QPixmap
from PySide6.QtWidgets import QButtonGroup, QFileDialog, QLabel, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabTools.tabTools_Andreal_ui import Ui_TabTools_Andreal
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import ANDREAL_EXECUTABLE, ANDREAL_FOLDER
from ui.extends.tabs.tabTools.tabTools_Andreal import AndrealHelper
from ui.implements.components.chartSelector import ChartSelector
from ui.implements.components.songIdSelector import SongIdSelectorMode
@ -80,8 +80,8 @@ class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
self.andrealFolderSelector.filesSelected.connect(self.setHelperPaths)
self.andrealExecutableSelector.filesSelected.connect(self.setHelperPaths)
self.andrealFolderSelector.connectSettings(ANDREAL_FOLDER)
self.andrealExecutableSelector.connectSettings(ANDREAL_EXECUTABLE)
self.andrealFolderSelector.connectSettings(SettingsKeys.Andreal.Folder)
self.andrealExecutableSelector.connectSettings(SettingsKeys.Andreal.Executable)
self.generatePreviewButton.clicked.connect(self.requestPreview)
self.generateImageButton.clicked.connect(self.requestGenerate)
@ -131,13 +131,8 @@ class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
@Slot()
def on_imageTypeWhatIsThisButton_clicked(self):
QMessageBox.information(
self,
None,
# fmt: off
QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description"),
# fmt: on
)
message = QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description") # fmt: skip
QMessageBox.information(self, None, message)
def imageFormat(self):
buttonId = self.imageFormatButtonGroup.checkedId()

View File

@ -90,10 +90,8 @@ class PlayRatingCalculatorDialog(QDialog):
self.acceptButton = QPushButton(self)
self.acceptButton.setText(
# fmt: off
QCoreApplication.translate("StepCalculator", "playRatingCalculatorDialog.acceptButton")
# fmt: on
)
) # fmt: skip
self.acceptButton.setEnabled(False)
self.verticalLayout.addWidget(self.acceptButton)

View File

@ -70,12 +70,12 @@
<translation>Continue</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="117"/>
<location filename="../../startup/databaseChecker.py" line="122"/>
<source>dialog.tryInitExistingDatabase</source>
<translation>The existing database doesn&apos;t seem to be initialized properly, try initialize again?</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="133"/>
<location filename="../../startup/databaseChecker.py" line="138"/>
<source>dialog.confirmNewDatabase</source>
<translation>Database file does not exist. Create now?</translation>
</message>
@ -224,85 +224,118 @@
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source>
<translation>ICC Profile Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.useQt</source>
<translation>Use Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source>
<translation>Use PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source>
<translation>Try fix</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source>
<translation>Add Image</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="71"/>
<location filename="../../designer/components/ocrQueue.ui" line="38"/>
<source>queue.removeSelected</source>
<translation>Remove Selected</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="81"/>
<location filename="../../designer/components/ocrQueue.ui" line="48"/>
<source>queue.removeAll</source>
<translation>Remove All</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="101"/>
<location filename="../../designer/components/ocrQueue.ui" line="68"/>
<source>queue.optionsButton</source>
<translation>Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source>
<translation>Start OCR</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="153"/>
<location filename="../../designer/components/ocrQueue.ui" line="127"/>
<source>results</source>
<translation>Results</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="162"/>
<location filename="../../designer/components/ocrQueue.ui" line="136"/>
<source>results.acceptSelectedButton</source>
<translation>Accept Selected</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="169"/>
<location filename="../../designer/components/ocrQueue.ui" line="143"/>
<source>results.acceptAllButton</source>
<translation>Accept All</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="189"/>
<location filename="../../designer/components/ocrQueue.ui" line="163"/>
<source>results.ignoreValidate</source>
<translation>Ignore
validation</translation>
</message>
</context>
<context>
<name>OcrQueueOptionsDialog</name>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="14"/>
<source>OCR Options</source>
<translation>OCR Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="22"/>
<source>iccOptionsGroupBox</source>
<translation>ICC Profile Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="28"/>
<source>icc.useQt</source>
<translation>Use Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="35"/>
<source>icc.usePIL</source>
<translation>Use PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="45"/>
<source>icc.tryFix</source>
<translation>Try fix</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="55"/>
<source>dateOptionsGroupBox</source>
<translation>Date Source</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="64"/>
<source>date.readFromExif</source>
<translation>Read from image EXIF</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="74"/>
<source>date.useCreationDate</source>
<translation>File creation time</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="84"/>
<source>date.useModifyDate</source>
<translation>File last modification time</translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/components/ocrQueue.py" line="347"/>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.select</source>
<translation>Select</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<location filename="../../extends/components/ocrQueue.py" line="351"/>
<source>horizontalHeader.title.imagePreview</source>
<translation>Image Preview</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="349"/>
<location filename="../../extends/components/ocrQueue.py" line="352"/>
<source>horizontalHeader.title.chart</source>
<translation>Chart</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="350"/>
<location filename="../../extends/components/ocrQueue.py" line="353"/>
<source>horizontalHeader.title.score</source>
<translation>Score</translation>
</message>

View File

@ -70,12 +70,12 @@
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="117"/>
<location filename="../../startup/databaseChecker.py" line="122"/>
<source>dialog.tryInitExistingDatabase</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="133"/>
<location filename="../../startup/databaseChecker.py" line="138"/>
<source>dialog.confirmNewDatabase</source>
<translation></translation>
</message>
@ -224,84 +224,117 @@
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source>
<translation>ICC </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.useQt</source>
<translation>使 Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source>
<translation>使 PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="71"/>
<location filename="../../designer/components/ocrQueue.ui" line="38"/>
<source>queue.removeSelected</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="81"/>
<location filename="../../designer/components/ocrQueue.ui" line="48"/>
<source>queue.removeAll</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="101"/>
<location filename="../../designer/components/ocrQueue.ui" line="68"/>
<source>queue.optionsButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source>
<translation> OCR</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="153"/>
<location filename="../../designer/components/ocrQueue.ui" line="127"/>
<source>results</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="162"/>
<location filename="../../designer/components/ocrQueue.ui" line="136"/>
<source>results.acceptSelectedButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="169"/>
<location filename="../../designer/components/ocrQueue.ui" line="143"/>
<source>results.acceptAllButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="189"/>
<location filename="../../designer/components/ocrQueue.ui" line="163"/>
<source>results.ignoreValidate</source>
<translation></translation>
</message>
</context>
<context>
<name>OcrQueueOptionsDialog</name>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="14"/>
<source>OCR Options</source>
<translation>OCR </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="22"/>
<source>iccOptionsGroupBox</source>
<translation>ICC </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="28"/>
<source>icc.useQt</source>
<translation>使 Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="35"/>
<source>icc.usePIL</source>
<translation>使 PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="45"/>
<source>icc.tryFix</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="55"/>
<source>dateOptionsGroupBox</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="64"/>
<source>date.readFromExif</source>
<translation> EXIF </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="74"/>
<source>date.useCreationDate</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="84"/>
<source>date.useModifyDate</source>
<translation></translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/components/ocrQueue.py" line="347"/>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.select</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<location filename="../../extends/components/ocrQueue.py" line="351"/>
<source>horizontalHeader.title.imagePreview</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="349"/>
<location filename="../../extends/components/ocrQueue.py" line="352"/>
<source>horizontalHeader.title.chart</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="350"/>
<location filename="../../extends/components/ocrQueue.py" line="353"/>
<source>horizontalHeader.title.score</source>
<translation></translation>
</message>

View File

@ -6,8 +6,8 @@ from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, QSysInfo, Qt, QUrl, Slot
from PySide6.QtWidgets import QDialog, QMessageBox
from core.settings import SettingsKeys, settings
from ui.extends.shared.database import create_engine
from ui.extends.shared.settings import Settings
from .databaseChecker_ui import Ui_DatabaseChecker
@ -29,8 +29,7 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory)
self.confirmDbByExistingSettings = False
self.settings = Settings(self)
if dbUrlString := self.settings.databaseUrl():
if dbUrlString := settings.stringValue(SettingsKeys.General.DatabaseUrl):
dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://"))
dbFileInfo = QFileInfo(dbFileUrl.toLocalFile())
if dbFileInfo.exists():
@ -45,6 +44,9 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.selectFile(QDir.currentPath())
self.dbFilenameLineEdit.setText("arcaea_offline.db")
def writeDatabaseUrlToSettings(self, databaseUrl: str):
settings.setValue(SettingsKeys.General.DatabaseUrl, databaseUrl)
def dbPath(self):
return QDir(self.dbDirSelector.selectedFiles()[0])
@ -79,7 +81,7 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
db = Database(create_engine(dbSqliteUrl))
if db.check_init():
flags |= DatabaseCheckerResult.Initted
self.settings.setDatabaseUrl(self.dbSqliteUrl().toString())
self.writeDatabaseUrlToSettings(self.dbSqliteUrl().toString())
return flags
@ -108,20 +110,18 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
@Slot()
def on_confirmDbPathButton_clicked(self):
dbSqliteUrl = self.dbSqliteUrl()
self.settings.setDatabaseUrl(dbSqliteUrl.toString())
self.writeDatabaseUrlToSettings(dbSqliteUrl.toString())
result = self.confirmDb()
if result & DatabaseCheckerResult.Initted:
if not self.confirmDbByExistingSettings:
self.settings.setDatabaseUrl(dbSqliteUrl.toString())
self.writeDatabaseUrlToSettings(dbSqliteUrl.toString())
elif result & DatabaseCheckerResult.FileExist:
confirm_try_init = QMessageBox.question(
self,
None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"),
# fmt: on
)
) # fmt: skip
if confirm_try_init == QMessageBox.StandardButton.Yes:
try:
Database().init(checkfirst=True)
@ -134,10 +134,8 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
confirm_new_database = QMessageBox.question(
self,
None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"),
# fmt: on
)
) # fmt: skip
if confirm_new_database == QMessageBox.StandardButton.Yes:
db = Database(create_engine(dbSqliteUrl))
db.init()