This commit is contained in:
2023-07-07 01:41:19 +08:00
commit 95da43261e
83 changed files with 7529 additions and 0 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.py]
indent_size = 4
indent_style = space

170
.gitignore vendored Normal file
View File

@ -0,0 +1,170 @@
__debug*
arcaea_offline.db
arcaea_offline.ini
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Nuitka
*.dist/
*.build/
*.onefile-build/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

52
index.py Normal file
View File

@ -0,0 +1,52 @@
import logging
import sys
import traceback
from arcaea_offline.database import Database
from PySide6.QtCore import QLibraryInfo, QLocale, QTranslator
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QDialog, QMessageBox
from ui.startup.databaseChecker import DatabaseChecker
from ui.implements.mainwindow import MainWindow
import ui.resources.images.images_rc
import ui.resources.translations.translations_rc
logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True)
if __name__ == "__main__":
locale = QLocale.system()
translator = QTranslator()
translator_load_success = translator.load(QLocale.system(), "", "", ":/lang/")
if not translator_load_success:
translator.load(":/lang/en_US.qm")
baseTranslator = QTranslator()
baseTranslator.load(
QLocale.system(),
"qt",
"_",
QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath),
)
app = QApplication(sys.argv)
app.installTranslator(translator)
app.installTranslator(baseTranslator)
databaseChecker = DatabaseChecker()
result = databaseChecker.exec()
if result == QDialog.DialogCode.Accepted:
try:
Database()
except Exception as e:
QMessageBox.critical(
None, "Database Error", "\n".join(traceback.format_exception(e))
)
sys.exit(1)
window = MainWindow()
window.setWindowIcon(QIcon(":/images/icon.png"))
window.show()
sys.exit(app.exec())
else:
sys.exit(1)

40
pyproject.toml Normal file
View File

@ -0,0 +1,40 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "arcaea-offline-pyside-ui"
version = "0.1.0"
authors = [{ name = "283375", email = "log_283375@163.com" }]
description = "No description."
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"arcaea-offline==0.1.0",
"arcaea-offline-ocr==0.1.0",
"exif==1.6.0",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
]
[project.urls]
"Homepage" = "https://github.com/283375/arcaea-offline-pyside-ui"
"Bug Tracker" = "https://github.com/283375/arcaea-offline-pyside-ui/issues"
[tool.black]
force-exclude = '''
(
ui/designer
| .*_rc.py
)
'''
[tool.isort]
profile = "black"
extend_skip = ["ui/designer"]
extend_skip_glob = ["*_rc.py"]
[tool.pyright]
ignore = ["**/__debug*.*"]

0
ui/__init__.py Normal file
View File

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChartSelector</class>
<widget class="QWidget" name="ChartSelector">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>295</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">ChartSelector</string>
</property>
<layout class="QVBoxLayout" name="mainVerticalLayout">
<item>
<widget class="QGroupBox" name="songIdSelectorGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>songIdSelector.title</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="fuzzySearchLineEdit">
<property name="frame">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>fuzzySearch.lineEdit.placeholder</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="packageComboBox"/>
</item>
<item>
<widget class="QComboBox" name="songIdComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="songIdSelectorQuickActionsGroupBox">
<property name="title">
<string>songIdSelector.quickActions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="previousPackageButton">
<property name="text">
<string>songIdSelector.quickActions.previousPackageButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="previousSongIdButton">
<property name="text">
<string>songIdSelector.quickActions.previousSongIdButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextSongIdButton">
<property name="text">
<string>songIdSelector.quickActions.nextSongIdButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextPackageButton">
<property name="text">
<string>songIdSelector.quickActions.nextPackageButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ratingClassGroupBox">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>ratingClassSelector.title</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="RatingClassRadioButton" name="pstButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">PAST</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="RatingClassRadioButton" name="prsButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">PRESENT</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="RatingClassRadioButton" name="ftrButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">FUTURE</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="RatingClassRadioButton" name="bydButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">BEYOND</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="resultsHorizontalLayout">
<item>
<widget class="QLabel" name="resultLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">...</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>resetButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RatingClassRadioButton</class>
<extends>QRadioButton</extends>
<header>ui.implements.components.ratingClassRadioButton</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>bydButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'chartSelector.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QComboBox, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QSizePolicy,
QSpacerItem, QVBoxLayout, QWidget)
from ui.implements.components.ratingClassRadioButton import RatingClassRadioButton
class Ui_ChartSelector(object):
def setupUi(self, ChartSelector):
if not ChartSelector.objectName():
ChartSelector.setObjectName(u"ChartSelector")
ChartSelector.resize(671, 295)
ChartSelector.setWindowTitle(u"ChartSelector")
self.mainVerticalLayout = QVBoxLayout(ChartSelector)
self.mainVerticalLayout.setObjectName(u"mainVerticalLayout")
self.songIdSelectorGroupBox = QGroupBox(ChartSelector)
self.songIdSelectorGroupBox.setObjectName(u"songIdSelectorGroupBox")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.songIdSelectorGroupBox.sizePolicy().hasHeightForWidth())
self.songIdSelectorGroupBox.setSizePolicy(sizePolicy)
self.horizontalLayout = QHBoxLayout(self.songIdSelectorGroupBox)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.widget = QWidget(self.songIdSelectorGroupBox)
self.widget.setObjectName(u"widget")
self.widget.setMinimumSize(QSize(300, 0))
self.verticalLayout = QVBoxLayout(self.widget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.fuzzySearchLineEdit = QLineEdit(self.widget)
self.fuzzySearchLineEdit.setObjectName(u"fuzzySearchLineEdit")
self.fuzzySearchLineEdit.setFrame(True)
self.fuzzySearchLineEdit.setClearButtonEnabled(True)
self.verticalLayout.addWidget(self.fuzzySearchLineEdit)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
self.packageComboBox = QComboBox(self.widget)
self.packageComboBox.setObjectName(u"packageComboBox")
self.verticalLayout.addWidget(self.packageComboBox)
self.songIdComboBox = QComboBox(self.widget)
self.songIdComboBox.setObjectName(u"songIdComboBox")
self.verticalLayout.addWidget(self.songIdComboBox)
self.horizontalLayout.addWidget(self.widget)
self.songIdSelectorQuickActionsGroupBox = QGroupBox(self.songIdSelectorGroupBox)
self.songIdSelectorQuickActionsGroupBox.setObjectName(u"songIdSelectorQuickActionsGroupBox")
self.verticalLayout_2 = QVBoxLayout(self.songIdSelectorQuickActionsGroupBox)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.previousPackageButton = QPushButton(self.songIdSelectorQuickActionsGroupBox)
self.previousPackageButton.setObjectName(u"previousPackageButton")
self.verticalLayout_2.addWidget(self.previousPackageButton)
self.previousSongIdButton = QPushButton(self.songIdSelectorQuickActionsGroupBox)
self.previousSongIdButton.setObjectName(u"previousSongIdButton")
self.verticalLayout_2.addWidget(self.previousSongIdButton)
self.nextSongIdButton = QPushButton(self.songIdSelectorQuickActionsGroupBox)
self.nextSongIdButton.setObjectName(u"nextSongIdButton")
self.verticalLayout_2.addWidget(self.nextSongIdButton)
self.nextPackageButton = QPushButton(self.songIdSelectorQuickActionsGroupBox)
self.nextPackageButton.setObjectName(u"nextPackageButton")
self.verticalLayout_2.addWidget(self.nextPackageButton)
self.horizontalLayout.addWidget(self.songIdSelectorQuickActionsGroupBox)
self.mainVerticalLayout.addWidget(self.songIdSelectorGroupBox)
self.ratingClassGroupBox = QGroupBox(ChartSelector)
self.ratingClassGroupBox.setObjectName(u"ratingClassGroupBox")
self.ratingClassGroupBox.setMinimumSize(QSize(200, 0))
self.horizontalLayout_2 = QHBoxLayout(self.ratingClassGroupBox)
self.horizontalLayout_2.setSpacing(0)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.pstButton = RatingClassRadioButton(self.ratingClassGroupBox)
self.pstButton.setObjectName(u"pstButton")
sizePolicy1 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.pstButton.sizePolicy().hasHeightForWidth())
self.pstButton.setSizePolicy(sizePolicy1)
self.pstButton.setText(u"PAST")
self.pstButton.setAutoExclusive(False)
self.horizontalLayout_2.addWidget(self.pstButton)
self.prsButton = RatingClassRadioButton(self.ratingClassGroupBox)
self.prsButton.setObjectName(u"prsButton")
sizePolicy1.setHeightForWidth(self.prsButton.sizePolicy().hasHeightForWidth())
self.prsButton.setSizePolicy(sizePolicy1)
self.prsButton.setText(u"PRESENT")
self.prsButton.setAutoExclusive(False)
self.horizontalLayout_2.addWidget(self.prsButton)
self.ftrButton = RatingClassRadioButton(self.ratingClassGroupBox)
self.ftrButton.setObjectName(u"ftrButton")
sizePolicy1.setHeightForWidth(self.ftrButton.sizePolicy().hasHeightForWidth())
self.ftrButton.setSizePolicy(sizePolicy1)
self.ftrButton.setText(u"FUTURE")
self.ftrButton.setAutoExclusive(False)
self.horizontalLayout_2.addWidget(self.ftrButton)
self.bydButton = RatingClassRadioButton(self.ratingClassGroupBox)
self.bydButton.setObjectName(u"bydButton")
self.bydButton.setEnabled(False)
sizePolicy1.setHeightForWidth(self.bydButton.sizePolicy().hasHeightForWidth())
self.bydButton.setSizePolicy(sizePolicy1)
self.bydButton.setText(u"BEYOND")
self.bydButton.setAutoExclusive(False)
self.horizontalLayout_2.addWidget(self.bydButton)
self.mainVerticalLayout.addWidget(self.ratingClassGroupBox)
self.resultsHorizontalLayout = QHBoxLayout()
self.resultsHorizontalLayout.setObjectName(u"resultsHorizontalLayout")
self.resultLabel = QLabel(ChartSelector)
self.resultLabel.setObjectName(u"resultLabel")
sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.resultLabel.sizePolicy().hasHeightForWidth())
self.resultLabel.setSizePolicy(sizePolicy2)
self.resultLabel.setText(u"...")
self.resultLabel.setTextFormat(Qt.RichText)
self.resultsHorizontalLayout.addWidget(self.resultLabel)
self.resetButton = QPushButton(ChartSelector)
self.resetButton.setObjectName(u"resetButton")
self.resultsHorizontalLayout.addWidget(self.resetButton)
self.mainVerticalLayout.addLayout(self.resultsHorizontalLayout)
self.retranslateUi(ChartSelector)
QMetaObject.connectSlotsByName(ChartSelector)
# setupUi
def retranslateUi(self, ChartSelector):
self.songIdSelectorGroupBox.setTitle(QCoreApplication.translate("ChartSelector", u"songIdSelector.title", None))
self.fuzzySearchLineEdit.setPlaceholderText(QCoreApplication.translate("ChartSelector", u"fuzzySearch.lineEdit.placeholder", None))
self.songIdSelectorQuickActionsGroupBox.setTitle(QCoreApplication.translate("ChartSelector", u"songIdSelector.quickActions", None))
self.previousPackageButton.setText(QCoreApplication.translate("ChartSelector", u"songIdSelector.quickActions.previousPackageButton", None))
self.previousSongIdButton.setText(QCoreApplication.translate("ChartSelector", u"songIdSelector.quickActions.previousSongIdButton", None))
self.nextSongIdButton.setText(QCoreApplication.translate("ChartSelector", u"songIdSelector.quickActions.nextSongIdButton", None))
self.nextPackageButton.setText(QCoreApplication.translate("ChartSelector", u"songIdSelector.quickActions.nextPackageButton", None))
self.ratingClassGroupBox.setTitle(QCoreApplication.translate("ChartSelector", u"ratingClassSelector.title", None))
self.resetButton.setText(QCoreApplication.translate("ChartSelector", u"resetButton", None))
pass
# retranslateUi

View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DbTableViewer</class>
<widget class="QWidget" name="DbTableViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>681</width>
<height>575</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">DbTableViewer</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>actions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="action_removeSelectedButton">
<property name="text">
<string>actions.removeSelected</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>actions.refresh</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QTableView" name="tableView">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>view</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>view.sort.label</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="sort_comboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sort_descendingCheckBox">
<property name="text">
<string>view.sort.descendingCheckBox</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>view.filter.label</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>view.filter.configureButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'dbTableViewer.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QAbstractItemView, QApplication, QCheckBox, QComboBox,
QGridLayout, QGroupBox, QHBoxLayout, QHeaderView,
QLabel, QPushButton, QSizePolicy, QSpacerItem,
QTableView, QVBoxLayout, QWidget)
class Ui_DbTableViewer(object):
def setupUi(self, DbTableViewer):
if not DbTableViewer.objectName():
DbTableViewer.setObjectName(u"DbTableViewer")
DbTableViewer.resize(681, 575)
DbTableViewer.setWindowTitle(u"DbTableViewer")
self.gridLayout = QGridLayout(DbTableViewer)
self.gridLayout.setObjectName(u"gridLayout")
self.groupBox = QGroupBox(DbTableViewer)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout = QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName(u"verticalLayout")
self.action_removeSelectedButton = QPushButton(self.groupBox)
self.action_removeSelectedButton.setObjectName(u"action_removeSelectedButton")
self.verticalLayout.addWidget(self.action_removeSelectedButton)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
self.refreshButton = QPushButton(self.groupBox)
self.refreshButton.setObjectName(u"refreshButton")
self.verticalLayout.addWidget(self.refreshButton)
self.gridLayout.addWidget(self.groupBox, 0, 1, 1, 1)
self.tableView = QTableView(DbTableViewer)
self.tableView.setObjectName(u"tableView")
self.tableView.setEditTriggers(QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed)
self.tableView.setProperty("showDropIndicator", False)
self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
self.tableView.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.tableView.verticalHeader().setVisible(False)
self.gridLayout.addWidget(self.tableView, 0, 0, 1, 1)
self.groupBox_2 = QGroupBox(DbTableViewer)
self.groupBox_2.setObjectName(u"groupBox_2")
self.verticalLayout_3 = QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.label = QLabel(self.groupBox_2)
self.label.setObjectName(u"label")
self.horizontalLayout_2.addWidget(self.label)
self.sort_comboBox = QComboBox(self.groupBox_2)
self.sort_comboBox.setObjectName(u"sort_comboBox")
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sort_comboBox.sizePolicy().hasHeightForWidth())
self.sort_comboBox.setSizePolicy(sizePolicy)
self.horizontalLayout_2.addWidget(self.sort_comboBox)
self.sort_descendingCheckBox = QCheckBox(self.groupBox_2)
self.sort_descendingCheckBox.setObjectName(u"sort_descendingCheckBox")
self.sort_descendingCheckBox.setChecked(True)
self.horizontalLayout_2.addWidget(self.sort_descendingCheckBox)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(-1, 9, -1, 9)
self.label_2 = QLabel(self.groupBox_2)
self.label_2.setObjectName(u"label_2")
self.horizontalLayout.addWidget(self.label_2)
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
self.pushButton = QPushButton(self.groupBox_2)
self.pushButton.setObjectName(u"pushButton")
self.horizontalLayout.addWidget(self.pushButton)
self.verticalLayout_3.addLayout(self.horizontalLayout)
self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1)
self.retranslateUi(DbTableViewer)
QMetaObject.connectSlotsByName(DbTableViewer)
# setupUi
def retranslateUi(self, DbTableViewer):
self.groupBox.setTitle(QCoreApplication.translate("DbTableViewer", u"actions", None))
self.action_removeSelectedButton.setText(QCoreApplication.translate("DbTableViewer", u"actions.removeSelected", None))
self.refreshButton.setText(QCoreApplication.translate("DbTableViewer", u"actions.refresh", None))
self.groupBox_2.setTitle(QCoreApplication.translate("DbTableViewer", u"view", None))
self.label.setText(QCoreApplication.translate("DbTableViewer", u"view.sort.label", None))
self.sort_descendingCheckBox.setText(QCoreApplication.translate("DbTableViewer", u"view.sort.descendingCheckBox", None))
self.label_2.setText(QCoreApplication.translate("DbTableViewer", u"view.filter.label", None))
self.pushButton.setText(QCoreApplication.translate("DbTableViewer", u"view.filter.configureButton", None))
pass
# retranslateUi

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileSelector</class>
<widget class="QWidget" name="FileSelector">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>559</width>
<height>42</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">FileSelector</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="elidedLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectButton">
<property name="text">
<string>selectButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ElidedLabel</class>
<extends>QLabel</extends>
<header>ui.implements.components.elidedLabel</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'fileSelector.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QHBoxLayout, QPushButton, QSizePolicy,
QWidget)
from ui.implements.components.elidedLabel import ElidedLabel
class Ui_FileSelector(object):
def setupUi(self, FileSelector):
if not FileSelector.objectName():
FileSelector.setObjectName(u"FileSelector")
FileSelector.resize(559, 42)
FileSelector.setWindowTitle(u"FileSelector")
self.horizontalLayout_2 = QHBoxLayout(FileSelector)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.elidedLabel = ElidedLabel(FileSelector)
self.elidedLabel.setObjectName(u"elidedLabel")
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.elidedLabel.sizePolicy().hasHeightForWidth())
self.elidedLabel.setSizePolicy(sizePolicy)
self.elidedLabel.setText(u"...")
self.horizontalLayout_2.addWidget(self.elidedLabel)
self.selectButton = QPushButton(FileSelector)
self.selectButton.setObjectName(u"selectButton")
self.horizontalLayout_2.addWidget(self.selectButton)
self.retranslateUi(FileSelector)
QMetaObject.connectSlotsByName(FileSelector)
# setupUi
def retranslateUi(self, FileSelector):
self.selectButton.setText(QCoreApplication.translate("FileSelector", u"selectButton", None))
pass
# retranslateUi

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScoreEditor</class>
<widget class="QWidget" name="ScoreEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>253</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">ScoreEditor</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>formLabel.score</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="FocusSelectAllLineEdit" name="scoreLineEdit">
<property name="inputMask">
<string notr="true">B9'999'999;_</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string notr="true">PURE</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="pureSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string notr="true">FAR</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="farSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string notr="true">LOST</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="lostSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>formLabel.time</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDateTimeEdit" name="dateTimeEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="dateTime">
<datetime>
<hour>0</hour>
<minute>0</minute>
<second>0</second>
<year>2017</year>
<month>1</month>
<day>22</day>
</datetime>
</property>
<property name="minimumDate">
<date>
<year>2017</year>
<month>1</month>
<day>22</day>
</date>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string notr="true">MAX RECALL</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="maxRecallSpinBox">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-1</number>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="validateLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="commitButton">
<property name="text">
<string>commitButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>formLabel.clearType</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="clearTypeComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FocusSelectAllLineEdit</class>
<extends>QLineEdit</extends>
<header>ui.implements.components.focusSelectAllLineEdit</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'scoreEditor.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QComboBox, QDateTimeEdit, QFormLayout,
QHBoxLayout, QLabel, QPushButton, QSizePolicy,
QSpacerItem, QSpinBox, QWidget)
from ui.implements.components.focusSelectAllLineEdit import FocusSelectAllLineEdit
class Ui_ScoreEditor(object):
def setupUi(self, ScoreEditor):
if not ScoreEditor.objectName():
ScoreEditor.setObjectName(u"ScoreEditor")
ScoreEditor.resize(365, 253)
ScoreEditor.setWindowTitle(u"ScoreEditor")
self.formLayout = QFormLayout(ScoreEditor)
self.formLayout.setObjectName(u"formLayout")
self.formLayout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label = QLabel(ScoreEditor)
self.label.setObjectName(u"label")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
self.scoreLineEdit = FocusSelectAllLineEdit(ScoreEditor)
self.scoreLineEdit.setObjectName(u"scoreLineEdit")
self.scoreLineEdit.setInputMask(u"B9'999'999;_")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.scoreLineEdit)
self.label_2 = QLabel(ScoreEditor)
self.label_2.setObjectName(u"label_2")
self.label_2.setText(u"PURE")
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)
self.pureSpinBox = QSpinBox(ScoreEditor)
self.pureSpinBox.setObjectName(u"pureSpinBox")
self.pureSpinBox.setMinimumSize(QSize(100, 0))
self.pureSpinBox.setMaximum(0)
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.pureSpinBox)
self.label_3 = QLabel(ScoreEditor)
self.label_3.setObjectName(u"label_3")
self.label_3.setText(u"FAR")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3)
self.farSpinBox = QSpinBox(ScoreEditor)
self.farSpinBox.setObjectName(u"farSpinBox")
self.farSpinBox.setMinimumSize(QSize(100, 0))
self.farSpinBox.setMaximum(0)
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.farSpinBox)
self.label_4 = QLabel(ScoreEditor)
self.label_4.setObjectName(u"label_4")
self.label_4.setText(u"LOST")
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_4)
self.lostSpinBox = QSpinBox(ScoreEditor)
self.lostSpinBox.setObjectName(u"lostSpinBox")
self.lostSpinBox.setMinimumSize(QSize(100, 0))
self.lostSpinBox.setMaximum(0)
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.lostSpinBox)
self.label_5 = QLabel(ScoreEditor)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_5)
self.dateTimeEdit = QDateTimeEdit(ScoreEditor)
self.dateTimeEdit.setObjectName(u"dateTimeEdit")
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.dateTimeEdit.sizePolicy().hasHeightForWidth())
self.dateTimeEdit.setSizePolicy(sizePolicy)
self.dateTimeEdit.setDateTime(QDateTime(QDate(2017, 1, 22), QTime(0, 0, 0)))
self.dateTimeEdit.setMinimumDate(QDate(2017, 1, 22))
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.dateTimeEdit)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.formLayout.setItem(5, QFormLayout.LabelRole, self.verticalSpacer)
self.label_6 = QLabel(ScoreEditor)
self.label_6.setObjectName(u"label_6")
self.label_6.setText(u"MAX RECALL")
self.formLayout.setWidget(6, QFormLayout.LabelRole, self.label_6)
self.maxRecallSpinBox = QSpinBox(ScoreEditor)
self.maxRecallSpinBox.setObjectName(u"maxRecallSpinBox")
self.maxRecallSpinBox.setMinimumSize(QSize(100, 0))
self.maxRecallSpinBox.setMinimum(-1)
self.maxRecallSpinBox.setMaximum(0)
self.maxRecallSpinBox.setValue(-1)
self.formLayout.setWidget(6, QFormLayout.FieldRole, self.maxRecallSpinBox)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.validateLabel = QLabel(ScoreEditor)
self.validateLabel.setObjectName(u"validateLabel")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.validateLabel.sizePolicy().hasHeightForWidth())
self.validateLabel.setSizePolicy(sizePolicy1)
self.validateLabel.setText(u"...")
self.validateLabel.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.horizontalLayout.addWidget(self.validateLabel)
self.commitButton = QPushButton(ScoreEditor)
self.commitButton.setObjectName(u"commitButton")
self.horizontalLayout.addWidget(self.commitButton)
self.formLayout.setLayout(8, QFormLayout.SpanningRole, self.horizontalLayout)
self.label_8 = QLabel(ScoreEditor)
self.label_8.setObjectName(u"label_8")
self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_8)
self.clearTypeComboBox = QComboBox(ScoreEditor)
self.clearTypeComboBox.setObjectName(u"clearTypeComboBox")
self.clearTypeComboBox.setEnabled(False)
self.clearTypeComboBox.setMinimumSize(QSize(100, 0))
self.formLayout.setWidget(7, QFormLayout.FieldRole, self.clearTypeComboBox)
self.retranslateUi(ScoreEditor)
QMetaObject.connectSlotsByName(ScoreEditor)
# setupUi
def retranslateUi(self, ScoreEditor):
self.label.setText(QCoreApplication.translate("ScoreEditor", u"formLabel.score", None))
self.label_5.setText(QCoreApplication.translate("ScoreEditor", u"formLabel.time", None))
self.commitButton.setText(QCoreApplication.translate("ScoreEditor", u"commitButton", None))
self.label_8.setText(QCoreApplication.translate("ScoreEditor", u"formLabel.clearType", None))
pass
# retranslateUi

