4 Commits

Author SHA1 Message Date
ac0d4fec8a wip: navigation system - NavItemWidgets 2023-11-23 00:42:34 +08:00
7821816d09 wip: navigation components 2023-11-18 23:05:10 +08:00
a930cffe39 wip: navigating system 2023-11-07 22:04:31 +08:00
4442e496aa feat: SlidingStackedWidget 2023-10-30 01:55:16 +08:00
69 changed files with 1534 additions and 1460 deletions

View File

@ -1,65 +0,0 @@
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:
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 imageio"
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
- 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
- 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/**/*

View File

@ -1,78 +0,0 @@
name: Build Executable
run-name: ${{ github.actor }} started a build request.
on:
workflow_dispatch:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
permissions:
contents: write
discussions: write
jobs:
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"
- 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
- 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
- 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
with:
discussion_category_name: New releases
draft: true
generate_release_notes: true
files: |
build/*.exe
build/*.bin
build/*.app/**/*

View File

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

View File

@ -1,27 +1,9 @@
# Arcaea Offline PySide UI # Arcaea Offline PySide UI
GUI for both [283375/arcaea-offline](https://github.com/283375/arcaea-offline) and [ArcaeaOffline/core-ocr](https://github.com/ArcaeaOffline/core-ocr). GUI for both [283375/arcaea-offline](https://github.com/283375/arcaea-offline) and [283375/arcaea-offline-ocr](https://github.com/283375/arcaea-offline-ocr)
## Prerequisites ## Before you run `python index.py`...
* Install requirements * Install requirements
* Release translation files from `ui/resources/lang/*.ts` * Release translation files from `ui/resources/lang/*.ts`
* Run `prebuild.py`
* Compile `ui/resources/resources.qrc` to `ui/resources/resources_rc.py` * Compile `ui/resources/resources.qrc` to `ui/resources/resources_rc.py`
You can refer to the [GitHub Actions file](./.github/workflows/build.yml) for a rough reference.
```
pip install -r ./requirements.txt
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
```
Sometimes you have to install the latest, unpublished version of `arcaea-offline` and `arcaea-offline-ocr`.
```
pip uninstall -y arcaea-offline arcaea-offline-ocr
pip install git+https://github.com/283375/arcaea-offline
pip install git+https://github.com/ArcaeaOffline/core-ocr
```

View File

@ -1,10 +0,0 @@
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

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

View File

@ -1,44 +0,0 @@
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()

View File

@ -1,26 +0,0 @@
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

View File

@ -1,17 +0,0 @@
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

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

View File

@ -40,6 +40,7 @@ def getBuildToolsVer():
def writeVersionFile(): def writeVersionFile():
versionFile = Path("ui/resources/VERSION") versionFile = Path("ui/resources/VERSION")
assert versionFile.exists()
versionText = ( versionText = (
"arcaea-offline-pyside-ui\n{gitDesc}\n{buildToolsVer}\n\n" "arcaea-offline-pyside-ui\n{gitDesc}\n{buildToolsVer}\n\n"

View File

@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "arcaea-offline-pyside-ui" name = "arcaea-offline-pyside-ui"
version = "0.3.9" version = "0.1.0"
authors = [{ name = "283375", email = "log_283375@163.com" }] authors = [{ name = "283375", email = "log_283375@163.com" }]
description = "No description." description = "No description."
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = [ dependencies = [
"arcaea-offline==0.2.2", "arcaea-offline==0.1.0",
"arcaea-offline-ocr==0.0.99", "arcaea-offline-ocr==0.1.0",
"exif==1.6.0", "exif==1.6.0",
"PySide6==6.5.2", "PySide6==6.5.2",
] ]
@ -21,37 +21,22 @@ classifiers = [
] ]
[project.urls] [project.urls]
"Homepage" = "https://github.com/ArcaeaOffline/client-pyside6" "Homepage" = "https://github.com/283375/arcaea-offline-pyside-ui"
"Bug Tracker" = "https://github.com/ArcaeaOffline/client-pyside6/issues" "Bug Tracker" = "https://github.com/283375/arcaea-offline-pyside-ui/issues"
[tool.ruff] [tool.black]
exclude = ["*_ui.py", "*_rc.py"] force-exclude = '''
(
ui/designer
| .*_ui.py
| .*_rc.py
)
'''
[tool.ruff.lint] [tool.isort]
# Full list: https://docs.astral.sh/ruff/rules profile = "black"
select = [ extend_skip = ["ui/designer"]
"E", # pycodestyle (Error) extend_skip_glob = ["*_ui.py", "*_rc.py"]
"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] [tool.pyright]
ignore = ["**/__debug*.*"] ignore = ["**/__debug*.*"]

View File

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

View File

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

0
tests/ui/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,31 @@
from ui.navigation.navhost import NavHost
from ui.navigation.navitem import NavItem
class TestNavHost:
def test_auto_append_parent(self):
navHost = NavHost()
navHost.registerNavItem(NavItem(id="aaa.bbb.ccc.ddd"))
navItems = navHost.navItems
assert NavItem(id="aaa.bbb.ccc.ddd") in navItems
assert NavItem(id="aaa.bbb.ccc") in navItems
assert NavItem(id="aaa.bbb") in navItems
assert NavItem(id="aaa") in navItems
def test_auto_select_child(self):
navHost = NavHost()
navHost.registerNavItem(NavItem(id="aaa"))
navHost.registerNavItem(NavItem(id="bbb"))
assert navHost.currentNavItem.id == "aaa"
navHost.registerNavItem(NavItem(id="aaa.bbb"))
navHost.registerNavItem(NavItem(id="aaa.ccc"))
navHost.navigate("aaa")
assert navHost.currentNavItem.id == "aaa.bbb"

View File

View File

View File

@ -0,0 +1,47 @@
from PySide6.QtWidgets import QWidget
from ui.navigation.navhost import NavHost, navHost
from ui.navigation.navitem import NavItem
from ui.navigation.navitemwidgets import NavItemWidgets
from ui.widgets.slidingstackedwidget import SlidingStackedWidget
class AnimatedStackedNavItemsWidgets(SlidingStackedWidget):
def __init__(
self, navItemWidgets: NavItemWidgets, navHost: NavHost = navHost, parent=None
):
super().__init__(parent)
self.navItemWidgets = navItemWidgets
self.navHost = navHost
self.navHost.activated.connect(self.__switchTo)
self.animationFinished.connect(self.endChangingWidget)
def __switchTo(self, oldNavItem: NavItem, newNavItem: NavItem):
oldNavItemDepth = self.navHost.getNavItemDepth(oldNavItem.id)
newNavItemDepth = self.navHost.getNavItemDepth(newNavItem.id)
if oldNavItemDepth != newNavItemDepth:
slidingDirection = (
self.slidingDirection.RightToLeft
if newNavItemDepth > oldNavItemDepth
else self.slidingDirection.LeftToRight
)
else:
slidingDirection = self.slidingDirection.TopToBottom
newWidget = self.navItemWidgets.get(newNavItem.id) or QWidget()
self.startChangingWidget(newWidget, slidingDirection)
def startChangingWidget(self, newWidget: QWidget, slidingDirection):
newIndex = self.addWidget(newWidget)
[self.widget(i).setEnabled(False) for i in range(self.count())]
self.slideInIdx(newIndex, slidingDirection)
def endChangingWidget(self):
oldWidget = self.widget(0)
self.removeWidget(oldWidget)
newWidget = self.widget(0)
newWidget.setEnabled(True)

View File

@ -22,6 +22,39 @@
<string>queue.title</string> <string>queue.title</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <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> <item>
<widget class="QPushButton" name="ocr_addImageButton"> <widget class="QPushButton" name="ocr_addImageButton">
<property name="text"> <property name="text">
@ -62,13 +95,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QPushButton" name="optionsDialogButton">
<property name="text">
<string>queue.optionsButton</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="ocr_startButton"> <widget class="QPushButton" name="ocr_startButton">
<property name="text"> <property name="text">

View File

@ -1,140 +0,0 @@
<?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

@ -1,107 +0,0 @@
# -*- 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) QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QGroupBox, from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QGroupBox,
QHBoxLayout, QHeaderView, QLabel, QProgressBar, QHBoxLayout, QHeaderView, QLabel, QProgressBar,
QPushButton, QSizePolicy, QSpacerItem, QTableView, QPushButton, QRadioButton, QSizePolicy, QSpacerItem,
QVBoxLayout, QWidget) QTableView, QVBoxLayout, QWidget)
class Ui_OcrQueue(object): class Ui_OcrQueue(object):
def setupUi(self, OcrQueue): def setupUi(self, OcrQueue):
@ -34,6 +34,29 @@ class Ui_OcrQueue(object):
self.groupBox_3.setObjectName(u"groupBox_3") self.groupBox_3.setObjectName(u"groupBox_3")
self.verticalLayout_2 = QVBoxLayout(self.groupBox_3) self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2") 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 = QPushButton(self.groupBox_3)
self.ocr_addImageButton.setObjectName(u"ocr_addImageButton") self.ocr_addImageButton.setObjectName(u"ocr_addImageButton")
@ -55,11 +78,6 @@ class Ui_OcrQueue(object):
self.verticalLayout_2.addItem(self.verticalSpacer) 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 = QPushButton(self.groupBox_3)
self.ocr_startButton.setObjectName(u"ocr_startButton") self.ocr_startButton.setObjectName(u"ocr_startButton")
@ -136,10 +154,13 @@ class Ui_OcrQueue(object):
def retranslateUi(self, OcrQueue): def retranslateUi(self, OcrQueue):
self.groupBox_3.setTitle(QCoreApplication.translate("OcrQueue", u"queue.title", None)) 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_addImageButton.setText(QCoreApplication.translate("OcrQueue", u"queue.addImageButton", None))
self.ocr_removeSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeSelected", None)) self.ocr_removeSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeSelected", None))
self.ocr_removeAllButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeAll", 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.ocr_startButton.setText(QCoreApplication.translate("OcrQueue", u"queue.startOcrButton", None))
self.groupBox_5.setTitle(QCoreApplication.translate("OcrQueue", u"results", None)) self.groupBox_5.setTitle(QCoreApplication.translate("OcrQueue", u"results", None))
self.ocr_acceptSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"results.acceptSelectedButton", None)) self.ocr_acceptSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"results.acceptSelectedButton", None))
@ -148,3 +169,4 @@ class Ui_OcrQueue(object):
self.statusLabel.setText("") self.statusLabel.setText("")
pass pass
# retranslateUi # retranslateUi

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>580</width> <width>630</width>
<height>551</height> <height>528</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -17,21 +17,70 @@
<property name="labelAlignment"> <property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<item row="1" column="0"> <item row="0" column="0">
<widget class="QPushButton" name="syncArcSongDbButton">
<property name="text">
<string>syncArcSongDbButton</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>syncArcSongDb.description</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="importSt3Button">
<property name="text">
<string>importSt3Button</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>importSt3.description</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="exportScoresButton">
<property name="text">
<string>exportScoresButton</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>exportScores.description</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="importPacklistButton"> <widget class="QPushButton" name="importPacklistButton">
<property name="text"> <property name="text">
<string>importPacklistButton</string> <string>importPacklistButton</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>importPacklist.description</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="importSonglistButton"> <widget class="QPushButton" name="importSonglistButton">
<property name="text"> <property name="text">
<string>importSonglistButton</string> <string>importSonglistButton</string>
@ -39,129 +88,69 @@
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>importPacklist.description</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>importSonglist.description</string> <string>importSonglist.description</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="9" column="0">
<widget class="QPushButton" name="exportArcsongJsonButton">
<property name="text">
<string>exportArcsongJsonButton</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>exportArcsongJson.description</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="importApkButton"> <widget class="QPushButton" name="importApkButton">
<property name="text"> <property name="text">
<string>importApkButton</string> <string>importApkButton</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>importApk.description</string> <string>importApk.description</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_11">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>chartInfoGroup</string>
</property>
</widget>
</item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QPushButton" name="syncArcSongDbButton">
<property name="text">
<string>syncArcSongDbButton</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>syncArcSongDb.description</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="label_12">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>importScoreGroup</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QPushButton" name="importSt3Button">
<property name="text">
<string>importSt3Button</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>importSt3.description</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QPushButton" name="importOnlineButton"> <widget class="QPushButton" name="importOnlineButton">
<property name="text"> <property name="text">
<string>importOnlineButton</string> <string>importOnlineButton</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="1"> <item row="6" column="1">
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>importOnline.description</string> <string>importOnline.description</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="0" colspan="2"> <item row="10" column="0">
<widget class="QLabel" name="label_13">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>exportScoreGroup</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QPushButton" name="exportScoresButton">
<property name="text">
<string>exportScoresButton</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>exportScores.description</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QPushButton" name="exportSmartRteB30Button"> <widget class="QPushButton" name="exportSmartRteB30Button">
<property name="text"> <property name="text">
<string>exportSmartRteB30Button</string> <string>exportSmartRteB30Button</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="1"> <item row="10" column="1">
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
<string>exportSmartRteB30.description</string> <string>exportSmartRteB30.description</string>
@ -174,124 +163,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0" colspan="2">
<widget class="QLabel" name="label_14">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>miscGroup</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QPushButton" name="exportArcsongJsonButton">
<property name="text">
<string>exportArcsongJsonButton</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>exportArcsongJson.description</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_10">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>packSongInfoGroup</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="12" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="16" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="syncChartInfoDbButton">
<property name="text">
<string>syncChartInfoDbButton</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_15">
<property name="text">
<string>syncChartInfoDb.description</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -15,168 +15,123 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon, QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter, QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform) QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QFormLayout, QLabel, QPushButton, from PySide6.QtWidgets import (QApplication, QFormLayout, QFrame, QLabel,
QSizePolicy, QSpacerItem, QWidget) QPushButton, QSizePolicy, QWidget)
class Ui_TabDb_Manage(object): class Ui_TabDb_Manage(object):
def setupUi(self, TabDb_Manage): def setupUi(self, TabDb_Manage):
if not TabDb_Manage.objectName(): if not TabDb_Manage.objectName():
TabDb_Manage.setObjectName(u"TabDb_Manage") TabDb_Manage.setObjectName(u"TabDb_Manage")
TabDb_Manage.resize(580, 551) TabDb_Manage.resize(630, 528)
TabDb_Manage.setWindowTitle(u"TabDb_Manage") TabDb_Manage.setWindowTitle(u"TabDb_Manage")
self.formLayout = QFormLayout(TabDb_Manage) self.formLayout = QFormLayout(TabDb_Manage)
self.formLayout.setObjectName(u"formLayout") self.formLayout.setObjectName(u"formLayout")
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.importPacklistButton = QPushButton(TabDb_Manage)
self.importPacklistButton.setObjectName(u"importPacklistButton")
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.importPacklistButton)
self.label_4 = QLabel(TabDb_Manage)
self.label_4.setObjectName(u"label_4")
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.label_4)
self.importSonglistButton = QPushButton(TabDb_Manage)
self.importSonglistButton.setObjectName(u"importSonglistButton")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.importSonglistButton)
self.label_5 = QLabel(TabDb_Manage)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.label_5)
self.importApkButton = QPushButton(TabDb_Manage)
self.importApkButton.setObjectName(u"importApkButton")
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.importApkButton)
self.label_7 = QLabel(TabDb_Manage)
self.label_7.setObjectName(u"label_7")
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.label_7)
self.label_11 = QLabel(TabDb_Manage)
self.label_11.setObjectName(u"label_11")
font = QFont()
font.setPointSize(12)
font.setBold(False)
self.label_11.setFont(font)
self.formLayout.setWidget(5, QFormLayout.SpanningRole, self.label_11)
self.syncArcSongDbButton = QPushButton(TabDb_Manage) self.syncArcSongDbButton = QPushButton(TabDb_Manage)
self.syncArcSongDbButton.setObjectName(u"syncArcSongDbButton") self.syncArcSongDbButton.setObjectName(u"syncArcSongDbButton")
self.formLayout.setWidget(6, QFormLayout.LabelRole, self.syncArcSongDbButton) self.formLayout.setWidget(0, QFormLayout.LabelRole, self.syncArcSongDbButton)
self.label = QLabel(TabDb_Manage) self.label = QLabel(TabDb_Manage)
self.label.setObjectName(u"label") self.label.setObjectName(u"label")
self.formLayout.setWidget(6, QFormLayout.FieldRole, self.label) self.formLayout.setWidget(0, QFormLayout.FieldRole, self.label)
self.label_12 = QLabel(TabDb_Manage)
self.label_12.setObjectName(u"label_12")
self.label_12.setFont(font)
self.formLayout.setWidget(9, QFormLayout.SpanningRole, self.label_12)
self.importSt3Button = QPushButton(TabDb_Manage) self.importSt3Button = QPushButton(TabDb_Manage)
self.importSt3Button.setObjectName(u"importSt3Button") self.importSt3Button.setObjectName(u"importSt3Button")
self.formLayout.setWidget(10, QFormLayout.LabelRole, self.importSt3Button) self.formLayout.setWidget(5, QFormLayout.LabelRole, self.importSt3Button)
self.label_2 = QLabel(TabDb_Manage) self.label_2 = QLabel(TabDb_Manage)
self.label_2.setObjectName(u"label_2") self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(10, QFormLayout.FieldRole, self.label_2) self.formLayout.setWidget(5, QFormLayout.FieldRole, self.label_2)
self.importOnlineButton = QPushButton(TabDb_Manage) self.line = QFrame(TabDb_Manage)
self.importOnlineButton.setObjectName(u"importOnlineButton") self.line.setObjectName(u"line")
self.line.setFrameShape(QFrame.HLine)
self.line.setFrameShadow(QFrame.Sunken)
self.formLayout.setWidget(11, QFormLayout.LabelRole, self.importOnlineButton) self.formLayout.setWidget(7, QFormLayout.SpanningRole, self.line)
self.label_8 = QLabel(TabDb_Manage)
self.label_8.setObjectName(u"label_8")
self.formLayout.setWidget(11, QFormLayout.FieldRole, self.label_8)
self.label_13 = QLabel(TabDb_Manage)
self.label_13.setObjectName(u"label_13")
self.label_13.setFont(font)
self.formLayout.setWidget(13, QFormLayout.SpanningRole, self.label_13)
self.exportScoresButton = QPushButton(TabDb_Manage) self.exportScoresButton = QPushButton(TabDb_Manage)
self.exportScoresButton.setObjectName(u"exportScoresButton") self.exportScoresButton.setObjectName(u"exportScoresButton")
self.formLayout.setWidget(14, QFormLayout.LabelRole, self.exportScoresButton) self.formLayout.setWidget(8, QFormLayout.LabelRole, self.exportScoresButton)
self.label_3 = QLabel(TabDb_Manage) self.label_3 = QLabel(TabDb_Manage)
self.label_3.setObjectName(u"label_3") self.label_3.setObjectName(u"label_3")
self.formLayout.setWidget(14, QFormLayout.FieldRole, self.label_3) self.formLayout.setWidget(8, QFormLayout.FieldRole, self.label_3)
self.line_2 = QFrame(TabDb_Manage)
self.line_2.setObjectName(u"line_2")
self.line_2.setFrameShape(QFrame.HLine)
self.line_2.setFrameShadow(QFrame.Sunken)
self.formLayout.setWidget(1, QFormLayout.SpanningRole, self.line_2)
self.importPacklistButton = QPushButton(TabDb_Manage)
self.importPacklistButton.setObjectName(u"importPacklistButton")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.importPacklistButton)
self.importSonglistButton = QPushButton(TabDb_Manage)
self.importSonglistButton.setObjectName(u"importSonglistButton")
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.importSonglistButton)
self.label_4 = QLabel(TabDb_Manage)
self.label_4.setObjectName(u"label_4")
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.label_4)
self.label_5 = QLabel(TabDb_Manage)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.label_5)
self.exportArcsongJsonButton = QPushButton(TabDb_Manage)
self.exportArcsongJsonButton.setObjectName(u"exportArcsongJsonButton")
self.formLayout.setWidget(9, QFormLayout.LabelRole, self.exportArcsongJsonButton)
self.label_6 = QLabel(TabDb_Manage)
self.label_6.setObjectName(u"label_6")
self.formLayout.setWidget(9, QFormLayout.FieldRole, self.label_6)
self.importApkButton = QPushButton(TabDb_Manage)
self.importApkButton.setObjectName(u"importApkButton")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.importApkButton)
self.label_7 = QLabel(TabDb_Manage)
self.label_7.setObjectName(u"label_7")
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.label_7)
self.importOnlineButton = QPushButton(TabDb_Manage)
self.importOnlineButton.setObjectName(u"importOnlineButton")
self.formLayout.setWidget(6, QFormLayout.LabelRole, self.importOnlineButton)
self.label_8 = QLabel(TabDb_Manage)
self.label_8.setObjectName(u"label_8")
self.formLayout.setWidget(6, QFormLayout.FieldRole, self.label_8)
self.exportSmartRteB30Button = QPushButton(TabDb_Manage) self.exportSmartRteB30Button = QPushButton(TabDb_Manage)
self.exportSmartRteB30Button.setObjectName(u"exportSmartRteB30Button") self.exportSmartRteB30Button.setObjectName(u"exportSmartRteB30Button")
self.formLayout.setWidget(15, QFormLayout.LabelRole, self.exportSmartRteB30Button) self.formLayout.setWidget(10, QFormLayout.LabelRole, self.exportSmartRteB30Button)
self.label_9 = QLabel(TabDb_Manage) self.label_9 = QLabel(TabDb_Manage)
self.label_9.setObjectName(u"label_9") self.label_9.setObjectName(u"label_9")
self.label_9.setOpenExternalLinks(True) self.label_9.setOpenExternalLinks(True)
self.label_9.setTextInteractionFlags(Qt.LinksAccessibleByKeyboard|Qt.LinksAccessibleByMouse) self.label_9.setTextInteractionFlags(Qt.LinksAccessibleByKeyboard|Qt.LinksAccessibleByMouse)
self.formLayout.setWidget(15, QFormLayout.FieldRole, self.label_9) self.formLayout.setWidget(10, QFormLayout.FieldRole, self.label_9)
self.label_14 = QLabel(TabDb_Manage)
self.label_14.setObjectName(u"label_14")
self.label_14.setFont(font)
self.formLayout.setWidget(17, QFormLayout.SpanningRole, self.label_14)
self.exportArcsongJsonButton = QPushButton(TabDb_Manage)
self.exportArcsongJsonButton.setObjectName(u"exportArcsongJsonButton")
self.formLayout.setWidget(18, QFormLayout.LabelRole, self.exportArcsongJsonButton)
self.label_6 = QLabel(TabDb_Manage)
self.label_6.setObjectName(u"label_6")
self.formLayout.setWidget(18, QFormLayout.FieldRole, self.label_6)
self.label_10 = QLabel(TabDb_Manage)
self.label_10.setObjectName(u"label_10")
self.label_10.setFont(font)
self.formLayout.setWidget(0, QFormLayout.SpanningRole, self.label_10)
self.verticalSpacer = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(4, QFormLayout.LabelRole, self.verticalSpacer)
self.verticalSpacer_2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(8, QFormLayout.LabelRole, self.verticalSpacer_2)
self.verticalSpacer_3 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(12, QFormLayout.LabelRole, self.verticalSpacer_3)
self.verticalSpacer_4 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(16, QFormLayout.LabelRole, self.verticalSpacer_4)
self.syncChartInfoDbButton = QPushButton(TabDb_Manage)
self.syncChartInfoDbButton.setObjectName(u"syncChartInfoDbButton")
self.formLayout.setWidget(7, QFormLayout.LabelRole, self.syncChartInfoDbButton)
self.label_15 = QLabel(TabDb_Manage)
self.label_15.setObjectName(u"label_15")
self.formLayout.setWidget(7, QFormLayout.FieldRole, self.label_15)
self.retranslateUi(TabDb_Manage) self.retranslateUi(TabDb_Manage)
@ -185,30 +140,24 @@ class Ui_TabDb_Manage(object):
# setupUi # setupUi
def retranslateUi(self, TabDb_Manage): def retranslateUi(self, TabDb_Manage):
self.importPacklistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklistButton", None))
self.label_4.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklist.description", None))
self.importSonglistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglistButton", None))
self.label_5.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglist.description", None))
self.importApkButton.setText(QCoreApplication.translate("TabDb_Manage", u"importApkButton", None))
self.label_7.setText(QCoreApplication.translate("TabDb_Manage", u"importApk.description", None))
self.label_11.setText(QCoreApplication.translate("TabDb_Manage", u"chartInfoGroup", None))
self.syncArcSongDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDbButton", None)) self.syncArcSongDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDbButton", None))
self.label.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDb.description", None)) self.label.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDb.description", None))
self.label_12.setText(QCoreApplication.translate("TabDb_Manage", u"importScoreGroup", None))
self.importSt3Button.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3Button", None)) self.importSt3Button.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3Button", None))
self.label_2.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3.description", None)) self.label_2.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3.description", None))
self.importOnlineButton.setText(QCoreApplication.translate("TabDb_Manage", u"importOnlineButton", None))
self.label_8.setText(QCoreApplication.translate("TabDb_Manage", u"importOnline.description", None))
self.label_13.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoreGroup", None))
self.exportScoresButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoresButton", None)) self.exportScoresButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoresButton", None))
self.label_3.setText(QCoreApplication.translate("TabDb_Manage", u"exportScores.description", None)) self.label_3.setText(QCoreApplication.translate("TabDb_Manage", u"exportScores.description", None))
self.exportSmartRteB30Button.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30Button", None)) self.importPacklistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklistButton", None))
self.label_9.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30.description", None)) self.importSonglistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglistButton", None))
self.label_14.setText(QCoreApplication.translate("TabDb_Manage", u"miscGroup", None)) self.label_4.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklist.description", None))
self.label_5.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglist.description", None))
self.exportArcsongJsonButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJsonButton", None)) self.exportArcsongJsonButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJsonButton", None))
self.label_6.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJson.description", None)) self.label_6.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJson.description", None))
self.label_10.setText(QCoreApplication.translate("TabDb_Manage", u"packSongInfoGroup", None)) self.importApkButton.setText(QCoreApplication.translate("TabDb_Manage", u"importApkButton", None))
self.syncChartInfoDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncChartInfoDbButton", None)) self.label_7.setText(QCoreApplication.translate("TabDb_Manage", u"importApk.description", None))
self.label_15.setText(QCoreApplication.translate("TabDb_Manage", u"syncChartInfoDb.description", None)) self.importOnlineButton.setText(QCoreApplication.translate("TabDb_Manage", u"importOnlineButton", None))
self.label_8.setText(QCoreApplication.translate("TabDb_Manage", u"importOnline.description", None))
self.exportSmartRteB30Button.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30Button", None))
self.label_9.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30.description", None))
pass pass
# retranslateUi # retranslateUi