92
ui/designer/mainwindow.ui Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>601</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Arcaea Offline</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="TabOverview" name="tab_overview">
<attribute name="title">
<string>tab.overview</string>
</attribute>
</widget>
<widget class="TabInputScore" name="tab_input">
<attribute name="title">
<string>tab.input</string>
</attribute>
</widget>
<widget class="TabDbEntry" name="tab_db">
<attribute name="title">
<string>tab.db</string>
</attribute>
</widget>
<widget class="QWidget" name="tab_ocr">
<attribute name="title">
<string>tab.ocr</string>
</attribute>
</widget>
<widget class="TabSettings" name="tab_settings">
<attribute name="title">
<string>tab.settings</string>
</attribute>
</widget>
<widget class="TabAbout" name="tab_about">
<attribute name="title">
<string>tab.about</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>TabInputScore</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabInputScore</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabOverview</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabOverview</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabSettings</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabSettings</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabAbout</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabAbout</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabDbEntry</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabDbEntry</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'mainwindow.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QMainWindow, QSizePolicy, QTabWidget,
QVBoxLayout, QWidget)
from ui.implements.tabs.tabAbout import TabAbout
from ui.implements.tabs.tabDbEntry import TabDbEntry
from ui.implements.tabs.tabInputScore import TabInputScore
from ui.implements.tabs.tabOverview import TabOverview
from ui.implements.tabs.tabSettings import TabSettings
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(800, 601)
MainWindow.setWindowTitle(u"Arcaea Offline")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tabWidget = QTabWidget(self.centralwidget)
self.tabWidget.setObjectName(u"tabWidget")
self.tab_overview = TabOverview()
self.tab_overview.setObjectName(u"tab_overview")
self.tabWidget.addTab(self.tab_overview, "")
self.tab_input = TabInputScore()
self.tab_input.setObjectName(u"tab_input")
self.tabWidget.addTab(self.tab_input, "")
self.tab_db = TabDbEntry()
self.tab_db.setObjectName(u"tab_db")
self.tabWidget.addTab(self.tab_db, "")
self.tab_ocr = QWidget()
self.tab_ocr.setObjectName(u"tab_ocr")
self.tabWidget.addTab(self.tab_ocr, "")
self.tab_settings = TabSettings()
self.tab_settings.setObjectName(u"tab_settings")
self.tabWidget.addTab(self.tab_settings, "")
self.tab_about = TabAbout()
self.tab_about.setObjectName(u"tab_about")
self.tabWidget.addTab(self.tab_about, "")
self.verticalLayout.addWidget(self.tabWidget)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_overview), QCoreApplication.translate("MainWindow", u"tab.overview", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_input), QCoreApplication.translate("MainWindow", u"tab.input", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_db), QCoreApplication.translate("MainWindow", u"tab.db", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_ocr), QCoreApplication.translate("MainWindow", u"tab.ocr", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_settings), QCoreApplication.translate("MainWindow", u"tab.settings", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_about), QCoreApplication.translate("MainWindow", u"tab.about", None))
pass
# retranslateUi

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDefault</class>
<widget class="QWidget" name="SettingsDefault">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>682</width>
<height>493</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">SettingsDefault</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="rowWrapPolicy">
<enum>QFormLayout::DontWrapRows</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>devicesJsonFile</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="FileSelector" name="devicesJsonFileSelector" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="devicesJsonFileResetButton">
<property name="text">
<string>devicesJsonPath.resetButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>deviceUuid</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="DevicesComboBox" name="devicesComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deviceUuidResetButton">
<property name="text">
<string>defaultDevice.resetButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>tesseractFile</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="FileSelector" name="tesseractFileSelector" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>500000</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components.fileSelector</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DevicesComboBox</class>
<extends>QComboBox</extends>
<header>ui.implements.components</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'settingsDefault.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QFormLayout, QHBoxLayout, QLabel,
QPushButton, QSizePolicy, QSpacerItem, QWidget)
from ui.implements.components import DevicesComboBox
from ui.implements.components.fileSelector import FileSelector
class Ui_SettingsDefault(object):
def setupUi(self, SettingsDefault):
if not SettingsDefault.objectName():
SettingsDefault.setObjectName(u"SettingsDefault")
SettingsDefault.resize(682, 493)
SettingsDefault.setWindowTitle(u"SettingsDefault")
self.formLayout = QFormLayout(SettingsDefault)
self.formLayout.setObjectName(u"formLayout")
self.formLayout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
self.formLayout.setRowWrapPolicy(QFormLayout.DontWrapRows)
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label_2 = QLabel(SettingsDefault)
self.label_2.setObjectName(u"label_2")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_2)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.devicesJsonFileSelector = FileSelector(SettingsDefault)
self.devicesJsonFileSelector.setObjectName(u"devicesJsonFileSelector")
sizePolicy1 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.devicesJsonFileSelector.sizePolicy().hasHeightForWidth())
self.devicesJsonFileSelector.setSizePolicy(sizePolicy1)
self.devicesJsonFileSelector.setMinimumSize(QSize(200, 0))
self.horizontalLayout_2.addWidget(self.devicesJsonFileSelector)
self.devicesJsonFileResetButton = QPushButton(SettingsDefault)
self.devicesJsonFileResetButton.setObjectName(u"devicesJsonFileResetButton")
self.horizontalLayout_2.addWidget(self.devicesJsonFileResetButton)
self.formLayout.setLayout(0, QFormLayout.FieldRole, self.horizontalLayout_2)
self.label_3 = QLabel(SettingsDefault)
self.label_3.setObjectName(u"label_3")
sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
self.label_3.setSizePolicy(sizePolicy)
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_3)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.devicesComboBox = DevicesComboBox(SettingsDefault)
self.devicesComboBox.setObjectName(u"devicesComboBox")
sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.devicesComboBox.sizePolicy().hasHeightForWidth())
self.devicesComboBox.setSizePolicy(sizePolicy2)
self.devicesComboBox.setMinimumSize(QSize(200, 0))
self.horizontalLayout.addWidget(self.devicesComboBox)
self.deviceUuidResetButton = QPushButton(SettingsDefault)
self.deviceUuidResetButton.setObjectName(u"deviceUuidResetButton")
self.horizontalLayout.addWidget(self.deviceUuidResetButton)
self.formLayout.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout)
self.label_4 = QLabel(SettingsDefault)
self.label_4.setObjectName(u"label_4")
sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth())
self.label_4.setSizePolicy(sizePolicy)
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_4)
self.tesseractFileSelector = FileSelector(SettingsDefault)
self.tesseractFileSelector.setObjectName(u"tesseractFileSelector")
sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.tesseractFileSelector.sizePolicy().hasHeightForWidth())
self.tesseractFileSelector.setSizePolicy(sizePolicy3)
self.tesseractFileSelector.setMinimumSize(QSize(200, 0))
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.tesseractFileSelector)
self.verticalSpacer = QSpacerItem(20, 500000, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.formLayout.setItem(5, QFormLayout.FieldRole, self.verticalSpacer)
self.retranslateUi(SettingsDefault)
QMetaObject.connectSlotsByName(SettingsDefault)
# setupUi
def retranslateUi(self, SettingsDefault):
self.label_2.setText(QCoreApplication.translate("SettingsDefault", u"devicesJsonFile", None))
self.devicesJsonFileResetButton.setText(QCoreApplication.translate("SettingsDefault", u"devicesJsonPath.resetButton", None))
self.label_3.setText(QCoreApplication.translate("SettingsDefault", u"deviceUuid", None))
self.deviceUuidResetButton.setText(QCoreApplication.translate("SettingsDefault", u"defaultDevice.resetButton", None))
self.label_4.setText(QCoreApplication.translate("SettingsDefault", u"tesseractFile", None))
pass
# retranslateUi

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabAbout</class>
<widget class="QWidget" name="TabAbout">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>587</width>
<height>431</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabAbout</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="logoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string notr="true">arcaea-offline-pyside-ui</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string notr="true">A part of &lt;a href=&quot;https://github.com/283375/arcaea-offline&quot;&gt;arcaea-offline project&lt;/a&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="aboutQtButton">
<property name="text">
<string>About Qt</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabAbout.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QHBoxLayout, QLabel, QPushButton,
QSizePolicy, QSpacerItem, QVBoxLayout, QWidget)
class Ui_TabAbout(object):
def setupUi(self, TabAbout):
if not TabAbout.objectName():
TabAbout.setObjectName(u"TabAbout")
TabAbout.resize(587, 431)
TabAbout.setWindowTitle(u"TabAbout")
self.verticalLayout = QVBoxLayout(TabAbout)
self.verticalLayout.setObjectName(u"verticalLayout")
self.logoLabel = QLabel(TabAbout)
self.logoLabel.setObjectName(u"logoLabel")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.logoLabel.sizePolicy().hasHeightForWidth())
self.logoLabel.setSizePolicy(sizePolicy)
self.logoLabel.setText(u"")
self.logoLabel.setAlignment(Qt.AlignCenter)
self.verticalLayout.addWidget(self.logoLabel)
self.label = QLabel(TabAbout)
self.label.setObjectName(u"label")
font = QFont()
font.setPointSize(14)
self.label.setFont(font)
self.label.setText(u"arcaea-offline-pyside-ui")
self.label.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
self.verticalLayout.addWidget(self.label)
self.label_2 = QLabel(TabAbout)
self.label_2.setObjectName(u"label_2")
self.label_2.setText(u"A part of <a href=\"https://github.com/283375/arcaea-offline\">arcaea-offline project</a>")
self.label_2.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.label_2.setOpenExternalLinks(True)
self.verticalLayout.addWidget(self.label_2)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
self.aboutQtButton = QPushButton(TabAbout)
self.aboutQtButton.setObjectName(u"aboutQtButton")
self.horizontalLayout.addWidget(self.aboutQtButton)
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer_2)
self.verticalLayout.addLayout(self.horizontalLayout)
self.retranslateUi(TabAbout)
QMetaObject.connectSlotsByName(TabAbout)
# setupUi
def retranslateUi(self, TabAbout):
self.aboutQtButton.setText(QCoreApplication.translate("TabAbout", u"About Qt", None))
pass
# retranslateUi

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabDb_Manage</class>
<widget class="QWidget" name="TabDb_Manage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>528</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabDb_Manage</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<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>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabDb_Manage.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QFormLayout, QLabel, QPushButton,
QSizePolicy, QWidget)
class Ui_TabDb_Manage(object):
def setupUi(self, TabDb_Manage):
if not TabDb_Manage.objectName():
TabDb_Manage.setObjectName(u"TabDb_Manage")
TabDb_Manage.resize(630, 528)
TabDb_Manage.setWindowTitle(u"TabDb_Manage")
self.formLayout = QFormLayout(TabDb_Manage)
self.formLayout.setObjectName(u"formLayout")
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.syncArcSongDbButton = QPushButton(TabDb_Manage)
self.syncArcSongDbButton.setObjectName(u"syncArcSongDbButton")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.syncArcSongDbButton)
self.label = QLabel(TabDb_Manage)
self.label.setObjectName(u"label")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.label)
self.retranslateUi(TabDb_Manage)
QMetaObject.connectSlotsByName(TabDb_Manage)
# setupUi
def retranslateUi(self, TabDb_Manage):
self.syncArcSongDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDbButton", None))
self.label.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDb.description", None))
pass
# retranslateUi

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabDbEntry</class>
<widget class="QWidget" name="TabDbEntry">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>648</width>
<height>579</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabDbEntry</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="TabDb_Manage" name="tab_manage">
<attribute name="title">
<string>tab.manage</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TabDb_Manage</class>
<extends>QWidget</extends>
<header>ui.implements.tabs.tabDb.tabDb_Manage</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabDbEntry.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QSizePolicy, QTabWidget, QVBoxLayout,
QWidget)
from ui.implements.tabs.tabDb.tabDb_Manage import TabDb_Manage
class Ui_TabDbEntry(object):
def setupUi(self, TabDbEntry):
if not TabDbEntry.objectName():
TabDbEntry.setObjectName(u"TabDbEntry")
TabDbEntry.resize(648, 579)
TabDbEntry.setWindowTitle(u"TabDbEntry")
self.verticalLayout = QVBoxLayout(TabDbEntry)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tabWidget = QTabWidget(TabDbEntry)
self.tabWidget.setObjectName(u"tabWidget")
self.tab_manage = TabDb_Manage()
self.tab_manage.setObjectName(u"tab_manage")
self.tabWidget.addTab(self.tab_manage, "")
self.verticalLayout.addWidget(self.tabWidget)
self.retranslateUi(TabDbEntry)
self.tabWidget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(TabDbEntry)
# setupUi
def retranslateUi(self, TabDbEntry):
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_manage), QCoreApplication.translate("TabDbEntry", u"tab.manage", None))
pass
# retranslateUi

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabInputScore</class>
<widget class="QWidget" name="TabInputScore">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>514</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabInputScore</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>tab.selectChart</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ChartSelector" name="chartSelector" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>tab.scoreEdit</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ScoreEditor" name="scoreEditor" native="true"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ChartSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components.chartSelector</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ScoreEditor</class>
<extends>QWidget</extends>
<header>ui.implements.components.scoreEditor</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabInputScore.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QGroupBox, QSizePolicy, QVBoxLayout,
QWidget)
from ui.implements.components.chartSelector import ChartSelector
from ui.implements.components.scoreEditor import ScoreEditor
class Ui_TabInputScore(object):
def setupUi(self, TabInputScore):
if not TabInputScore.objectName():
TabInputScore.setObjectName(u"TabInputScore")
TabInputScore.resize(514, 400)
TabInputScore.setWindowTitle(u"TabInputScore")
self.verticalLayout = QVBoxLayout(TabInputScore)
self.verticalLayout.setObjectName(u"verticalLayout")
self.groupBox = QGroupBox(TabInputScore)
self.groupBox.setObjectName(u"groupBox")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
self.groupBox.setSizePolicy(sizePolicy)
self.verticalLayout_2 = QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.chartSelector = ChartSelector(self.groupBox)
self.chartSelector.setObjectName(u"chartSelector")
self.verticalLayout_2.addWidget(self.chartSelector)
self.verticalLayout.addWidget(self.groupBox)
self.groupBox_2 = QGroupBox(TabInputScore)
self.groupBox_2.setObjectName(u"groupBox_2")
self.verticalLayout_3 = QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.scoreEditor = ScoreEditor(self.groupBox_2)
self.scoreEditor.setObjectName(u"scoreEditor")
self.verticalLayout_3.addWidget(self.scoreEditor)
self.verticalLayout.addWidget(self.groupBox_2)
self.retranslateUi(TabInputScore)
QMetaObject.connectSlotsByName(TabInputScore)
# setupUi
def retranslateUi(self, TabInputScore):
self.groupBox.setTitle(QCoreApplication.translate("TabInputScore", u"tab.selectChart", None))
self.groupBox_2.setTitle(QCoreApplication.translate("TabInputScore", u"tab.scoreEdit", None))
pass
# retranslateUi

198
ui/designer/tabs/tabOcr.ui Normal file
View File

@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabOcr</class>
<widget class="QWidget" name="TabOcr">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>632</width>
<height>527</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabOcr</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="openWizardButton">
<property name="text">
<string>openWizardButton</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>deviceSelector.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="FileSelector" name="deviceFileSelector" native="true"/>
</item>
<item>
<widget class="DevicesComboBox" name="deviceComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>tesseractSelector.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="FileSelector" name="tesseractFileSelector" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>ocr.title</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>ocr.queue.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="ocr_addImageButton">
<property name="text">
<string>ocr.queue.addImageButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_removeSelectedButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>ocr.queue.removeSelected</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_removeAllButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>ocr.queue.removeAll</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ocr_startButton">
<property name="text">
<string>ocr.queue.startOcrButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>ocr.results</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="ocr_acceptSelectedButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>ocr.results.acceptSelectedButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_acceptAllButton">
<property name="text">
<string>ocr.results.acceptAllButton</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="ocr_ignoreValidateCheckBox">
<property name="text">
<string>ocr.results.ignoreValidate</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DevicesComboBox</class>
<extends>QComboBox</extends>
<header>ui.implements.components</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabOcrDisabled</class>
<widget class="QWidget" name="TabOcrDisabled">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>468</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabOcrDisabled</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>ocrDisabled.title</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="contentLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabOcrDisabled.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QGridLayout, QLabel, QSizePolicy,
QSpacerItem, QVBoxLayout, QWidget)
class Ui_TabOcrDisabled(object):
def setupUi(self, TabOcrDisabled):
if not TabOcrDisabled.objectName():
TabOcrDisabled.setObjectName(u"TabOcrDisabled")
TabOcrDisabled.resize(564, 468)
TabOcrDisabled.setWindowTitle(u"TabOcrDisabled")
self.gridLayout = QGridLayout(TabOcrDisabled)
self.gridLayout.setObjectName(u"gridLayout")
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.gridLayout.addItem(self.horizontalSpacer, 1, 0, 1, 1)
self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.gridLayout.addItem(self.verticalSpacer_2, 2, 1, 1, 1)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.gridLayout.addItem(self.verticalSpacer, 0, 1, 1, 1)
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.gridLayout.addItem(self.horizontalSpacer_2, 1, 2, 1, 1)
self.widget = QWidget(TabOcrDisabled)
self.widget.setObjectName(u"widget")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.widget.setSizePolicy(sizePolicy)
self.verticalLayout = QVBoxLayout(self.widget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(self.widget)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.contentLabel = QLabel(self.widget)
self.contentLabel.setObjectName(u"contentLabel")
sizePolicy.setHeightForWidth(self.contentLabel.sizePolicy().hasHeightForWidth())
self.contentLabel.setSizePolicy(sizePolicy)
self.contentLabel.setText(u"...")
self.verticalLayout.addWidget(self.contentLabel)
self.gridLayout.addWidget(self.widget, 1, 1, 1, 1)
self.retranslateUi(TabOcrDisabled)
QMetaObject.connectSlotsByName(TabOcrDisabled)
# setupUi
def retranslateUi(self, TabOcrDisabled):
self.label.setText(QCoreApplication.translate("TabOcrDisabled", u"ocrDisabled.title", None))
pass
# retranslateUi

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabOcr.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QAbstractItemView, QApplication, QCheckBox, QGroupBox,
QHBoxLayout, QHeaderView, QPushButton, QSizePolicy,
QSpacerItem, QTableView, QVBoxLayout, QWidget)
from ui.implements.components import (DevicesComboBox, FileSelector)
class Ui_TabOcr(object):
def setupUi(self, TabOcr):
if not TabOcr.objectName():
TabOcr.setObjectName(u"TabOcr")
TabOcr.resize(632, 527)
TabOcr.setWindowTitle(u"TabOcr")
self.verticalLayout_3 = QVBoxLayout(TabOcr)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.openWizardButton = QPushButton(TabOcr)
self.openWizardButton.setObjectName(u"openWizardButton")
self.verticalLayout_3.addWidget(self.openWizardButton)
self.groupBox = QGroupBox(TabOcr)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout = QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName(u"verticalLayout")
self.deviceFileSelector = FileSelector(self.groupBox)
self.deviceFileSelector.setObjectName(u"deviceFileSelector")
self.verticalLayout.addWidget(self.deviceFileSelector)
self.deviceComboBox = DevicesComboBox(self.groupBox)
self.deviceComboBox.setObjectName(u"deviceComboBox")
self.verticalLayout.addWidget(self.deviceComboBox)
self.verticalLayout_3.addWidget(self.groupBox)
self.groupBox_4 = QGroupBox(TabOcr)
self.groupBox_4.setObjectName(u"groupBox_4")
self.verticalLayout_5 = QVBoxLayout(self.groupBox_4)
self.verticalLayout_5.setObjectName(u"verticalLayout_5")
self.tesseractFileSelector = FileSelector(self.groupBox_4)
self.tesseractFileSelector.setObjectName(u"tesseractFileSelector")
self.verticalLayout_5.addWidget(self.tesseractFileSelector)
self.verticalLayout_3.addWidget(self.groupBox_4)
self.groupBox_2 = QGroupBox(TabOcr)
self.groupBox_2.setObjectName(u"groupBox_2")
self.horizontalLayout = QHBoxLayout(self.groupBox_2)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.groupBox_3 = QGroupBox(self.groupBox_2)
self.groupBox_3.setObjectName(u"groupBox_3")
self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.ocr_addImageButton = QPushButton(self.groupBox_3)
self.ocr_addImageButton.setObjectName(u"ocr_addImageButton")
self.verticalLayout_2.addWidget(self.ocr_addImageButton)
self.ocr_removeSelectedButton = QPushButton(self.groupBox_3)
self.ocr_removeSelectedButton.setObjectName(u"ocr_removeSelectedButton")
self.ocr_removeSelectedButton.setEnabled(True)
self.verticalLayout_2.addWidget(self.ocr_removeSelectedButton)
self.ocr_removeAllButton = QPushButton(self.groupBox_3)
self.ocr_removeAllButton.setObjectName(u"ocr_removeAllButton")
self.ocr_removeAllButton.setEnabled(True)
self.verticalLayout_2.addWidget(self.ocr_removeAllButton)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout_2.addItem(self.verticalSpacer)
self.ocr_startButton = QPushButton(self.groupBox_3)
self.ocr_startButton.setObjectName(u"ocr_startButton")
self.verticalLayout_2.addWidget(self.ocr_startButton)
self.horizontalLayout.addWidget(self.groupBox_3)
self.tableView = QTableView(self.groupBox_2)
self.tableView.setObjectName(u"tableView")
self.tableView.setEditTriggers(QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed)
self.tableView.setSelectionMode(QAbstractItemView.MultiSelection)
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
self.tableView.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.horizontalLayout.addWidget(self.tableView)
self.groupBox_5 = QGroupBox(self.groupBox_2)
self.groupBox_5.setObjectName(u"groupBox_5")
self.verticalLayout_4 = QVBoxLayout(self.groupBox_5)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.ocr_acceptSelectedButton = QPushButton(self.groupBox_5)
self.ocr_acceptSelectedButton.setObjectName(u"ocr_acceptSelectedButton")
self.ocr_acceptSelectedButton.setEnabled(True)
self.verticalLayout_4.addWidget(self.ocr_acceptSelectedButton)
self.ocr_acceptAllButton = QPushButton(self.groupBox_5)
self.ocr_acceptAllButton.setObjectName(u"ocr_acceptAllButton")
self.verticalLayout_4.addWidget(self.ocr_acceptAllButton)
self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout_4.addItem(self.verticalSpacer_2)
self.ocr_ignoreValidateCheckBox = QCheckBox(self.groupBox_5)
self.ocr_ignoreValidateCheckBox.setObjectName(u"ocr_ignoreValidateCheckBox")
self.verticalLayout_4.addWidget(self.ocr_ignoreValidateCheckBox)
self.horizontalLayout.addWidget(self.groupBox_5)
self.verticalLayout_3.addWidget(self.groupBox_2)
self.retranslateUi(TabOcr)
QMetaObject.connectSlotsByName(TabOcr)
# setupUi
def retranslateUi(self, TabOcr):
self.openWizardButton.setText(QCoreApplication.translate("TabOcr", u"openWizardButton", None))
self.groupBox.setTitle(QCoreApplication.translate("TabOcr", u"deviceSelector.title", None))
self.groupBox_4.setTitle(QCoreApplication.translate("TabOcr", u"tesseractSelector.title", None))
self.groupBox_2.setTitle(QCoreApplication.translate("TabOcr", u"ocr.title", None))
self.groupBox_3.setTitle(QCoreApplication.translate("TabOcr", u"ocr.queue.title", None))
self.ocr_addImageButton.setText(QCoreApplication.translate("TabOcr", u"ocr.queue.addImageButton", None))
self.ocr_removeSelectedButton.setText(QCoreApplication.translate("TabOcr", u"ocr.queue.removeSelected", None))
self.ocr_removeAllButton.setText(QCoreApplication.translate("TabOcr", u"ocr.queue.removeAll", None))
self.ocr_startButton.setText(QCoreApplication.translate("TabOcr", u"ocr.queue.startOcrButton", None))
self.groupBox_5.setTitle(QCoreApplication.translate("TabOcr", u"ocr.results", None))
self.ocr_acceptSelectedButton.setText(QCoreApplication.translate("TabOcr", u"ocr.results.acceptSelectedButton", None))
self.ocr_acceptAllButton.setText(QCoreApplication.translate("TabOcr", u"ocr.results.acceptAllButton", None))
self.ocr_ignoreValidateCheckBox.setText(QCoreApplication.translate("TabOcr", u"ocr.results.ignoreValidate", None))
pass
# retranslateUi

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabOverview</class>
<widget class="QWidget" name="TabOverview">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>696</width>
<height>509</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabOverview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="b30Label">
<property name="font">
<font>
<pointsize>30</pointsize>
</font>
</property>
<property name="text">
<string notr="true">0.00</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string notr="true">B30</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="r10Label">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>30</pointsize>
</font>
</property>
<property name="text">
<string notr="true">--</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string notr="true">R10</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabOverview.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QApplication, QHBoxLayout, QLabel, QSizePolicy,
QVBoxLayout, QWidget)
class Ui_TabOverview(object):
def setupUi(self, TabOverview):
if not TabOverview.objectName():
TabOverview.setObjectName(u"TabOverview")
TabOverview.resize(696, 509)
TabOverview.setWindowTitle(u"TabOverview")
self.verticalLayout = QVBoxLayout(TabOverview)
self.verticalLayout.setObjectName(u"verticalLayout")
self.widget = QWidget(TabOverview)
self.widget.setObjectName(u"widget")
self.verticalLayout.addWidget(self.widget)
self.widget_2 = QWidget(TabOverview)
self.widget_2.setObjectName(u"widget_2")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth())
self.widget_2.setSizePolicy(sizePolicy)
self.horizontalLayout = QHBoxLayout(self.widget_2)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.widget_3 = QWidget(self.widget_2)
self.widget_3.setObjectName(u"widget_3")
self.verticalLayout_2 = QVBoxLayout(self.widget_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.b30Label = QLabel(self.widget_3)
self.b30Label.setObjectName(u"b30Label")
font = QFont()
font.setPointSize(30)
self.b30Label.setFont(font)
self.b30Label.setText(u"0.00")
self.b30Label.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
self.verticalLayout_2.addWidget(self.b30Label)
self.label_2 = QLabel(self.widget_3)
self.label_2.setObjectName(u"label_2")
font1 = QFont()
font1.setPointSize(20)
self.label_2.setFont(font1)
self.label_2.setText(u"B30")
self.label_2.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.verticalLayout_2.addWidget(self.label_2)
self.horizontalLayout.addWidget(self.widget_3)
self.widget_4 = QWidget(self.widget_2)
self.widget_4.setObjectName(u"widget_4")
self.widget_4.setEnabled(False)
self.verticalLayout_3 = QVBoxLayout(self.widget_4)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.r10Label = QLabel(self.widget_4)
self.r10Label.setObjectName(u"r10Label")
self.r10Label.setEnabled(False)
self.r10Label.setFont(font)
self.r10Label.setText(u"--")
self.r10Label.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
self.verticalLayout_3.addWidget(self.r10Label)
self.label_4 = QLabel(self.widget_4)
self.label_4.setObjectName(u"label_4")
self.label_4.setEnabled(False)
self.label_4.setFont(font1)
self.label_4.setText(u"R10")
self.label_4.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.verticalLayout_3.addWidget(self.label_4)
self.horizontalLayout.addWidget(self.widget_4)
self.verticalLayout.addWidget(self.widget_2)
self.retranslateUi(TabOverview)
QMetaObject.connectSlotsByName(TabOverview)
# setupUi
def retranslateUi(self, TabOverview):
pass
# retranslateUi

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabSettings</class>
<widget class="QWidget" name="TabSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>562</width>
<height>499</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabSettings</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<widget class="QListWidget" name="listWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="SettingsDefault" name="page_default"/>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SettingsDefault</class>
<extends>QWidget</extends>
<header>ui.implements.settings.settingsDefault</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabSettings.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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 (QAbstractItemView, QAbstractScrollArea, QApplication, QHBoxLayout,
QListWidget, QListWidgetItem, QSizePolicy, QStackedWidget,
QWidget)
from ui.implements.settings.settingsDefault import SettingsDefault
class Ui_TabSettings(object):
def setupUi(self, TabSettings):
if not TabSettings.objectName():
TabSettings.setObjectName(u"TabSettings")
TabSettings.resize(562, 499)
TabSettings.setWindowTitle(u"TabSettings")
self.horizontalLayout = QHBoxLayout(TabSettings)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.listWidget = QListWidget(TabSettings)
self.listWidget.setObjectName(u"listWidget")
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setMinimumSize(QSize(100, 0))
self.listWidget.setBaseSize(QSize(100, 0))
self.listWidget.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.listWidget.setAlternatingRowColors(True)
self.listWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
self.horizontalLayout.addWidget(self.listWidget)
self.stackedWidget = QStackedWidget(TabSettings)
self.stackedWidget.setObjectName(u"stackedWidget")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
self.stackedWidget.setSizePolicy(sizePolicy1)
self.page_default = SettingsDefault()
self.page_default.setObjectName(u"page_default")
self.stackedWidget.addWidget(self.page_default)
self.horizontalLayout.addWidget(self.stackedWidget)
self.horizontalLayout.setStretch(1, 1)
self.retranslateUi(TabSettings)
QMetaObject.connectSlotsByName(TabSettings)
# setupUi
def retranslateUi(self, TabSettings):
pass
# retranslateUi

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

16
ui/extends/color.py Normal file
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

@ -0,0 +1,33 @@
from arcaea_offline.database import Database
from arcaea_offline.models import Chart
from arcaea_offline.utils import rating_class_to_short_text
from PySide6.QtCore import Qt
from PySide6.QtGui import QStandardItem, QStandardItemModel
class FuzzySearchCompleterModel(QStandardItemModel):
def fillDbFuzzySearchResults(self, db: Database, kw: str):
self.clear()
results = db.fuzzy_search_song_id(kw, limit=10)
results = sorted(results, key=lambda r: r.confidence, reverse=True)
songIds = [r.song_id for r in results]
charts: list[Chart] = []
for songId in songIds:
dbChartRows = db.get_charts_by_song_id(songId)
_charts = [Chart.from_db_row(dbRow) for dbRow in dbChartRows]
_charts = sorted(_charts, key=lambda c: c.rating_class, reverse=True)
charts += _charts
for chart in charts:
displayText = (
f"{chart.name_en} [{rating_class_to_short_text(chart.rating_class)}]"
)
item = QStandardItem(kw)
item.setData(kw)
item.setData(displayText, Qt.ItemDataRole.UserRole + 75)
item.setData(
f"{chart.song_id}, {chart.package_id}", Qt.ItemDataRole.UserRole + 76
)
item.setData(chart, Qt.ItemDataRole.UserRole + 10)
self.appendRow(item)

View File

@ -0,0 +1,5 @@
from PySide6.QtCore import QAbstractTableModel
class DbTableModel(QAbstractTableModel):
pass

18
ui/extends/ocr.py Normal file
View File

@ -0,0 +1,18 @@
try:
import json
from arcaea_offline_ocr.device import Device
def load_devices_json(filepath: str) -> list[Device]:
with open(filepath, "r", encoding="utf-8") as f:
file_content = f.read()
if len(file_content) == 0:
return []
content = json.loads(file_content)
assert isinstance(content, list)
return [Device.from_json_object(item) for item in content]
except Exception:
def load_devices_json(*args, **kwargs):
pass

0
ui/extends/score.py Normal file
View File

57
ui/extends/settings.py Normal file
View File

@ -0,0 +1,57 @@
from PySide6.QtCore import QDir, QSettings
__all__ = [
"DATABASE_PATH",
"DEVICES_JSON_FILE",
"DEVICE_UUID",
"TESSERACT_FILE",
"Settings",
]
DATABASE_PATH = "General/DatabasePath"
DEVICES_JSON_FILE = "Ocr/DevicesJsonFile"
DEVICE_UUID = "Ocr/DeviceUuid"
TESSERACT_FILE = "Ocr/TesseractFile"
class Settings(QSettings):
def __init__(self, parent=None):
super().__init__(
QDir.current().absoluteFilePath("arcaea_offline.ini"),
QSettings.Format.IniFormat,
parent,
)
def devicesJsonFile(self) -> str | None:
return self.value(DEVICES_JSON_FILE, None, str)
def setDevicesJsonFile(self, path: str):
self.setValue(DEVICES_JSON_FILE, path)
self.sync()
def resetDevicesJsonFile(self):
self.setValue(DEVICES_JSON_FILE, None)
self.sync()
def deviceUuid(self) -> str | None:
return self.value(DEVICE_UUID, None, str)
def setDeviceUuid(self, uuid: str):
self.setValue(DEVICE_UUID, uuid)
self.sync()
def resetDeviceUuid(self):
self.setValue(DEVICE_UUID, None)
self.sync()
def tesseractPath(self):
return self.value(TESSERACT_FILE, None, str)
def setTesseractPath(self, path: str):
self.setValue(TESSERACT_FILE, path)
self.sync()
def resetTesseractPath(self):
self.setValue(TESSERACT_FILE, None)
self.sync()

View File

View File

@ -0,0 +1,152 @@
from typing import Callable
from PySide6.QtCore import QEvent, QModelIndex, QObject, QPoint, QSize, Qt
from PySide6.QtGui import QBrush, QColor, QFont, QFontMetrics, QLinearGradient, QPainter
from PySide6.QtWidgets import QApplication, QStyledItemDelegate, QStyleOptionViewItem
class TextSegmentDelegate(QStyledItemDelegate):
VerticalPadding = 3
HorizontalPadding = 5
TextRole = 3375
ColorRole = TextRole + 1
BrushRole = TextRole + 2
GradientWrapperRole = TextRole + 3
FontRole = TextRole + 20
def getTextSegments(
self, index: QModelIndex, option
) -> list[
list[
dict[
int,
str
| QColor
| QBrush
| Callable[[float, float, float, float], QLinearGradient]
| QFont,
]
]
]:
return []
def sizeHint(self, option, index) -> QSize:
width = 0
height = self.VerticalPadding
fm: QFontMetrics = option.fontMetrics
for line in self.getTextSegments(index, option):
lineWidth = 4 * self.HorizontalPadding
lineHeight = 0
for textFrag in line:
font = textFrag.get(self.FontRole)
_fm = QFontMetrics(font) if font else fm
text = textFrag[self.TextRole]
textWidth = _fm.horizontalAdvance(text)
textHeight = _fm.height()
lineWidth += textWidth
lineHeight = max(lineHeight, textHeight)
width = max(lineWidth, width)
height += lineHeight + self.VerticalPadding
return QSize(width, height)
def paint(
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
):
self.initStyleOption(option, index)
# draw text only
baseX = option.rect.x() + self.HorizontalPadding
baseY = option.rect.y() + self.VerticalPadding
maxWidth = option.rect.width() - (2 * self.HorizontalPadding)
fm: QFontMetrics = option.fontMetrics
painter.save()
for line in self.getTextSegments(index, option):
lineBaseX = baseX
lineBaseY = baseY
lineHeight = 0
for textFrag in line:
painter.save()
# elide text, get font values
text = textFrag[self.TextRole]
fragMaxWidth = maxWidth - (lineBaseX - baseX)
font = textFrag.get(self.FontRole)
if font:
painter.setFont(font)
_fm = QFontMetrics(font)
else:
_fm = fm
lineHeight = max(lineHeight, _fm.height())
elidedText = _fm.elidedText(
text, Qt.TextElideMode.ElideRight, fragMaxWidth
)
# confirm proper color
brush = textFrag.get(self.BrushRole)
gradientWrapper = textFrag.get(self.GradientWrapperRole)
color = textFrag.get(self.ColorRole)
pen = painter.pen()
if brush:
pen.setBrush(brush)
elif gradientWrapper:
gradient = gradientWrapper(
lineBaseX,
lineBaseY + lineHeight - _fm.height(),
fragMaxWidth,
_fm.height(),
)
pen.setBrush(gradient)
elif color:
pen.setColor(color)
painter.setPen(pen)
painter.drawText(
QPoint(lineBaseX, lineBaseY + lineHeight - _fm.descent()),
elidedText,
)
painter.restore()
# if text elided, skip to next line
# remember to add height before skipping
if _fm.boundingRect(text).width() >= fragMaxWidth:
break
lineBaseX += _fm.horizontalAdvance(elidedText)
baseY += lineHeight + self.VerticalPadding
painter.restore()
def super_styledItemDelegate_paint(self, painter, option, index):
return super().paint(painter, option, index)
class NoCommitWhenFocusOutEventFilter(QObject):
"""
--DEPRECATED--
The default QAbstractItemDelegate implementation has a private function
`editorEventFilter()`, when editor sends focusOut/hide event, it emits the
`commitData(editor)` signal. We don't want this since we need to validate
the input, so we filter the event out and handle it by ourselves.
Reimplement `checkIsEditor(self, val) -> bool` to ensure this filter is
working. The default implementation always return `False`.
"""
def checkIsEditor(self, val) -> bool:
return False
def eventFilter(self, object: QObject, event: QEvent) -> bool:
if self.checkIsEditor(object) and event.type() in [
QEvent.Type.FocusOut,
QEvent.Type.Hide,
]:
widget = QApplication.focusWidget()
while widget:
# check if focus changed into editor's child
if self.checkIsEditor(widget):
return False
widget = widget.parentWidget()
object.hide()
object.deleteLater()
return True
return False

View File

@ -0,0 +1,176 @@
from typing import Union
from arcaea_offline.models import Chart
from arcaea_offline.utils import rating_class_to_short_text, rating_class_to_text
from PySide6.QtCore import QDateTime, QModelIndex, Qt, Signal
from PySide6.QtGui import QBrush, QColor
from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
QLabel,
QMessageBox,
QPushButton,
QSizePolicy,
QStyleOptionViewItem,
QWidget,
)
from ui.implements.components.chartSelector import ChartSelector
from .base import TextSegmentDelegate
def chartToRichText(chart: Chart):
if isinstance(chart, Chart):
text = f"{chart.name_en} [{rating_class_to_short_text(chart.rating_class)}]"
text += "<br>"
text += f'<font color="gray">({chart.song_id}, {chart.package_id})</font>'
else:
text = "(unknown chart)"
return text
class ChartSelectorDelegateWrapper(ChartSelector):
accepted = Signal()
rejected = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.delegateHLine = QFrame(self)
self.delegateHLine.setFrameShape(QFrame.Shape.HLine)
self.delegateHLine.setFrameShadow(QFrame.Shadow.Plain)
self.delegateHLine.setFixedHeight(5)
self.mainVerticalLayout.insertWidget(0, self.delegateHLine)
self.delegateHeader = QWidget(self)
self.delegateHeaderHBoxLayout = QHBoxLayout(self.delegateHeader)
self.delegateHeaderHBoxLayout.setContentsMargins(0, 0, 0, 0)
self.mainVerticalLayout.insertWidget(0, self.delegateHeader)
self.editorLabel = QLabel(self)
self.editorLabel.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
)
self.delegateHeaderHBoxLayout.addWidget(self.editorLabel)
self.editorCommitButton = QPushButton("Commit", self.delegateHeader)
self.editorCommitButton.clicked.connect(self.accepted)
self.delegateHeaderHBoxLayout.addWidget(self.editorCommitButton)
self.editorDiscardButton = QPushButton("Discard", self.delegateHeader)
self.editorDiscardButton.clicked.connect(self.rejected)
self.delegateHeaderHBoxLayout.addWidget(self.editorDiscardButton)
def setText(self, chart: Chart, _extra: str = None):
text = "Editing "
text += _extra or ""
text += "<br>"
text += (
chartToRichText(chart) if isinstance(chart, Chart) else "(unknown chart)"
)
self.editorLabel.setText(text)
def validate(self):
return isinstance(self.value(), Chart)
class ChartDelegate(TextSegmentDelegate):
RatingClassColors = [
QColor("#399bb2"),
QColor("#809955"),
QColor("#702d60"),
QColor("#710f25"),
]
ChartInvalidBackgroundColor = QColor("#e6a23c")
def getChart(self, index: QModelIndex) -> Chart | None:
return None
def getTextSegments(self, index: QModelIndex, option):
chart = self.getChart(index)
if not isinstance(chart, Chart):
return [
[{self.TextRole: "Chart Invalid", self.ColorRole: QColor("#ff0000")}]
]
return [
[
{self.TextRole: f"{chart.name_en}"},
],
[
{
self.TextRole: f"{rating_class_to_text(chart.rating_class)} {chart.rating / 10:.1f}",
self.ColorRole: self.RatingClassColors[chart.rating_class],
},
],
[
{
self.TextRole: f"({chart.song_id}, {chart.package_id})",
self.ColorRole: option.widget.palette().placeholderText().color(),
},
],
]
def paintWarningBackground(self, index: QModelIndex) -> bool:
return True
def paint(self, painter, option, index):
# draw chartInvalid warning background
chart = self.getChart(index)
if not isinstance(chart, Chart) and self.paintWarningBackground(index):
painter.save()
painter.setPen(Qt.PenStyle.NoPen)
bgColor = QColor(self.ChartInvalidBackgroundColor)
bgColor.setAlpha(50)
painter.setBrush(bgColor)
painter.drawRect(option.rect)
painter.restore()
option.text = ""
super().paint(painter, option, index)
def checkIsEditor(self, val):
return isinstance(val, ChartSelectorDelegateWrapper)
def _closeEditor(self):
editor = self.sender()
self.closeEditor.emit(editor)
def _commitEditor(self):
editor = self.sender()
if editor.validate():
confirm = QMessageBox.question(
editor,
"Confirm",
f"Are you sure to change chart to<br><br>{chartToRichText(editor.value())}",
)
if confirm == QMessageBox.StandardButton.Yes:
self.commitData.emit(editor)
self.closeEditor.emit(editor)
else:
QMessageBox.critical(editor, "Invalid chart", "Cannot commit")
def createEditor(
self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex
) -> ChartSelectorDelegateWrapper:
if isinstance(self.getChart(index), Chart):
editor = ChartSelectorDelegateWrapper(parent)
editor.setWindowFlag(Qt.WindowType.Sheet, True)
editor.setWindowFlag(Qt.WindowType.FramelessWindowHint, True)
editor.setText(self.getChart(index))
editor.move(parent.mapToGlobal(parent.pos()))
editor.accepted.connect(self._commitEditor)
editor.rejected.connect(self._closeEditor)
return editor
def updateEditorGeometry(self, editor: QWidget, option, index: QModelIndex) -> None:
editor.move(editor.pos() + option.rect.topLeft())
editor.setMaximumWidth(option.rect.width())
def setEditorData(self, editor: ChartSelectorDelegateWrapper, index: QModelIndex):
if self.checkIsEditor(editor) and isinstance(self.getChart(index), Chart):
editor.selectChart(self.getChart(index))
return super().setEditorData(editor, index)
def setModelData(self, editor: ChartSelectorDelegateWrapper, model, index):
...