View File

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

View File

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

View File

@ -82,7 +82,6 @@ class ChartDelegate(TextSegmentDelegate):
QColor("#809955"), QColor("#809955"),
QColor("#702d60"), QColor("#702d60"),
QColor("#710f25"), QColor("#710f25"),
QColor("#8b77a4"),
] ]
ChartInvalidBackgroundColor = QColor("#e6a23c") ChartInvalidBackgroundColor = QColor("#e6a23c")

View File

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

View File

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

View File

@ -0,0 +1,111 @@
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

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

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 arcaea_offline_ocr.b30.shared import B30OcrResultItem
from PySide6.QtGui import QImage from PySide6.QtGui import QImage
from ui.extends.components.ocrQueue import OcrRunnable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from ui.extends.components.ocrQueue import OcrRunnable
class ChieriV4OcrRunnable(OcrRunnable): class ChieriV4OcrRunnable(OcrRunnable):
def __init__(self, ocr: ChieriBotV4Ocr, component): def __init__(self, ocr: ChieriBotV4Ocr, component):

View File

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

View File

@ -41,7 +41,7 @@ class AndrealExecuteRunnable(QRunnable):
self.signals.completed.emit(self.jsonPath, imageBytes) self.signals.completed.emit(self.jsonPath, imageBytes)
except Exception as e: except Exception as e:
imageBytes = None imageBytes = None
logger.exception("%s error", self.__class__.__name__) logger.exception(f"{self.__class__.__name__} error")
self.signals.error.emit(self.jsonPath, str(e)) self.signals.error.emit(self.jsonPath, str(e))
finally: finally:
os.unlink(self.jsonPath) os.unlink(self.jsonPath)
@ -84,10 +84,7 @@ class AndrealHelper(QObject):
def request(self, jsonPath: str, arguments: list[str]): def request(self, jsonPath: str, arguments: list[str]):
logger.debug( logger.debug(
"%s received request jsonPath=%r arguments=%r", f"{self.__class__.__name__} received request {jsonPath=} {arguments=}"
self.__class__.__name__,
jsonPath,
arguments,
) )
runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments) runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments)
runnable.signals.error.connect(self.error) 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.QtGui import QDragEnterEvent, QDragLeaveEvent, QDropEvent
from PySide6.QtWidgets import QFileDialog, QWidget from PySide6.QtWidgets import QFileDialog, QWidget
from core.settings import settings
from ui.designer.components.fileSelector_ui import Ui_FileSelector from ui.designer.components.fileSelector_ui import Ui_FileSelector
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class FileSelector(Ui_FileSelector, QWidget): class FileSelector(Ui_FileSelector, QWidget):
@ -122,13 +122,13 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles: if self.__selectedFiles:
return return
if value := settings.value(self.settingsKey): if value := Settings().value(self.settingsKey):
self.selectFile(value) self.selectFile(value)
settings.updated.connect(self.settingsUpdated) Settings().updated.connect(self.settingsUpdated)
def disconnectSettings(self): def disconnectSettings(self):
settings.updated.disconnect(self.settingsUpdated) Settings().updated.disconnect(self.settingsUpdated)
self.settingsKey = None self.settingsKey = None
def settingsUpdated(self, key: str): def settingsUpdated(self, key: str):
@ -139,4 +139,4 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles: if self.__selectedFiles:
return 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.QtCore import Qt, QTimer, Slot
from PySide6.QtGui import QColor, QPalette from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QWidget from PySide6.QtWidgets import QButtonGroup, QWidget
from ui.designer.components.ocrQueue_ui import Ui_OcrQueue from ui.designer.components.ocrQueue_ui import Ui_OcrQueue
from ui.extends.components.ocrQueue import ( from ui.extends.components.ocrQueue import (
@ -13,7 +13,6 @@ from ui.extends.components.ocrQueue import (
OcrScoreDelegate, OcrScoreDelegate,
) )
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.implements.components.ocrQueueOptionsDialog import OcrQueueOptionsDialog
class OcrQueue(Ui_OcrQueue, QWidget): class OcrQueue(Ui_OcrQueue, QWidget):
@ -27,9 +26,6 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.__model: Optional[OcrQueueModel] = None self.__model: Optional[OcrQueueModel] = None
self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None
self.optionsDialog = OcrQueueOptionsDialog(self)
self.optionsDialog.iccOptionsChanged.connect(self.setIccOption)
self.__firstResizeDone = False self.__firstResizeDone = False
self.resizeTimer = QTimer(self) self.resizeTimer = QTimer(self)
self.resizeTimer.timeout.connect(self.tableView.resizeRowsToContents) self.resizeTimer.timeout.connect(self.tableView.resizeRowsToContents)
@ -45,6 +41,13 @@ class OcrQueue(Ui_OcrQueue, QWidget):
tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor) tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor)
self.tableView.setPalette(tableViewPalette) 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 = QTimer(self)
self.statusLabelClearTimer.setSingleShot(True) self.statusLabelClearTimer.setSingleShot(True)
self.statusLabelClearTimer.timeout.connect(self.clearStatusMessage) self.statusLabelClearTimer.timeout.connect(self.clearStatusMessage)
@ -90,10 +93,9 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.ocr_acceptAllButton.setEnabled(__bool) self.ocr_acceptAllButton.setEnabled(__bool)
self.ocr_ignoreValidateCheckBox.setEnabled(__bool) self.ocr_ignoreValidateCheckBox.setEnabled(__bool)
@Slot(int) def updateIccOption(self):
def setIccOption(self, option):
if self.model(): if self.model():
self.model().iccOption = option self.model().iccOption = self.iccOptionButtonGroup.checkedId()
def showStatusMessage(self, message: str): def showStatusMessage(self, message: str):
self.statusLabel.setText(message) self.statusLabel.setText(message)
@ -129,10 +131,6 @@ class OcrQueue(Ui_OcrQueue, QWidget):
def modelReseted(self): def modelReseted(self):
self.progressBar.setMaximum(0) self.progressBar.setMaximum(0)
@Slot()
def on_optionsDialogButton_clicked(self):
self.optionsDialog.exec()
@Slot() @Slot()
def on_ocr_removeSelectedButton_clicked(self): def on_ocr_removeSelectedButton_clicked(self):
if self.model(): if self.model():

View File

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

View File

@ -1,4 +1,3 @@
import logging
from typing import Type from typing import Type
from PySide6.QtCore import Signal from PySide6.QtCore import Signal
@ -7,8 +6,6 @@ from PySide6.QtWidgets import QHBoxLayout, QSizePolicy, QVBoxLayout, QWidget
from ui.implements.components.ratingClassRadioButton import RatingClassRadioButton from ui.implements.components.ratingClassRadioButton import RatingClassRadioButton
logger = logging.getLogger(__name__)
class RatingClassSelector(QWidget): class RatingClassSelector(QWidget):
valueChanged = Signal() valueChanged = Signal()
@ -44,30 +41,16 @@ class RatingClassSelector(QWidget):
self.bydButton.setAutoExclusive(False) self.bydButton.setAutoExclusive(False)
self.preferredLayout.addWidget(self.bydButton) self.preferredLayout.addWidget(self.bydButton)
self.etrButton = RatingClassRadioButton(self) self.buttons = [self.pstButton, self.prsButton, self.ftrButton, self.bydButton]
self.etrButton.setObjectName("etrButton")
self.etrButton.setText("ETERNAL")
self.etrButton.setAutoExclusive(False)
self.preferredLayout.addWidget(self.etrButton)
self.buttons = [
self.pstButton,
self.prsButton,
self.ftrButton,
self.bydButton,
self.etrButton,
]
self.pstButton.setColors(QColor("#399bb2"), QColor("#f0f8fa")) self.pstButton.setColors(QColor("#399bb2"), QColor("#f0f8fa"))
self.prsButton.setColors(QColor("#809955"), QColor("#f7f9f4")) self.prsButton.setColors(QColor("#809955"), QColor("#f7f9f4"))
self.ftrButton.setColors(QColor("#702d60"), QColor("#f7ebf4")) self.ftrButton.setColors(QColor("#702d60"), QColor("#f7ebf4"))
self.bydButton.setColors(QColor("#710f25"), QColor("#f9ced8")) self.bydButton.setColors(QColor("#710f25"), QColor("#f9ced8"))
self.etrButton.setColors(QColor("#4f2c7a"), QColor("#e4daf1"))
self.pstButton.clicked.connect(self.select) self.pstButton.clicked.connect(self.select)
self.prsButton.clicked.connect(self.select) self.prsButton.clicked.connect(self.select)
self.ftrButton.clicked.connect(self.select) self.ftrButton.clicked.connect(self.select)
self.bydButton.clicked.connect(self.select) self.bydButton.clicked.connect(self.select)
self.etrButton.clicked.connect(self.select)
self.reset() self.reset()
self.setButtonsEnabled([]) self.setButtonsEnabled([])
@ -123,10 +106,9 @@ class RatingClassSelector(QWidget):
if ratingClass is None or isinstance(ratingClass, bool): if ratingClass is None or isinstance(ratingClass, bool):
button = self.sender() button = self.sender()
elif ratingClass in range(len(self.buttons)): elif ratingClass in range(4):
button = self.buttons[ratingClass] button = self.buttons[ratingClass]
else: else:
logger.debug("Cannot select ratingClass=%s, condition check failed", ratingClass)
return return
if not button.isEnabled(): if not button.isEnabled():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,10 +14,9 @@ from arcaea_offline.external.arcaea import (
) )
from arcaea_offline.external.arcaea.common import ArcaeaParser from arcaea_offline.external.arcaea.common import ArcaeaParser
from arcaea_offline.external.arcsong import ArcsongDbParser from arcaea_offline.external.arcsong import ArcsongDbParser
from arcaea_offline.external.chart_info_db import ChartInfoDbParser
from arcaea_offline.external.smartrte import SmartRteB30CsvConverter from arcaea_offline.external.smartrte import SmartRteB30CsvConverter
from arcaea_offline.models import Difficulty, Pack, Song from arcaea_offline.models import Difficulty, Pack, Song
from PySide6.QtCore import QDateTime, QDir, Slot from PySide6.QtCore import QDir, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget
from ui.designer.tabs.tabDb.tabDb_Manage_ui import Ui_TabDb_Manage from ui.designer.tabs.tabDb.tabDb_Manage_ui import Ui_TabDb_Manage
@ -58,29 +57,6 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
self, "Sync Error", "\n".join(traceback.format_exception(e)) self, "Sync Error", "\n".join(traceback.format_exception(e))
) )
@Slot()
def on_syncChartInfoDbButton_clicked(self):
dbFile, filter = QFileDialog.getOpenFileName(
self, None, "", "DB File (*.db);;*"
)
if not dbFile:
return
try:
db = Database()
parser = ChartInfoDbParser(dbFile)
with db.sessionmaker() as session:
parser.write_database(session)
session.commit()
databaseUpdateSignals.chartInfoUpdated.emit()
QMessageBox.information(self, None, "OK")
except Exception as e:
logging.exception("Sync chart info database error")
QMessageBox.critical(
self, "Sync Error", "\n".join(traceback.format_exception(e))
)
def importFromArcaeaParser( def importFromArcaeaParser(
self, parser: ArcaeaParser, instance, logName, path self, parser: ArcaeaParser, instance, logName, path
) -> int: ) -> int:
@ -91,7 +67,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
session.commit() session.commit()
databaseUpdateSignals.songAddOrDelete.emit() databaseUpdateSignals.songAddOrDelete.emit()
itemNum = len([item for item in parser.parse() if isinstance(item, instance)]) itemNum = len([item for item in parser.parse() if isinstance(item, instance)])
logger.info("updated %d %s from %s", itemNum, logName, path) logger.info(f"updated {itemNum} {logName} from {path}")
return itemNum return itemNum
def importPacklist(self, packlistPath): def importPacklist(self, packlistPath):
@ -161,7 +137,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
return return
try: try:
logger.info("Importing %s", apkFile) logger.info(f"Importing {apkFile}")
with zipfile.ZipFile(apkFile) as zf: with zipfile.ZipFile(apkFile) as zf:
packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist" packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist"
@ -193,9 +169,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database() db = Database()
parser = St3ScoreParser(dbFile) parser = St3ScoreParser(dbFile)
logger.info( logger.info(
"Got %d items from %s, writing into database...", f"Got {len(parser.parse())} items from {dbFile}, writing into database..."
len(parser.parse()),
dbFile,
) )
with db.sessionmaker() as session: with db.sessionmaker() as session:
parser.write_database(session) parser.write_database(session)
@ -220,9 +194,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database() db = Database()
parser = ArcaeaOnlineParser(apiResultFile) parser = ArcaeaOnlineParser(apiResultFile)
logger.info( logger.info(
"Got %d items from %s, writing into database...", f"Got {len(parser.parse())} items from {apiResultFile}, writing into database..."
len(parser.parse()),
apiResultFile,
) )
with db.sessionmaker() as session: with db.sessionmaker() as session:
parser.write_database(session) parser.write_database(session)
@ -236,14 +208,14 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
@Slot() @Slot()
def on_exportScoresButton_clicked(self): def on_exportScoresButton_clicked(self):
scores = Database().export_scores_def_v2() scores = Database().export_scores()
timestamp = QDateTime.currentMSecsSinceEpoch() version = Database().version()
content = json.dumps(scores, ensure_ascii=False) content = json.dumps(scores, ensure_ascii=False)
exportLocation, _filter = QFileDialog.getSaveFileName( exportLocation, _filter = QFileDialog.getSaveFileName(
self, self,
"Save your scores to...", "Save your scores to...",
QDir.current().filePath(f"arcaea-offline-def-v2-scores-{timestamp}.json"), QDir.current().filePath(f"arcaea-offline-scores-v{version}.json"),
"JSON (*.json);;*", "JSON (*.json);;*",
) )
with open(exportLocation, "w", encoding="utf-8") as f: with open(exportLocation, "w", encoding="utf-8") as f:

View File

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

View File

@ -9,7 +9,6 @@ from PIL import Image
from PySide6.QtCore import Signal, Slot from PySide6.QtCore import Signal, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget 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.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30
from ui.extends.components.ocrQueue import OcrQueueModel from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.ocr.dependencies import ( from ui.extends.ocr.dependencies import (
@ -17,6 +16,11 @@ from ui.extends.ocr.dependencies import (
getPhashDatabaseStatusText, getPhashDatabaseStatusText,
) )
from ui.extends.shared.language import LanguageChangeEventFilter 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 from ui.extends.tabs.tabOcr.tabOcr_B30 import ChieriV4OcrRunnable, b30ResultToScore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,15 +55,9 @@ class TabOcr_B30(Ui_TabOcr_B30, QWidget):
self.ocr = None self.ocr = None
logger.info("Applying settings...") logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings( self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
SettingsKeys.Ocr.KnnModelFile self.dependencies_b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE)
) self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
self.dependencies_b30KnnModelSelector.connectSettings(
SettingsKeys.Ocr.B30KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.ocrQueueModel = OcrQueueModel(self) self.ocrQueueModel = OcrQueueModel(self)
self.ocrQueue.setModel(self.ocrQueueModel) 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.QtCore import Slot
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QWidget 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.designer.tabs.tabOcr.tabOcr_Device_ui import Ui_TabOcr_Device
from ui.extends.components.ocrQueue import OcrQueueModel from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.shared.language import LanguageChangeEventFilter 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 from ui.extends.tabs.tabOcr.tabOcr_Device import ScoreConverter, TabDeviceOcrRunnable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -54,12 +54,8 @@ class TabOcr_Device(Ui_TabOcr_Device, QWidget):
) )
logger.info("Applying settings...") logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings( self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
SettingsKeys.Ocr.KnnModelFile self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.options_usePresetCheckBox.setChecked(True) self.options_usePresetCheckBox.setChecked(True)
self.options_usePresetCheckBox.setEnabled(False) self.options_usePresetCheckBox.setEnabled(False)

View File

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

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

View File

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

View File

151
ui/navigation/navhost.py Normal file
View File

@ -0,0 +1,151 @@
import logging
from dataclasses import dataclass
from typing import Optional
from PySide6.QtCore import QObject, Signal
from .navitem import NavItem
logger = logging.getLogger(__name__)
@dataclass
class NavItemRelatives:
parent: Optional[NavItem]
children: list[NavItem]
class NavHost(QObject):
activated = Signal(NavItem, NavItem)
navItemsChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.__navItems: list[NavItem] = []
self.__cachedNavItems: list[NavItem] = []
self.__currentNavItem: NavItem = None
def __flushCachedNavItems(self):
navItems = set(self.__navItems)
for item in self.__navItems:
parts = item.id.split(".")
for i in range(1, len(parts)):
parentItemId = ".".join(parts[:i])
navItems.add(NavItem(id=parentItemId))
self.__cachedNavItems = list(navItems)
@property
def navItems(self) -> list[NavItem]:
return self.__cachedNavItems
@property
def currentNavItem(self) -> NavItem:
if self.__currentNavItem:
return self.__currentNavItem
if self.__navItems:
self.__currentNavItem = self.__navItems[0]
return self.__currentNavItem
def findNavItem(self, navItemId: str) -> Optional[NavItem]:
navItemIds = [item.id for item in self.__navItems]
try:
index = navItemIds.index(navItemId)
return self.__navItems[index]
except IndexError:
return None
def isNavigatingBack(self, oldNavItemId: str, newNavItemId: str) -> bool:
# sourcery skip: class-extract-method
# | oldNavItemId | newNavItemId | back? |
# |-----------------------|--------------------|-------|
# | database.manage.packs | database | True |
# | ocr.device | ocr | True |
# | database.manage.songs | ocr.b30 | False |
# | database | database | False |
oldNavItemIdSplitted = self.getNavItemIdSplitted(oldNavItemId)
newNavItemIdSplitted = self.getNavItemIdSplitted(newNavItemId)
return self.getNavItemDepth(newNavItemId) < self.getNavItemDepth(
oldNavItemId
) and all((idFrag in oldNavItemIdSplitted) for idFrag in newNavItemIdSplitted)
def isChild(self, childNavItemId: str, parentNavItemId: str) -> bool:
childNavItemIdSplitted = self.getNavItemIdSplitted(childNavItemId)
parentNavItemIdSplitted = self.getNavItemIdSplitted(parentNavItemId)
return all(
(idFrag in childNavItemIdSplitted) for idFrag in parentNavItemIdSplitted
)
def getNavItemIdSplitted(self, navItemId: str) -> list[str]:
return navItemId.split(".")
def getNavItemDepth(self, navItemId: str) -> int:
return len(self.getNavItemIdSplitted(navItemId))
def getNavItemRelatives(self, navItemId: str) -> NavItemRelatives:
parent = None
if "." in navItemId:
navItemIdSplitted = navItemId.split(".")
parentId = navItemIdSplitted[:-1]
parent = self.findNavItem(".".join(parentId))
if not navItemId:
# return root navItems
children = [navItem for navItem in self.__navItems if "." not in navItem.id]
else:
children = [
navItem
for navItem in self.__navItems
if navItem.id.startswith(navItemId)
and navItem.id != navItemId
and self.getNavItemDepth(navItem.id)
== self.getNavItemDepth(navItemId) + 1
]
return NavItemRelatives(parent=parent, children=children)
def registerNavItem(self, item: NavItem):
self.__navItems.append(item)
self.__flushCachedNavItems()
self.navItemsChanged.emit()
def navigate(self, navItemId: str):
oldNavItem = self.__currentNavItem or NavItem("")
newNavItem = self.findNavItem(navItemId)
if newNavItem is None:
raise IndexError(
f"Cannot find '{navItemId}' in {repr(self)}. "
"Maybe try registering it?"
)
# if the navItem have children, navigate to first child
# but if the navItem is going back, e.g. 'database.manage' -> 'database'
# then don't navigate to it's child.
if self.isNavigatingBack(oldNavItem.id, newNavItem.id):
# navItem is going back
currentNavItem = newNavItem
else:
newNavItemRelatives = self.getNavItemRelatives(newNavItem.id)
if newNavItemRelatives.children:
currentNavItem = newNavItemRelatives.children[0]
else:
currentNavItem = newNavItem
self.__currentNavItem = currentNavItem
self.activated.emit(oldNavItem, self.currentNavItem)
def navigateUp(self):
navItemRelatives = self.getNavItemRelatives(self.currentNavItem.id)
if navItemRelatives.parent:
self.navigate(navItemRelatives.parent.id)
navHost = NavHost()

17
ui/navigation/navitem.py Normal file
View File

@ -0,0 +1,17 @@
from dataclasses import dataclass
from typing import Optional
from PySide6.QtCore import QCoreApplication
from PySide6.QtGui import QIcon, QPixmap
@dataclass
class NavItem:
id: str
icon: Optional[QIcon | QPixmap | str] = None
def text(self):
return QCoreApplication.translate("NavItem", f"{self.id}.title")
def __hash__(self):
return hash(self.id)

View File

@ -0,0 +1,84 @@
from PySide6.QtCore import QObject
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QLabel, QSizePolicy, QSpacerItem, QVBoxLayout, QWidget
from ui.navigation.navhost import NavHost, navHost
from ui.navigation.navitem import NavItem
from ui.navigation.navsidebar import NavigationSideBar
class DefaultParentNavItemWidget(QWidget):
def __init__(self, navItem: NavItem, navItemChildren: list[NavItem], parent=None):
super().__init__(parent)
self.navItem = navItem
self.partialNavHost = NavHost(self)
self.partialNavHost.registerNavItem(navItem)
for _navItem in navItemChildren:
self.partialNavHost.registerNavItem(_navItem)
self.partialNavHost.navigate(navItem.id)
self.verticalLayout = QVBoxLayout(self)
self.navItemLabelFont = QFont(self.font())
self.navItemLabelFont.setPointSize(14)
self.navItemLabel = QLabel(self)
self.navItemLabel.setFont(self.navItemLabelFont)
spacer = QSpacerItem(
20, 20, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding
)
self.verticalLayout.addSpacerItem(spacer)
self.navSideBar = NavigationSideBar(self, self.partialNavHost)
self.navSideBar.navigateUpButton.setEnabled(False)
self.verticalLayout.addWidget(self.navSideBar)
self.verticalLayout.addSpacerItem(spacer)
self.retranslateUi()
def retranslateUi(self):
self.navItemLabel.setText(self.navItem.text())
class NavItemWidgets(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.__map: dict[str, QWidget] = {}
# this reference holds all the `DefaultParentNavItemWidget`s
# since these widgets are created with no parents, not keeping a reference to
# them may result in errors, even a silent app crash that is hard to debug
self.__defaultParentWidgetRefs: dict[str, QWidget] = {}
def register(self, navItemId: str, widget: QWidget):
self.__map[navItemId] = widget
def unregister(self, navItemId: str) -> bool:
try:
widget = self.__map.pop(navItemId)
widget.deleteLater()
return True
except KeyError:
return False
def get(self, navItemId: str) -> QWidget | None:
widget = self.__map.get(navItemId)
if widget is not None:
return widget
elif navItemChildren := navHost.getNavItemRelatives(navItemId).children:
if self.__defaultParentWidgetRefs.get(navItemId) is None:
defaultParentNavItemWidget = DefaultParentNavItemWidget(
navHost.findNavItem(navItemId), navItemChildren
)
self.__defaultParentWidgetRefs[navItemId] = defaultParentNavItemWidget
defaultParentNavItemWidget.partialNavHost.activated.connect(
lambda o, n: navHost.navigate(n.id)
)
return self.__defaultParentWidgetRefs.get(navItemId)
else:
return None

190
ui/navigation/navsidebar.py Normal file
View File

@ -0,0 +1,190 @@
from PySide6.QtCore import QModelIndex, Qt, Signal, Slot
from PySide6.QtGui import QFont, QIcon, QKeySequence, QShortcut
from PySide6.QtWidgets import (
QListWidget,
QListWidgetItem,
QPushButton,
QVBoxLayout,
QWidget,
)
from ui.navigation.navhost import NavHost, NavItem, navHost
from ui.widgets.slidingstackedwidget import SlidingStackedWidget
class NavItemListWidget(QListWidget):
NavItemRole = Qt.ItemDataRole.UserRole
def __init__(self, parent=None):
super().__init__(parent)
font = QFont(self.font())
font.setPointSize(14)
self.setFont(font)
self.clicked.connect(self.activated)
def setNavItems(self, items: list[NavItem]):
self.clear()
for navItem in items:
if navItem.icon:
listWidgetItem = QListWidgetItem(QIcon(navItem.icon), navItem.text())
else:
listWidgetItem = QListWidgetItem(navItem.text())
listWidgetItem.setData(self.NavItemRole, navItem)
listWidgetItem.setTextAlignment(
Qt.AlignmentFlag.AlignLeading | Qt.AlignmentFlag.AlignVCenter
)
self.addItem(listWidgetItem)
def selectNavItem(self, navItemId: str):
navItemIds = [
self.item(r).data(self.NavItemRole).id for r in range(self.count())
]
index = navItemIds.index(navItemId)
self.setCurrentIndex(self.model().index(index, 0))
class NavigationWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.navHost = navHost
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.backButton = QPushButton(QIcon(":/icons/back.svg"), "")
self.backButton.setFlat(True)
self.backButton.setFixedHeight(20)
self.verticalLayout.addWidget(self.backButton)
self.navListWidget = NavItemListWidget(self)
self.verticalLayout.addWidget(self.navListWidget)
def setNavigationItems(self, items: list[NavItem]):
self.navListWidget.setNavItems(items)
class NavigationSideBar(QWidget):
navItemActivated = Signal(NavItem)
def __init__(self, parent=None, navHost=navHost):
super().__init__(parent)
self.navHost = None
self.navigateUpKeyboardShortcut = QShortcut(
QKeySequence(Qt.Modifier.ALT | Qt.Key.Key_Left), self, lambda: True
)
self.verticalLayout = QVBoxLayout(self)
self.navigateUpButton = QPushButton(QIcon(":/icons/back.svg"), "")
self.navigateUpButton.setFlat(True)
self.navigateUpButton.setFixedHeight(20)
self.verticalLayout.addWidget(self.navigateUpButton)
self.slidingStackedWidget = SlidingStackedWidget(self)
self.slidingStackedWidget.animationFinished.connect(
self.endChangingNavItemListWidget
)
self.verticalLayout.addWidget(self.slidingStackedWidget)
navItemListWidget = NavItemListWidget(self)
navItemListWidget.activated.connect(self.navItemListWidgetActivatedProxy)
self.slidingStackedWidget.addWidget(navItemListWidget)
self.setNavHost(navHost)
self.reloadNavWidget()
def setNavHost(self, navHost: NavHost):
if self.navHost is not None:
self.navHost.navItemsChanged.disconnect(self.reloadNavWidget)
self.navHost.activated.disconnect(self.navItemChanged)
self.navigateUpKeyboardShortcut.activated.disconnect(
self.navHost.navigateUp
)
self.navigateUpButton.clicked.disconnect(self.navHost.navigateUp)
self.navHost = navHost
self.navHost.navItemsChanged.connect(self.reloadNavWidget)
self.navHost.activated.connect(self.navItemChanged)
self.navigateUpKeyboardShortcut.activated.connect(self.navHost.navigateUp)
self.navigateUpButton.clicked.connect(self.navHost.navigateUp)
@Slot(QModelIndex)
def navItemListWidgetActivatedProxy(self, index: QModelIndex):
self.navHost.navigate(index.data(NavItemListWidget.NavItemRole).id)
self.navItemActivated.emit(index.data(NavItemListWidget.NavItemRole))
def fillNavItemListWidget(
self, currentNavItem: NavItem, listWidget: NavItemListWidget
):
currentNavItemParent = self.navHost.getNavItemRelatives(
currentNavItem.id
).parent
currentNavItems = self.navHost.getNavItemRelatives(
currentNavItemParent.id if currentNavItemParent else ""
)
listWidget.setNavItems(currentNavItems.children)
listWidget.selectNavItem(currentNavItem.id)
def reloadNavWidget(self):
self.fillNavItemListWidget(
self.navHost.currentNavItem, self.slidingStackedWidget.widget(0)
)
self.navItemChanged(self.navHost.currentNavItem, self.navHost.currentNavItem)
@Slot(NavItem, NavItem)
def navItemChanged(self, oldNavItem: NavItem, newNavItem: NavItem):
# update navigateUpButton text
if newNavItemParent := self.navHost.getNavItemRelatives(newNavItem.id).parent:
self.navigateUpButton.setText(newNavItemParent.text())
else:
self.navigateUpButton.setText("Arcaea Offline")
# update navItemListWidget
oldNavItemIdSplitted = self.navHost.getNavItemIdSplitted(oldNavItem.id)
newNavItemIdSplitted = self.navHost.getNavItemIdSplitted(newNavItem.id)
oldNavItemDepth = len(oldNavItemIdSplitted)
newNavItemDepth = len(newNavItemIdSplitted)
if oldNavItemDepth != newNavItemDepth:
# navItem depth changed, replace current NavItemListWidget
newNavItemListWidget = NavItemListWidget(self)
slidingDirection = (
self.slidingStackedWidget.slidingDirection.RightToLeft
if newNavItemDepth > oldNavItemDepth
else self.slidingStackedWidget.slidingDirection.LeftToRight
)
self.fillNavItemListWidget(newNavItem, newNavItemListWidget)
newNavItemListWidget.activated.connect(self.navItemListWidgetActivatedProxy)
self.startChangingNavItemListWidget(newNavItemListWidget, slidingDirection)
def startChangingNavItemListWidget(
self, newNavItemListWidget: NavItemListWidget, slidingDirection
):
newIndex = self.slidingStackedWidget.addWidget(newNavItemListWidget)
[
self.slidingStackedWidget.widget(i).setEnabled(False)
for i in range(self.slidingStackedWidget.count())
]
self.navigateUpButton.setEnabled(False)
self.navigateUpKeyboardShortcut.setEnabled(False)
self.slidingStackedWidget.slideInIdx(newIndex, slidingDirection)
def endChangingNavItemListWidget(self):
oldWidget = self.slidingStackedWidget.widget(0)
self.slidingStackedWidget.removeWidget(oldWidget)
oldWidget.deleteLater()
newWidget = self.slidingStackedWidget.widget(0)
newWidget.setEnabled(True)
self.navigateUpButton.setEnabled(True)
self.navigateUpKeyboardShortcut.setEnabled(True)

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="back.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="24.40625"
inkscape:cx="15.323944"
inkscape:cy="10.632522"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid4"
units="px"
originx="12"
originy="12"
spacingx="1"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="图层 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1"
d="M 17,2 7,12 17,22"
id="path2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -70,12 +70,12 @@
<translation>Continue</translation> <translation>Continue</translation>
</message> </message>
<message> <message>
<location filename="../../startup/databaseChecker.py" line="122"/> <location filename="../../startup/databaseChecker.py" line="117"/>
<source>dialog.tryInitExistingDatabase</source> <source>dialog.tryInitExistingDatabase</source>
<translation>The existing database doesn&apos;t seem to be initialized properly, try initialize again?</translation> <translation>The existing database doesn&apos;t seem to be initialized properly, try initialize again?</translation>
</message> </message>
<message> <message>
<location filename="../../startup/databaseChecker.py" line="138"/> <location filename="../../startup/databaseChecker.py" line="133"/>
<source>dialog.confirmNewDatabase</source> <source>dialog.confirmNewDatabase</source>
<translation>Database file does not exist. Create now?</translation> <translation>Database file does not exist. Create now?</translation>
</message> </message>
@ -224,118 +224,85 @@
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/> <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> <source>queue.addImageButton</source>
<translation>Add Image</translation> <translation>Add Image</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="38"/> <location filename="../../designer/components/ocrQueue.ui" line="71"/>
<source>queue.removeSelected</source> <source>queue.removeSelected</source>
<translation>Remove Selected</translation> <translation>Remove Selected</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="48"/> <location filename="../../designer/components/ocrQueue.ui" line="81"/>
<source>queue.removeAll</source> <source>queue.removeAll</source>
<translation>Remove All</translation> <translation>Remove All</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="68"/> <location filename="../../designer/components/ocrQueue.ui" line="101"/>
<source>queue.optionsButton</source>
<translation>Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source> <source>queue.startOcrButton</source>
<translation>Start OCR</translation> <translation>Start OCR</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="127"/> <location filename="../../designer/components/ocrQueue.ui" line="153"/>
<source>results</source> <source>results</source>
<translation>Results</translation> <translation>Results</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="136"/> <location filename="../../designer/components/ocrQueue.ui" line="162"/>
<source>results.acceptSelectedButton</source> <source>results.acceptSelectedButton</source>
<translation>Accept Selected</translation> <translation>Accept Selected</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="143"/> <location filename="../../designer/components/ocrQueue.ui" line="169"/>
<source>results.acceptAllButton</source> <source>results.acceptAllButton</source>
<translation>Accept All</translation> <translation>Accept All</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="163"/> <location filename="../../designer/components/ocrQueue.ui" line="189"/>
<source>results.ignoreValidate</source> <source>results.ignoreValidate</source>
<translation>Ignore <translation>Ignore
validation</translation> validation</translation>
</message> </message>
</context> </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> <context>
<name>OcrTableModel</name> <name>OcrTableModel</name>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="348"/> <location filename="../../extends/components/ocrQueue.py" line="347"/>
<source>horizontalHeader.title.select</source> <source>horizontalHeader.title.select</source>
<translation>Select</translation> <translation>Select</translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="351"/> <location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.imagePreview</source> <source>horizontalHeader.title.imagePreview</source>
<translation>Image Preview</translation> <translation>Image Preview</translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="352"/> <location filename="../../extends/components/ocrQueue.py" line="349"/>
<source>horizontalHeader.title.chart</source> <source>horizontalHeader.title.chart</source>
<translation>Chart</translation> <translation>Chart</translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="353"/> <location filename="../../extends/components/ocrQueue.py" line="350"/>
<source>horizontalHeader.title.score</source> <source>horizontalHeader.title.score</source>
<translation>Score</translation> <translation>Score</translation>
</message> </message>
@ -705,127 +672,92 @@ validation</translation>
<context> <context>
<name>TabDb_Manage</name> <name>TabDb_Manage</name>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="78"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>syncArcSongDbButton</source> <source>syncArcSongDbButton</source>
<translation>Sync arcsong.db</translation> <translation>Sync arcsong.db</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="85"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>syncArcSongDb.description</source> <source>syncArcSongDb.description</source>
<translation>Update chart info</translation> <translation>Update chart info</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="98"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<source>importScoreGroup</source>
<translation>Import Score</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="105"/>
<source>importSt3Button</source> <source>importSt3Button</source>
<translation>Game Save Database</translation> <translation>Import from Game Save</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="112"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<source>importSt3.description</source> <source>importSt3.description</source>
<translation>Import scores from your game save database</translation> <translation>Import scores from your game save</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="139"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>exportScoreGroup</source> <source>exportScoresButton</source>
<translation>Export Scores</translation> <translation>Export Scores</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="146"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="65"/>
<source>exportScoresButton</source>
<translation>D.E.F. V2</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="153"/>
<source>exportScores.description</source> <source>exportScores.description</source>
<translation>Export all your scores in &lt;i&gt;Arcaea Offline Data Exchange Format V2&lt;/i&gt; formed JSON file</translation> <translation>Export all your scores to a JSON file</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="186"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="79"/>
<source>miscGroup</source>
<translation>Miscellaneous</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="284"/>
<source>syncChartInfoDbButton</source>
<translation>Sync Chart Info Database</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="291"/>
<source>syncChartInfoDb.description</source>
<translation>Update chart info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>importPacklistButton</source> <source>importPacklistButton</source>
<translation>Import packlist</translation> <translation>Import packlist</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="86"/>
<source>importSonglistButton</source> <source>importSonglistButton</source>
<translation>Import songlist</translation> <translation>Import songlist</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="93"/>
<source>importPacklist.description</source> <source>importPacklist.description</source>
<translation>Import packlist file</translation> <translation>Import packlist file</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="100"/>
<source>importSonglist.description</source> <source>importSonglist.description</source>
<translation>Import songlist file</translation> <translation>Import songlist file</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="193"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="107"/>
<source>exportArcsongJsonButton</source> <source>exportArcsongJsonButton</source>
<translation>Export arcsong.json</translation> <translation>Export arcsong.json</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="200"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="114"/>
<source>exportArcsongJson.description</source> <source>exportArcsongJson.description</source>
<translation>Export arcsong.json file based on the information in database</translation> <translation>Export arcsong.json file</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="51"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="121"/>
<source>importApkButton</source> <source>importApkButton</source>
<translation>Import from APK</translation> <translation>Import from APK</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="213"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="128"/>
<source>packSongInfoGroup</source>
<translation>Pack/Song Info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>importApk.description</source> <source>importApk.description</source>
<translation>Import packlist and songlist from .apk file</translation> <translation>Import packlist and songlist from .apk file</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="71"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="135"/>
<source>chartInfoGroup</source>
<translation>Chart Info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="119"/>
<source>importOnlineButton</source> <source>importOnlineButton</source>
<translation>Arcaea Online</translation> <translation>Import from Arcaea Online</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="126"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="142"/>
<source>importOnline.description</source> <source>importOnline.description</source>
<translation>Import scores from the Arcaea Online API result</translation> <translation>Import scores from the result of Arcaea Online API</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="160"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="149"/>
<source>exportSmartRteB30Button</source> <source>exportSmartRteB30Button</source>
<translation>SmartRTE B30</translation> <translation>Export Scores (CSV, SmartRTE B30)</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="167"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="156"/>
<source>exportSmartRteB30.description</source> <source>exportSmartRteB30.description</source>
<translation>Export all your scores to &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; compatible CSV file</translation> <translation>Export all your scores to &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; compatible CSV file</translation>
</message> </message>
@ -908,27 +840,27 @@ validation</translation>
<translation>Delete Selected Scores</translation> <translation>Delete Selected Scores</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="158"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
<source>quickSelectComboBox.idEarlier</source> <source>quickSelectComboBox.idEarlier</source>
<translation>Earlier ID</translation> <translation>Earlier ID</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="164"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
<source>quickSelectComboBox.dateEarlier</source> <source>quickSelectComboBox.dateEarlier</source>
<translation>Earlier date</translation> <translation>Earlier date</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="170"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
<source>quickSelectComboBox.columnsIntegral</source> <source>quickSelectComboBox.columnsIntegral</source>
<translation>More complete data</translation> <translation>More complete data</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="295"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
<source>deleteSelectionDialog.content {}</source> <source>deleteSelectionDialog.content {}</source>
<translation>Deleting {} scores from database, this cannot be undone!&lt;br&gt;Confirm?</translation> <translation>Deleting {} scores from database, this cannot be undone!&lt;br&gt;Confirm?</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="317"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
<source>scan_noColumnsDialog.content</source> <source>scan_noColumnsDialog.content</source>
<translation>You haven&apos;t selected any column! Are you sure to continue?</translation> <translation>You haven&apos;t selected any column! Are you sure to continue?</translation>
</message> </message>

View File

@ -70,12 +70,12 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../startup/databaseChecker.py" line="122"/> <location filename="../../startup/databaseChecker.py" line="117"/>
<source>dialog.tryInitExistingDatabase</source> <source>dialog.tryInitExistingDatabase</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../startup/databaseChecker.py" line="138"/> <location filename="../../startup/databaseChecker.py" line="133"/>
<source>dialog.confirmNewDatabase</source> <source>dialog.confirmNewDatabase</source>
<translation></translation> <translation></translation>
</message> </message>
@ -224,117 +224,84 @@
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/> <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> <source>queue.addImageButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="38"/> <location filename="../../designer/components/ocrQueue.ui" line="71"/>
<source>queue.removeSelected</source> <source>queue.removeSelected</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="48"/> <location filename="../../designer/components/ocrQueue.ui" line="81"/>
<source>queue.removeAll</source> <source>queue.removeAll</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="68"/> <location filename="../../designer/components/ocrQueue.ui" line="101"/>
<source>queue.optionsButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source> <source>queue.startOcrButton</source>
<translation> OCR</translation> <translation> OCR</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="127"/> <location filename="../../designer/components/ocrQueue.ui" line="153"/>
<source>results</source> <source>results</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="136"/> <location filename="../../designer/components/ocrQueue.ui" line="162"/>
<source>results.acceptSelectedButton</source> <source>results.acceptSelectedButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="143"/> <location filename="../../designer/components/ocrQueue.ui" line="169"/>
<source>results.acceptAllButton</source> <source>results.acceptAllButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="163"/> <location filename="../../designer/components/ocrQueue.ui" line="189"/>
<source>results.ignoreValidate</source> <source>results.ignoreValidate</source>
<translation></translation> <translation></translation>
</message> </message>
</context> </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> <context>
<name>OcrTableModel</name> <name>OcrTableModel</name>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="348"/> <location filename="../../extends/components/ocrQueue.py" line="347"/>
<source>horizontalHeader.title.select</source> <source>horizontalHeader.title.select</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="351"/> <location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.imagePreview</source> <source>horizontalHeader.title.imagePreview</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="352"/> <location filename="../../extends/components/ocrQueue.py" line="349"/>
<source>horizontalHeader.title.chart</source> <source>horizontalHeader.title.chart</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../extends/components/ocrQueue.py" line="353"/> <location filename="../../extends/components/ocrQueue.py" line="350"/>
<source>horizontalHeader.title.score</source> <source>horizontalHeader.title.score</source>
<translation></translation> <translation></translation>
</message> </message>
@ -704,127 +671,92 @@
<context> <context>
<name>TabDb_Manage</name> <name>TabDb_Manage</name>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="78"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>syncArcSongDbButton</source> <source>syncArcSongDbButton</source>
<translation> arcsong.db</translation> <translation> arcsong.db</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="85"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>syncArcSongDb.description</source> <source>syncArcSongDb.description</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="98"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<source>importScoreGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="105"/>
<source>importSt3Button</source> <source>importSt3Button</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="112"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<source>importSt3.description</source> <source>importSt3.description</source>
<translation>姿&lt;br&gt;退</translation> <translation>姿&lt;br&gt;退</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="139"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>exportScoreGroup</source> <source>exportScoresButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="146"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="65"/>
<source>exportScoresButton</source>
<translation> V2</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="153"/>
<source>exportScores.description</source> <source>exportScores.description</source>
<translation> &lt;i&gt;Arcaea Offline V2&lt;/i&gt; JSON </translation> <translation> JSON </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="186"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="79"/>
<source>miscGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="284"/>
<source>syncChartInfoDbButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="291"/>
<source>syncChartInfoDb.description</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>importPacklistButton</source> <source>importPacklistButton</source>
<translation> packlist</translation> <translation> packlist</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="86"/>
<source>importSonglistButton</source> <source>importSonglistButton</source>
<translation> songlist</translation> <translation> songlist</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="93"/>
<source>importPacklist.description</source> <source>importPacklist.description</source>
<translation> packlist </translation> <translation> packlist </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="100"/>
<source>importSonglist.description</source> <source>importSonglist.description</source>
<translation> songlist </translation> <translation> songlist </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="193"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="107"/>
<source>exportArcsongJsonButton</source> <source>exportArcsongJsonButton</source>
<translation> arcsong.json</translation> <translation> arcsong.json</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="200"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="114"/>
<source>exportArcsongJson.description</source> <source>exportArcsongJson.description</source>
<translation> arcsong.json </translation> <translation> arcsong.json </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="51"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="121"/>
<source>importApkButton</source> <source>importApkButton</source>
<translation> APK </translation> <translation> APK </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="213"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="128"/>
<source>packSongInfoGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>importApk.description</source> <source>importApk.description</source>
<translation> .apk packlist songlist</translation> <translation> .apk packlist songlist</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="71"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="135"/>
<source>chartInfoGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="119"/>
<source>importOnlineButton</source> <source>importOnlineButton</source>
<translation>Arcaea Online</translation> <translation> Arcaea Online </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="126"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="142"/>
<source>importOnline.description</source> <source>importOnline.description</source>
<translation> Arcaea Online API </translation> <translation> Arcaea Online API </translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="160"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="149"/>
<source>exportSmartRteB30Button</source> <source>exportSmartRteB30Button</source>
<translation>SmartRTE B30</translation> <translation>CSVSmartRTE B30</translation>
</message> </message>
<message> <message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="167"/> <location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="156"/>
<source>exportSmartRteB30.description</source> <source>exportSmartRteB30.description</source>
<translation> &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; 的 CSV 文件</translation> <translation> &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; 的 CSV 文件</translation>
</message> </message>
@ -907,27 +839,27 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="158"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
<source>quickSelectComboBox.idEarlier</source> <source>quickSelectComboBox.idEarlier</source>
<translation>ID </translation> <translation>ID </translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="164"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
<source>quickSelectComboBox.dateEarlier</source> <source>quickSelectComboBox.dateEarlier</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="170"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
<source>quickSelectComboBox.columnsIntegral</source> <source>quickSelectComboBox.columnsIntegral</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="295"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
<source>deleteSelectionDialog.content {}</source> <source>deleteSelectionDialog.content {}</source>
<translation> {} &lt;br&gt;</translation> <translation> {} &lt;br&gt;</translation>
</message> </message>
<message> <message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="317"/> <location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
<source>scan_noColumnsDialog.content</source> <source>scan_noColumnsDialog.content</source>
<translation></translation> <translation></translation>
</message> </message>

View File

@ -8,6 +8,8 @@
<file>fonts/GeosansLight.ttf</file> <file>fonts/GeosansLight.ttf</file>
<file>icons/back.svg</file>
<file>images/icon.png</file> <file>images/icon.png</file>
<file>images/logo.png</file> <file>images/logo.png</file>
<file>images/jacket-placeholder.png</file> <file>images/jacket-placeholder.png</file>

View File

@ -3,11 +3,11 @@ import traceback
from enum import IntEnum from enum import IntEnum
from arcaea_offline.database import Database from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, QSysInfo, Qt, QUrl, Slot from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, Qt, QUrl, Slot
from PySide6.QtWidgets import QDialog, QMessageBox from PySide6.QtWidgets import QDialog, QMessageBox
from core.settings import SettingsKeys, settings
from ui.extends.shared.database import create_engine from ui.extends.shared.database import create_engine
from ui.extends.shared.settings import Settings
from .databaseChecker_ui import Ui_DatabaseChecker from .databaseChecker_ui import Ui_DatabaseChecker
@ -29,7 +29,8 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory) self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory)
self.confirmDbByExistingSettings = False self.confirmDbByExistingSettings = False
if dbUrlString := settings.stringValue(SettingsKeys.General.DatabaseUrl): self.settings = Settings(self)
if dbUrlString := self.settings.databaseUrl():
dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://")) dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://"))
dbFileInfo = QFileInfo(dbFileUrl.toLocalFile()) dbFileInfo = QFileInfo(dbFileUrl.toLocalFile())
if dbFileInfo.exists(): if dbFileInfo.exists():
@ -44,9 +45,6 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.selectFile(QDir.currentPath()) self.dbDirSelector.selectFile(QDir.currentPath())
self.dbFilenameLineEdit.setText("arcaea_offline.db") self.dbFilenameLineEdit.setText("arcaea_offline.db")
def writeDatabaseUrlToSettings(self, databaseUrl: str):
settings.setValue(SettingsKeys.General.DatabaseUrl, databaseUrl)
def dbPath(self): def dbPath(self):
return QDir(self.dbDirSelector.selectedFiles()[0]) return QDir(self.dbDirSelector.selectedFiles()[0])
@ -61,13 +59,8 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
return QUrl.fromLocalFile(self.dbFileInfo().filePath()) return QUrl.fromLocalFile(self.dbFileInfo().filePath())
def dbSqliteUrl(self): def dbSqliteUrl(self):
kernelType = QSysInfo.kernelType() # dbSqliteUrl.setScheme("sqlite")
# the slash count varies depending on the kernel return QUrl(self.dbFileUrl().toString().replace("file://", "sqlite://"))
# https://docs.sqlalchemy.org/en/20/core/engines.html#sqlite
if kernelType == "winnt":
return QUrl(self.dbFileUrl().toString().replace("file://", "sqlite://"))
else:
return QUrl(self.dbFileUrl().toString().replace("file://", "sqlite:///"))
def confirmDb(self) -> DatabaseCheckerResult: def confirmDb(self) -> DatabaseCheckerResult:
flags = 0x000 flags = 0x000
@ -81,7 +74,7 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
db = Database(create_engine(dbSqliteUrl)) db = Database(create_engine(dbSqliteUrl))
if db.check_init(): if db.check_init():
flags |= DatabaseCheckerResult.Initted flags |= DatabaseCheckerResult.Initted
self.writeDatabaseUrlToSettings(self.dbSqliteUrl().toString()) self.settings.setDatabaseUrl(self.dbSqliteUrl().toString())
return flags return flags
@ -110,18 +103,20 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
@Slot() @Slot()
def on_confirmDbPathButton_clicked(self): def on_confirmDbPathButton_clicked(self):
dbSqliteUrl = self.dbSqliteUrl() dbSqliteUrl = self.dbSqliteUrl()
self.writeDatabaseUrlToSettings(dbSqliteUrl.toString()) self.settings.setDatabaseUrl(dbSqliteUrl.toString())
result = self.confirmDb() result = self.confirmDb()
if result & DatabaseCheckerResult.Initted: if result & DatabaseCheckerResult.Initted:
if not self.confirmDbByExistingSettings: if not self.confirmDbByExistingSettings:
self.writeDatabaseUrlToSettings(dbSqliteUrl.toString()) self.settings.setDatabaseUrl(dbSqliteUrl.toString())
elif result & DatabaseCheckerResult.FileExist: elif result & DatabaseCheckerResult.FileExist:
confirm_try_init = QMessageBox.question( confirm_try_init = QMessageBox.question(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"), QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"),
) # fmt: skip # fmt: on
)
if confirm_try_init == QMessageBox.StandardButton.Yes: if confirm_try_init == QMessageBox.StandardButton.Yes:
try: try:
Database().init(checkfirst=True) Database().init(checkfirst=True)
@ -134,8 +129,10 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
confirm_new_database = QMessageBox.question( confirm_new_database = QMessageBox.question(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"), QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"),
) # fmt: skip # fmt: on
)
if confirm_new_database == QMessageBox.StandardButton.Yes: if confirm_new_database == QMessageBox.StandardButton.Yes:
db = Database(create_engine(dbSqliteUrl)) db = Database(create_engine(dbSqliteUrl))
db.init() db.init()