View File

@ -0,0 +1,35 @@
from PySide6.QtCore import QModelIndex, Qt
from PySide6.QtWidgets import QStyle, QStyleOptionViewItem
from .base import TextSegmentDelegate
class DescriptionDelegate(TextSegmentDelegate):
MainTextRole = Qt.ItemDataRole.UserRole + 75
DescriptionTextRole = Qt.ItemDataRole.UserRole + 76
def getMainText(self, index: QModelIndex) -> str | None:
return index.data(self.MainTextRole)
def getDescriptionText(self, index: QModelIndex) -> str | None:
return index.data(self.DescriptionTextRole)
def getTextSegments(self, index: QModelIndex, option):
return [
[
{self.TextRole: self.getMainText(index) or ""},
{self.TextRole: " "},
{
self.TextRole: self.getDescriptionText(index) or "",
self.ColorRole: option.widget.palette().placeholderText().color(),
},
]
]
def paint(self, painter, option, index):
super().paint(painter, option, index)
optionNoText = QStyleOptionViewItem(option)
optionNoText.text = ""
style = option.widget.style() # type: QStyle
style.drawControl(QStyle.ControlElement.CE_ItemViewItem, optionNoText, painter)

View File

@ -0,0 +1,247 @@
from typing import Union
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.models import Chart, Score, ScoreInsert
from arcaea_offline.utils import (
rating_class_to_text,
score_to_grade_text,
zip_score_grade,
)
from PySide6.QtCore import QAbstractItemModel, QDateTime, QModelIndex, Qt, Signal
from PySide6.QtGui import QColor, QFont, QLinearGradient
from PySide6.QtWidgets import (
QAbstractItemDelegate,
QFrame,
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QWidget,
)
from ui.implements.components.scoreEditor import ScoreEditor
from ..utils import keepWidgetInScreen
from .base import TextSegmentDelegate
class ScoreEditorDelegateWrapper(ScoreEditor):
rejected = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.hLine = QFrame(self)
self.hLine.setFrameShape(QFrame.Shape.HLine)
self.hLine.setFrameShadow(QFrame.Shadow.Plain)
self.hLine.setFixedHeight(5)
self.formLayout.insertRow(0, self.hLine)
self.delegateHeader = QWidget(self)
self.delegateHeaderHBoxLayout = QHBoxLayout(self.delegateHeader)
self.delegateHeaderHBoxLayout.setContentsMargins(0, 0, 0, 0)
self.editorLabel = QLabel(self.delegateHeader)
self.editorLabel.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
)
self.delegateHeaderHBoxLayout.addWidget(self.editorLabel)
self.editorDiscardButton = QPushButton("Discard", self.delegateHeader)
self.editorDiscardButton.clicked.connect(self.rejected)
self.delegateHeaderHBoxLayout.addWidget(self.editorDiscardButton)
self.formLayout.insertRow(0, self.delegateHeader)
def setText(self, score: Score | ScoreInsert, _extra: str = None):
text = "Editing "
text += _extra or ""
text += f"score {score.score}"
text += f"<br>(P{score.pure} F{score.far} L{score.lost} | MR{score.max_recall})"
self.editorLabel.setText(text)
class ScoreDelegate(TextSegmentDelegate):
@staticmethod
def createGradeGradientWrapper(topColor: QColor, bottomColor: QColor):
def wrapper(x, y, width, height):
gradient = QLinearGradient(x + (width / 2), y, x + (width / 2), y + height)
gradient.setColorAt(0.1, topColor)
gradient.setColorAt(0.9, bottomColor)
return gradient
return wrapper
ScoreMismatchBackgroundColor = QColor("#e6a23c")
PureFarLostColors = [
QColor("#f22ec6"),
QColor("#ff9028"),
QColor("#ff0c43"),
]
GradeGradientsWrappers = [ # EX+, EX, AA, A. B, C, D
createGradeGradientWrapper(QColor("#83238c"), QColor("#2c72ae")),
createGradeGradientWrapper(QColor("#721b6b"), QColor("#295b8d")),
createGradeGradientWrapper(QColor("#5a3463"), QColor("#9b4b8d")),
createGradeGradientWrapper(QColor("#46324d"), QColor("#92588a")),
createGradeGradientWrapper(QColor("#43334a"), QColor("#755b7c")),
createGradeGradientWrapper(QColor("#3b2b27"), QColor("#80566b")),
createGradeGradientWrapper(QColor("#5d1d35"), QColor("#9f3c55")),
]
def getScore(self, index: QModelIndex) -> Score | None:
return None
def getScoreInsert(self, index: QModelIndex) -> ScoreInsert | None:
return None
def _getScore(self, index: QModelIndex):
score = self.getScore(index)
scoreInsert = self.getScoreInsert(index)
return scoreInsert if score is None else score
def getChart(self, index: QModelIndex) -> Chart | None:
return None
def getScoreValidateOk(self, index: QModelIndex) -> bool | None:
score = self._getScore(index)
chart = self.getChart(index)
if isinstance(score, (Score, ScoreInsert)) and isinstance(chart, Chart):
scoreRange = calculate_score_range(chart, score.pure, score.far)
return scoreRange[0] <= score.score <= scoreRange[1]
def getScoreGradeGradientWrapper(self, score: int):
return zip_score_grade(score, self.GradeGradientsWrappers)
def getTextSegments(self, index, option):
score = self._getScore(index)
chart = self.getChart(index)
if not (isinstance(score, (Score, ScoreInsert)) and isinstance(chart, Chart)):
return [
[
{
self.TextRole: "Chart/Score Invalid",
self.ColorRole: QColor("#ff0000"),
}
]
]
score_font = QFont(option.font)
score_font.setPointSize(12)
score_grade_font = QFont(score_font)
score_grade_font.setBold(True)
return [
[
{
self.TextRole: score_to_grade_text(score.score),
self.GradientWrapperRole: self.getScoreGradeGradientWrapper(
score.score
),
self.FontRole: score_grade_font,
},
{self.TextRole: " | "},
{self.TextRole: str(score.score), self.FontRole: score_font},
],
[
{
self.TextRole: f"PURE {score.pure}",
self.ColorRole: self.PureFarLostColors[0],
},
{self.TextRole: " "},
{
self.TextRole: f"FAR {score.far}",
self.ColorRole: self.PureFarLostColors[1],
},
{self.TextRole: " "},
{
self.TextRole: f"LOST {score.lost}",
self.ColorRole: self.PureFarLostColors[2],
},
{self.TextRole: " | "},
{self.TextRole: f"MAX RECALL {score.max_recall}"},
],
[
{
self.TextRole: QDateTime.fromSecsSinceEpoch(score.time).toString(
"yyyy-MM-dd hh:mm:ss"
)
}
],
]
def paintWarningBackground(self, index: QModelIndex) -> bool:
return True
def paint(self, painter, option, index):
# draw scoreMismatch warning background
score = self._getScore(index)
chart = self.getChart(index)
if (
isinstance(score, (Score, ScoreInsert))
and isinstance(chart, Chart)
and self.paintWarningBackground(index)
):
scoreValidateOk = self.getScoreValidateOk(index)
if not scoreValidateOk:
painter.save()
painter.setPen(Qt.PenStyle.NoPen)
bgColor = QColor(self.ScoreMismatchBackgroundColor)
bgColor.setAlpha(50)
painter.setBrush(bgColor)
painter.drawRect(option.rect)
painter.restore()
option.text = ""
super().paint(painter, option, index)
def _closeEditor(self):
editor = self.sender()
self.closeEditor.emit(editor)
def _commitEditor(self):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor)
def createEditor(self, parent, option, index) -> ScoreEditorDelegateWrapper:
score = self._getScore(index)
chart = self.getChart(index)
if isinstance(score, (Score, ScoreInsert)) and isinstance(chart, Chart):
editor = ScoreEditorDelegateWrapper(parent)
editor.setWindowFlag(Qt.WindowType.Sheet, True)
editor.setWindowFlag(Qt.WindowType.FramelessWindowHint, True)
editor.setWindowTitle(
f"{chart.name_en}({chart.song_id}) | {rating_class_to_text(chart.rating_class)} | {chart.package_id}"
)
editor.setText(self._getScore(index))
editor.setValidateBeforeAccept(False)
editor.move(parent.mapToGlobal(parent.pos()))
editor.accepted.connect(self._commitEditor)
editor.rejected.connect(self._closeEditor)
editor.show()
return editor
return super().createEditor(parent, option, index)
def updateEditorGeometry(self, editor, option, index):
editor.setMaximumWidth(option.rect.width())
editor.move(editor.pos() + option.rect.topLeft())
keepWidgetInScreen(editor)
def setEditorData(self, editor: ScoreEditorDelegateWrapper, index) -> None:
score = self._getScore(index)
chart = self.getChart(index)
if isinstance(score, (Score, ScoreInsert)) and isinstance(chart, Chart):
editor.setChart(chart)
editor.setValue(score)
def confirmSetModelData(self, editor: ScoreEditorDelegateWrapper):
return editor.triggerValidateMessageBox()
def setModelData(
self,
editor: ScoreEditorDelegateWrapper,
model: QAbstractItemModel,
index: QModelIndex,
):
...

View File

@ -0,0 +1,35 @@
from typing import Union
from arcaea_offline.database import Database
from PySide6.QtCore import QAbstractTableModel, Qt
class DbTableModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
self._horizontalHeaders = []
self.retranslateHeaders()
self._db = Database()
def retranslateHeaders(self):
...
def syncDb(self):
...
def headerData(self, section: int, orientation: Qt.Orientation, role: int):
if (
orientation == Qt.Orientation.Horizontal
and self._horizontalHeaders
and 0 <= section < len(self._horizontalHeaders)
and role == Qt.ItemDataRole.DisplayRole
):
return self._horizontalHeaders[section]
return super().headerData(section, orientation, role)
def columnCount(self, parent=None):
if self._horizontalHeaders:
return len(self._horizontalHeaders)
return super().columnCount(parent)

View File

@ -0,0 +1,197 @@
from arcaea_offline.calculate import calculate_score
from arcaea_offline.models import Chart, Score, ScoreInsert
from PySide6.QtCore import QCoreApplication, QModelIndex, QSortFilterProxyModel, Qt
from .base import DbTableModel
class DbScoreTableModel(DbTableModel):
IdRole = Qt.ItemDataRole.UserRole + 10
ChartRole = Qt.ItemDataRole.UserRole + 11
ScoreRole = Qt.ItemDataRole.UserRole + 12
PttRole = Qt.ItemDataRole.UserRole + 13
def __init__(self, parent=None):
super().__init__(parent)
self.__items = []
def retranslateHeaders(self):
self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.potential"),
# fmt: on
]
def syncDb(self):
newScores = [Score.from_db_row(dbRow) for dbRow in self._db.get_scores()]
newScores = sorted(newScores, key=lambda x: x.id)
newCharts = [
Chart.from_db_row(dbRow)
for dbRow in [
self._db.get_chart(score.song_id, score.rating_class)
for score in newScores
]
]
newPtts = []
for chart, score in zip(newCharts, newScores):
if isinstance(chart, Chart) and isinstance(score, Score):
newPtts.append(calculate_score(chart, score).potential)
else:
newPtts.append(None)
newScoreIds = [score.id for score in newScores]
oldScoreIds = [item[self.ScoreRole].id for item in self.__items]
deleteIds = list(set(oldScoreIds) - set(newScoreIds))
newIds = list(set(newScoreIds) - set(oldScoreIds))
deleteRowIndexes = [oldScoreIds.index(deleteId) for deleteId in deleteIds]
# first delete rows
for deleteRowIndex in sorted(deleteRowIndexes, reverse=True):
self.beginRemoveRows(QModelIndex(), deleteRowIndex, deleteRowIndex)
self.__items.pop(deleteRowIndex)
self.endRemoveRows()
# now update existing datas
for oldItem, newChart, newScore, newPtt in zip(
self.__items, newCharts, newScores, newPtts
):
oldItem[self.IdRole] = newScore.id
oldItem[self.ChartRole] = newChart
oldItem[self.ScoreRole] = newScore
oldItem[self.PttRole] = newPtt
# finally insert new rows
for newId in newIds:
insertRowIndex = self.rowCount()
itemListIndex = newScoreIds.index(newId)
score = newScores[itemListIndex]
chart = newCharts[itemListIndex]
ptt = newPtts[itemListIndex]
self.beginInsertRows(QModelIndex(), insertRowIndex, insertRowIndex)
self.__items.append(
{
self.IdRole: score.id,
self.ChartRole: chart,
self.ScoreRole: score,
self.PttRole: ptt,
}
)
self.endInsertRows()
# trigger view update
topLeft = self.index(0, 0)
bottomRight = self.index(self.rowCount() - 1, self.columnCount() - 1)
self.dataChanged.emit(
topLeft,
bottomRight,
[Qt.ItemDataRole.DisplayRole, self.IdRole, self.ChartRole, self.ScoreRole],
)
def rowCount(self, *args):
return len(self.__items)
def data(self, index, role):
if index.isValid() and self.checkIndex(index):
if index.column() == 0 and role in [
Qt.ItemDataRole.DisplayRole,
self.IdRole,
]:
return self.__items[index.row()][self.IdRole]
elif index.column() == 1 and role == self.ChartRole:
return self.__items[index.row()][self.ChartRole]
elif index.column() == 2 and role in [self.ChartRole, self.ScoreRole]:
return self.__items[index.row()][role]
elif index.column() == 3:
if role == Qt.ItemDataRole.DisplayRole:
return f"{self.__items[index.row()][self.PttRole]:.3f}"
elif role == self.PttRole:
return self.__items[index.row()][self.PttRole]
return None
def setData(self, index, value, role):
if not (index.isValid() and self.checkIndex(index)):
return False
if (
index.column() == 2
and isinstance(value, ScoreInsert)
and role == self.ScoreRole
):
self._db.update_score(self.__items[index.row()][self.IdRole], value)
self.syncDb()
return True
return False
def flags(self, index) -> Qt.ItemFlag:
flags = super().flags(index)
flags |= Qt.ItemFlag.ItemIsSelectable
if index.column() in [1, 2]:
flags |= Qt.ItemFlag.ItemIsEditable
return flags
def _removeRow(self, row: int, syncDb: bool = True):
if not 0 <= row < self.rowCount():
return False
try:
self._db.delete_score(self.__items[row][self.IdRole])
if syncDb:
self.syncDb()
return True
except Exception:
return False
def removeRow(self, row: int, parent=...):
return self._removeRow(row)
def removeRows(self, row: int, count: int, parent=...):
maxRow = min(self.rowCount() - 1, row + count - 1)
if row > maxRow:
return False
result = all(
self._removeRow(row, syncDb=False) for row in range(row, row + count)
)
self.syncDb()
return result
def removeRowList(self, rowList: list[int]):
result = all(
self._removeRow(row, syncDb=False) for row in sorted(rowList, reverse=True)
)
self.syncDb()
return result
class DbScoreTableSortFilterProxyModel(QSortFilterProxyModel):
Sort_C2_ScoreRole = Qt.ItemDataRole.UserRole + 75
Sort_C2_TimeRole = Qt.ItemDataRole.UserRole + 76
def lessThan(self, source_left, source_right) -> bool:
if source_left.column() != source_right.column():
return
column = source_left.column()
if column == 0:
return source_left.data(DbScoreTableModel.IdRole) < source_right.data(
DbScoreTableModel.IdRole
)
elif column == 2:
score_left = source_left.data(DbScoreTableModel.ScoreRole)
score_right = source_right.data(DbScoreTableModel.ScoreRole)
if isinstance(score_left, Score) and isinstance(score_right, Score):
if self.sortRole() == self.Sort_C2_ScoreRole:
return score_left.score < score_right.score
elif self.sortRole() == self.Sort_C2_TimeRole:
return score_left.time < score_right.time
elif column == 3:
return source_left.data(DbScoreTableModel.PttRole) < source_right.data(
DbScoreTableModel.PttRole
)
return super().lessThan(source_left, source_right)