0
ui/widgets/__init__.py Normal file
View File

View File

@ -0,0 +1,231 @@
"""
Adapted from https://github.com/Qt-Widgets/SlidingStackedWidget-1
MIT License
Copyright (c) 2020 Tim Schneeberger (ThePBone) <tim.schneeberger(at)outlook.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from enum import IntEnum
from PySide6.QtCore import (
QAbstractAnimation,
QEasingCurve,
QParallelAnimationGroup,
QPoint,
QPropertyAnimation,
Signal,
)
from PySide6.QtWidgets import (
QGraphicsEffect,
QGraphicsOpacityEffect,
QStackedWidget,
QWidget,
)
class SlidingDirection(IntEnum):
Auto = 0
LeftToRight = 1
RightToLeft = 2
TopToBottom = 3
BottomToTop = 4
class SlidingStackedWidget(QStackedWidget):
slidingDirection = SlidingDirection
animationFinished = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.vertical = False
self.speedMs = 300
self.animationEasingCurve = QEasingCurve.Type.OutQuart
self.animationCurrentIndex = 0
self.animationNextIndex = 0
self.animationCurrentPoint = QPoint(0, 0)
self.animationRunning = False
self.wrap = False
self.opacityAnimation = False
def setVertical(self, vertical: bool):
self.vertical = vertical
def setSpeedMs(self, speedMs: int):
self.speedMs = speedMs
def setAnimationEasingCurve(self, easingCurve: QEasingCurve.Type):
self.animationEasingCurve = easingCurve
def setWrap(self, wrap: bool):
self.wrap = wrap
def setOpacityAnimation(self, value: bool):
self.opacityAnimation = value
def slideInNext(self) -> bool:
currentIndex = self.currentIndex()
if self.wrap or (currentIndex < self.count() - 1):
self.slideInIdx(currentIndex + 1)
else:
return False
return True
def slideInPrev(self) -> bool:
currentIndex = self.currentIndex()
if self.wrap or (currentIndex > 0):
self.slideInIdx(currentIndex - 1)
else:
return False
return True
def slideInIdx(self, idx: int, direction: SlidingDirection = SlidingDirection.Auto):
if idx > self.count() - 1:
direction = (
SlidingDirection.TopToBottom
if self.vertical
else SlidingDirection.RightToLeft
)
idx %= self.count()
elif idx < 0:
direction = (
SlidingDirection.BottomToTop
if self.vertical
else SlidingDirection.LeftToRight
)
idx = (idx + self.count()) % self.count()
self.slideInWgt(self.widget(idx), direction)
def slideInWgt(self, newwidget: QWidget, direction: SlidingDirection):
if self.animationRunning:
return
self.animationRunning = True
autoDirection = SlidingDirection.LeftToRight
currentIndex = self.currentIndex()
nextIndex = self.indexOf(newwidget)
if currentIndex == nextIndex:
self.animationRunning = False
return
elif currentIndex < nextIndex:
autoDirection = (
SlidingDirection.TopToBottom
if self.vertical
else SlidingDirection.RightToLeft
)
else:
autoDirection = (
SlidingDirection.BottomToTop
if self.vertical
else SlidingDirection.LeftToRight
)
if direction == SlidingDirection.Auto:
direction = autoDirection
offsetX = self.frameRect().width()
offsetY = self.frameRect().height()
self.widget(nextIndex).setGeometry(0, 0, offsetX, offsetY)
if direction == SlidingDirection.BottomToTop:
offsetX = 0
offsetY = -offsetY
elif direction == SlidingDirection.TopToBottom:
offsetX = 0
elif direction == SlidingDirection.RightToLeft:
offsetX = -offsetX
offsetY = 0
elif direction == SlidingDirection.LeftToRight:
offsetY = 0
nextPoint = self.widget(nextIndex).pos()
currentPoint = self.widget(currentIndex).pos()
self.animationCurrentPoint = currentPoint
self.widget(nextIndex).move(nextPoint.x() - offsetX, nextPoint.y() - offsetY)
self.widget(nextIndex).show()
self.widget(nextIndex).raise_()
currentWidgetAnimation = self.widgetPosAnimation(currentIndex)
currentWidgetAnimation.setStartValue(QPoint(currentPoint.x(), currentPoint.y()))
currentWidgetAnimation.setEndValue(
QPoint(offsetX + currentPoint.x(), offsetY + currentPoint.y())
)
nextWidgetAnimation = self.widgetPosAnimation(nextIndex)
nextWidgetAnimation.setStartValue(
QPoint(-offsetX + nextPoint.x(), offsetY + nextPoint.y())
)
nextWidgetAnimation.setEndValue(QPoint(nextPoint.x(), nextPoint.y()))
animationGroup = QParallelAnimationGroup(self)
animationGroup.addAnimation(currentWidgetAnimation)
animationGroup.addAnimation(nextWidgetAnimation)
if self.opacityAnimation:
currentWidgetOpacityEffect = QGraphicsOpacityEffect()
currentWidgetOpacityEffectAnimation = self.widgetOpacityAnimation(
currentIndex, currentWidgetOpacityEffect, 1, 0
)
nextWidgetOpacityEffect = QGraphicsOpacityEffect()
nextWidgetOpacityEffect.setOpacity(0)
nextWidgetOpacityEffectAnimation = self.widgetOpacityAnimation(
nextIndex, nextWidgetOpacityEffect, 0, 1
)
animationGroup.addAnimation(currentWidgetOpacityEffectAnimation)
animationGroup.addAnimation(nextWidgetOpacityEffectAnimation)
animationGroup.finished.connect(self.animationDoneSlot)
self.animationNextIndex = nextIndex
self.animationCurrentIndex = currentIndex
self.animationRunning = True
animationGroup.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
def widgetPosAnimation(self, widgetIndex: int):
result = QPropertyAnimation(self.widget(widgetIndex), b"pos")
result.setDuration(self.speedMs)
result.setEasingCurve(self.animationEasingCurve)
return result
def widgetOpacityAnimation(
self, widgetIndex: int, graphicEffect: QGraphicsEffect, startValue, endValue
):
self.widget(widgetIndex).setGraphicsEffect(graphicEffect)
result = QPropertyAnimation(graphicEffect, b"opacity")
result.setDuration(round(self.speedMs / 2))
result.setStartValue(startValue)
result.setEndValue(endValue)
result.finished.connect(
lambda: graphicEffect.deleteLater() if graphicEffect is not None else ...
)
return result
def animationDoneSlot(self):
self.setCurrentIndex(self.animationNextIndex)
self.widget(self.animationCurrentIndex).hide()
self.widget(self.animationCurrentIndex).move(self.animationCurrentPoint)
self.animationRunning = False
self.animationFinished.emit()