View File

@ -0,0 +1,54 @@
from PySide6.QtCore import QPoint
from PySide6.QtGui import QGuiApplication, QScreen
from PySide6.QtWidgets import QWidget
def keepWidgetInScreen(widget: QWidget, screen: QScreen = None):
"""ensure your widget is visible"""
# see https://doc.qt.io/qt-6/application-windows.html
# for why using frameGeometry.width() / frameGeometry.height()
# instead of width() / height().
screen = screen or QGuiApplication.primaryScreen()
screenAvailableGeometry = screen.availableGeometry()
# X boundary
if widget.pos().x() < screenAvailableGeometry.x():
pos = QPoint(widget.pos())
pos.setX(screenAvailableGeometry.x())
widget.move(pos)
elif (
widget.pos().x() + widget.frameGeometry().width()
> screenAvailableGeometry.width()
):
pos = QPoint(widget.pos())
pos.setX(
pos.x()
- (
pos.x()
+ widget.frameGeometry().width()
- screenAvailableGeometry.width()
)
)
widget.move(pos)
# Y boundary
if widget.pos().y() < screenAvailableGeometry.y():
pos = QPoint(widget.pos())
pos.setY(screenAvailableGeometry.y())
widget.move(pos)
elif (
widget.pos().y() + widget.frameGeometry().height()
> screenAvailableGeometry.height()
):
pos = QPoint(widget.pos())
pos.setY(
pos.y()
- (
pos.y()
+ widget.frameGeometry().height()
- screenAvailableGeometry.height()
)
)
widget.move(pos)

476
ui/extends/tabs/tabOcr.py Normal file
View File

@ -0,0 +1,476 @@
import contextlib
import logging
from typing import Any
import exif
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.database import Database
from arcaea_offline.models import Chart, ScoreInsert
from arcaea_offline_ocr.device import Device
from arcaea_offline_ocr.recognize import RecognizeResult, recognize
from PySide6.QtCore import (
QAbstractListModel,
QAbstractTableModel,
QCoreApplication,
QDateTime,
QFileInfo,
QModelIndex,
QObject,
QRect,
QRunnable,
QSize,
Qt,
QThreadPool,
Signal,
Slot,
)
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QLabel, QStyledItemDelegate, QWidget
from ui.extends.shared.delegates.chartDelegate import ChartDelegate
from ui.extends.shared.delegates.scoreDelegate import ScoreDelegate
from ui.implements.components.scoreEditor import ScoreEditor
logger = logging.getLogger(__name__)
class OcrTaskSignals(QObject):
resultReady = Signal(int, RecognizeResult)
finished = Signal(int)
class OcrTask(QRunnable):
def __init__(self, index: int, device: Device, imagePath: str):
super().__init__()
self.index = index
self.device = device
self.imagePath = imagePath
self.signals = OcrTaskSignals()
def run(self):
try:
result = recognize(self.imagePath, self.device)
self.signals.resultReady.emit(self.index, result)
logger.info(
f"OcrTask {self.imagePath} with {repr(self.device)} got result {repr(result)}"
)
except Exception as e:
logger.exception(
f"OcrTask {self.imagePath} with {repr(self.device)} failed"
)
finally:
self.signals.finished.emit(self.index)
class OcrQueueModel(QAbstractListModel):
ImagePathRole = Qt.ItemDataRole.UserRole + 1
ImagePixmapRole = Qt.ItemDataRole.UserRole + 2
RecognizeResultRole = Qt.ItemDataRole.UserRole + 10
ScoreInsertRole = Qt.ItemDataRole.UserRole + 11
ChartRole = Qt.ItemDataRole.UserRole + 12
ScoreValidateOkRole = Qt.ItemDataRole.UserRole + 13
started = Signal()
finished = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.__db = Database()
self.__items: list[dict[int, Any]] = []
@property
def imagePaths(self):
return [item.get(self.ImagePathRole) for item in self.__items]
def clear(self):
self.beginResetModel()
self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
self.__items.clear()
self.endRemoveRows()
self.endResetModel()
def rowCount(self, *args):
return len(self.__items)
def data(self, index, role):
if (
index.isValid()
and 0 <= index.row() < self.rowCount()
and index.column() == 0
):
return self.__items[index.row()].get(role)
return None
def setData(self, *args):
return False
def addItem(self, imagePath: str):
if imagePath in self.imagePaths or not QFileInfo(imagePath).exists():
return
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.__items.append(
{
self.ImagePathRole: imagePath,
self.ImagePixmapRole: QPixmap(imagePath),
self.RecognizeResultRole: None,
self.ScoreInsertRole: None,
self.ChartRole: None,
self.ScoreValidateOkRole: False,
}
)
self.endInsertRows()
def updateOcrResult(self, row: int, result: RecognizeResult) -> bool:
if not 0 <= row < self.rowCount() or not isinstance(result, RecognizeResult):
return False
item = self.__items[row]
imagePath: str = item[self.ImagePathRole]
datetime = None
with contextlib.suppress(Exception):
with open(imagePath, "rb") as imgf:
exifImage = exif.Image(imgf.read())
if exifImage.has_exif and exifImage.get("datetime_original"):
datetimeStr = exifImage.get("datetime_original")
datetime = QDateTime.fromString(datetimeStr, "yyyy:MM:dd hh:mm:ss")
if not isinstance(datetime, QDateTime):
datetime = QFileInfo(imagePath).birthTime()
score = ScoreInsert(
song_id=self.__db.fuzzy_search_song_id(result.title)[0][0],
rating_class=result.rating_class,
score=result.score,
pure=result.pure,
far=result.far,
lost=result.lost,
time=datetime.toSecsSinceEpoch(),
max_recall=result.max_recall,
clear_type=None,
)
chart = Chart.from_db_row(
self.__db.get_chart(score.song_id, score.rating_class)
)
item[self.RecognizeResultRole] = result
self.setItemChart(row, chart)
self.setItemScore(row, score)
modelIndex = self.index(row, 0)
self.dataChanged.emit(
modelIndex,
modelIndex,
[self.RecognizeResultRole, self.ScoreInsertRole, self.ChartRole],
)
return True
@Slot(int, RecognizeResult)
def ocrTaskReady(self, row: int, result: RecognizeResult):
self.updateOcrResult(row, result)
@Slot(int)
def ocrTaskFinished(self, row: int):
self.__taskFinishedNum += 1
if self.__taskFinishedNum == self.__taskNum:
self.finished.emit()
def startQueue(self, device: Device):
self.__taskNum = self.rowCount()
self.__taskFinishedNum = 0
self.started.emit()
for row in range(self.rowCount()):
modelIndex = self.index(row, 0)
imagePath: str = modelIndex.data(self.ImagePathRole)
task = OcrTask(row, device, imagePath)
task.signals.resultReady.connect(self.ocrTaskReady)
task.signals.finished.connect(self.ocrTaskFinished)
QThreadPool.globalInstance().start(task)
def updateScoreValidateOk(self, row: int):
if not 0 <= row < self.rowCount():
return
item = self.__items[row]
chart = item[self.ChartRole]
score = item[self.ScoreInsertRole]
if isinstance(chart, Chart) and isinstance(score, ScoreInsert):
scoreRange = calculate_score_range(chart, score.pure, score.far)
scoreValidateOk = scoreRange[0] <= score.score <= scoreRange[1]
item[self.ScoreValidateOkRole] = scoreValidateOk
else:
item[self.ScoreValidateOkRole] = False
modelIndex = self.index(row, 0)
self.dataChanged.emit(modelIndex, modelIndex, [self.ScoreValidateOkRole])
def setItemChart(self, row: int, chart: Chart):
if not 0 <= row < self.rowCount() or not isinstance(chart, Chart):
return False
item = self.__items[row]
item[self.ChartRole] = chart
updatedRoles = [self.ChartRole]
self.updateScoreValidateOk(row)
modelIndex = self.index(row, 0)
self.dataChanged.emit(modelIndex, modelIndex, updatedRoles)
return True
def setItemScore(self, row: int, score: ScoreInsert) -> bool:
if not 0 <= row < self.rowCount() or not isinstance(score, ScoreInsert):
return False
item = self.__items[row]
item[self.ScoreInsertRole] = score
updatedRoles = [self.ScoreInsertRole]
self.updateScoreValidateOk(row)
modelIndex = self.index(row, 0)
self.dataChanged.emit(modelIndex, modelIndex, updatedRoles)
return True
def acceptItem(self, row: int, ignoreValidate: bool = False):
if not 0 <= row < self.rowCount():
return
item = self.__items[row]
score = item[self.ScoreInsertRole]
if not isinstance(score, ScoreInsert) or (
not item[self.ScoreValidateOkRole] and not ignoreValidate
):
return
try:
self.__db.insert_score(score)
self.beginRemoveRows(QModelIndex(), row, row)
self.__items.pop(row)
self.endRemoveRows()
return
except Exception as e:
logger.exception(f"Error accepting {repr(item)}")
return
def acceptItems(self, __rows: list[int], ignoreValidate: bool = False):
items = sorted(__rows, reverse=True)
[self.acceptItem(item, ignoreValidate) for item in items]
def acceptAllItems(self, ignoreValidate: bool = False):
self.acceptItems([*range(self.rowCount())], ignoreValidate)
def removeItem(self, row: int):
if not 0 <= row < self.rowCount():
return
self.beginRemoveRows(QModelIndex(), row, row)
self.__items.pop(row)
self.endRemoveRows()
def removeItems(self, __rows: list[int]):
rows = sorted(__rows, reverse=True)
[self.removeItem(row) for row in rows]
class OcrQueueTableProxyModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
self.retranslateHeaders()
self.__sourceModel = None
self.__columnRoleMapping = [
[Qt.ItemDataRole.CheckStateRole],
[OcrQueueModel.ImagePathRole, OcrQueueModel.ImagePixmapRole],
[
OcrQueueModel.RecognizeResultRole,
OcrQueueModel.ChartRole,
],
[
OcrQueueModel.RecognizeResultRole,
OcrQueueModel.ScoreInsertRole,
OcrQueueModel.ChartRole,
OcrQueueModel.ScoreValidateOkRole,
],
]
def retranslateHeaders(self):
self.__horizontalHeaders = [
# fmt: off
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.select"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.imagePreview"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.chart"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.score"),
# fmt: on
]
def sourceModel(self) -> OcrQueueModel:
return self.__sourceModel
def setSourceModel(self, sourceModel):
if not isinstance(sourceModel, OcrQueueModel):
return False
# connect signals
sourceModel.rowsAboutToBeInserted.connect(self.rowsAboutToBeInserted)
sourceModel.rowsInserted.connect(self.rowsInserted)
sourceModel.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved)
sourceModel.rowsRemoved.connect(self.rowsRemoved)
sourceModel.dataChanged.connect(self.dataChanged)
sourceModel.layoutAboutToBeChanged.connect(self.layoutAboutToBeChanged)
sourceModel.layoutChanged.connect(self.layoutChanged)
self.__sourceModel = sourceModel
return True
def rowCount(self, *args):
return self.sourceModel().rowCount()
def columnCount(self, *args):
return len(self.__horizontalHeaders)
def headerData(self, section: int, orientation: Qt.Orientation, role: int):
if (
orientation == Qt.Orientation.Horizontal
and 0 <= section < len(self.__horizontalHeaders)
and role == Qt.ItemDataRole.DisplayRole
):
return self.__horizontalHeaders[section]
return None
def data(self, index, role):
if (
0 <= index.row() < self.rowCount()
and 0 <= index.column() < self.columnCount()
and role in self.__columnRoleMapping[index.column()]
):
srcIndex = self.sourceModel().index(index.row(), 0)
return srcIndex.data(role)
return None
def setData(self, index, value, role):
if index.column() == 2 and role == OcrQueueModel.ChartRole:
return self.sourceModel().setItemChart(index.row(), value)
if index.column() == 3 and role == OcrQueueModel.ScoreInsertRole:
return self.sourceModel().setItemScore(index.row(), value)
return False
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
flags = (
self.sourceModel().flags(index)
if isinstance(self.sourceModel(), OcrQueueModel)
else super().flags(index)
)
flags = flags | Qt.ItemFlag.ItemIsEnabled
flags = flags | Qt.ItemFlag.ItemIsEditable
flags = flags | Qt.ItemFlag.ItemIsSelectable
if index.column() == 0:
flags = flags & ~Qt.ItemFlag.ItemIsEnabled & ~Qt.ItemFlag.ItemIsEditable
return flags
class ImageDelegate(QStyledItemDelegate):
def getPixmap(self, index: QModelIndex):
return index.data(OcrQueueModel.ImagePixmapRole)
def getImagePath(self, index: QModelIndex):
return index.data(OcrQueueModel.ImagePathRole)
def scalePixmap(self, pixmap: QPixmap):
return pixmap.scaled(
100,
100,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation,
)
def paint(self, painter, option, index):
pixmap = self.getPixmap(index)
if not isinstance(pixmap, QPixmap):
imagePath = self.getImagePath(index)
option.text = imagePath
super().paint(painter, option, index)
else:
pixmap = self.scalePixmap(pixmap)
# https://stackoverflow.com/a/32047499/16484891
# CC BY-SA 3.0
x = option.rect.center().x() - pixmap.rect().width() / 2
y = option.rect.center().y() - pixmap.rect().height() / 2
painter.drawPixmap(
QRect(x, y, pixmap.rect().width(), pixmap.rect().height()), pixmap
)
def sizeHint(self, option, index) -> QSize:
pixmap = self.getPixmap(index)
if isinstance(pixmap, QPixmap):
pixmap = self.scalePixmap(pixmap)
return pixmap.size()
else:
return QSize(100, 75)
def createEditor(self, parent, option, index) -> QWidget:
pixmap = self.getPixmap(index)
if isinstance(pixmap, QPixmap):
label = QLabel(parent)
label.setWindowFlags(Qt.WindowType.Window)
label.setWindowFlag(Qt.WindowType.WindowMinimizeButtonHint, False)
label.setWindowFlag(Qt.WindowType.WindowMaximizeButtonHint, False)
label.setWindowFlag(Qt.WindowType.WindowCloseButtonHint, True)
label.setWindowTitle(QFileInfo(self.getImagePath(index)).fileName())
pixmap = pixmap.scaled(
800,
800,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation,
)
label.setMinimumSize(pixmap.size())
label.setPixmap(pixmap)
label.move(parent.mapToGlobal(parent.pos()))
return label
def setModelData(self, *args):
...
def updateEditorGeometry(self, *args):
...
class TableChartDelegate(ChartDelegate):
def getChart(self, index: QModelIndex) -> Chart | None:
return index.data(OcrQueueModel.ChartRole)
def paintWarningBackground(self, index: QModelIndex) -> bool:
return isinstance(
index.data(OcrQueueModel.RecognizeResultRole), RecognizeResult
)
def setModelData(self, editor, model: OcrQueueTableProxyModel, index):
if editor.validate():
model.setData(index, editor.value(), OcrQueueModel.ChartRole)
class TableScoreDelegate(ScoreDelegate):
def getScoreInsert(self, index: QModelIndex):
return index.data(OcrQueueModel.ScoreInsertRole)
def getChart(self, index: QModelIndex):
return index.data(OcrQueueModel.ChartRole)
def getScoreValidateOk(self, index: QModelIndex):
return index.data(OcrQueueModel.ScoreValidateOkRole)
def paintWarningBackground(self, index: QModelIndex) -> bool:
return isinstance(
index.data(OcrQueueModel.RecognizeResultRole), RecognizeResult
)
# def createEditor(self, parent, option, index):
# editor = super().createEditor(parent, option, index)
# editor.setManualHandleCommit(True)
# return editor
def setModelData(self, editor, model: OcrQueueTableProxyModel, index):
# userAcceptMessageBox = editor.triggerValidateMessageBox()
# if userAcceptMessageBox:
# model.setData(index, editor.value(), OcrQueueModel.ScoreInsertRole)
if super().confirmSetModelData(editor):
model.setData(index, editor.value(), OcrQueueModel.ScoreInsertRole)

View File

@ -0,0 +1,6 @@
from .chartSelector import ChartSelector
from .devicesComboBox import DevicesComboBox
from .elidedLabel import ElidedLabel
from .fileSelector import FileSelector
from .ratingClassRadioButton import RatingClassRadioButton
from .scoreEditor import ScoreEditor

View File

@ -0,0 +1,254 @@
from typing import Literal
from arcaea_offline.database import Database
from arcaea_offline.models import Chart, Package
from arcaea_offline.utils import rating_class_to_text
from PySide6.QtCore import QModelIndex, Qt, Signal, Slot
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QCompleter, QWidget
from ui.designer.components.chartSelector_ui import Ui_ChartSelector
from ui.extends.components.chartSelector import FuzzySearchCompleterModel
from ui.extends.shared.delegates.descriptionDelegate import DescriptionDelegate
from ui.implements.components.ratingClassRadioButton import RatingClassRadioButton
class ChartSelector(Ui_ChartSelector, QWidget):
valueChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.db = Database()
self.db.register_update_hook(self.fillPackageComboBox)
self.setupUi(self)
self.pstButton.setColors(QColor("#399bb2"), QColor("#f0f8fa"))
self.prsButton.setColors(QColor("#809955"), QColor("#f7f9f4"))
self.ftrButton.setColors(QColor("#702d60"), QColor("#f7ebf4"))
self.bydButton.setColors(QColor("#710f25"), QColor("#f9ced8"))
self.__RATING_CLASS_BUTTONS = [
self.pstButton,
self.prsButton,
self.ftrButton,
self.bydButton,
]
self.pstButton.clicked.connect(self.selectRatingClass)
self.prsButton.clicked.connect(self.selectRatingClass)
self.ftrButton.clicked.connect(self.selectRatingClass)
self.bydButton.clicked.connect(self.selectRatingClass)
self.deselectAllRatingClassButtons()
self.updateRatingClassButtonsEnabled([])
self.previousPackageButton.clicked.connect(
lambda: self.quickSwitchSelection("previous", "package")
)
self.previousSongIdButton.clicked.connect(
lambda: self.quickSwitchSelection("previous", "songId")
)
self.nextSongIdButton.clicked.connect(
lambda: self.quickSwitchSelection("next", "songId")
)
self.nextPackageButton.clicked.connect(
lambda: self.quickSwitchSelection("next", "package")
)
self.valueChanged.connect(self.updateResultLabel)
self.fillPackageComboBox()
self.packageComboBox.setCurrentIndex(-1)
self.songIdComboBox.setCurrentIndex(-1)
self.fuzzySearchCompleterModel = FuzzySearchCompleterModel()
self.fuzzySearchCompleter = QCompleter(self.fuzzySearchCompleterModel)
self.fuzzySearchCompleter.popup().setItemDelegate(
DescriptionDelegate(self.fuzzySearchCompleter.popup())
)
self.fuzzySearchCompleter.activated[QModelIndex].connect(
self.fuzzySearchCompleterSetSelection
)
self.fuzzySearchLineEdit.setCompleter(self.fuzzySearchCompleter)
self.packageComboBox.setItemDelegate(DescriptionDelegate(self.packageComboBox))
self.songIdComboBox.setItemDelegate(DescriptionDelegate(self.songIdComboBox))
self.pstButton.toggled.connect(self.valueChanged)
self.prsButton.toggled.connect(self.valueChanged)
self.ftrButton.toggled.connect(self.valueChanged)
self.bydButton.toggled.connect(self.valueChanged)
self.packageComboBox.currentIndexChanged.connect(self.valueChanged)
self.songIdComboBox.currentIndexChanged.connect(self.valueChanged)
def quickSwitchSelection(
self,
direction: Literal["previous", "next"],
model: Literal["package", "songId"],
):
minIndex = 0
if model == "package":
maxIndex = self.packageComboBox.count() - 1
currentIndex = self.packageComboBox.currentIndex() + (
1 if direction == "next" else -1
)
currentIndex = max(min(maxIndex, currentIndex), minIndex)
self.packageComboBox.setCurrentIndex(currentIndex)
elif model == "songId":
maxIndex = self.songIdComboBox.count() - 1
currentIndex = self.songIdComboBox.currentIndex() + (
1 if direction == "next" else -1
)
currentIndex = max(min(maxIndex, currentIndex), minIndex)
self.songIdComboBox.setCurrentIndex(currentIndex)
else:
return
def value(self):
packageId = self.packageComboBox.currentData()
songId = self.songIdComboBox.currentData()
ratingClass = self.selectedRatingClass()
if packageId and songId and isinstance(ratingClass, int):
return Chart.from_db_row(self.db.get_chart(songId, ratingClass))
return None
@Slot()
def updateResultLabel(self):
chart = self.value()
if isinstance(chart, Chart):
package = Package.from_db_row(
self.db.get_package_by_package_id(chart.package_id)
)
texts = [
[package.name, chart.name_en, rating_class_to_text(chart.rating_class)],
[package.id, chart.song_id, str(chart.rating_class)],
]
texts = [" | ".join(t) for t in texts]
text = f'{texts[0]}<br><font color="gray">{texts[1]}</font>'
self.resultLabel.setText(text)
else:
self.resultLabel.setText("...")
def fillPackageComboBox(self):
self.packageComboBox.clear()
packages = [Package.from_db_row(dbRow) for dbRow in self.db.get_packages()]
for package in packages:
self.packageComboBox.addItem(f"{package.name} ({package.id})", package.id)
row = self.packageComboBox.count() - 1
self.packageComboBox.setItemData(
row, package.name, DescriptionDelegate.MainTextRole
)
self.packageComboBox.setItemData(
row, package.id, DescriptionDelegate.DescriptionTextRole
)
self.packageComboBox.setCurrentIndex(-1)
def fillSongIdComboBox(self):
self.songIdComboBox.clear()
packageId = self.packageComboBox.currentData()
if packageId:
charts = [
Chart.from_db_row(dbRow)
for dbRow in self.db.get_charts_by_package_id(packageId)
]
inserted_song_ids = []
for chart in charts:
if chart.song_id not in inserted_song_ids:
self.songIdComboBox.addItem(
f"{chart.name_en} ({chart.song_id})", chart.song_id
)
inserted_song_ids.append(chart.song_id)
row = self.songIdComboBox.count() - 1
self.songIdComboBox.setItemData(
row, chart.name_en, DescriptionDelegate.MainTextRole
)
self.songIdComboBox.setItemData(
row, chart.song_id, DescriptionDelegate.DescriptionTextRole
)
self.songIdComboBox.setCurrentIndex(-1)
@Slot()
def on_packageComboBox_activated(self):
self.fillSongIdComboBox()
@Slot(int)
def on_songIdComboBox_currentIndexChanged(self, index: int):
rating_classes = []
if index > -1:
charts = [
Chart.from_db_row(dbRow)
for dbRow in self.db.get_charts_by_song_id(
self.songIdComboBox.currentData()
)
]
rating_classes = [chart.rating_class for chart in charts]
self.updateRatingClassButtonsEnabled(rating_classes)
@Slot()
def on_resetButton_clicked(self):
self.packageComboBox.setCurrentIndex(-1)
self.songIdComboBox.setCurrentIndex(-1)
@Slot(str)
def on_fuzzySearchLineEdit_textChanged(self, text: str):
if text:
self.fuzzySearchCompleterModel.fillDbFuzzySearchResults(self.db, text)
else:
self.fuzzySearchCompleterModel.clear()
def selectChart(self, chart: Chart):
packageIdIndex = self.packageComboBox.findData(chart.package_id)
if packageIdIndex > -1:
self.packageComboBox.setCurrentIndex(packageIdIndex)
else:
# QMessageBox
return
self.fillSongIdComboBox()
songIdIndex = self.songIdComboBox.findData(chart.song_id)
if songIdIndex > -1:
self.songIdComboBox.setCurrentIndex(songIdIndex)
else:
# QMessageBox
return
self.selectRatingClass(chart.rating_class)
@Slot(QModelIndex)
def fuzzySearchCompleterSetSelection(self, index: QModelIndex):
chart = index.data(Qt.ItemDataRole.UserRole + 10) # type: Chart
self.selectChart(chart)
self.fuzzySearchLineEdit.clear()
self.fuzzySearchLineEdit.clearFocus()
def ratingClassButtons(self):
return self.__RATING_CLASS_BUTTONS
def selectedRatingClass(self):
for i, button in enumerate(self.__RATING_CLASS_BUTTONS):
if button.isChecked():
return i
def updateRatingClassButtonsEnabled(self, rating_classes: list[int]):
for i, button in enumerate(self.__RATING_CLASS_BUTTONS):
if i in rating_classes:
button.setEnabled(True)
else:
button.setChecked(False)
button.setEnabled(False)
def deselectAllRatingClassButtons(self):
[button.setChecked(False) for button in self.__RATING_CLASS_BUTTONS]
@Slot()
def selectRatingClass(self, rating_class: int | None = None):
if type(rating_class) == int and rating_class in range(4):
self.deselectAllRatingClassButtons()
button = self.__RATING_CLASS_BUTTONS[rating_class]
if button.isEnabled():
button.setChecked(True)
else:
button = self.sender()
if isinstance(button, RatingClassRadioButton) and button.isEnabled():
self.deselectAllRatingClassButtons()
button.setChecked(True)

View File

@ -0,0 +1,9 @@
from PySide6.QtWidgets import QWidget
from ui.designer.components.dbTableViewer_ui import Ui_DbTableViewer
class DbTableViewer(Ui_DbTableViewer, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)

View File

@ -0,0 +1,32 @@
from arcaea_offline_ocr.device import Device
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QComboBox
from ui.extends.ocr import load_devices_json
from ui.extends.shared.delegates.descriptionDelegate import DescriptionDelegate
class DevicesComboBox(QComboBox):
DeviceUuidRole = Qt.ItemDataRole.UserRole + 10
def __init__(self, parent=None):
super().__init__(parent)
self.setItemDelegate(DescriptionDelegate(self))
def setDevices(self, devices: list[Device]):
self.clear()
for device in devices:
self.addItem(f"{device.name} ({device.uuid})", device)
row = self.count() - 1
self.setItemData(row, device.uuid, self.DeviceUuidRole)
self.setItemData(row, device.name, DescriptionDelegate.MainTextRole)
self.setItemData(row, device.uuid, DescriptionDelegate.DescriptionTextRole)
self.setCurrentIndex(-1)
def loadDevicesJson(self, path: str):
devices = load_devices_json(path)
self.setDevices(devices)
def selectDevice(self, deviceUuid: str):
index = self.findData(deviceUuid, self.DeviceUuidRole)
self.setCurrentIndex(index)

View File

@ -0,0 +1,50 @@
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLabel
class ElidedLabel(QLabel):
"""
Adapted from https://wiki.qt.io/Elided_Label
"""
def __init__(self, parent=None):
super().__init__(parent)
self.__elideMode: Qt.TextElideMode = Qt.TextElideMode.ElideNone
self.__cachedElidedText = ""
self.__cachedText = ""
def elideMode(self):
return self.__elideMode
def setElideMode(self, mode):
self.__elideMode = mode
self.__cachedText = ""
self.update()
def resizeEvent(self, event):
super().resizeEvent(event)
self.__cachedText = ""
def paintEvent(self, event) -> None:
if self.__elideMode == Qt.TextElideMode.ElideNone:
return super().paintEvent(event)
self.updateCachedTexts()
super().setText(self.__cachedElidedText)
super().paintEvent(event)
super().setText(self.__cachedText)
def updateCachedTexts(self):
text = self.text()
if self.__cachedText == text:
return
self.__cachedText = text
fontMetrics = self.fontMetrics()
self.__cachedElidedText = fontMetrics.elidedText(
self.text(), self.__elideMode, self.width(), Qt.TextFlag.TextShowMnemonic
)
# make sure to show at least the first character
if self.__cachedText:
firstChar = f"{self.__cachedText[0]}..."
self.setMinimumWidth(fontMetrics.horizontalAdvance(firstChar) + 1)

View File

@ -0,0 +1,91 @@
from PySide6.QtCore import QDir, QFileInfo, QMetaObject, Qt, Signal, Slot
from PySide6.QtWidgets import QFileDialog, QWidget
from ui.designer.components.fileSelector_ui import Ui_FileSelector
class FileSelector(Ui_FileSelector, QWidget):
accepted = Signal()
filesSelected = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.reset()
self.elidedLabel.setElideMode(Qt.TextElideMode.ElideMiddle)
self.accepted.connect(self.filesSelected)
self.accepted.connect(self.updateLabel)
self.filesSelected.connect(self.updateLabel)
self.__mode = self.getOpenFileNames
def getOpenFileNames(self):
selectedFiles, filter = QFileDialog.getOpenFileNames(
self,
self.__caption,
self.__startDirectory,
self.__filter,
"",
options=self.__options,
)
if selectedFiles:
self.__selectedFiles = selectedFiles
self.accepted.emit()
def getExistingDirectory(self):
selectedDir = QFileDialog.getExistingDirectory(
self,
self.__caption,
self.__startDirectory,
QFileDialog.Option.ShowDirsOnly | self.__options,
)
if selectedDir:
self.__selectedFiles = [selectedDir]
self.accepted.emit()
def selectFile(self, filename: str):
fileInfo = QFileInfo(filename)
if not fileInfo.exists():
return
self.__selectedFiles = [fileInfo.absoluteFilePath()]
self.__startDirectory = fileInfo.dir().absolutePath()
self.filesSelected.emit()
def selectedFiles(self):
return self.__selectedFiles
def setNameFilters(self, filters: list[str]):
self.__filter = ";;".join(filters) if filters else ""
def setOptions(self, options: QFileDialog.Option):
self.__options = options
def setMode(self, mode):
if mode in [self.getOpenFileNames, self.getExistingDirectory]:
self.__mode = mode
else:
raise ValueError("Invalid mode")
def reset(self):
self.__selectedFiles = []
self.__caption = None
self.__startDirectory = QDir.currentPath()
self.__filter = ""
self.__options = QFileDialog.Option(0)
self.updateLabel()
def updateLabel(self):
selectedFiles = self.selectedFiles()
if not selectedFiles:
self.elidedLabel.setText("...")
else:
self.elidedLabel.setText("<br>".join(selectedFiles))
@Slot()
def on_selectButton_clicked(self):
self.__mode()

View File

@ -0,0 +1,15 @@
from PySide6.QtWidgets import QLineEdit
class FocusSelectAllLineEdit(QLineEdit):
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.selectAll()
def focusInEvent(self, event):
super().focusInEvent(event)
self.selectAll()
def focusOutEvent(self, event):
super().focusOutEvent(event)
self.deselect()

View File

@ -0,0 +1,100 @@
from PySide6.QtCore import Slot
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QGraphicsColorizeEffect, QRadioButton
from ui.extends.color import mix_color
STYLESHEET = """
QRadioButton {{
padding: 10px;
background-color: qlineargradient(spread:pad, x1:0.7, y1:0.5, x2:1, y2:0.525, stop:0 {dark_color}, stop:1 {mid_color});
color: {text_color};
}}
QRadioButton::indicator {{
border: 2px solid palette(Window);
width: 7px;
height: 7px;
border-radius: 0px;
}}
QRadioButton::indicator:unchecked {{
background-color: palette(Window);
}}
QPushButton::indicator:checked {{
background-color: {mid_color};
}}
"""
class RatingClassRadioButton(QRadioButton):
def __init__(self, parent):
super().__init__(parent)
self.toggled.connect(self.updateCheckedEffect)
self.grayscaleEffect = QGraphicsColorizeEffect(self)
self.grayscaleEffect.setColor("#000000")
def setColors(self, dark_color: QColor, text_color: QColor):
self._dark_color = dark_color
self._text_color = text_color
self._mid_color = mix_color(dark_color, text_color, 0.616)
self.updateEffects()
def isColorsSet(self) -> bool:
return (
hasattr(self, "_dark_color")
and hasattr(self, "_text_color")
and hasattr(self, "_mid_color")
and isinstance(self._dark_color, QColor)
and isinstance(self._text_color, QColor)
and isinstance(self._mid_color, QColor)
)
def setNormalStyleSheet(self):
self.setStyleSheet(
STYLESHEET.format(
dark_color=self._dark_color.name(QColor.NameFormat.HexArgb),
mid_color=self._mid_color.name(QColor.NameFormat.HexArgb),
text_color=self._text_color.name(QColor.NameFormat.HexArgb),
)
)
def setDisabledStyleSheet(self):
self.setStyleSheet(
STYLESHEET.format(
dark_color="#282828",
mid_color="#282828",
text_color="#9e9e9e",
).replace("palette(Window)", "#333333")
)
@Slot()
def updateEnabledEffect(self):
if self.isColorsSet():
if self.isEnabled():
self.setNormalStyleSheet()
else:
self.setDisabledStyleSheet()
@Slot()
def updateCheckedEffect(self):
if self.isColorsSet():
if self.isEnabled():
self.grayscaleEffect.setStrength(0.0 if self.isChecked() else 1.0)
self.setGraphicsEffect(self.grayscaleEffect)
@Slot()
def updateEffects(self):
self.updateCheckedEffect()
self.updateEnabledEffect()
def setChecked(self, arg__1: bool):
super().setChecked(arg__1)
self.updateEffects()
def setEnabled(self, arg__1: bool):
super().setEnabled(arg__1)
self.updateEffects()

View File

@ -0,0 +1,197 @@
from enum import IntEnum
from typing import Optional
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.models import Chart, Score, ScoreInsert
from PySide6.QtCore import QCoreApplication, QDateTime, Signal, Slot
from PySide6.QtWidgets import QMessageBox, QWidget
from ui.designer.components.scoreEditor_ui import Ui_ScoreEditor
class ScoreValidateResult(IntEnum):
Ok = 0
ScoreMismatch = 1
ScoreEmpty = 2
ChartInvalid = 50
class ScoreEditor(Ui_ScoreEditor, QWidget):
valueChanged = Signal()
accepted = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.__validateBeforeAccept = True
self.__chart = None
self.scoreLineEdit.textChanged.connect(self.valueChanged)
self.pureSpinBox.valueChanged.connect(self.valueChanged)
self.farSpinBox.valueChanged.connect(self.valueChanged)
self.lostSpinBox.valueChanged.connect(self.valueChanged)
self.dateTimeEdit.dateTimeChanged.connect(self.valueChanged)
self.maxRecallSpinBox.valueChanged.connect(self.valueChanged)
self.clearTypeComboBox.currentIndexChanged.connect(self.valueChanged)
self.valueChanged.connect(self.validateScore)
self.valueChanged.connect(self.updateValidateLabel)
self.clearTypeComboBox.addItem("HARD LOST", -1)
self.clearTypeComboBox.addItem("TRACK LOST", 0)
self.clearTypeComboBox.addItem("TRACK COMPLETE", 1)
self.clearTypeComboBox.setCurrentIndex(-1)
def setValidateBeforeAccept(self, __bool: bool):
self.__validateBeforeAccept = __bool
def triggerValidateMessageBox(self):
validate = self.validateScore()
if validate == ScoreValidateResult.Ok:
return True
if validate == ScoreValidateResult.ChartInvalid:
QMessageBox.critical(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "chartInvalidDialog.title"),
QCoreApplication.translate("ScoreEditor", "chartInvalidDialog.title"),
# fmt: on
)
return False
if validate == ScoreValidateResult.ScoreMismatch:
result = QMessageBox.warning(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "scoreMismatchDialog.title"),
QCoreApplication.translate("ScoreEditor", "scoreMismatchDialog.content"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
return result == QMessageBox.StandardButton.Yes
elif validate == ScoreValidateResult.ScoreEmpty:
result = QMessageBox.warning(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "emptyScoreDialog.title"),
QCoreApplication.translate("ScoreEditor", "emptyScoreDialog.content"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
return result == QMessageBox.StandardButton.Yes
else:
return False
@Slot()
def on_commitButton_clicked(self):
userAccept = (
self.triggerValidateMessageBox() if self.__validateBeforeAccept else True
)
if userAccept:
self.accepted.emit()
def score(self):
score_text = self.scoreLineEdit.text().replace("'", "")
return int(score_text) if score_text else 0
def setMinimums(self):
self.pureSpinBox.setMinimum(0)
self.farSpinBox.setMinimum(0)
self.lostSpinBox.setMinimum(0)
self.maxRecallSpinBox.setMinimum(-1)
def setLimits(self, chart: Chart):
self.setMinimums()
self.pureSpinBox.setMaximum(chart.note)
self.farSpinBox.setMaximum(chart.note)
self.lostSpinBox.setMaximum(chart.note)
self.maxRecallSpinBox.setMaximum(chart.note)
def resetLimits(self):
self.setMinimums()
self.pureSpinBox.setMaximum(0)
self.farSpinBox.setMaximum(0)
self.lostSpinBox.setMaximum(0)
self.maxRecallSpinBox.setMaximum(0)
def setChart(self, chart: Optional[Chart]):
if isinstance(chart, Chart):
self.__chart = chart
self.setLimits(chart)
else:
self.__chart = None
self.resetLimits()
self.updateValidateLabel()
def validateScore(self) -> ScoreValidateResult:
if not isinstance(self.__chart, Chart):
return ScoreValidateResult.ChartInvalid
score = self.value()
score_range = calculate_score_range(self.__chart, score.pure, score.far)
score_in_range = score_range[0] <= score.score <= score_range[1]
note_in_range = score.pure + score.far + score.lost <= self.__chart.note
if not score_in_range or not note_in_range:
return ScoreValidateResult.ScoreMismatch
if score.score == 0:
return ScoreValidateResult.ScoreEmpty
return ScoreValidateResult.Ok
def updateValidateLabel(self):
validate = self.validateScore()
if validate == ScoreValidateResult.Ok:
text = QCoreApplication.translate("ScoreEditor", "validate.ok")
elif validate == ScoreValidateResult.ChartInvalid:
text = QCoreApplication.translate("ScoreEditor", "validate.chartInvalid")
elif validate == ScoreValidateResult.ScoreMismatch:
text = QCoreApplication.translate("ScoreEditor", "validate.scoreMismatch")
elif validate == ScoreValidateResult.ScoreEmpty:
text = QCoreApplication.translate("ScoreEditor", "validate.scoreEmpty")
else:
text = QCoreApplication.translate("ScoreEditor", "validate.unknownState")
self.validateLabel.setText(text)
def value(self):
if isinstance(self.__chart, Chart):
return ScoreInsert(
song_id=self.__chart.song_id,
rating_class=self.__chart.rating_class,
score=self.score(),
pure=self.pureSpinBox.value(),
far=self.farSpinBox.value(),
lost=self.lostSpinBox.value(),
time=self.dateTimeEdit.dateTime().toSecsSinceEpoch(),
max_recall=self.maxRecallSpinBox.value()
if self.maxRecallSpinBox.value() > -1
else None,
clear_type=None,
)
def setValue(self, score: Score | ScoreInsert):
if isinstance(score, (Score, ScoreInsert)):
scoreText = str(score.score)
scoreText = scoreText.rjust(8, "0")
self.scoreLineEdit.setText(scoreText)
self.pureSpinBox.setValue(score.pure)
self.farSpinBox.setValue(score.far)
self.lostSpinBox.setValue(score.lost)
self.dateTimeEdit.setDateTime(QDateTime.fromSecsSinceEpoch(score.time))
if score.max_recall is not None:
self.maxRecallSpinBox.setValue(score.max_recall)
if score.clear_type is not None:
self.clearTypeComboBox.setCurrentIndex(score.clear_type)
def reset(self):
self.setChart(None)
self.scoreLineEdit.setText("''")
self.pureSpinBox.setValue(0)
self.farSpinBox.setValue(0)
self.lostSpinBox.setValue(0)
self.maxRecallSpinBox.setValue(-1)
self.clearTypeComboBox.setCurrentIndex(-1)

View File

@ -0,0 +1,38 @@
from traceback import format_exception
from PySide6.QtWidgets import QMainWindow
from ui.designer.mainwindow_ui import Ui_MainWindow
from ui.implements.tabs.tabOcr import TabOcr
# try:
# import arcaea_offline_ocr
# from ui.implements.tabs.tabOcr import TabOcr
# OCR_ENABLED_FLAG = True
# except Exception as e:
# from ui.implements.tabs.tabOcrDisabled import TabOcrDisabled
# OCR_ENABLED_FLAG = False
# OCR_ERROR_TEXT = "\n".join(format_exception(e))
class MainWindow(Ui_MainWindow, QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
currentIndex = self.tabWidget.currentIndex()
ocrTabIndex = self.tabWidget.indexOf(self.tab_ocr)
self.tabWidget.removeTab(ocrTabIndex)
self.tab_ocr.deleteLater()
# if OCR_ENABLED_FLAG:
# self.tab_ocr = TabOcr(self.tabWidget)
# else:
# self.tab_ocr = TabOcrDisabled(self.tabWidget)
# self.tab_ocr.contentLabel.setText(OCR_ERROR_TEXT)
self.tab_ocr = TabOcr(self.tabWidget)
self.tabWidget.insertTab(ocrTabIndex, self.tab_ocr, "")
self.tabWidget.setCurrentIndex(currentIndex)
self.retranslateUi(self)

View File

@ -0,0 +1,73 @@
from arcaea_offline.database import Database
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QWidget
from ui.designer.settings.settingsDefault_ui import Ui_SettingsDefault
from ui.extends.ocr import load_devices_json
from ui.extends.settings import *
class SettingsDefault(Ui_SettingsDefault, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.settings = Settings(self)
self.devicesJsonFileSelector.filesSelected.connect(self.fillDevicesComboBox)
self.devicesJsonFileResetButton.clicked.connect(self.resetDevicesJsonFile)
self.deviceUuidResetButton.clicked.connect(self.resetDeviceUuid)
devicesJsonPath = self.settings.devicesJsonFile()
self.devicesJsonFileSelector.selectFile(devicesJsonPath)
tesseractPath = self.settings.tesseractPath()
self.tesseractFileSelector.selectFile(tesseractPath)
self.devicesJsonFileSelector.accepted.connect(
self.on_devicesJsonFileSelector_accepted
)
self.tesseractFileSelector.accepted.connect(
self.on_tesseractFileSelector_accepted
)
def setDevicesJsonFile(self):
try:
filename = self.devicesJsonFileSelector.selectedFiles()[0]
devices = load_devices_json(filename)
assert isinstance(devices, list)
self.settings.setDevicesJsonFile(filename)
except Exception as e:
print(e)
# QMessageBox
return
def resetDevicesJsonFile(self):
self.devicesJsonFileSelector.reset()
self.settings.resetDevicesJsonFile()
def on_devicesJsonFileSelector_accepted(self):
self.setDevicesJsonFile()
def fillDevicesComboBox(self):
devicesJsonPath = self.devicesJsonFileSelector.selectedFiles()[0]
self.devicesComboBox.loadDevicesJson(devicesJsonPath)
storedDeviceUuid = self.settings.deviceUuid()
self.devicesComboBox.selectDevice(storedDeviceUuid)
@Slot()
def on_devicesComboBox_activated(self):
device = self.devicesComboBox.currentData()
if device:
self.settings.setDeviceUuid(device.uuid)
def resetDeviceUuid(self):
self.devicesComboBox.setCurrentIndex(-1)
self.settings.resetDeviceUuid()
def setTesseractFile(self):
file = self.tesseractFileSelector.selectedFiles()[0]
self.settings.setTesseractPath(file)
def on_tesseractFileSelector_accepted(self):
self.setTesseractFile()

View File

@ -0,0 +1,23 @@
from PySide6.QtCore import Qt, Slot
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QMessageBox, QWidget
from ui.designer.tabs.tabAbout_ui import Ui_TabAbout
class TabAbout(Ui_TabAbout, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
logoPixmap = QPixmap(":/images/logo.png").scaled(
300,
300,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation,
)
self.logoLabel.setPixmap(logoPixmap)
@Slot()
def on_aboutQtButton_clicked(self):
QMessageBox.aboutQt(self)

View File

@ -0,0 +1,30 @@
import logging
import traceback
from arcaea_offline.database import Database
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget
from ui.designer.tabs.tabDb.tabDb_Manage_ui import Ui_TabDb_Manage
logger = logging.getLogger(__name__)
class TabDb_Manage(Ui_TabDb_Manage, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
@Slot()
def on_syncArcSongDbButton_clicked(self):
dbFile, filter = QFileDialog.getOpenFileName(
self, None, "", "DB File (*.db);;*"
)
try:
Database().update_arcsong_db(dbFile)
QMessageBox.information(self, "OK", "OK")
except Exception as e:
logging.exception("Sync arcsong.db error")
QMessageBox.critical(
self, "Sync Error", "\n".join(traceback.format_exception(e))
)

View File

@ -0,0 +1,114 @@
from arcaea_offline.models import ScoreInsert
from PySide6.QtCore import QModelIndex, Qt, Slot
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QMessageBox
from ui.extends.shared.delegates.chartDelegate import ChartDelegate
from ui.extends.shared.delegates.scoreDelegate import ScoreDelegate
from ui.extends.shared.models.tables.score import (
DbScoreTableModel,
DbScoreTableSortFilterProxyModel,
)
from ui.implements.components.dbTableViewer import DbTableViewer
class TableChartDelegate(ChartDelegate):
def getChart(self, index):
return index.data(DbScoreTableModel.ChartRole)
class TableScoreDelegate(ScoreDelegate):
def getChart(self, index):
return index.data(DbScoreTableModel.ChartRole)
def getScoreInsert(self, index: QModelIndex) -> ScoreInsert | None:
return super().getScoreInsert(index)
def getScore(self, index):
return index.data(DbScoreTableModel.ScoreRole)
def setModelData(self, editor, model, index):
if super().confirmSetModelData(editor):
model.setData(index, editor.value(), DbScoreTableModel.ScoreRole)
class DbScoreTableViewer(DbTableViewer):
def __init__(self, parent=None):
super().__init__(parent)
self.tableModel = DbScoreTableModel(self)
self.tableProxyModel = DbScoreTableSortFilterProxyModel(self)
self.tableProxyModel.setSourceModel(self.tableModel)
self.tableView.setModel(self.tableProxyModel)
self.tableView.setItemDelegateForColumn(1, TableChartDelegate(self.tableView))
self.tableView.setItemDelegateForColumn(2, TableScoreDelegate(self.tableView))
tableViewPalette = QPalette(self.tableView.palette())
highlightColor = QColor(tableViewPalette.color(QPalette.ColorRole.Highlight))
highlightColor.setAlpha(25)
tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor)
self.tableView.setPalette(tableViewPalette)
self.tableModel.dataChanged.connect(self.resizeTableView)
self.fillSortComboBox()
def fillSortComboBox(self):
self.sort_comboBox.addItem("ID", [0, 1])
self.sort_comboBox.addItem(
"Score", [2, DbScoreTableSortFilterProxyModel.Sort_C2_ScoreRole]
)
self.sort_comboBox.addItem(
"Time", [2, DbScoreTableSortFilterProxyModel.Sort_C2_TimeRole]
)
self.sort_comboBox.addItem("Potential", [3, 1])
self.sort_comboBox.setCurrentIndex(0)
self.on_sort_comboBox_activated()
@Slot()
def resizeTableView(self):
self.tableView.resizeRowsToContents()
self.tableView.resizeColumnsToContents()
@Slot()
def on_sort_comboBox_activated(self):
self.sortProxyModel()
@Slot()
def on_sort_descendingCheckBox_toggled(self):
self.sortProxyModel()
@Slot()
def sortProxyModel(self):
if self.sort_comboBox.currentIndex() > -1:
column, role = self.sort_comboBox.currentData()
self.tableProxyModel.setSortRole(role)
self.tableProxyModel.sort(
column,
Qt.SortOrder.DescendingOrder
if self.sort_descendingCheckBox.isChecked()
else Qt.SortOrder.AscendingOrder,
)
@Slot()
def on_action_removeSelectedButton_clicked(self):
rows = [
srcIndex.row()
for srcIndex in [
self.tableProxyModel.mapToSource(proxyIndex)
for proxyIndex in self.tableView.selectionModel().selectedRows()
]
]
result = QMessageBox.warning(
self,
"Warning",
f"Removing {len(rows)} row(s). Are you sure?",
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
if result == QMessageBox.StandardButton.Yes:
self.tableModel.removeRowList(rows)
@Slot()
def on_refreshButton_clicked(self):
self.tableModel.syncDb()
self.resizeTableView()

View File

@ -0,0 +1,16 @@
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QWidget
from ui.designer.tabs.tabDbEntry_ui import Ui_TabDbEntry
from ui.implements.tabs.tabDb.tabDb_ScoreTableViewer import DbScoreTableViewer
class TabDbEntry(Ui_TabDbEntry, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.tabWidget.addTab(
DbScoreTableViewer(self),
QCoreApplication.translate("TabDbEntry", "tab.scoreTableViewer"),
)

View File

@ -0,0 +1,33 @@
import traceback
from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QModelIndex
from PySide6.QtWidgets import QMessageBox, QWidget
from ui.designer.tabs.tabInputScore_ui import Ui_TabInputScore
class TabInputScore(Ui_TabInputScore, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.chartSelector.valueChanged.connect(self.updateScoreEditorChart)
self.scoreEditor.accepted.connect(self.commit)
def updateScoreEditorChart(self):
chart = self.chartSelector.value()
self.scoreEditor.setChart(chart)
def commit(self):
try:
Database().insert_score(self.scoreEditor.value())
self.scoreEditor.reset()
except Exception as e:
QMessageBox.critical(
self,
# fmt: off
QCoreApplication.translate("General", "tracebackFormatExceptionOnly.title"),
QCoreApplication.translate("General", "tracebackFormatExceptionOnly.content").format(traceback.format_exception_only(e))
# fmt: on
)

View File

@ -0,0 +1,133 @@
import pytesseract
from arcaea_offline_ocr_device_creation_wizard.implements.wizard import Wizard
from PySide6.QtCore import QModelIndex, Qt, Slot
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QFileDialog, QWidget
from ui.designer.tabs.tabOcr_ui import Ui_TabOcr
from ui.extends.settings import Settings
from ui.extends.tabs.tabOcr import (
ImageDelegate,
OcrQueueModel,
OcrQueueTableProxyModel,
TableChartDelegate,
TableScoreDelegate,
)
class TabOcr(Ui_TabOcr, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.deviceFileSelector.filesSelected.connect(self.deviceFileSelected)
self.tesseractFileSelector.filesSelected.connect(
self.tesseractFileSelectorFilesSelected
)
settings = Settings()
self.deviceFileSelector.selectFile(settings.devicesJsonFile())
self.tesseractFileSelector.selectFile(settings.tesseractPath())
self.deviceComboBox.selectDevice(settings.deviceUuid())
self.ocrQueueModel = OcrQueueModel(self)
self.ocrQueueModel.dataChanged.connect(self.resizeViewWhenScoreChanged)
self.ocrQueueModel.started.connect(self.ocrStarted)
self.ocrQueueModel.finished.connect(self.ocrFinished)
self.ocrQueueProxyModel = OcrQueueTableProxyModel(self)
self.ocrQueueProxyModel.setSourceModel(self.ocrQueueModel)
self.tableView.setModel(self.ocrQueueProxyModel)
self.tableView.setItemDelegateForColumn(1, ImageDelegate(self.tableView))
self.tableView.setItemDelegateForColumn(2, TableChartDelegate(self.tableView))
self.tableView.setItemDelegateForColumn(3, TableScoreDelegate(self.tableView))
tableViewPalette = QPalette(self.tableView.palette())
highlightColor = QColor(tableViewPalette.color(QPalette.ColorRole.Highlight))
highlightColor.setAlpha(25)
tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor)
self.tableView.setPalette(tableViewPalette)
@Slot(QModelIndex, QModelIndex, list)
def resizeViewWhenScoreChanged(
self, topleft: QModelIndex, bottomRight: QModelIndex, roles: list[int]
):
if OcrQueueModel.ScoreInsertRole in roles:
rows = [*range(topleft.row(), bottomRight.row() + 1)]
[self.tableView.resizeRowToContents(row) for row in rows]
self.tableView.resizeColumnsToContents()
@Slot()
def on_openWizardButton_clicked(self):
wizard = Wizard(self)
wizard.open()
def deviceFileSelected(self):
selectedFiles = self.deviceFileSelector.selectedFiles()
if selectedFiles:
file = selectedFiles[0]
self.deviceComboBox.loadDevicesJson(file)
def tesseractFileSelectorFilesSelected(self):
selectedFiles = self.tesseractFileSelector.selectedFiles()
if selectedFiles:
pytesseract.pytesseract.tesseract_cmd = selectedFiles[0]
def setOcrButtonsEnabled(self, __bool: bool):
self.ocr_addImageButton.setEnabled(__bool)
self.ocr_removeSelectedButton.setEnabled(__bool)
self.ocr_removeAllButton.setEnabled(__bool)
self.ocr_startButton.setEnabled(__bool)
self.ocr_acceptSelectedButton.setEnabled(__bool)
self.ocr_acceptAllButton.setEnabled(__bool)
self.ocr_ignoreValidateCheckBox.setEnabled(__bool)
@Slot()
def on_ocr_addImageButton_clicked(self):
files, _filter = QFileDialog.getOpenFileNames(
self, None, "", "Image Files (*.png *.jpg *.jpeg *.bmp *.webp);;*"
)
for file in files:
self.ocrQueueModel.addItem(file)
self.tableView.resizeRowsToContents()
self.tableView.resizeColumnsToContents()
@Slot()
def on_ocr_startButton_clicked(self):
self.ocrQueueModel.startQueue(self.deviceComboBox.currentData())
def ocrStarted(self):
self.setOcrButtonsEnabled(False)
def ocrFinished(self):
self.setOcrButtonsEnabled(True)
@Slot()
def on_ocr_removeSelectedButton_clicked(self):
rows = [
modelIndex.row()
for modelIndex in self.tableView.selectionModel().selectedRows(0)
]
self.ocrQueueModel.removeItems(rows)
@Slot()
def on_ocr_removeAllButton_clicked(self):
self.ocrQueueModel.clear()
@Slot()
def on_ocr_acceptSelectedButton_clicked(self):
ignoreValidate = (
self.ocr_ignoreValidateCheckBox.checkState() == Qt.CheckState.Checked
)
rows = [
modelIndex.row()
for modelIndex in self.tableView.selectionModel().selectedRows(0)
]
self.ocrQueueModel.acceptItems(rows, ignoreValidate)
@Slot()
def on_ocr_acceptAllButton_clicked(self):
ignoreValidate = (
self.ocr_ignoreValidateCheckBox.checkState() == Qt.CheckState.Checked
)
self.ocrQueueModel.acceptAllItems(ignoreValidate)

View File

@ -0,0 +1,9 @@
from PySide6.QtWidgets import QWidget
from ui.designer.tabs.tabOcrDisabled_ui import Ui_TabOcrDisabled
class TabOcrDisabled(Ui_TabOcrDisabled, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)

View File

@ -0,0 +1,18 @@
from arcaea_offline.database import Database
from PySide6.QtWidgets import QWidget
from ui.designer.tabs.tabOverview_ui import Ui_TabOverview
class TabOverview(Ui_TabOverview, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.db = Database()
self.db.register_update_hook(self.updateOverview)
self.updateOverview()
def updateOverview(self):
b30 = self.db.get_b30() or 0.00
self.b30Label.setText(str(f"{b30:.3f}"))

View File

@ -0,0 +1,23 @@
from PySide6.QtCore import QModelIndex, Slot
from PySide6.QtWidgets import QListWidgetItem, QWidget
from ui.designer.tabs.tabSettings_ui import Ui_TabSettings
class SettingsEntryItem(QListWidgetItem):
pass
class TabSettings(Ui_TabSettings, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.listWidget.addItem("Default")
self.listWidget.activated.connect(self.switchPage)
self.listWidget.setCurrentRow(self.stackedWidget.currentIndex())
@Slot(QModelIndex)
def switchPage(self, index: QModelIndex):
self.stackedWidget.setCurrentIndex(index.row())

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,7 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/images">
<file>icon.png</file>
<file>logo.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

View File

@ -0,0 +1,460 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>ChartSelector</name>
<message>
<location filename="../../designer/components/chartSelector.ui" line="56"/>
<source>fuzzySearch.lineEdit.placeholder</source>
<translation>Input here...</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="26"/>
<source>songIdSelector.title</source>
<translation>Select Song</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="88"/>
<source>songIdSelector.quickActions</source>
<translation>Quick Actions</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="94"/>
<source>songIdSelector.quickActions.previousPackageButton</source>
<translation>Previous Package</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="101"/>
<source>songIdSelector.quickActions.previousSongIdButton</source>
<translation>Previous Song</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="108"/>
<source>songIdSelector.quickActions.nextSongIdButton</source>
<translation>Next Song</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="115"/>
<source>songIdSelector.quickActions.nextPackageButton</source>
<translation>Next Package</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="134"/>
<source>ratingClassSelector.title</source>
<translation>Rating Select</translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="231"/>
<source>resetButton</source>
<translation>Reset</translation>
</message>
</context>
<context>
<name>DatabaseChecker</name>
<message>
<location filename="../../startup/databaseChecker.ui" line="23"/>
<location filename="../../startup/databaseChecker_ui.py" line="130"/>
<source>dbPathLabel</source>
<translation>Database Path</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="33"/>
<location filename="../../startup/databaseChecker_ui.py" line="133"/>
<source>dbVersionLabel</source>
<translation>Database Version</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="47"/>
<location filename="../../startup/databaseChecker_ui.py" line="136"/>
<source>dbInitLabel</source>
<translation>Initialize</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="54"/>
<location filename="../../startup/databaseChecker_ui.py" line="139"/>
<source>dbCheckConnLabel</source>
<translation>Database Connection</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="61"/>
<location filename="../../startup/databaseChecker_ui.py" line="142"/>
<source>dbInitButton</source>
<translation>Initialize Database</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="91"/>
<location filename="../../startup/databaseChecker_ui.py" line="145"/>
<source>continueButton</source>
<translation>Continue</translation>
</message>
</context>
<context>
<name>DbScoreTableModel</name>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="22"/>
<source>horizontalHeader.id</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="23"/>
<source>horizontalHeader.chart</source>
<translation>Chart</translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="24"/>
<source>horizontalHeader.score</source>
<translation>Score</translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="25"/>
<source>horizontalHeader.potential</source>
<translation>Potential</translation>
</message>
</context>
<context>
<name>DbTableViewer</name>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="20"/>
<source>actions</source>
<translation>Actions</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="26"/>
<source>actions.removeSelected</source>
<translation>Remove Selected</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="46"/>
<source>actions.refresh</source>
<translation>Refresh</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="81"/>
<source>view</source>
<translation>View</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="89"/>
<source>view.sort.label</source>
<translation>Sort By</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="106"/>
<source>view.sort.descendingCheckBox</source>
<translation>Descending</translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="126"/>
<source>view.filter.label</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="146"/>
<source>view.filter.configureButton</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FileSelector</name>
<message>
<location filename="../../designer/components/fileSelector.ui" line="45"/>
<source>selectButton</source>
<translation>Select...</translation>
</message>
</context>
<context>
<name>General</name>
<message>
<location filename="../../implements/tabs/tabInputScore.py" line="30"/>
<source>tracebackFormatExceptionOnly.title</source>
<translation>Error</translation>
</message>
<message>
<location filename="../../implements/tabs/tabInputScore.py" line="31"/>
<source>tracebackFormatExceptionOnly.content</source>
<translation>Unexpected Error&lt;br&gt;{0}</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../designer/mainwindow.ui" line="25"/>
<source>tab.overview</source>
<translation>Overview</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="30"/>
<source>tab.input</source>
<translation>Input</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="35"/>
<source>tab.db</source>
<translation>Database</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="40"/>
<source>tab.ocr</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="45"/>
<source>tab.settings</source>
<translation>Settings</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="50"/>
<source>tab.about</source>
<translation>About</translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="298"/>
<source>horizontalHeader.title.select</source>
<translation>Select</translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="299"/>
<source>horizontalHeader.title.imagePreview</source>
<translation>Image Preview</translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="300"/>
<source>horizontalHeader.title.chart</source>
<translation>Chart</translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="301"/>
<source>horizontalHeader.title.score</source>
<translation>Score</translation>
</message>
</context>
<context>
<name>ScoreEditor</name>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="26"/>
<source>formLabel.score</source>
<translation>Score</translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="100"/>
<source>formLabel.time</source>
<translation>Time</translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="191"/>
<source>commitButton</source>
<translation>Commit</translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="200"/>
<source>formLabel.clearType</source>
<translation>Clear Type</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="77"/>
<source>emptyScoreDialog.title</source>
<translation>Empty Score</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="78"/>
<source>emptyScoreDialog.content</source>
<translation>Are you sure to commit an empty score?</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="57"/>
<location filename="../../implements/components/scoreEditor.py" line="58"/>
<source>chartInvalidDialog.title</source>
<translation>Chart Invalid</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="66"/>
<source>scoreMismatchDialog.title</source>
<translation>Possible Invalid Score</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="67"/>
<source>scoreMismatchDialog.content</source>
<translation>The entered score may not match the selected chart. Commit this score anyway?</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="149"/>
<source>validate.ok</source>
<translation>OK</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="151"/>
<source>validate.chartInvalid</source>
<translation>Chart invalid</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="153"/>
<source>validate.scoreMismatch</source>
<translation>Possible invalid score</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="155"/>
<source>validate.scoreEmpty</source>
<translation>Empty score</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="158"/>
<source>validate.unknownState</source>
<translation>Unknown</translation>
</message>
</context>
<context>
<name>SettingsDefault</name>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="35"/>
<source>devicesJsonFile</source>
<translation>Default devices.json</translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="75"/>
<source>deviceUuid</source>
<translation>Default Device</translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="115"/>
<source>tesseractFile</source>
<translation>tesseract Path</translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="100"/>
<source>defaultDevice.resetButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="60"/>
<source>devicesJsonPath.resetButton</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TabAbout</name>
<message>
<location filename="../../designer/tabs/tabAbout.ui" line="79"/>
<source>About Qt</source>
<translation>About Qt</translation>
</message>
</context>
<context>
<name>TabDbEntry</name>
<message>
<location filename="../../designer/tabs/tabDbEntry.ui" line="24"/>
<source>tab.manage</source>
<translation>Manage</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDbEntry.py" line="15"/>
<source>tab.scoreTableViewer</source>
<translation>TABLE [Score]</translation>
</message>
</context>
<context>
<name>TabDb_Manage</name>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>syncArcSongDbButton</source>
<translation>Sync arcsong.db</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>syncArcSongDb.description</source>
<translation>Write chart info to database</translation>
</message>
</context>
<context>
<name>TabInputScore</name>
<message>
<location filename="../../designer/tabs/tabInputScore.ui" line="26"/>
<source>tab.selectChart</source>
<translation>Chart Selector</translation>
</message>
<message>
<location filename="../../designer/tabs/tabInputScore.ui" line="50"/>
<source>tab.scoreEdit</source>
<translation>Score Edit</translation>
</message>
</context>
<context>
<name>TabOcr</name>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="20"/>
<source>openWizardButton</source>
<translation>Open Device Creation Wizard</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="27"/>
<source>deviceSelector.title</source>
<translation>Select Device</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="42"/>
<source>tesseractSelector.title</source>
<translation>Select tesseract Path</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="54"/>
<source>ocr.title</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="60"/>
<source>ocr.queue.title</source>
<translation>Queue</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="66"/>
<source>ocr.queue.addImageButton</source>
<translation>Add Image</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="76"/>
<source>ocr.queue.removeSelected</source>
<translation>Remove Selected</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="86"/>
<source>ocr.queue.removeAll</source>
<translation>Remove All</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="106"/>
<source>ocr.queue.startOcrButton</source>
<translation>Start OCR</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="135"/>
<source>ocr.results</source>
<translation>Results</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="144"/>
<source>ocr.results.acceptSelectedButton</source>
<translation>Accept Selected</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="151"/>
<source>ocr.results.acceptAllButton</source>
<translation>Accept All</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="171"/>
<source>ocr.results.ignoreValidate</source>
<translation>Ignore
validation</translation>
</message>
</context>
<context>
<name>TabOcrDisabled</name>
<message>
<location filename="../../designer/tabs/tabOcrDisabled.ui" line="81"/>
<source>ocrDisabled.title</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,54 @@
import argparse
import os
import sys
from pathlib import Path
ap = argparse.ArgumentParser()
ap.add_argument(
"-no-obsolete",
action="store_true",
default=False,
required=False,
dest="no_obsolete",
)
args = ap.parse_args(sys.argv[1:])
script_file_path = Path(__file__)
root_dir_path = Path(script_file_path.parent.parent.parent)
output_dir_path = Path(script_file_path.parent)
designer = root_dir_path / "designer"
extends = root_dir_path / "extends"
implements = root_dir_path / "implements"
startup = root_dir_path / "startup"
assert designer.exists()
assert extends.exists()
assert implements.exists()
assert startup.exists()
no_obsolete = args.no_obsolete
commands = [
(
"pyside6-lupdate"
" -extensions py,ui"
f" {designer.absolute()} {extends.absolute()} {implements.absolute()} {startup.absolute()}"
f" -ts {str((output_dir_path / 'zh_CN.ts').absolute())}"
), # zh_CN
(
"pyside6-lupdate"
" -extensions py,ui"
f" {designer.absolute()} {extends.absolute()} {implements.absolute()} {startup.absolute()}"
f" -ts {str((output_dir_path / 'en_US.ts').absolute())}"
), # en_US
]
if no_obsolete:
commands = [f"{command} -no-obsolete" for command in commands]
for command in commands:
print(f"Executing '{command}'")
output = os.popen(command).read()
print(output)

View File

@ -0,0 +1,7 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/lang">
<file>zh_CN.qm</file>
<file>en_US.qm</file>
</qresource>
</RCC>

View File

@ -0,0 +1,459 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>ChartSelector</name>
<message>
<location filename="../../designer/components/chartSelector.ui" line="56"/>
<source>fuzzySearch.lineEdit.placeholder</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="26"/>
<source>songIdSelector.title</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="88"/>
<source>songIdSelector.quickActions</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="94"/>
<source>songIdSelector.quickActions.previousPackageButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="101"/>
<source>songIdSelector.quickActions.previousSongIdButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="108"/>
<source>songIdSelector.quickActions.nextSongIdButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="115"/>
<source>songIdSelector.quickActions.nextPackageButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="134"/>
<source>ratingClassSelector.title</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/chartSelector.ui" line="231"/>
<source>resetButton</source>
<translation></translation>
</message>
</context>
<context>
<name>DatabaseChecker</name>
<message>
<location filename="../../startup/databaseChecker.ui" line="23"/>
<location filename="../../startup/databaseChecker_ui.py" line="130"/>
<source>dbPathLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="33"/>
<location filename="../../startup/databaseChecker_ui.py" line="133"/>
<source>dbVersionLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="47"/>
<location filename="../../startup/databaseChecker_ui.py" line="136"/>
<source>dbInitLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="54"/>
<location filename="../../startup/databaseChecker_ui.py" line="139"/>
<source>dbCheckConnLabel</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="61"/>
<location filename="../../startup/databaseChecker_ui.py" line="142"/>
<source>dbInitButton</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.ui" line="91"/>
<location filename="../../startup/databaseChecker_ui.py" line="145"/>
<source>continueButton</source>
<translation></translation>
</message>
</context>
<context>
<name>DbScoreTableModel</name>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="22"/>
<source>horizontalHeader.id</source>
<translation>ID</translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="23"/>
<source>horizontalHeader.chart</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="24"/>
<source>horizontalHeader.score</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/shared/models/tables/score.py" line="25"/>
<source>horizontalHeader.potential</source>
<translation> PTT</translation>
</message>
</context>
<context>
<name>DbTableViewer</name>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="20"/>
<source>actions</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="26"/>
<source>actions.removeSelected</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="46"/>
<source>actions.refresh</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="81"/>
<source>view</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="89"/>
<source>view.sort.label</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="106"/>
<source>view.sort.descendingCheckBox</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="126"/>
<source>view.filter.label</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/components/dbTableViewer.ui" line="146"/>
<source>view.filter.configureButton</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FileSelector</name>
<message>
<location filename="../../designer/components/fileSelector.ui" line="45"/>
<source>selectButton</source>
<translation></translation>
</message>
</context>
<context>
<name>General</name>
<message>
<location filename="../../implements/tabs/tabInputScore.py" line="30"/>
<source>tracebackFormatExceptionOnly.title</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/tabs/tabInputScore.py" line="31"/>
<source>tracebackFormatExceptionOnly.content</source>
<translation>{0}</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../designer/mainwindow.ui" line="25"/>
<source>tab.overview</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="30"/>
<source>tab.input</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="35"/>
<source>tab.db</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="40"/>
<source>tab.ocr</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="45"/>
<source>tab.settings</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/mainwindow.ui" line="50"/>
<source>tab.about</source>
<translation></translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="298"/>
<source>horizontalHeader.title.select</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="299"/>
<source>horizontalHeader.title.imagePreview</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="300"/>
<source>horizontalHeader.title.chart</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/tabs/tabOcr.py" line="301"/>
<source>horizontalHeader.title.score</source>
<translation></translation>
</message>
</context>
<context>
<name>ScoreEditor</name>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="26"/>
<source>formLabel.score</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="100"/>
<source>formLabel.time</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="191"/>
<source>commitButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/scoreEditor.ui" line="200"/>
<source>formLabel.clearType</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="77"/>
<source>emptyScoreDialog.title</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="78"/>
<source>emptyScoreDialog.content</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="57"/>
<location filename="../../implements/components/scoreEditor.py" line="58"/>
<source>chartInvalidDialog.title</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="66"/>
<source>scoreMismatchDialog.title</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="67"/>
<source>scoreMismatchDialog.content</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="149"/>
<source>validate.ok</source>
<translation>OK</translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="151"/>
<source>validate.chartInvalid</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="153"/>
<source>validate.scoreMismatch</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="155"/>
<source>validate.scoreEmpty</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/components/scoreEditor.py" line="158"/>
<source>validate.unknownState</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingsDefault</name>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="35"/>
<source>devicesJsonFile</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="75"/>
<source>deviceUuid</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="115"/>
<source>tesseractFile</source>
<translation>tesseract </translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="100"/>
<source>defaultDevice.resetButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/settings/settingsDefault.ui" line="60"/>
<source>devicesJsonPath.resetButton</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TabAbout</name>
<message>
<location filename="../../designer/tabs/tabAbout.ui" line="79"/>
<source>About Qt</source>
<translation> Qt</translation>
</message>
</context>
<context>
<name>TabDbEntry</name>
<message>
<location filename="../../designer/tabs/tabDbEntry.ui" line="24"/>
<source>tab.manage</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/tabs/tabDbEntry.py" line="15"/>
<source>tab.scoreTableViewer</source>
<translation> []</translation>
</message>
</context>
<context>
<name>TabDb_Manage</name>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>syncArcSongDbButton</source>
<translation> arcsong.db</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>syncArcSongDb.description</source>
<translation></translation>
</message>
</context>
<context>
<name>TabInputScore</name>
<message>
<location filename="../../designer/tabs/tabInputScore.ui" line="26"/>
<source>tab.selectChart</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabInputScore.ui" line="50"/>
<source>tab.scoreEdit</source>
<translation></translation>
</message>
</context>
<context>
<name>TabOcr</name>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="20"/>
<source>openWizardButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="27"/>
<source>deviceSelector.title</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="42"/>
<source>tesseractSelector.title</source>
<translation> tesseract </translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="54"/>
<source>ocr.title</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="60"/>
<source>ocr.queue.title</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="66"/>
<source>ocr.queue.addImageButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="76"/>
<source>ocr.queue.removeSelected</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="86"/>
<source>ocr.queue.removeAll</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="106"/>
<source>ocr.queue.startOcrButton</source>
<translation> OCR</translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="135"/>
<source>ocr.results</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="144"/>
<source>ocr.results.acceptSelectedButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="151"/>
<source>ocr.results.acceptAllButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabOcr.ui" line="171"/>
<source>ocr.results.ignoreValidate</source>
<translation></translation>
</message>
</context>
<context>
<name>TabOcrDisabled</name>
<message>
<location filename="../../designer/tabs/tabOcrDisabled.ui" line="81"/>
<source>ocrDisabled.title</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

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

View File

@ -0,0 +1,83 @@
import traceback
from arcaea_offline.database import Database
from PySide6.QtCore import QDir, QFile, Qt, QTimer, Slot
from PySide6.QtWidgets import QDialog, QMessageBox
from ui.extends.settings import Settings
from .databaseChecker_ui import Ui_DatabaseChecker
class DatabaseChecker(Ui_DatabaseChecker, QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setWindowFlag(Qt.WindowType.WindowMinimizeButtonHint, False)
self.setWindowFlag(Qt.WindowType.WindowMaximizeButtonHint, False)
self.setWindowFlag(Qt.WindowType.WindowCloseButtonHint, True)
self.dbFileSelector.setMode(self.dbFileSelector.getExistingDirectory)
self.dbFileSelector.filesSelected.connect(self.fileSelected)
self.settings = Settings(self)
dbDir = self.settings.value("Default/DbDir", None, str)
if dbDir and QFile(QDir(dbDir).filePath(Database.dbFilename)).exists():
self.dbFileSelector.selectFile(dbDir)
result = self.checkDbVersion()
if result:
QTimer.singleShot(50, self.accept)
else:
self.dbFileSelector.selectFile(QDir.currentPath())
def fileSelected(self):
self.checkDbVersion()
def checkDbVersion(self) -> str | None:
dbQDir = QDir(self.dbFileSelector.selectedFiles()[0])
dbDir = dbQDir.absolutePath()
dbDir = self.dbFileSelector.selectedFiles()[0]
dbQFile = QFile(QDir(dbDir).filePath(Database.dbFilename))
if not dbQFile.exists():
result = QMessageBox.question(self, "Database", "Create database file now?")
if result != QMessageBox.StandardButton.Yes:
return
dbQFile.open(QFile.OpenModeFlag.WriteOnly)
dbQFile.close()
Database.dbDir = dbDir
try:
with Database().conn as conn:
version = conn.execute(
"SELECT value FROM properties WHERE key = 'db_version'"
).fetchone()[0]
self.dbVersionLabel.setText(version)
self.continueButton.setEnabled(True)
self.dbCheckConnLabel.setText('<font color="green">OK</font>')
self.settings.setValue("Default/DbDir", dbDir)
return version
except Exception as e:
QMessageBox.critical(
self, "Database Error", "\n".join(traceback.format_exception(e))
)
self.dbInitButton.setEnabled(True)
self.continueButton.setEnabled(False)
self.dbCheckConnLabel.setText('<font color="red">Error</font>')
return False
@Slot()
def on_dbInitButton_clicked(self):
try:
Database().init()
except Exception as e:
QMessageBox.critical(
self, "Database Error", "\n".join(traceback.format_exception(e))
)
finally:
self.checkDbVersion()
@Slot()
def on_continueButton_clicked(self):
self.accept()

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseChecker</class>
<widget class="QWidget" name="DatabaseChecker">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>250</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">DatabaseChecker</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>dbPathLabel</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="FileSelector" name="dbFileSelector" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>dbVersionLabel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="dbVersionLabel">
<property name="text">
<string notr="true">-</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>dbInitLabel</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>dbCheckConnLabel</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="dbInitButton">
<property name="text">
<string>dbInitButton</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="dbCheckConnLabel">
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="continueButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>continueButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components.fileSelector</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'databaseChecker.ui'
##
## Created by: Qt User Interface Compiler version 6.5.0
##
## 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,
Qt,
QTime,
QUrl,
)
from PySide6.QtGui import (
QBrush,
QColor,
QConicalGradient,
QCursor,
QFont,
QFontDatabase,
QGradient,
QIcon,
QImage,
QKeySequence,
QLinearGradient,
QPainter,
QPalette,
QPixmap,
QRadialGradient,
QTransform,
)
from PySide6.QtWidgets import (
QApplication,
QFormLayout,
QLabel,
QPushButton,
QSizePolicy,
QSpacerItem,
QWidget,
)
from ui.implements.components.fileSelector import FileSelector
class Ui_DatabaseChecker(object):
def setupUi(self, DatabaseChecker):
if not DatabaseChecker.objectName():
DatabaseChecker.setObjectName("DatabaseChecker")
DatabaseChecker.resize(350, 250)
DatabaseChecker.setWindowTitle("DatabaseChecker")
self.formLayout = QFormLayout(DatabaseChecker)
self.formLayout.setObjectName("formLayout")
self.formLayout.setLabelAlignment(
Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter
)
self.label = QLabel(DatabaseChecker)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
self.dbFileSelector = FileSelector(DatabaseChecker)
self.dbFileSelector.setObjectName("dbFileSelector")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.dbFileSelector)
self.label_2 = QLabel(DatabaseChecker)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)
self.dbVersionLabel = QLabel(DatabaseChecker)
self.dbVersionLabel.setObjectName("dbVersionLabel")
self.dbVersionLabel.setText("-")
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.dbVersionLabel)
self.label_4 = QLabel(DatabaseChecker)
self.label_4.setObjectName("label_4")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_4)
self.label_5 = QLabel(DatabaseChecker)
self.label_5.setObjectName("label_5")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_5)
self.dbInitButton = QPushButton(DatabaseChecker)
self.dbInitButton.setObjectName("dbInitButton")
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.dbInitButton)
self.dbCheckConnLabel = QLabel(DatabaseChecker)
self.dbCheckConnLabel.setObjectName("dbCheckConnLabel")
self.dbCheckConnLabel.setText("...")
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.dbCheckConnLabel)
self.verticalSpacer = QSpacerItem(
20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
)
self.formLayout.setItem(3, QFormLayout.FieldRole, self.verticalSpacer)
self.continueButton = QPushButton(DatabaseChecker)
self.continueButton.setObjectName("continueButton")
self.continueButton.setEnabled(False)
self.formLayout.setWidget(5, QFormLayout.SpanningRole, self.continueButton)
self.retranslateUi(DatabaseChecker)
QMetaObject.connectSlotsByName(DatabaseChecker)
# setupUi
def retranslateUi(self, DatabaseChecker):
self.label.setText(
QCoreApplication.translate("DatabaseChecker", "dbPathLabel", None)
)
self.label_2.setText(
QCoreApplication.translate("DatabaseChecker", "dbVersionLabel", None)
)
self.label_4.setText(
QCoreApplication.translate("DatabaseChecker", "dbInitLabel", None)
)
self.label_5.setText(
QCoreApplication.translate("DatabaseChecker", "dbCheckConnLabel", None)
)
self.dbInitButton.setText(
QCoreApplication.translate("DatabaseChecker", "dbInitButton", None)
)
self.continueButton.setText(
QCoreApplication.translate("DatabaseChecker", "continueButton", None)
)
pass
# retranslateUi