mirror of
https://github.com/283375/arcaea-offline-pyside-ui.git
synced 2025-07-01 04:16:26 +00:00
Compare commits
25 Commits
381f27db87
...
ui-refacto
Author | SHA1 | Date | |
---|---|---|---|
ac0d4fec8a
|
|||
7821816d09
|
|||
a930cffe39
|
|||
4442e496aa
|
|||
1ec302d98c
|
|||
3d6e5f997e | |||
495f6dc424
|
|||
0b599e3d9c
|
|||
a51a67fae3
|
|||
b48e177ae8
|
|||
865fc8b7c8
|
|||
1eeec6f745
|
|||
8558f5e403
|
|||
1a37310091
|
|||
d460e935b4
|
|||
38d2e4ad5a
|
|||
28599cfb04
|
|||
1d01356327
|
|||
738975a83d
|
|||
5adea908f9
|
|||
21ca1018db
|
|||
51e15c68e0
|
|||
00f680edd3
|
|||
7dee8114bf
|
|||
90e66a43fe
|
14
.pre-commit-config.yaml
Normal file
14
.pre-commit-config.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
2
index.py
2
index.py
@ -68,7 +68,7 @@ if __name__ == "__main__":
|
||||
|
||||
databaseChecker = DatabaseChecker()
|
||||
databaseChecker.setWindowIcon(QIcon(":/images/icon.png"))
|
||||
databaseCheckResult = databaseChecker.confirmDb()
|
||||
databaseCheckResult = databaseChecker.confirmDb() if Settings().databaseUrl() else 0
|
||||
|
||||
if not databaseCheckResult & DatabaseCheckerResult.Initted:
|
||||
result = databaseChecker.exec()
|
||||
|
113
prebuild.py
113
prebuild.py
@ -1,70 +1,87 @@
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from importlib import metadata
|
||||
from pathlib import Path
|
||||
|
||||
# fill VERSION file
|
||||
versionFile = Path("ui/resources/VERSION")
|
||||
assert versionFile.exists()
|
||||
|
||||
versionTexts = []
|
||||
def getGitDesc():
|
||||
gitDescribe = subprocess.run(
|
||||
["git", "describe", "--tags", "--long"],
|
||||
capture_output=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
if gitDescribe.returncode == 0:
|
||||
return gitDescribe.stdout.replace("\n", "")
|
||||
|
||||
projectVersionText = "arcaea-offline-pyside-ui\n"
|
||||
gitDescribe = os.popen("git describe --tags --long")
|
||||
gitDescribeContent = gitDescribe.read().replace("\n", "")
|
||||
if gitDescribe.close() is None:
|
||||
projectVersionText += f"{gitDescribeContent}"
|
||||
else:
|
||||
gitRevParse = os.popen("git rev-parse --short HEAD")
|
||||
gitRevParseContent = gitRevParse.read().replace("\n", "")
|
||||
projectVersionText += f"commit {gitRevParseContent}"
|
||||
gitRevParse.close()
|
||||
projectVersionText += "\n"
|
||||
# describe failed, try rev-parse
|
||||
gitRevParse = subprocess.run(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
capture_output=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
if gitRevParse.returncode == 0:
|
||||
return f"commit {gitRevParse.stdout}".replace("\n", "")
|
||||
|
||||
versionTexts.append(projectVersionText)
|
||||
return "version/commit unknown"
|
||||
|
||||
|
||||
# detect pip
|
||||
pipName = None
|
||||
possiblePipNames = ["pip3", "pip"]
|
||||
for possiblePipName in possiblePipNames:
|
||||
result = os.popen(possiblePipName).read()
|
||||
if (
|
||||
"<command> [options]" in result
|
||||
and "install" in result
|
||||
and "--upgrade" in result
|
||||
):
|
||||
pipName = possiblePipName
|
||||
break
|
||||
def getBuildToolsVer():
|
||||
texts = []
|
||||
possibleBuildTools = ["Nuitka", "pyinstaller"]
|
||||
for possibleBuildTool in possibleBuildTools:
|
||||
try:
|
||||
version = metadata.version(possibleBuildTool)
|
||||
texts.append(f"{possibleBuildTool}=={version}")
|
||||
except metadata.PackageNotFoundError:
|
||||
texts.append(f"{possibleBuildTool} not installed")
|
||||
return ", ".join(texts)
|
||||
|
||||
|
||||
# if possiblePipName:
|
||||
# pipFreezeLines = os.popen(f"{possiblePipName} freeze").read().split("\n")
|
||||
# text = [
|
||||
# pipFreezeResult
|
||||
# for pipFreezeResult in pipFreezeLines
|
||||
# if (
|
||||
# "arcaea-offline" in pipFreezeResult
|
||||
# or "PySide6" in pipFreezeResult
|
||||
# or "exif" in pipFreezeResult
|
||||
# or "opencv-python" in pipFreezeResult
|
||||
# or "SQLAlchemy" in pipFreezeResult
|
||||
# )
|
||||
# ]
|
||||
# versionTexts.append("\n".join(text))
|
||||
def writeVersionFile():
|
||||
versionFile = Path("ui/resources/VERSION")
|
||||
assert versionFile.exists()
|
||||
|
||||
importLibTexts = [
|
||||
versionText = (
|
||||
"arcaea-offline-pyside-ui\n{gitDesc}\n{buildToolsVer}\n\n"
|
||||
"{pythonVer}\n\n"
|
||||
"{depsVer}\n"
|
||||
)
|
||||
|
||||
gitDesc = getGitDesc()
|
||||
buildToolsVer = getBuildToolsVer()
|
||||
|
||||
pythonVer = f"{platform.python_implementation()} {platform.python_version()} ({platform.python_build()[0]})"
|
||||
|
||||
importLibTexts = [
|
||||
f"{module}=={metadata.version(module)}"
|
||||
for module in [
|
||||
for module in sorted(
|
||||
[
|
||||
"arcaea-offline",
|
||||
"arcaea-offline-ocr",
|
||||
"exif",
|
||||
"numpy",
|
||||
"opencv-python",
|
||||
"Pillow",
|
||||
"PySide6",
|
||||
"SQLAlchemy",
|
||||
"SQLAlchemy-Utils",
|
||||
"Whoosh",
|
||||
],
|
||||
key=lambda s: s.lower(),
|
||||
)
|
||||
]
|
||||
]
|
||||
versionTexts.append("\n".join(importLibTexts))
|
||||
importLibText = "\n".join(importLibTexts)
|
||||
|
||||
with versionFile.open("w", encoding="utf-8") as vf:
|
||||
vf.write("\n".join(versionTexts))
|
||||
with versionFile.open("w", encoding="utf-8") as vf:
|
||||
vf.write(
|
||||
versionText.format(
|
||||
gitDesc=gitDesc,
|
||||
buildToolsVer=buildToolsVer,
|
||||
pythonVer=pythonVer,
|
||||
depsVer=importLibText,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
writeVersionFile()
|
||||
|
@ -28,6 +28,7 @@ classifiers = [
|
||||
force-exclude = '''
|
||||
(
|
||||
ui/designer
|
||||
| .*_ui.py
|
||||
| .*_rc.py
|
||||
)
|
||||
'''
|
||||
@ -35,7 +36,7 @@ force-exclude = '''
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
extend_skip = ["ui/designer"]
|
||||
extend_skip_glob = ["*_rc.py"]
|
||||
extend_skip_glob = ["*_ui.py", "*_rc.py"]
|
||||
|
||||
[tool.pyright]
|
||||
ignore = ["**/__debug*.*"]
|
||||
|
@ -1,2 +1,5 @@
|
||||
black == 23.7.0
|
||||
isort == 5.12.0
|
||||
imageio==2.31.4
|
||||
Nuitka==1.8.4
|
||||
pytest==7.4.3
|
||||
|
@ -2,3 +2,4 @@ arcaea-offline==0.1.0
|
||||
arcaea-offline-ocr==0.1.0
|
||||
exif==1.6.0
|
||||
PySide6==6.5.2
|
||||
typing-extensions==4.8.0
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/ui/__init__.py
Normal file
0
tests/ui/__init__.py
Normal file
0
tests/ui/navigation/__init__.py
Normal file
0
tests/ui/navigation/__init__.py
Normal file
31
tests/ui/navigation/test_navhost.py
Normal file
31
tests/ui/navigation/test_navhost.py
Normal file
@ -0,0 +1,31 @@
|
||||
from ui.navigation.navhost import NavHost
|
||||
from ui.navigation.navitem import NavItem
|
||||
|
||||
|
||||
class TestNavHost:
|
||||
def test_auto_append_parent(self):
|
||||
navHost = NavHost()
|
||||
|
||||
navHost.registerNavItem(NavItem(id="aaa.bbb.ccc.ddd"))
|
||||
|
||||
navItems = navHost.navItems
|
||||
|
||||
assert NavItem(id="aaa.bbb.ccc.ddd") in navItems
|
||||
assert NavItem(id="aaa.bbb.ccc") in navItems
|
||||
assert NavItem(id="aaa.bbb") in navItems
|
||||
assert NavItem(id="aaa") in navItems
|
||||
|
||||
def test_auto_select_child(self):
|
||||
navHost = NavHost()
|
||||
|
||||
navHost.registerNavItem(NavItem(id="aaa"))
|
||||
navHost.registerNavItem(NavItem(id="bbb"))
|
||||
|
||||
assert navHost.currentNavItem.id == "aaa"
|
||||
|
||||
navHost.registerNavItem(NavItem(id="aaa.bbb"))
|
||||
navHost.registerNavItem(NavItem(id="aaa.ccc"))
|
||||
|
||||
navHost.navigate("aaa")
|
||||
|
||||
assert navHost.currentNavItem.id == "aaa.bbb"
|
0
ui/components/__init__.py
Normal file
0
ui/components/__init__.py
Normal file
0
ui/components/mainwindow/__init__.py
Normal file
0
ui/components/mainwindow/__init__.py
Normal file
47
ui/components/mainwindow/animatedstackednavitemwidgets.py
Normal file
47
ui/components/mainwindow/animatedstackednavitemwidgets.py
Normal file
@ -0,0 +1,47 @@
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from ui.navigation.navhost import NavHost, navHost
|
||||
from ui.navigation.navitem import NavItem
|
||||
from ui.navigation.navitemwidgets import NavItemWidgets
|
||||
from ui.widgets.slidingstackedwidget import SlidingStackedWidget
|
||||
|
||||
|
||||
class AnimatedStackedNavItemsWidgets(SlidingStackedWidget):
|
||||
def __init__(
|
||||
self, navItemWidgets: NavItemWidgets, navHost: NavHost = navHost, parent=None
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.navItemWidgets = navItemWidgets
|
||||
self.navHost = navHost
|
||||
|
||||
self.navHost.activated.connect(self.__switchTo)
|
||||
self.animationFinished.connect(self.endChangingWidget)
|
||||
|
||||
def __switchTo(self, oldNavItem: NavItem, newNavItem: NavItem):
|
||||
oldNavItemDepth = self.navHost.getNavItemDepth(oldNavItem.id)
|
||||
newNavItemDepth = self.navHost.getNavItemDepth(newNavItem.id)
|
||||
|
||||
if oldNavItemDepth != newNavItemDepth:
|
||||
slidingDirection = (
|
||||
self.slidingDirection.RightToLeft
|
||||
if newNavItemDepth > oldNavItemDepth
|
||||
else self.slidingDirection.LeftToRight
|
||||
)
|
||||
else:
|
||||
slidingDirection = self.slidingDirection.TopToBottom
|
||||
|
||||
newWidget = self.navItemWidgets.get(newNavItem.id) or QWidget()
|
||||
self.startChangingWidget(newWidget, slidingDirection)
|
||||
|
||||
def startChangingWidget(self, newWidget: QWidget, slidingDirection):
|
||||
newIndex = self.addWidget(newWidget)
|
||||
[self.widget(i).setEnabled(False) for i in range(self.count())]
|
||||
self.slideInIdx(newIndex, slidingDirection)
|
||||
|
||||
def endChangingWidget(self):
|
||||
oldWidget = self.widget(0)
|
||||
self.removeWidget(oldWidget)
|
||||
|
||||
newWidget = self.widget(0)
|
||||
newWidget.setEnabled(True)
|
286
ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui
Normal file
286
ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui
Normal file
@ -0,0 +1,286 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TabDb_RemoveDuplicateScores</class>
|
||||
<widget class="QWidget" name="TabDb_RemoveDuplicateScores">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">TabDb_RemoveDuplicateScores</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>scan.title</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_scoreCheckBox">
|
||||
<property name="text">
|
||||
<string>scan.option.score</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_pureCheckBox">
|
||||
<property name="text">
|
||||
<string notr="true">PURE</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_farCheckBox">
|
||||
<property name="text">
|
||||
<string notr="true">FAR</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_lostCheckBox">
|
||||
<property name="text">
|
||||
<string notr="true">LOST</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_maxRecallCheckBox">
|
||||
<property name="text">
|
||||
<string notr="true">MAX RECALL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_dateCheckBox">
|
||||
<property name="text">
|
||||
<string>scan.option.date</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_modifierCheckBox">
|
||||
<property name="text">
|
||||
<string>scan.option.modifier</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="scan_option_clearTypeCheckBox">
|
||||
<property name="text">
|
||||
<string>scan.option.clearType</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scan_scanButton">
|
||||
<property name="text">
|
||||
<string>scan.scanButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeView">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>quickSelect.title</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>quickSelect.description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="quickSelect_comboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</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="QPushButton" name="quickSelect_selectButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>quickSelect.selectButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="deselectAllButton">
|
||||
<property name="text">
|
||||
<string>deselectAllButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="reverseSelectionButton">
|
||||
<property name="text">
|
||||
<string>reverseSelectionButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<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="collapseAllButton">
|
||||
<property name="text">
|
||||
<string>collapseAllButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="expandAllButton">
|
||||
<property name="text">
|
||||
<string>expandAllButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="resetModelButton">
|
||||
<property name="text">
|
||||
<string>resetModelButton</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="deleteSelectionButton">
|
||||
<property name="font">
|
||||
<font>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton { color: red };</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>deleteSelectionButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>scan_option_scoreCheckBox</tabstop>
|
||||
<tabstop>scan_option_pureCheckBox</tabstop>
|
||||
<tabstop>scan_option_farCheckBox</tabstop>
|
||||
<tabstop>scan_option_lostCheckBox</tabstop>
|
||||
<tabstop>scan_option_maxRecallCheckBox</tabstop>
|
||||
<tabstop>scan_option_dateCheckBox</tabstop>
|
||||
<tabstop>scan_option_modifierCheckBox</tabstop>
|
||||
<tabstop>scan_option_clearTypeCheckBox</tabstop>
|
||||
<tabstop>scan_scanButton</tabstop>
|
||||
<tabstop>treeView</tabstop>
|
||||
<tabstop>quickSelect_comboBox</tabstop>
|
||||
<tabstop>quickSelect_selectButton</tabstop>
|
||||
<tabstop>deselectAllButton</tabstop>
|
||||
<tabstop>reverseSelectionButton</tabstop>
|
||||
<tabstop>collapseAllButton</tabstop>
|
||||
<tabstop>expandAllButton</tabstop>
|
||||
<tabstop>resetModelButton</tabstop>
|
||||
<tabstop>deleteSelectionButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
245
ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py
Normal file
245
ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py
Normal file
@ -0,0 +1,245 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'tabDb_RemoveDuplicateScores.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.5.2
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
|
||||
QGroupBox, QHBoxLayout, QHeaderView, QLabel,
|
||||
QPushButton, QSizePolicy, QSpacerItem, QTreeView,
|
||||
QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_TabDb_RemoveDuplicateScores(object):
|
||||
def setupUi(self, TabDb_RemoveDuplicateScores):
|
||||
if not TabDb_RemoveDuplicateScores.objectName():
|
||||
TabDb_RemoveDuplicateScores.setObjectName(u"TabDb_RemoveDuplicateScores")
|
||||
TabDb_RemoveDuplicateScores.resize(600, 500)
|
||||
TabDb_RemoveDuplicateScores.setWindowTitle(u"TabDb_RemoveDuplicateScores")
|
||||
self.verticalLayout_2 = QVBoxLayout(TabDb_RemoveDuplicateScores)
|
||||
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||
self.groupBox_2 = QGroupBox(TabDb_RemoveDuplicateScores)
|
||||
self.groupBox_2.setObjectName(u"groupBox_2")
|
||||
self.verticalLayout = QVBoxLayout(self.groupBox_2)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
self.verticalLayout_4 = QVBoxLayout()
|
||||
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
||||
self.horizontalLayout_2 = QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.scan_option_scoreCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_scoreCheckBox.setObjectName(u"scan_option_scoreCheckBox")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.scan_option_scoreCheckBox)
|
||||
|
||||
self.scan_option_pureCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_pureCheckBox.setObjectName(u"scan_option_pureCheckBox")
|
||||
self.scan_option_pureCheckBox.setText(u"PURE")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.scan_option_pureCheckBox)
|
||||
|
||||
self.scan_option_farCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_farCheckBox.setObjectName(u"scan_option_farCheckBox")
|
||||
self.scan_option_farCheckBox.setText(u"FAR")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.scan_option_farCheckBox)
|
||||
|
||||
self.scan_option_lostCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_lostCheckBox.setObjectName(u"scan_option_lostCheckBox")
|
||||
self.scan_option_lostCheckBox.setText(u"LOST")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.scan_option_lostCheckBox)
|
||||
|
||||
self.scan_option_maxRecallCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_maxRecallCheckBox.setObjectName(u"scan_option_maxRecallCheckBox")
|
||||
self.scan_option_maxRecallCheckBox.setText(u"MAX RECALL")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.scan_option_maxRecallCheckBox)
|
||||
|
||||
|
||||
self.verticalLayout_4.addLayout(self.horizontalLayout_2)
|
||||
|
||||
|
||||
self.verticalLayout.addLayout(self.verticalLayout_4)
|
||||
|
||||
self.horizontalLayout_3 = QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||
self.scan_option_dateCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_dateCheckBox.setObjectName(u"scan_option_dateCheckBox")
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.scan_option_dateCheckBox)
|
||||
|
||||
self.scan_option_modifierCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_modifierCheckBox.setObjectName(u"scan_option_modifierCheckBox")
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.scan_option_modifierCheckBox)
|
||||
|
||||
self.scan_option_clearTypeCheckBox = QCheckBox(self.groupBox_2)
|
||||
self.scan_option_clearTypeCheckBox.setObjectName(u"scan_option_clearTypeCheckBox")
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.scan_option_clearTypeCheckBox)
|
||||
|
||||
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_3)
|
||||
|
||||
self.scan_scanButton = QPushButton(self.groupBox_2)
|
||||
self.scan_scanButton.setObjectName(u"scan_scanButton")
|
||||
|
||||
self.verticalLayout.addWidget(self.scan_scanButton)
|
||||
|
||||
|
||||
self.verticalLayout_2.addWidget(self.groupBox_2)
|
||||
|
||||
self.horizontalLayout = QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.treeView = QTreeView(TabDb_RemoveDuplicateScores)
|
||||
self.treeView.setObjectName(u"treeView")
|
||||
self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.treeView.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.treeView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
self.treeView.setHeaderHidden(True)
|
||||
|
||||
self.horizontalLayout.addWidget(self.treeView)
|
||||
|
||||
self.verticalLayout_6 = QVBoxLayout()
|
||||
self.verticalLayout_6.setObjectName(u"verticalLayout_6")
|
||||
self.groupBox = QGroupBox(TabDb_RemoveDuplicateScores)
|
||||
self.groupBox.setObjectName(u"groupBox")
|
||||
self.verticalLayout_3 = QVBoxLayout(self.groupBox)
|
||||
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
|
||||
self.label = QLabel(self.groupBox)
|
||||
self.label.setObjectName(u"label")
|
||||
|
||||
self.verticalLayout_3.addWidget(self.label)
|
||||
|
||||
self.quickSelect_comboBox = QComboBox(self.groupBox)
|
||||
self.quickSelect_comboBox.setObjectName(u"quickSelect_comboBox")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.quickSelect_comboBox.sizePolicy().hasHeightForWidth())
|
||||
self.quickSelect_comboBox.setSizePolicy(sizePolicy)
|
||||
|
||||
self.verticalLayout_3.addWidget(self.quickSelect_comboBox)
|
||||
|
||||
self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
|
||||
self.verticalLayout_3.addItem(self.verticalSpacer_2)
|
||||
|
||||
self.quickSelect_selectButton = QPushButton(self.groupBox)
|
||||
self.quickSelect_selectButton.setObjectName(u"quickSelect_selectButton")
|
||||
sizePolicy.setHeightForWidth(self.quickSelect_selectButton.sizePolicy().hasHeightForWidth())
|
||||
self.quickSelect_selectButton.setSizePolicy(sizePolicy)
|
||||
|
||||
self.verticalLayout_3.addWidget(self.quickSelect_selectButton)
|
||||
|
||||
|
||||
self.verticalLayout_6.addWidget(self.groupBox)
|
||||
|
||||
self.groupBox_3 = QGroupBox(TabDb_RemoveDuplicateScores)
|
||||
self.groupBox_3.setObjectName(u"groupBox_3")
|
||||
self.verticalLayout_5 = QVBoxLayout(self.groupBox_3)
|
||||
self.verticalLayout_5.setObjectName(u"verticalLayout_5")
|
||||
self.deselectAllButton = QPushButton(self.groupBox_3)
|
||||
self.deselectAllButton.setObjectName(u"deselectAllButton")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.deselectAllButton)
|
||||
|
||||
self.reverseSelectionButton = QPushButton(self.groupBox_3)
|
||||
self.reverseSelectionButton.setObjectName(u"reverseSelectionButton")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.reverseSelectionButton)
|
||||
|
||||
self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
|
||||
self.verticalLayout_5.addItem(self.verticalSpacer_3)
|
||||
|
||||
self.collapseAllButton = QPushButton(self.groupBox_3)
|
||||
self.collapseAllButton.setObjectName(u"collapseAllButton")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.collapseAllButton)
|
||||
|
||||
self.expandAllButton = QPushButton(self.groupBox_3)
|
||||
self.expandAllButton.setObjectName(u"expandAllButton")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.expandAllButton)
|
||||
|
||||
self.resetModelButton = QPushButton(self.groupBox_3)
|
||||
self.resetModelButton.setObjectName(u"resetModelButton")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.resetModelButton)
|
||||
|
||||
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
|
||||
self.verticalLayout_5.addItem(self.verticalSpacer)
|
||||
|
||||
self.deleteSelectionButton = QPushButton(self.groupBox_3)
|
||||
self.deleteSelectionButton.setObjectName(u"deleteSelectionButton")
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
self.deleteSelectionButton.setFont(font)
|
||||
self.deleteSelectionButton.setStyleSheet(u"QPushButton { color: red };")
|
||||
|
||||
self.verticalLayout_5.addWidget(self.deleteSelectionButton)
|
||||
|
||||
|
||||
self.verticalLayout_6.addWidget(self.groupBox_3)
|
||||
|
||||
|
||||
self.horizontalLayout.addLayout(self.verticalLayout_6)
|
||||
|
||||
|
||||
self.verticalLayout_2.addLayout(self.horizontalLayout)
|
||||
|
||||
QWidget.setTabOrder(self.scan_option_scoreCheckBox, self.scan_option_pureCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_pureCheckBox, self.scan_option_farCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_farCheckBox, self.scan_option_lostCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_lostCheckBox, self.scan_option_maxRecallCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_maxRecallCheckBox, self.scan_option_dateCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_dateCheckBox, self.scan_option_modifierCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_modifierCheckBox, self.scan_option_clearTypeCheckBox)
|
||||
QWidget.setTabOrder(self.scan_option_clearTypeCheckBox, self.scan_scanButton)
|
||||
QWidget.setTabOrder(self.scan_scanButton, self.treeView)
|
||||
QWidget.setTabOrder(self.treeView, self.quickSelect_comboBox)
|
||||
QWidget.setTabOrder(self.quickSelect_comboBox, self.quickSelect_selectButton)
|
||||
QWidget.setTabOrder(self.quickSelect_selectButton, self.deselectAllButton)
|
||||
QWidget.setTabOrder(self.deselectAllButton, self.reverseSelectionButton)
|
||||
QWidget.setTabOrder(self.reverseSelectionButton, self.collapseAllButton)
|
||||
QWidget.setTabOrder(self.collapseAllButton, self.expandAllButton)
|
||||
QWidget.setTabOrder(self.expandAllButton, self.resetModelButton)
|
||||
QWidget.setTabOrder(self.resetModelButton, self.deleteSelectionButton)
|
||||
|
||||
self.retranslateUi(TabDb_RemoveDuplicateScores)
|
||||
|
||||
QMetaObject.connectSlotsByName(TabDb_RemoveDuplicateScores)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, TabDb_RemoveDuplicateScores):
|
||||
self.groupBox_2.setTitle(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.title", None))
|
||||
self.scan_option_scoreCheckBox.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.option.score", None))
|
||||
self.scan_option_dateCheckBox.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.option.date", None))
|
||||
self.scan_option_modifierCheckBox.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.option.modifier", None))
|
||||
self.scan_option_clearTypeCheckBox.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.option.clearType", None))
|
||||
self.scan_scanButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.scanButton", None))
|
||||
self.groupBox.setTitle(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"quickSelect.title", None))
|
||||
self.label.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"quickSelect.description", None))
|
||||
self.quickSelect_selectButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"quickSelect.selectButton", None))
|
||||
self.groupBox_3.setTitle("")
|
||||
self.deselectAllButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"deselectAllButton", None))
|
||||
self.reverseSelectionButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"reverseSelectionButton", None))
|
||||
self.collapseAllButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"collapseAllButton", None))
|
||||
self.expandAllButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"expandAllButton", None))
|
||||
self.resetModelButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"resetModelButton", None))
|
||||
self.deleteSelectionButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"deleteSelectionButton", None))
|
||||
pass
|
||||
# retranslateUi
|
||||
|
@ -29,6 +29,11 @@
|
||||
<string>tab.chartInfoEditor</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="TabDb_RemoveDuplicateScores" name="tab_removeDuplicateScores">
|
||||
<attribute name="title">
|
||||
<string>tab.removeDuplicateScores</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -46,6 +51,12 @@
|
||||
<header>ui.implements.tabs.tabDb.tabDb_ChartInfoEditor</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TabDb_RemoveDuplicateScores</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>ui.implements.tabs.tabDb.tabDb_RemoveDuplicateScores</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -20,6 +20,7 @@ from PySide6.QtWidgets import (QApplication, QSizePolicy, QTabWidget, QVBoxLayou
|
||||
|
||||
from ui.implements.tabs.tabDb.tabDb_ChartInfoEditor import TabDb_ChartInfoEditor
|
||||
from ui.implements.tabs.tabDb.tabDb_Manage import TabDb_Manage
|
||||
from ui.implements.tabs.tabDb.tabDb_RemoveDuplicateScores import TabDb_RemoveDuplicateScores
|
||||
|
||||
class Ui_TabDbEntry(object):
|
||||
def setupUi(self, TabDbEntry):
|
||||
@ -37,6 +38,9 @@ class Ui_TabDbEntry(object):
|
||||
self.tab_chartInfoEditor = TabDb_ChartInfoEditor()
|
||||
self.tab_chartInfoEditor.setObjectName(u"tab_chartInfoEditor")
|
||||
self.tabWidget.addTab(self.tab_chartInfoEditor, "")
|
||||
self.tab_removeDuplicateScores = TabDb_RemoveDuplicateScores()
|
||||
self.tab_removeDuplicateScores.setObjectName(u"tab_removeDuplicateScores")
|
||||
self.tabWidget.addTab(self.tab_removeDuplicateScores, "")
|
||||
|
||||
self.verticalLayout.addWidget(self.tabWidget)
|
||||
|
||||
@ -52,6 +56,7 @@ class Ui_TabDbEntry(object):
|
||||
def retranslateUi(self, TabDbEntry):
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_manage), QCoreApplication.translate("TabDbEntry", u"tab.manage", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_chartInfoEditor), QCoreApplication.translate("TabDbEntry", u"tab.chartInfoEditor", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_removeDuplicateScores), QCoreApplication.translate("TabDbEntry", u"tab.removeDuplicateScores", None))
|
||||
pass
|
||||
# retranslateUi
|
||||
|
||||
|
@ -329,6 +329,36 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>sourceCode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string notr="true"><a href="https://github.com/283375/AndrealImageGenerator">283375/AndrealImageGenerator</a><br>(forked from <a href="https://github.com/Awbugl/AndrealImageGenerator">Awbugl/AndrealImageGenerator</a>)</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -220,6 +220,22 @@ class Ui_TabTools_Andreal(object):
|
||||
|
||||
self.formLayout.setLayout(7, QFormLayout.SpanningRole, self.horizontalLayout_5)
|
||||
|
||||
self.label_4 = QLabel(TabTools_Andreal)
|
||||
self.label_4.setObjectName(u"label_4")
|
||||
|
||||
self.formLayout.setWidget(9, QFormLayout.LabelRole, self.label_4)
|
||||
|
||||
self.label_7 = QLabel(TabTools_Andreal)
|
||||
self.label_7.setObjectName(u"label_7")
|
||||
self.label_7.setText(u"<a href=\"https://github.com/283375/AndrealImageGenerator\">283375/AndrealImageGenerator</a><br>(forked from <a href=\"https://github.com/Awbugl/AndrealImageGenerator\">Awbugl/AndrealImageGenerator</a>)")
|
||||
self.label_7.setOpenExternalLinks(True)
|
||||
|
||||
self.formLayout.setWidget(9, QFormLayout.FieldRole, self.label_7)
|
||||
|
||||
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
|
||||
self.formLayout.setItem(8, QFormLayout.SpanningRole, self.verticalSpacer)
|
||||
|
||||
|
||||
self.retranslateUi(TabTools_Andreal)
|
||||
self.imageFormat_jpgRadioButton.toggled.connect(self.jpgQualityHolderWidget.setEnabled)
|
||||
@ -241,6 +257,7 @@ class Ui_TabTools_Andreal(object):
|
||||
self.exportJsonButton.setText(QCoreApplication.translate("TabTools_Andreal", u"exportJsonButton", None))
|
||||
self.generatePreviewButton.setText(QCoreApplication.translate("TabTools_Andreal", u"generatePreviewButton", None))
|
||||
self.generateImageButton.setText(QCoreApplication.translate("TabTools_Andreal", u"generateImageButton", None))
|
||||
self.label_4.setText(QCoreApplication.translate("TabTools_Andreal", u"sourceCode", None))
|
||||
pass
|
||||
# retranslateUi
|
||||
|
||||
|
@ -82,6 +82,9 @@
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
@ -325,7 +328,7 @@
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
|
@ -68,6 +68,7 @@ class Ui_TabTools_ChartRecommend(object):
|
||||
self.chartsByConstant_modelView.setMinimumSize(QSize(150, 0))
|
||||
self.chartsByConstant_modelView.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.chartsByConstant_modelView.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.chartsByConstant_modelView.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.chartsByConstant_modelView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
self.chartsByConstant_modelView.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
|
||||
@ -228,7 +229,7 @@ class Ui_TabTools_ChartRecommend(object):
|
||||
self.chartsRecommendFromPlayRating_modelView.setSizePolicy(sizePolicy2)
|
||||
self.chartsRecommendFromPlayRating_modelView.setMinimumSize(QSize(200, 0))
|
||||
self.chartsRecommendFromPlayRating_modelView.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.chartsRecommendFromPlayRating_modelView.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.chartsRecommendFromPlayRating_modelView.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.chartsRecommendFromPlayRating_modelView.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.chartsRecommendFromPlayRating_modelView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
self.chartsRecommendFromPlayRating_modelView.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
|
@ -7,7 +7,7 @@ import numpy as np
|
||||
from arcaea_offline_ocr.phash_db import phash_opencv
|
||||
|
||||
|
||||
def preprocess_char_icon(img_gray: cv2.Mat):
|
||||
def preprocess_char_icon(img_gray: np.ndarray):
|
||||
h, w = img_gray.shape[:2]
|
||||
img = cv2.fillPoly(
|
||||
img_gray,
|
||||
@ -23,7 +23,7 @@ def preprocess_char_icon(img_gray: cv2.Mat):
|
||||
|
||||
|
||||
def build_image_phash_database(
|
||||
images: list[cv2.Mat],
|
||||
images: list[np.ndarray],
|
||||
labels: list[str],
|
||||
*,
|
||||
hash_size: int = 16,
|
||||
|
@ -1,6 +1,5 @@
|
||||
from arcaea_offline.models import Chart, Score, ScoreBest
|
||||
from PySide6.QtCore import QCoreApplication, QModelIndex, QSortFilterProxyModel, Qt
|
||||
from sqlalchemy import select
|
||||
|
||||
from .base import DbTableModel
|
||||
|
||||
@ -18,50 +17,45 @@ class DbB30TableModel(DbTableModel):
|
||||
def retranslateHeaders(self):
|
||||
self._horizontalHeaders = [
|
||||
# fmt: off
|
||||
QCoreApplication.translate("DB30TableModel", "horizontalHeader.id"),
|
||||
QCoreApplication.translate("DB30TableModel", "horizontalHeader.chart"),
|
||||
QCoreApplication.translate("DB30TableModel", "horizontalHeader.score"),
|
||||
QCoreApplication.translate("DB30TableModel", "horizontalHeader.potential"),
|
||||
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.id"),
|
||||
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.chart"),
|
||||
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.score"),
|
||||
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.potential"),
|
||||
# fmt: on
|
||||
]
|
||||
|
||||
def syncDb(self):
|
||||
self.beginResetModel()
|
||||
self.beginRemoveRows(QModelIndex(), 0, self.rowCount())
|
||||
self.__items.clear()
|
||||
self.endRemoveRows()
|
||||
self.endResetModel()
|
||||
|
||||
with self._db.sessionmaker() as session:
|
||||
results = list(
|
||||
session.scalars(
|
||||
select(ScoreBest).order_by(ScoreBest.potential.desc()).limit(40)
|
||||
results = (
|
||||
session.query(ScoreBest, Chart)
|
||||
.join(
|
||||
Chart,
|
||||
(ScoreBest.song_id == Chart.song_id)
|
||||
& (ScoreBest.rating_class == Chart.rating_class),
|
||||
)
|
||||
.order_by(ScoreBest.potential)
|
||||
.limit(50)
|
||||
.all()
|
||||
)
|
||||
|
||||
songIds = [r.id for r in results]
|
||||
ptts = [r.potential for r in results]
|
||||
|
||||
for scoreId, ptt in zip(songIds, ptts):
|
||||
score = self._db.get_score(scoreId)
|
||||
chart = self._db.get_chart(score.song_id, score.rating_class)
|
||||
|
||||
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||
self.beginInsertRows(QModelIndex(), 0, len(results) - 1)
|
||||
for scoreBest, chart in results:
|
||||
self.__items.append(
|
||||
{
|
||||
self.IdRole: score.id,
|
||||
self.IdRole: scoreBest.id,
|
||||
self.ChartRole: chart,
|
||||
self.ScoreRole: score,
|
||||
self.PttRole: ptt,
|
||||
self.ScoreRole: scoreBest,
|
||||
self.PttRole: scoreBest.potential,
|
||||
}
|
||||
)
|
||||
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)
|
||||
|
||||
@ -117,30 +111,35 @@ class DbB30TableSortFilterProxyModel(QSortFilterProxyModel):
|
||||
return super().headerData(section, orientation, role)
|
||||
return section + 1
|
||||
|
||||
def lessThan(self, source_left, source_right) -> bool:
|
||||
if source_left.column() != source_right.column():
|
||||
def lessThan(self, sourceLeft: QModelIndex, sourceRight: QModelIndex) -> bool:
|
||||
if sourceLeft.column() != sourceRight.column():
|
||||
return
|
||||
|
||||
column = source_left.column()
|
||||
column = sourceLeft.column()
|
||||
if column == 0:
|
||||
return source_left.data(DbB30TableModel.IdRole) < source_right.data(
|
||||
return sourceLeft.data(DbB30TableModel.IdRole) < sourceRight.data(
|
||||
DbB30TableModel.IdRole
|
||||
)
|
||||
elif column == 2:
|
||||
score_left = source_left.data(DbB30TableModel.ScoreRole)
|
||||
score_right = source_right.data(DbB30TableModel.ScoreRole)
|
||||
if isinstance(score_left, Score) and isinstance(score_right, Score):
|
||||
scoreLeft = sourceLeft.data(DbB30TableModel.ScoreRole)
|
||||
scoreRight = sourceRight.data(DbB30TableModel.ScoreRole)
|
||||
if isinstance(scoreLeft, Score) and isinstance(scoreRight, Score):
|
||||
if self.sortRole() == self.Sort_C2_ScoreRole:
|
||||
return score_left.score < score_right.score
|
||||
return scoreLeft.score < scoreRight.score
|
||||
elif self.sortRole() == self.Sort_C2_TimeRole:
|
||||
if score_left.date and score_right.date:
|
||||
return score_left.date < score_right.date
|
||||
elif score_left.date:
|
||||
if scoreLeft.date and scoreRight.date:
|
||||
return scoreLeft.date < scoreRight.date
|
||||
elif scoreLeft.date:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
elif column == 3:
|
||||
return source_left.data(DbB30TableModel.PttRole) < source_right.data(
|
||||
DbB30TableModel.PttRole
|
||||
)
|
||||
return super().lessThan(source_left, source_right)
|
||||
pttLeft = sourceLeft.data(DbB30TableModel.PttRole)
|
||||
pttRight = sourceRight.data(DbB30TableModel.PttRole)
|
||||
if pttLeft and pttRight:
|
||||
return pttLeft < pttRight
|
||||
elif pttLeft:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return super().lessThan(sourceLeft, sourceRight)
|
||||
|
@ -31,6 +31,9 @@ class AndrealExecuteRunnable(QRunnable):
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = subp.stdout
|
||||
if subp.returncode != 0:
|
||||
logger.error("AndrealImageGenerator Error: ")
|
||||
logger.error(result)
|
||||
b64Result = [s for s in result.split("\n") if s]
|
||||
imageBytes = base64.b64decode(
|
||||
re.sub(r"data:image/.*;base64,", "", b64Result[-1])
|
||||
|
@ -39,14 +39,12 @@ class PlayRatingCalculator(QWidget):
|
||||
return None
|
||||
|
||||
score = self.arcaeaScoreLineEdit.score()
|
||||
if score is None:
|
||||
return None
|
||||
|
||||
return calculate_play_rating(self.constant, score)
|
||||
return None if score is None else calculate_play_rating(self.constant, score)
|
||||
|
||||
def updateResultLabel(self):
|
||||
result = self.result
|
||||
self.resultLabel.setText(str(result) if result is not None else "...")
|
||||
self.resultLabel.setText(str(round(result, 3)) if result is not None else "...")
|
||||
self.resultLabel.setToolTip(str(result))
|
||||
|
||||
def on_copyButton_clicked(self):
|
||||
result = self.result
|
||||
@ -68,6 +66,7 @@ class PlayRatingCalculator(QWidget):
|
||||
self.resultLabel.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
|
||||
)
|
||||
self.resultLabel.setMinimumWidth(100)
|
||||
self.horizontalLayout.addWidget(self.resultLabel)
|
||||
|
||||
self.horizontalSpacer = QSpacerItem(
|
||||
|
353
ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py
Normal file
353
ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py
Normal file
@ -0,0 +1,353 @@
|
||||
from enum import IntEnum
|
||||
|
||||
from arcaea_offline.database import Database
|
||||
from arcaea_offline.models import Chart, Difficulty, Score, Song
|
||||
from PySide6.QtCore import QCoreApplication, QModelIndex, Qt, Slot
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import QMessageBox, QStyledItemDelegate, QWidget
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.orm import InstrumentedAttribute, Session
|
||||
|
||||
from ui.designer.tabs.tabDb.tabDb_RemoveDuplicateScores_ui import (
|
||||
Ui_TabDb_RemoveDuplicateScores,
|
||||
)
|
||||
from ui.extends.shared.delegates.chartDelegate import ChartDelegate
|
||||
from ui.extends.shared.delegates.scoreDelegate import ScoreDelegate
|
||||
from ui.extends.shared.language import LanguageChangeEventFilter
|
||||
|
||||
|
||||
class RemoveDuplicateScoresModel(QStandardItemModel):
|
||||
ScoreRole = Qt.ItemDataRole.UserRole
|
||||
ChartRole = Qt.ItemDataRole.UserRole + 10
|
||||
SongRole = Qt.ItemDataRole.UserRole + 11
|
||||
DifficultyRole = Qt.ItemDataRole.UserRole + 12
|
||||
|
||||
def setChartDelegateDatas(
|
||||
self, item: QStandardItem, songId: str, ratingClass: int, session: Session
|
||||
):
|
||||
chart = (
|
||||
session.query(Chart)
|
||||
.where((Chart.song_id == songId) & (Chart.rating_class == ratingClass))
|
||||
.first()
|
||||
)
|
||||
song = session.query(Song).where(Song.id == songId).first()
|
||||
difficulty = (
|
||||
session.query(Difficulty)
|
||||
.where(
|
||||
(Difficulty.song_id == songId)
|
||||
& (Difficulty.rating_class == ratingClass)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if chart is None and song is None and difficulty is None:
|
||||
chart = Chart(song_id=songId, rating_class=ratingClass, set="unknown")
|
||||
|
||||
item.setData(chart, self.ChartRole)
|
||||
item.setData(song, self.SongRole)
|
||||
item.setData(difficulty, self.DifficultyRole)
|
||||
|
||||
def getGroupKey(self, score: Score, columns: list[InstrumentedAttribute]) -> str:
|
||||
baseKeys = [score.song_id, str(score.rating_class)]
|
||||
for column in columns:
|
||||
key = f"{column.key}{getattr(score,column.key)}"
|
||||
baseKeys.append(key)
|
||||
return "||".join(baseKeys)
|
||||
|
||||
def setScores(self, scores: list[Score], columns: list[InstrumentedAttribute]):
|
||||
self.clear()
|
||||
|
||||
scoreKeyMap: dict[str, list[Score]] = {}
|
||||
for score in scores:
|
||||
key = self.getGroupKey(score, columns)
|
||||
if scoreKeyMap.get(key) is None:
|
||||
scoreKeyMap[key] = [score]
|
||||
else:
|
||||
scoreKeyMap[key].append(score)
|
||||
|
||||
db = Database()
|
||||
with db.sessionmaker() as session:
|
||||
for key, scores in scoreKeyMap.items():
|
||||
songId, ratingClass = key.split("||")[:2]
|
||||
ratingClass = int(ratingClass)
|
||||
|
||||
parentCheckBoxItem = QStandardItem(f"{len(scores)} items")
|
||||
parentChartItem = QStandardItem()
|
||||
self.setChartDelegateDatas(
|
||||
parentChartItem, songId, ratingClass, session
|
||||
)
|
||||
|
||||
for i, score in enumerate(scores):
|
||||
scoreCheckBoxItem = QStandardItem()
|
||||
scoreCheckBoxItem.setEditable(False)
|
||||
scoreCheckBoxItem.setCheckable(True)
|
||||
scoreCheckBoxItem.setEnabled(True)
|
||||
scoreItem = QStandardItem()
|
||||
scoreItem.setData(score, self.ScoreRole)
|
||||
scoreItem.setEditable(False)
|
||||
scoreItem.setEnabled(True)
|
||||
parentCheckBoxItem.setChild(i, 0, scoreCheckBoxItem)
|
||||
parentCheckBoxItem.setChild(i, 1, scoreItem)
|
||||
|
||||
self.appendRow([parentCheckBoxItem, parentChartItem])
|
||||
|
||||
|
||||
class TreeViewChartDelegate(ChartDelegate):
|
||||
def getChart(self, index: QModelIndex):
|
||||
return index.data(RemoveDuplicateScoresModel.ChartRole)
|
||||
|
||||
def getSong(self, index: QModelIndex):
|
||||
return index.data(RemoveDuplicateScoresModel.SongRole)
|
||||
|
||||
def getDifficulty(self, index: QModelIndex):
|
||||
return index.data(RemoveDuplicateScoresModel.DifficultyRole)
|
||||
|
||||
|
||||
class TreeViewScoreDelegate(ScoreDelegate):
|
||||
def getScore(self, index: QModelIndex):
|
||||
return index.data(RemoveDuplicateScoresModel.ScoreRole)
|
||||
|
||||
|
||||
class TreeViewProxyDelegate(QStyledItemDelegate):
|
||||
def __init__(
|
||||
self, chartDelegate: ChartDelegate, scoreDelegate: ScoreDelegate, parent=None
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.chartDelegate = chartDelegate
|
||||
self.scoreDelegate = scoreDelegate
|
||||
|
||||
def delegateForIndex(self, index: QModelIndex) -> QStyledItemDelegate:
|
||||
return self.scoreDelegate if index.parent().isValid() else self.chartDelegate
|
||||
|
||||
def sizeHint(self, option, index: QModelIndex):
|
||||
return self.delegateForIndex(index).sizeHint(option, index)
|
||||
|
||||
def paint(self, painter, option, index: QModelIndex):
|
||||
self.delegateForIndex(index).paint(painter, option, index)
|
||||
QStyledItemDelegate.paint(self, painter, option, index)
|
||||
|
||||
|
||||
class QuickSelectComboBoxValues(IntEnum):
|
||||
ID_EARLIER = 0
|
||||
DATE_EARLIER = 1
|
||||
COLUMNS_INTEGRAL = 2
|
||||
|
||||
|
||||
class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
|
||||
self.installEventFilter(self.languageChangeEventFilter)
|
||||
|
||||
self.db = Database()
|
||||
|
||||
self.removeDuplicateScoresModel = RemoveDuplicateScoresModel(self)
|
||||
self.treeView.setModel(self.removeDuplicateScoresModel)
|
||||
|
||||
self.treeViewChartDelegate = TreeViewChartDelegate(self.treeView)
|
||||
self.treeViewScoreDelegate = TreeViewScoreDelegate(self.treeView)
|
||||
self.treeViewProxyDelegate = TreeViewProxyDelegate(
|
||||
self.treeViewChartDelegate, self.treeViewScoreDelegate, self.treeView
|
||||
)
|
||||
self.treeView.setItemDelegateForColumn(1, self.treeViewProxyDelegate)
|
||||
|
||||
self.quickSelect_comboBox.addItem(
|
||||
# fmt: off
|
||||
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.idEarlier"),
|
||||
# fmt: on
|
||||
QuickSelectComboBoxValues.ID_EARLIER
|
||||
)
|
||||
self.quickSelect_comboBox.addItem(
|
||||
# fmt: off
|
||||
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.dateEarlier"),
|
||||
# fmt: on
|
||||
QuickSelectComboBoxValues.DATE_EARLIER
|
||||
)
|
||||
self.quickSelect_comboBox.addItem(
|
||||
# fmt: off
|
||||
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.columnsIntegral"),
|
||||
# fmt: on
|
||||
QuickSelectComboBoxValues.COLUMNS_INTEGRAL
|
||||
)
|
||||
|
||||
def getQueryColumns(self):
|
||||
columns: list[InstrumentedAttribute] = [Score.song_id, Score.rating_class]
|
||||
|
||||
if self.scan_option_scoreCheckBox.isChecked():
|
||||
columns.append(Score.score)
|
||||
if self.scan_option_pureCheckBox.isChecked():
|
||||
columns.append(Score.pure)
|
||||
if self.scan_option_farCheckBox.isChecked():
|
||||
columns.append(Score.far)
|
||||
if self.scan_option_lostCheckBox.isChecked():
|
||||
columns.append(Score.lost)
|
||||
if self.scan_option_maxRecallCheckBox.isChecked():
|
||||
columns.append(Score.max_recall)
|
||||
if self.scan_option_dateCheckBox.isChecked():
|
||||
columns.append(Score.date)
|
||||
if self.scan_option_modifierCheckBox.isChecked():
|
||||
columns.append(Score.modifier)
|
||||
if self.scan_option_clearTypeCheckBox.isChecked():
|
||||
columns.append(Score.clear_type)
|
||||
|
||||
return columns
|
||||
|
||||
def getQueryScores(self):
|
||||
columns = self.getQueryColumns()
|
||||
with self.db.sessionmaker() as session:
|
||||
groupBySubquery = (
|
||||
select(*columns).group_by(*columns).having(func.count() > 1).subquery()
|
||||
)
|
||||
selectInClause = [
|
||||
col == getattr(groupBySubquery.c, col.key) for col in columns
|
||||
]
|
||||
return session.query(Score).where(*selectInClause).all()
|
||||
|
||||
def scan(self):
|
||||
scores = self.getQueryScores()
|
||||
self.removeDuplicateScoresModel.setScores(scores, self.getQueryColumns())
|
||||
self.treeView.expandAll()
|
||||
|
||||
def deselectAll(self):
|
||||
for row in range(self.removeDuplicateScoresModel.rowCount()):
|
||||
parentItem = self.removeDuplicateScoresModel.item(row, 0)
|
||||
for childRow in range(parentItem.rowCount()):
|
||||
childCheckBoxItem = parentItem.child(childRow, 0)
|
||||
childCheckBoxItem.setCheckState(Qt.CheckState.Unchecked)
|
||||
|
||||
def quickSelect(self):
|
||||
mode = self.quickSelect_comboBox.currentData()
|
||||
|
||||
if mode is None:
|
||||
return
|
||||
|
||||
for row in range(self.removeDuplicateScoresModel.rowCount()):
|
||||
parentItem = self.removeDuplicateScoresModel.item(row, 0)
|
||||
|
||||
scores: list[Score] = []
|
||||
|
||||
for childRow in range(parentItem.rowCount()):
|
||||
childScoreItem = parentItem.child(childRow, 1)
|
||||
scores.append(childScoreItem.data(RemoveDuplicateScoresModel.ScoreRole))
|
||||
|
||||
if mode == QuickSelectComboBoxValues.ID_EARLIER:
|
||||
chosenRow = min(enumerate(scores), key=lambda i: i[1].id)[0]
|
||||
elif mode == QuickSelectComboBoxValues.DATE_EARLIER:
|
||||
chosenRow = min(
|
||||
enumerate(scores),
|
||||
key=lambda i: float("inf") if i[1].date is None else i[1].date,
|
||||
)[0]
|
||||
elif mode == QuickSelectComboBoxValues.COLUMNS_INTEGRAL:
|
||||
chosenRow = max(
|
||||
enumerate(scores),
|
||||
key=lambda i: sum(
|
||||
getattr(i[1], col.key) is not None
|
||||
for col in i[1].__table__.columns
|
||||
),
|
||||
)[0]
|
||||
|
||||
for childRow in range(parentItem.rowCount()):
|
||||
childCheckBoxItem = parentItem.child(childRow, 0)
|
||||
if childRow != chosenRow:
|
||||
childCheckBoxItem.setCheckState(Qt.CheckState.Checked)
|
||||
else:
|
||||
childCheckBoxItem.setCheckState(Qt.CheckState.Unchecked)
|
||||
|
||||
def reverseSelection(self):
|
||||
for row in range(self.removeDuplicateScoresModel.rowCount()):
|
||||
parentItem = self.removeDuplicateScoresModel.item(row, 0)
|
||||
# only when there's a checked item in this group, we perform a reversed selection
|
||||
# otherwise we ignore this group
|
||||
performReverse = any(
|
||||
parentItem.child(childRow, 0).checkState() == Qt.CheckState.Checked
|
||||
for childRow in range(parentItem.rowCount())
|
||||
)
|
||||
if not performReverse:
|
||||
continue
|
||||
|
||||
for childRow in range(parentItem.rowCount()):
|
||||
childCheckBoxItem = parentItem.child(childRow, 0)
|
||||
newCheckState = (
|
||||
Qt.CheckState.Unchecked
|
||||
if childCheckBoxItem.checkState() != Qt.CheckState.Unchecked
|
||||
else Qt.CheckState.Checked
|
||||
)
|
||||
childCheckBoxItem.setCheckState(newCheckState)
|
||||
|
||||
def deleteSelection(self):
|
||||
selectedScores: list[Score] = []
|
||||
for row in range(self.removeDuplicateScoresModel.rowCount()):
|
||||
parentItem = self.removeDuplicateScoresModel.item(row, 0)
|
||||
for childRow in range(parentItem.rowCount()):
|
||||
childCheckBoxItem = parentItem.child(childRow, 0)
|
||||
if childCheckBoxItem.checkState() == Qt.CheckState.Checked:
|
||||
childScoreItem = parentItem.child(childRow, 1)
|
||||
selectedScores.append(
|
||||
childScoreItem.data(RemoveDuplicateScoresModel.ScoreRole)
|
||||
)
|
||||
|
||||
confirm = QMessageBox.warning(
|
||||
self,
|
||||
None,
|
||||
# fmt: off
|
||||
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "deleteSelectionDialog.content {}").format(len(selectedScores)),
|
||||
# fmt: on
|
||||
QMessageBox.StandardButton.Yes,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if confirm != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
|
||||
with self.db.sessionmaker() as session:
|
||||
ids = [s.id for s in selectedScores]
|
||||
session.execute(delete(Score).where(Score.id.in_(ids)))
|
||||
session.commit()
|
||||
|
||||
self.scan()
|
||||
|
||||
@Slot()
|
||||
def on_scan_scanButton_clicked(self):
|
||||
if len(self.getQueryColumns()) <= 2:
|
||||
result = QMessageBox.warning(
|
||||
self,
|
||||
None,
|
||||
# fmt: off
|
||||
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "scan_noColumnsDialog.content"),
|
||||
# fmt: on
|
||||
QMessageBox.StandardButton.Yes,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if result != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
|
||||
self.scan()
|
||||
|
||||
@Slot()
|
||||
def on_quickSelect_selectButton_clicked(self):
|
||||
self.quickSelect()
|
||||
|
||||
@Slot()
|
||||
def on_deselectAllButton_clicked(self):
|
||||
self.deselectAll()
|
||||
|
||||
@Slot()
|
||||
def on_reverseSelectionButton_clicked(self):
|
||||
self.reverseSelection()
|
||||
|
||||
@Slot()
|
||||
def on_expandAllButton_clicked(self):
|
||||
self.treeView.expandAll()
|
||||
|
||||
@Slot()
|
||||
def on_collapseAllButton_clicked(self):
|
||||
self.treeView.collapseAll()
|
||||
|
||||
@Slot()
|
||||
def on_resetModelButton_clicked(self):
|
||||
self.removeDuplicateScoresModel.clear()
|
||||
|
||||
@Slot()
|
||||
def on_deleteSelectionButton_clicked(self):
|
||||
self.deleteSelection()
|
@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from arcaea_offline.database import Database
|
||||
from PySide6.QtCore import QCoreApplication
|
||||
from PySide6.QtGui import QShowEvent
|
||||
@ -6,6 +8,8 @@ from PySide6.QtWidgets import QWidget
|
||||
from ui.designer.tabs.tabOverview_ui import Ui_TabOverview
|
||||
from ui.extends.shared.language import LanguageChangeEventFilter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TabOverview(Ui_TabOverview, QWidget):
|
||||
def __init__(self, parent=None):
|
||||
@ -22,8 +26,14 @@ class TabOverview(Ui_TabOverview, QWidget):
|
||||
return super().showEvent(event)
|
||||
|
||||
def updateOverview(self):
|
||||
try:
|
||||
b30 = self.db.get_b30() or 0.00
|
||||
self.b30Label.setText(str(f"{b30:.3f}"))
|
||||
except Exception:
|
||||
logger.exception("Cannot get b30:")
|
||||
self.b30Label.setText("ERR")
|
||||
|
||||
try:
|
||||
self.databaseDescribeLabel.setText(
|
||||
self.describeFormatString.format(
|
||||
self.db.count_packs(),
|
||||
@ -34,6 +44,9 @@ class TabOverview(Ui_TabOverview, QWidget):
|
||||
self.db.count_scores(),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Cannot update overview:")
|
||||
self.databaseDescribeLabel.setText("ERR")
|
||||
|
||||
def retranslateUi(self, *args):
|
||||
super().retranslateUi(self)
|
||||
|
@ -2,31 +2,52 @@ import logging
|
||||
|
||||
from arcaea_offline.calculate import calculate_constants_from_play_rating
|
||||
from arcaea_offline.database import Database
|
||||
from arcaea_offline.models import Chart, ScoreBest
|
||||
from arcaea_offline.models import Chart, Score
|
||||
from arcaea_offline.utils.rating import rating_class_to_text
|
||||
from arcaea_offline.utils.score import score_to_grade_text
|
||||
from PySide6.QtCore import Slot
|
||||
from PySide6.QtWidgets import QWidget
|
||||
from PySide6.QtCore import QModelIndex, Qt, Slot
|
||||
from PySide6.QtWidgets import QDialog, QLabel, QVBoxLayout, QWidget
|
||||
|
||||
from ui.designer.tabs.tabTools.tabTools_ChartRecommend_ui import (
|
||||
Ui_TabTools_ChartRecommend,
|
||||
)
|
||||
from ui.extends.shared.language import LanguageChangeEventFilter
|
||||
from ui.extends.tabs.tabTools.tabTools_ChartRecommend import (
|
||||
ChartsModel,
|
||||
ChartsWithScoreBestModel,
|
||||
CustomChartDelegate,
|
||||
CustomScoreBestDelegate,
|
||||
)
|
||||
from ui.implements.components.playRatingCalculator import PlayRatingCalculator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def chartToText(chart: Chart):
|
||||
return f"{chart.artist} - {chart.title}<br>({chart.song_id}) {rating_class_to_text(chart.rating_class)}"
|
||||
class QuickPlayRatingCalculatorDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.verticalLayout = QVBoxLayout(self)
|
||||
|
||||
def scoreBestToText(score: ScoreBest):
|
||||
return f"{score_to_grade_text(score.score)} {score.score} > {score.potential:.4f}"
|
||||
self.chartLabel = QLabel(self)
|
||||
self.verticalLayout.addWidget(self.chartLabel)
|
||||
|
||||
self.playRatingCalculator = PlayRatingCalculator(self)
|
||||
self.verticalLayout.addWidget(self.playRatingCalculator)
|
||||
|
||||
self.setMinimumWidth(400)
|
||||
|
||||
self.playRatingCalculator.arcaeaScoreLineEdit.setFocus(
|
||||
Qt.FocusReason.PopupFocusReason
|
||||
)
|
||||
|
||||
def setChart(self, chart: Chart):
|
||||
self.chartLabel.setText(
|
||||
f"{chart.title} {rating_class_to_text(chart.rating_class)} {chart.constant / 10}"
|
||||
)
|
||||
self.playRatingCalculator.setConstant(chart.constant)
|
||||
|
||||
def setScore(self, score: Score):
|
||||
self.playRatingCalculator.arcaeaScoreLineEdit.setText(str(score))
|
||||
|
||||
|
||||
class TabTools_ChartRecommend(Ui_TabTools_ChartRecommend, QWidget):
|
||||
@ -34,6 +55,9 @@ class TabTools_ChartRecommend(Ui_TabTools_ChartRecommend, QWidget):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
|
||||
self.installEventFilter(self.languageChangeEventFilter)
|
||||
|
||||
self.db = Database()
|
||||
|
||||
self.chartsByConstantModel = ChartsModel(self)
|
||||
@ -62,6 +86,13 @@ class TabTools_ChartRecommend(Ui_TabTools_ChartRecommend, QWidget):
|
||||
self.updateChartsRecommendFromPlayRating
|
||||
)
|
||||
|
||||
self.chartsByConstant_modelView.doubleClicked.connect(
|
||||
self.openQuickPlayRatingCalculator_chartsByConstant
|
||||
)
|
||||
self.chartsRecommendFromPlayRating_modelView.doubleClicked.connect(
|
||||
self.openQuickPlayRatingCalculator_chartsRecommendFromPlayRating
|
||||
)
|
||||
|
||||
@Slot(float)
|
||||
def on_rangeFromPlayRating_playRatingSpinBox_valueChanged(self, value: float):
|
||||
try:
|
||||
@ -120,3 +151,26 @@ class TabTools_ChartRecommend(Ui_TabTools_ChartRecommend, QWidget):
|
||||
self.chartsRecommendFromPlayRatingModel.setChartAndScore(charts, scores)
|
||||
self.chartsRecommendFromPlayRating_modelView.resizeRowsToContents()
|
||||
self.chartsRecommendFromPlayRating_modelView.resizeColumnsToContents()
|
||||
|
||||
@Slot(QModelIndex)
|
||||
def openQuickPlayRatingCalculator_chartsByConstant(self, index: QModelIndex):
|
||||
dialog = QuickPlayRatingCalculatorDialog(self)
|
||||
chart = index.data(ChartsModel.ChartRole)
|
||||
dialog.setChart(chart)
|
||||
dialog.show()
|
||||
|
||||
@Slot(QModelIndex)
|
||||
def openQuickPlayRatingCalculator_chartsRecommendFromPlayRating(
|
||||
self, index: QModelIndex
|
||||
):
|
||||
dialog = QuickPlayRatingCalculatorDialog(self)
|
||||
|
||||
row = index.row()
|
||||
chartIndex = self.chartsRecommendFromPlayRatingModel.item(row, 0)
|
||||
scoreIndex = self.chartsRecommendFromPlayRatingModel.item(row, 1)
|
||||
|
||||
chart = chartIndex.data(ChartsWithScoreBestModel.ChartRole)
|
||||
score: Score = scoreIndex.data(ChartsWithScoreBestModel.ScoreBestRole)
|
||||
dialog.setChart(chart)
|
||||
dialog.setScore(score.score)
|
||||
dialog.show()
|
||||
|
0
ui/navigation/__init__.py
Normal file
0
ui/navigation/__init__.py
Normal file
151
ui/navigation/navhost.py
Normal file
151
ui/navigation/navhost.py
Normal file
@ -0,0 +1,151 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from .navitem import NavItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NavItemRelatives:
|
||||
parent: Optional[NavItem]
|
||||
children: list[NavItem]
|
||||
|
||||
|
||||
class NavHost(QObject):
|
||||
activated = Signal(NavItem, NavItem)
|
||||
|
||||
navItemsChanged = Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__navItems: list[NavItem] = []
|
||||
self.__cachedNavItems: list[NavItem] = []
|
||||
self.__currentNavItem: NavItem = None
|
||||
|
||||
def __flushCachedNavItems(self):
|
||||
navItems = set(self.__navItems)
|
||||
|
||||
for item in self.__navItems:
|
||||
parts = item.id.split(".")
|
||||
for i in range(1, len(parts)):
|
||||
parentItemId = ".".join(parts[:i])
|
||||
navItems.add(NavItem(id=parentItemId))
|
||||
|
||||
self.__cachedNavItems = list(navItems)
|
||||
|
||||
@property
|
||||
def navItems(self) -> list[NavItem]:
|
||||
return self.__cachedNavItems
|
||||
|
||||
@property
|
||||
def currentNavItem(self) -> NavItem:
|
||||
if self.__currentNavItem:
|
||||
return self.__currentNavItem
|
||||
|
||||
if self.__navItems:
|
||||
self.__currentNavItem = self.__navItems[0]
|
||||
return self.__currentNavItem
|
||||
|
||||
def findNavItem(self, navItemId: str) -> Optional[NavItem]:
|
||||
navItemIds = [item.id for item in self.__navItems]
|
||||
try:
|
||||
index = navItemIds.index(navItemId)
|
||||
return self.__navItems[index]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def isNavigatingBack(self, oldNavItemId: str, newNavItemId: str) -> bool:
|
||||
# sourcery skip: class-extract-method
|
||||
# | oldNavItemId | newNavItemId | back? |
|
||||
# |-----------------------|--------------------|-------|
|
||||
# | database.manage.packs | database | True |
|
||||
# | ocr.device | ocr | True |
|
||||
# | database.manage.songs | ocr.b30 | False |
|
||||
# | database | database | False |
|
||||
|
||||
oldNavItemIdSplitted = self.getNavItemIdSplitted(oldNavItemId)
|
||||
newNavItemIdSplitted = self.getNavItemIdSplitted(newNavItemId)
|
||||
|
||||
return self.getNavItemDepth(newNavItemId) < self.getNavItemDepth(
|
||||
oldNavItemId
|
||||
) and all((idFrag in oldNavItemIdSplitted) for idFrag in newNavItemIdSplitted)
|
||||
|
||||
def isChild(self, childNavItemId: str, parentNavItemId: str) -> bool:
|
||||
childNavItemIdSplitted = self.getNavItemIdSplitted(childNavItemId)
|
||||
parentNavItemIdSplitted = self.getNavItemIdSplitted(parentNavItemId)
|
||||
|
||||
return all(
|
||||
(idFrag in childNavItemIdSplitted) for idFrag in parentNavItemIdSplitted
|
||||
)
|
||||
|
||||
def getNavItemIdSplitted(self, navItemId: str) -> list[str]:
|
||||
return navItemId.split(".")
|
||||
|
||||
def getNavItemDepth(self, navItemId: str) -> int:
|
||||
return len(self.getNavItemIdSplitted(navItemId))
|
||||
|
||||
def getNavItemRelatives(self, navItemId: str) -> NavItemRelatives:
|
||||
parent = None
|
||||
if "." in navItemId:
|
||||
navItemIdSplitted = navItemId.split(".")
|
||||
parentId = navItemIdSplitted[:-1]
|
||||
parent = self.findNavItem(".".join(parentId))
|
||||
|
||||
if not navItemId:
|
||||
# return root navItems
|
||||
children = [navItem for navItem in self.__navItems if "." not in navItem.id]
|
||||
else:
|
||||
children = [
|
||||
navItem
|
||||
for navItem in self.__navItems
|
||||
if navItem.id.startswith(navItemId)
|
||||
and navItem.id != navItemId
|
||||
and self.getNavItemDepth(navItem.id)
|
||||
== self.getNavItemDepth(navItemId) + 1
|
||||
]
|
||||
|
||||
return NavItemRelatives(parent=parent, children=children)
|
||||
|
||||
def registerNavItem(self, item: NavItem):
|
||||
self.__navItems.append(item)
|
||||
self.__flushCachedNavItems()
|
||||
self.navItemsChanged.emit()
|
||||
|
||||
def navigate(self, navItemId: str):
|
||||
oldNavItem = self.__currentNavItem or NavItem("")
|
||||
newNavItem = self.findNavItem(navItemId)
|
||||
|
||||
if newNavItem is None:
|
||||
raise IndexError(
|
||||
f"Cannot find '{navItemId}' in {repr(self)}. "
|
||||
"Maybe try registering it?"
|
||||
)
|
||||
|
||||
# if the navItem have children, navigate to first child
|
||||
# but if the navItem is going back, e.g. 'database.manage' -> 'database'
|
||||
# then don't navigate to it's child.
|
||||
if self.isNavigatingBack(oldNavItem.id, newNavItem.id):
|
||||
# navItem is going back
|
||||
currentNavItem = newNavItem
|
||||
else:
|
||||
newNavItemRelatives = self.getNavItemRelatives(newNavItem.id)
|
||||
if newNavItemRelatives.children:
|
||||
currentNavItem = newNavItemRelatives.children[0]
|
||||
else:
|
||||
currentNavItem = newNavItem
|
||||
|
||||
self.__currentNavItem = currentNavItem
|
||||
self.activated.emit(oldNavItem, self.currentNavItem)
|
||||
|
||||
def navigateUp(self):
|
||||
navItemRelatives = self.getNavItemRelatives(self.currentNavItem.id)
|
||||
if navItemRelatives.parent:
|
||||
self.navigate(navItemRelatives.parent.id)
|
||||
|
||||
|
||||
navHost = NavHost()
|
17
ui/navigation/navitem.py
Normal file
17
ui/navigation/navitem.py
Normal file
@ -0,0 +1,17 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QCoreApplication
|
||||
from PySide6.QtGui import QIcon, QPixmap
|
||||
|
||||
|
||||
@dataclass
|
||||
class NavItem:
|
||||
id: str
|
||||
icon: Optional[QIcon | QPixmap | str] = None
|
||||
|
||||
def text(self):
|
||||
return QCoreApplication.translate("NavItem", f"{self.id}.title")
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
84
ui/navigation/navitemwidgets.py
Normal file
84
ui/navigation/navitemwidgets.py
Normal file
@ -0,0 +1,84 @@
|
||||
from PySide6.QtCore import QObject
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtWidgets import QLabel, QSizePolicy, QSpacerItem, QVBoxLayout, QWidget
|
||||
|
||||
from ui.navigation.navhost import NavHost, navHost
|
||||
from ui.navigation.navitem import NavItem
|
||||
from ui.navigation.navsidebar import NavigationSideBar
|
||||
|
||||
|
||||
class DefaultParentNavItemWidget(QWidget):
|
||||
def __init__(self, navItem: NavItem, navItemChildren: list[NavItem], parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.navItem = navItem
|
||||
|
||||
self.partialNavHost = NavHost(self)
|
||||
self.partialNavHost.registerNavItem(navItem)
|
||||
for _navItem in navItemChildren:
|
||||
self.partialNavHost.registerNavItem(_navItem)
|
||||
self.partialNavHost.navigate(navItem.id)
|
||||
|
||||
self.verticalLayout = QVBoxLayout(self)
|
||||
|
||||
self.navItemLabelFont = QFont(self.font())
|
||||
self.navItemLabelFont.setPointSize(14)
|
||||
self.navItemLabel = QLabel(self)
|
||||
self.navItemLabel.setFont(self.navItemLabelFont)
|
||||
|
||||
spacer = QSpacerItem(
|
||||
20, 20, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding
|
||||
)
|
||||
|
||||
self.verticalLayout.addSpacerItem(spacer)
|
||||
|
||||
self.navSideBar = NavigationSideBar(self, self.partialNavHost)
|
||||
self.navSideBar.navigateUpButton.setEnabled(False)
|
||||
self.verticalLayout.addWidget(self.navSideBar)
|
||||
|
||||
self.verticalLayout.addSpacerItem(spacer)
|
||||
|
||||
self.retranslateUi()
|
||||
|
||||
def retranslateUi(self):
|
||||
self.navItemLabel.setText(self.navItem.text())
|
||||
|
||||
|
||||
class NavItemWidgets(QObject):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__map: dict[str, QWidget] = {}
|
||||
# this reference holds all the `DefaultParentNavItemWidget`s
|
||||
# since these widgets are created with no parents, not keeping a reference to
|
||||
# them may result in errors, even a silent app crash that is hard to debug
|
||||
self.__defaultParentWidgetRefs: dict[str, QWidget] = {}
|
||||
|
||||
def register(self, navItemId: str, widget: QWidget):
|
||||
self.__map[navItemId] = widget
|
||||
|
||||
def unregister(self, navItemId: str) -> bool:
|
||||
try:
|
||||
widget = self.__map.pop(navItemId)
|
||||
widget.deleteLater()
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def get(self, navItemId: str) -> QWidget | None:
|
||||
widget = self.__map.get(navItemId)
|
||||
if widget is not None:
|
||||
return widget
|
||||
elif navItemChildren := navHost.getNavItemRelatives(navItemId).children:
|
||||
if self.__defaultParentWidgetRefs.get(navItemId) is None:
|
||||
defaultParentNavItemWidget = DefaultParentNavItemWidget(
|
||||
navHost.findNavItem(navItemId), navItemChildren
|
||||
)
|
||||
self.__defaultParentWidgetRefs[navItemId] = defaultParentNavItemWidget
|
||||
defaultParentNavItemWidget.partialNavHost.activated.connect(
|
||||
lambda o, n: navHost.navigate(n.id)
|
||||
)
|
||||
|
||||
return self.__defaultParentWidgetRefs.get(navItemId)
|
||||
else:
|
||||
return None
|
190
ui/navigation/navsidebar.py
Normal file
190
ui/navigation/navsidebar.py
Normal file
@ -0,0 +1,190 @@
|
||||
from PySide6.QtCore import QModelIndex, Qt, Signal, Slot
|
||||
from PySide6.QtGui import QFont, QIcon, QKeySequence, QShortcut
|
||||
from PySide6.QtWidgets import (
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from ui.navigation.navhost import NavHost, NavItem, navHost
|
||||
from ui.widgets.slidingstackedwidget import SlidingStackedWidget
|
||||
|
||||
|
||||
class NavItemListWidget(QListWidget):
|
||||
NavItemRole = Qt.ItemDataRole.UserRole
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
font = QFont(self.font())
|
||||
font.setPointSize(14)
|
||||
self.setFont(font)
|
||||
|
||||
self.clicked.connect(self.activated)
|
||||
|
||||
def setNavItems(self, items: list[NavItem]):
|
||||
self.clear()
|
||||
|
||||
for navItem in items:
|
||||
if navItem.icon:
|
||||
listWidgetItem = QListWidgetItem(QIcon(navItem.icon), navItem.text())
|
||||
else:
|
||||
listWidgetItem = QListWidgetItem(navItem.text())
|
||||
listWidgetItem.setData(self.NavItemRole, navItem)
|
||||
listWidgetItem.setTextAlignment(
|
||||
Qt.AlignmentFlag.AlignLeading | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
|
||||
self.addItem(listWidgetItem)
|
||||
|
||||
def selectNavItem(self, navItemId: str):
|
||||
navItemIds = [
|
||||
self.item(r).data(self.NavItemRole).id for r in range(self.count())
|
||||
]
|
||||
index = navItemIds.index(navItemId)
|
||||
self.setCurrentIndex(self.model().index(index, 0))
|
||||
|
||||
|
||||
class NavigationWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.navHost = navHost
|
||||
|
||||
self.verticalLayout = QVBoxLayout(self)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.backButton = QPushButton(QIcon(":/icons/back.svg"), "")
|
||||
self.backButton.setFlat(True)
|
||||
self.backButton.setFixedHeight(20)
|
||||
self.verticalLayout.addWidget(self.backButton)
|
||||
|
||||
self.navListWidget = NavItemListWidget(self)
|
||||
self.verticalLayout.addWidget(self.navListWidget)
|
||||
|
||||
def setNavigationItems(self, items: list[NavItem]):
|
||||
self.navListWidget.setNavItems(items)
|
||||
|
||||
|
||||
class NavigationSideBar(QWidget):
|
||||
navItemActivated = Signal(NavItem)
|
||||
|
||||
def __init__(self, parent=None, navHost=navHost):
|
||||
super().__init__(parent)
|
||||
|
||||
self.navHost = None
|
||||
self.navigateUpKeyboardShortcut = QShortcut(
|
||||
QKeySequence(Qt.Modifier.ALT | Qt.Key.Key_Left), self, lambda: True
|
||||
)
|
||||
|
||||
self.verticalLayout = QVBoxLayout(self)
|
||||
|
||||
self.navigateUpButton = QPushButton(QIcon(":/icons/back.svg"), "")
|
||||
self.navigateUpButton.setFlat(True)
|
||||
self.navigateUpButton.setFixedHeight(20)
|
||||
self.verticalLayout.addWidget(self.navigateUpButton)
|
||||
|
||||
self.slidingStackedWidget = SlidingStackedWidget(self)
|
||||
self.slidingStackedWidget.animationFinished.connect(
|
||||
self.endChangingNavItemListWidget
|
||||
)
|
||||
self.verticalLayout.addWidget(self.slidingStackedWidget)
|
||||
|
||||
navItemListWidget = NavItemListWidget(self)
|
||||
navItemListWidget.activated.connect(self.navItemListWidgetActivatedProxy)
|
||||
self.slidingStackedWidget.addWidget(navItemListWidget)
|
||||
|
||||
self.setNavHost(navHost)
|
||||
self.reloadNavWidget()
|
||||
|
||||
def setNavHost(self, navHost: NavHost):
|
||||
if self.navHost is not None:
|
||||
self.navHost.navItemsChanged.disconnect(self.reloadNavWidget)
|
||||
self.navHost.activated.disconnect(self.navItemChanged)
|
||||
self.navigateUpKeyboardShortcut.activated.disconnect(
|
||||
self.navHost.navigateUp
|
||||
)
|
||||
self.navigateUpButton.clicked.disconnect(self.navHost.navigateUp)
|
||||
|
||||
self.navHost = navHost
|
||||
|
||||
self.navHost.navItemsChanged.connect(self.reloadNavWidget)
|
||||
self.navHost.activated.connect(self.navItemChanged)
|
||||
self.navigateUpKeyboardShortcut.activated.connect(self.navHost.navigateUp)
|
||||
self.navigateUpButton.clicked.connect(self.navHost.navigateUp)
|
||||
|
||||
@Slot(QModelIndex)
|
||||
def navItemListWidgetActivatedProxy(self, index: QModelIndex):
|
||||
self.navHost.navigate(index.data(NavItemListWidget.NavItemRole).id)
|
||||
self.navItemActivated.emit(index.data(NavItemListWidget.NavItemRole))
|
||||
|
||||
def fillNavItemListWidget(
|
||||
self, currentNavItem: NavItem, listWidget: NavItemListWidget
|
||||
):
|
||||
currentNavItemParent = self.navHost.getNavItemRelatives(
|
||||
currentNavItem.id
|
||||
).parent
|
||||
currentNavItems = self.navHost.getNavItemRelatives(
|
||||
currentNavItemParent.id if currentNavItemParent else ""
|
||||
)
|
||||
listWidget.setNavItems(currentNavItems.children)
|
||||
listWidget.selectNavItem(currentNavItem.id)
|
||||
|
||||
def reloadNavWidget(self):
|
||||
self.fillNavItemListWidget(
|
||||
self.navHost.currentNavItem, self.slidingStackedWidget.widget(0)
|
||||
)
|
||||
self.navItemChanged(self.navHost.currentNavItem, self.navHost.currentNavItem)
|
||||
|
||||
@Slot(NavItem, NavItem)
|
||||
def navItemChanged(self, oldNavItem: NavItem, newNavItem: NavItem):
|
||||
# update navigateUpButton text
|
||||
if newNavItemParent := self.navHost.getNavItemRelatives(newNavItem.id).parent:
|
||||
self.navigateUpButton.setText(newNavItemParent.text())
|
||||
else:
|
||||
self.navigateUpButton.setText("Arcaea Offline")
|
||||
|
||||
# update navItemListWidget
|
||||
oldNavItemIdSplitted = self.navHost.getNavItemIdSplitted(oldNavItem.id)
|
||||
newNavItemIdSplitted = self.navHost.getNavItemIdSplitted(newNavItem.id)
|
||||
|
||||
oldNavItemDepth = len(oldNavItemIdSplitted)
|
||||
newNavItemDepth = len(newNavItemIdSplitted)
|
||||
|
||||
if oldNavItemDepth != newNavItemDepth:
|
||||
# navItem depth changed, replace current NavItemListWidget
|
||||
newNavItemListWidget = NavItemListWidget(self)
|
||||
slidingDirection = (
|
||||
self.slidingStackedWidget.slidingDirection.RightToLeft
|
||||
if newNavItemDepth > oldNavItemDepth
|
||||
else self.slidingStackedWidget.slidingDirection.LeftToRight
|
||||
)
|
||||
|
||||
self.fillNavItemListWidget(newNavItem, newNavItemListWidget)
|
||||
newNavItemListWidget.activated.connect(self.navItemListWidgetActivatedProxy)
|
||||
|
||||
self.startChangingNavItemListWidget(newNavItemListWidget, slidingDirection)
|
||||
|
||||
def startChangingNavItemListWidget(
|
||||
self, newNavItemListWidget: NavItemListWidget, slidingDirection
|
||||
):
|
||||
newIndex = self.slidingStackedWidget.addWidget(newNavItemListWidget)
|
||||
[
|
||||
self.slidingStackedWidget.widget(i).setEnabled(False)
|
||||
for i in range(self.slidingStackedWidget.count())
|
||||
]
|
||||
self.navigateUpButton.setEnabled(False)
|
||||
self.navigateUpKeyboardShortcut.setEnabled(False)
|
||||
self.slidingStackedWidget.slideInIdx(newIndex, slidingDirection)
|
||||
|
||||
def endChangingNavItemListWidget(self):
|
||||
oldWidget = self.slidingStackedWidget.widget(0)
|
||||
self.slidingStackedWidget.removeWidget(oldWidget)
|
||||
oldWidget.deleteLater()
|
||||
|
||||
newWidget = self.slidingStackedWidget.widget(0)
|
||||
newWidget.setEnabled(True)
|
||||
self.navigateUpButton.setEnabled(True)
|
||||
self.navigateUpKeyboardShortcut.setEnabled(True)
|
59
ui/resources/icons/back.svg
Normal file
59
ui/resources/icons/back.svg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="back.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="24.40625"
|
||||
inkscape:cx="15.323944"
|
||||
inkscape:cy="10.632522"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid4"
|
||||
units="px"
|
||||
originx="12"
|
||||
originy="12"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="图层 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 17,2 7,12 17,22"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -27,64 +27,45 @@
|
||||
<translation>Reset</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DB30TableModel</name>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="21"/>
|
||||
<source>horizontalHeader.id</source>
|
||||
<translation>ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="22"/>
|
||||
<source>horizontalHeader.chart</source>
|
||||
<translation>Chart</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="23"/>
|
||||
<source>horizontalHeader.score</source>
|
||||
<translation>Score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="24"/>
|
||||
<source>horizontalHeader.potential</source>
|
||||
<translation>Potential</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseChecker</name>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="23"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="162"/>
|
||||
<source>dbPathLabel</source>
|
||||
<translation>Database path</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="33"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="165"/>
|
||||
<source>dbFilenameLabel</source>
|
||||
<translation>Database filename</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="64"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="168"/>
|
||||
<source>confirmDbPathButton</source>
|
||||
<translation>Confirm</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="117"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="177"/>
|
||||
<source>dbVersionLabel</source>
|
||||
<translation>Database version</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="131"/>
|
||||
<source>dbReInitLabel</source>
|
||||
<translation>Re-initialize database</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="138"/>
|
||||
<source>dbReInitButton</source>
|
||||
<translation>Re-initialize</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="93"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="171"/>
|
||||
<source>dbCheckConnLabel</source>
|
||||
<translation>Database connection</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="110"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="174"/>
|
||||
<source>continueButton</source>
|
||||
<translation>Continue</translation>
|
||||
</message>
|
||||
@ -99,6 +80,29 @@
|
||||
<translation>Database file does not exist. Create now?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DbB30TableModel</name>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="20"/>
|
||||
<source>horizontalHeader.id</source>
|
||||
<translation>ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="21"/>
|
||||
<source>horizontalHeader.chart</source>
|
||||
<translation>Chart</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="22"/>
|
||||
<source>horizontalHeader.score</source>
|
||||
<translation>Score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="23"/>
|
||||
<source>horizontalHeader.potential</source>
|
||||
<translation>Potential</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DbScoreTableModel</name>
|
||||
<message>
|
||||
@ -303,6 +307,14 @@ validation</translation>
|
||||
<translation>Score</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PotentialCalculator</name>
|
||||
<message>
|
||||
<location filename="../../implements/components/playRatingCalculator.py" line="85"/>
|
||||
<source>copyButton</source>
|
||||
<translation>Copy</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ResettableItem</name>
|
||||
<message>
|
||||
@ -554,6 +566,14 @@ validation</translation>
|
||||
<translation>Next</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>StepCalculator</name>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabTools/tabTools_StepCalculator.py" line="96"/>
|
||||
<source>playRatingCalculatorDialog.acceptButton</source>
|
||||
<translation>Accept</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabAbout</name>
|
||||
<message>
|
||||
@ -579,6 +599,11 @@ validation</translation>
|
||||
<source>tab.chartInfoEditor</source>
|
||||
<translation>Chart Info Editor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDbEntry.ui" line="34"/>
|
||||
<source>tab.removeDuplicateScores</source>
|
||||
<translation>Remove Duplicate Scores</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDbEntry.py" line="20"/>
|
||||
<source>tab.scoreTableViewer</source>
|
||||
@ -737,6 +762,109 @@ validation</translation>
|
||||
<translation>Export all your scores to <a href="https://smartrte.github.io/b30gen.html">smartrte.github.io</a> compatible CSV file</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabDb_RemoveDuplicateScores</name>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="20"/>
|
||||
<source>scan.title</source>
|
||||
<translation>Scan Options</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="30"/>
|
||||
<source>scan.option.score</source>
|
||||
<translation>Score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="71"/>
|
||||
<source>scan.option.date</source>
|
||||
<translation>Date</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="78"/>
|
||||
<source>scan.option.modifier</source>
|
||||
<translation>Modifier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="85"/>
|
||||
<source>scan.option.clearType</source>
|
||||
<translation>Clear Type</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="94"/>
|
||||
<source>scan.scanButton</source>
|
||||
<translation>Scan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="124"/>
|
||||
<source>quickSelect.title</source>
|
||||
<translation>Quick Select</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="130"/>
|
||||
<source>quickSelect.description</source>
|
||||
<translation>Keep the first score item<br>that matches:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="166"/>
|
||||
<source>quickSelect.selectButton</source>
|
||||
<translation>Select</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="182"/>
|
||||
<source>deselectAllButton</source>
|
||||
<translation>Clear Selection</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="189"/>
|
||||
<source>reverseSelectionButton</source>
|
||||
<translation>Reverse Selection</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="209"/>
|
||||
<source>collapseAllButton</source>
|
||||
<translation>Collapse All Groups</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="216"/>
|
||||
<source>expandAllButton</source>
|
||||
<translation>Expand All Groups</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="223"/>
|
||||
<source>resetModelButton</source>
|
||||
<translation>Reset Model</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="251"/>
|
||||
<source>deleteSelectionButton</source>
|
||||
<translation>Delete Selected Scores</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
|
||||
<source>quickSelectComboBox.idEarlier</source>
|
||||
<translation>Earlier ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
|
||||
<source>quickSelectComboBox.dateEarlier</source>
|
||||
<translation>Earlier date</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
|
||||
<source>quickSelectComboBox.columnsIntegral</source>
|
||||
<translation>More complete data</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
|
||||
<source>deleteSelectionDialog.content {}</source>
|
||||
<translation>Deleting {} scores from database, this cannot be undone!<br>Confirm?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
|
||||
<source>scan_noColumnsDialog.content</source>
|
||||
<translation>You haven't selected any column! Are you sure to continue?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabOcrDisabled</name>
|
||||
<message>
|
||||
@ -891,7 +1019,7 @@ validation</translation>
|
||||
<context>
|
||||
<name>TabOverview</name>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabOverview.py" line="43"/>
|
||||
<location filename="../../implements/tabs/tabOverview.py" line="56"/>
|
||||
<source>databaseDescribeLabel {} {} {} {} {} {}</source>
|
||||
<translation>There are {} packs, {} songs, {} difficulties, {} chart info ({} complete) and {} scores in database.</translation>
|
||||
</message>
|
||||
@ -981,6 +1109,11 @@ validation</translation>
|
||||
<source>generateImageButton</source>
|
||||
<translation>Generate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="335"/>
|
||||
<source>sourceCode</source>
|
||||
<translation>Source code</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabTools/tabTools_Andreal.py" line="138"/>
|
||||
<source>imageWhatIsThisDialog.description</source>
|
||||
@ -990,7 +1123,7 @@ validation</translation>
|
||||
<context>
|
||||
<name>TabTools_ChartRecommend</name>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="99"/>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="102"/>
|
||||
<source>constantRangeFromPlayRating</source>
|
||||
<translation>Chart Constant Range from Play Rating</translation>
|
||||
</message>
|
||||
@ -1000,7 +1133,7 @@ validation</translation>
|
||||
<translation>Charts by Constant</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="245"/>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="248"/>
|
||||
<source>chartsRecommendFromPlayRating</source>
|
||||
<translation>Chart from Play Rating Based on Best Score</translation>
|
||||
</message>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@ -32,23 +32,31 @@ 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
|
||||
[
|
||||
"pyside6-lupdate",
|
||||
"-extensions",
|
||||
"py,ui",
|
||||
str(designer.absolute()),
|
||||
str(extends.absolute()),
|
||||
str(implements.absolute()),
|
||||
str(startup.absolute()),
|
||||
"-ts",
|
||||
str((output_dir_path / "zh_CN.ts").absolute()),
|
||||
], # zh_CN
|
||||
[
|
||||
"pyside6-lupdate",
|
||||
"-extensions",
|
||||
"py,ui",
|
||||
str(designer.absolute()),
|
||||
str(extends.absolute()),
|
||||
str(implements.absolute()),
|
||||
str(startup.absolute()),
|
||||
"-ts",
|
||||
str((output_dir_path / "en_US.ts").absolute()),
|
||||
], # en_US
|
||||
]
|
||||
if no_obsolete:
|
||||
commands = [f"{command} -no-obsolete" for command in commands]
|
||||
commands = [command + ["-no-obsolete"] for command in commands]
|
||||
|
||||
for command in commands:
|
||||
print(f"Executing '{command}'")
|
||||
output = os.popen(command).read()
|
||||
print(output)
|
||||
subprocess.run(command)
|
||||
|
@ -27,64 +27,45 @@
|
||||
<translation>重置</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DB30TableModel</name>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="21"/>
|
||||
<source>horizontalHeader.id</source>
|
||||
<translation>ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="22"/>
|
||||
<source>horizontalHeader.chart</source>
|
||||
<translation>谱面</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="23"/>
|
||||
<source>horizontalHeader.score</source>
|
||||
<translation>分数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="24"/>
|
||||
<source>horizontalHeader.potential</source>
|
||||
<translation>单曲 PTT</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseChecker</name>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="23"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="162"/>
|
||||
<source>dbPathLabel</source>
|
||||
<translation>数据库路径</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="33"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="165"/>
|
||||
<source>dbFilenameLabel</source>
|
||||
<translation>数据库文件名</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="64"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="168"/>
|
||||
<source>confirmDbPathButton</source>
|
||||
<translation>确认</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="117"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="177"/>
|
||||
<source>dbVersionLabel</source>
|
||||
<translation>数据库版本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="131"/>
|
||||
<source>dbReInitLabel</source>
|
||||
<translation>重新初始化数据库</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="138"/>
|
||||
<source>dbReInitButton</source>
|
||||
<translation>重新初始化</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="93"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="171"/>
|
||||
<source>dbCheckConnLabel</source>
|
||||
<translation>数据库连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../startup/databaseChecker.ui" line="110"/>
|
||||
<location filename="../../startup/databaseChecker_ui.py" line="174"/>
|
||||
<source>continueButton</source>
|
||||
<translation>继续</translation>
|
||||
</message>
|
||||
@ -99,6 +80,29 @@
|
||||
<translation>数据库文件不存在,是否创建?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DbB30TableModel</name>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="20"/>
|
||||
<source>horizontalHeader.id</source>
|
||||
<translation>ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="21"/>
|
||||
<source>horizontalHeader.chart</source>
|
||||
<translation>谱面</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="22"/>
|
||||
<source>horizontalHeader.score</source>
|
||||
<translation>分数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../extends/shared/models/tables/b30.py" line="23"/>
|
||||
<source>horizontalHeader.potential</source>
|
||||
<translation>单曲 PTT</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DbScoreTableModel</name>
|
||||
<message>
|
||||
@ -302,6 +306,14 @@
|
||||
<translation>分数</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PotentialCalculator</name>
|
||||
<message>
|
||||
<location filename="../../implements/components/playRatingCalculator.py" line="85"/>
|
||||
<source>copyButton</source>
|
||||
<translation>复制</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ResettableItem</name>
|
||||
<message>
|
||||
@ -553,6 +565,14 @@
|
||||
<translation>下一个</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>StepCalculator</name>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabTools/tabTools_StepCalculator.py" line="96"/>
|
||||
<source>playRatingCalculatorDialog.acceptButton</source>
|
||||
<translation>确定</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabAbout</name>
|
||||
<message>
|
||||
@ -578,6 +598,11 @@
|
||||
<source>tab.chartInfoEditor</source>
|
||||
<translation>谱面信息编辑器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDbEntry.ui" line="34"/>
|
||||
<source>tab.removeDuplicateScores</source>
|
||||
<translation>移除重复分数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDbEntry.py" line="20"/>
|
||||
<source>tab.scoreTableViewer</source>
|
||||
@ -736,6 +761,109 @@
|
||||
<translation>将所有分数导出为兼容 <a href="https://smartrte.github.io/b30gen.html">smartrte.github.io</a> 的 CSV 文件</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabDb_RemoveDuplicateScores</name>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="20"/>
|
||||
<source>scan.title</source>
|
||||
<translation>扫描选项</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="30"/>
|
||||
<source>scan.option.score</source>
|
||||
<translation>分数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="71"/>
|
||||
<source>scan.option.date</source>
|
||||
<translation>时间</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="78"/>
|
||||
<source>scan.option.modifier</source>
|
||||
<translation>Modifier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="85"/>
|
||||
<source>scan.option.clearType</source>
|
||||
<translation>Clear Type</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="94"/>
|
||||
<source>scan.scanButton</source>
|
||||
<translation>扫描</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="124"/>
|
||||
<source>quickSelect.title</source>
|
||||
<translation>快速选择</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="130"/>
|
||||
<source>quickSelect.description</source>
|
||||
<translation>仅保留第一个<br>符合以下条件的分数:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="166"/>
|
||||
<source>quickSelect.selectButton</source>
|
||||
<translation>选择</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="182"/>
|
||||
<source>deselectAllButton</source>
|
||||
<translation>清空选择</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="189"/>
|
||||
<source>reverseSelectionButton</source>
|
||||
<translation>反选</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="209"/>
|
||||
<source>collapseAllButton</source>
|
||||
<translation>折叠所有</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="216"/>
|
||||
<source>expandAllButton</source>
|
||||
<translation>展开所有</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="223"/>
|
||||
<source>resetModelButton</source>
|
||||
<translation>重置模型</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui" line="251"/>
|
||||
<source>deleteSelectionButton</source>
|
||||
<translation>删除已选分数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
|
||||
<source>quickSelectComboBox.idEarlier</source>
|
||||
<translation>ID 更早</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
|
||||
<source>quickSelectComboBox.dateEarlier</source>
|
||||
<translation>时间更早</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
|
||||
<source>quickSelectComboBox.columnsIntegral</source>
|
||||
<translation>数据更完整</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
|
||||
<source>deleteSelectionDialog.content {}</source>
|
||||
<translation>将从数据库中删除 {} 个分数。此操作无法撤销!<br>确认吗?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
|
||||
<source>scan_noColumnsDialog.content</source>
|
||||
<translation>还未选择任何字段!确定继续吗?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabOcrDisabled</name>
|
||||
<message>
|
||||
@ -890,7 +1018,7 @@
|
||||
<context>
|
||||
<name>TabOverview</name>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabOverview.py" line="43"/>
|
||||
<location filename="../../implements/tabs/tabOverview.py" line="56"/>
|
||||
<source>databaseDescribeLabel {} {} {} {} {} {}</source>
|
||||
<translation>数据库中有 {} 个曲包,{} 首歌曲,{} 个难度,{} 个谱面信息({} 个完整),{} 个分数记录。</translation>
|
||||
</message>
|
||||
@ -980,6 +1108,11 @@
|
||||
<source>generateImageButton</source>
|
||||
<translation>生成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="335"/>
|
||||
<source>sourceCode</source>
|
||||
<translation>源代码</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../implements/tabs/tabTools/tabTools_Andreal.py" line="138"/>
|
||||
<source>imageWhatIsThisDialog.description</source>
|
||||
@ -989,7 +1122,7 @@
|
||||
<context>
|
||||
<name>TabTools_ChartRecommend</name>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="99"/>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="102"/>
|
||||
<source>constantRangeFromPlayRating</source>
|
||||
<translation>由单曲 PTT 逆算谱面定数范围</translation>
|
||||
</message>
|
||||
@ -999,7 +1132,7 @@
|
||||
<translation>按定数查谱</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="245"/>
|
||||
<location filename="../../designer/tabs/tabTools/tabTools_ChartRecommend.ui" line="248"/>
|
||||
<source>chartsRecommendFromPlayRating</source>
|
||||
<translation>由单曲 PTT 结合最好成绩推荐谱面</translation>
|
||||
</message>
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
<file>fonts/GeosansLight.ttf</file>
|
||||
|
||||
<file>icons/back.svg</file>
|
||||
|
||||
<file>images/icon.png</file>
|
||||
<file>images/logo.png</file>
|
||||
<file>images/jacket-placeholder.png</file>
|
||||
|
@ -138,6 +138,11 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
|
||||
db.init()
|
||||
self.updateLabels()
|
||||
|
||||
@Slot()
|
||||
def on_dbReInitButton_clicked(self):
|
||||
Database().init(checkfirst=True)
|
||||
QMessageBox.information(self, None, "OK")
|
||||
|
||||
@Slot()
|
||||
def on_continueButton_clicked(self):
|
||||
self.accept()
|
||||
|
@ -125,6 +125,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>dbReInitLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="dbReInitButton">
|
||||
<property name="text">
|
||||
<string>dbReInitButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -8,173 +8,131 @@
|
||||
## 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,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSpacerItem,
|
||||
QWidget,
|
||||
)
|
||||
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, QFrame, QHBoxLayout,
|
||||
QLabel, QLineEdit, 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.setObjectName(u"DatabaseChecker")
|
||||
DatabaseChecker.resize(350, 250)
|
||||
DatabaseChecker.setWindowTitle("DatabaseChecker")
|
||||
DatabaseChecker.setWindowTitle(u"DatabaseChecker")
|
||||
self.formLayout = QFormLayout(DatabaseChecker)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.formLayout.setLabelAlignment(
|
||||
Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter
|
||||
)
|
||||
self.formLayout.setObjectName(u"formLayout")
|
||||
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
|
||||
self.label = QLabel(DatabaseChecker)
|
||||
self.label.setObjectName("label")
|
||||
self.label.setObjectName(u"label")
|
||||
|
||||
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
|
||||
|
||||
self.dbDirSelector = FileSelector(DatabaseChecker)
|
||||
self.dbDirSelector.setObjectName("dbDirSelector")
|
||||
self.dbDirSelector.setObjectName(u"dbDirSelector")
|
||||
|
||||
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.dbDirSelector)
|
||||
|
||||
self.label_3 = QLabel(DatabaseChecker)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.label_3.setObjectName(u"label_3")
|
||||
|
||||
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_3)
|
||||
|
||||
self.dbFilenameLineEdit = QLineEdit(DatabaseChecker)
|
||||
self.dbFilenameLineEdit.setObjectName("dbFilenameLineEdit")
|
||||
self.dbFilenameLineEdit.setObjectName(u"dbFilenameLineEdit")
|
||||
|
||||
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.dbFilenameLineEdit)
|
||||
|
||||
self.horizontalLayout = QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.horizontalSpacer = QSpacerItem(
|
||||
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
|
||||
)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||
|
||||
self.horizontalLayout.addItem(self.horizontalSpacer)
|
||||
|
||||
self.confirmDbPathButton = QPushButton(DatabaseChecker)
|
||||
self.confirmDbPathButton.setObjectName("confirmDbPathButton")
|
||||
self.confirmDbPathButton.setObjectName(u"confirmDbPathButton")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.confirmDbPathButton.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
sizePolicy.setHeightForWidth(self.confirmDbPathButton.sizePolicy().hasHeightForWidth())
|
||||
self.confirmDbPathButton.setSizePolicy(sizePolicy)
|
||||
|
||||
self.horizontalLayout.addWidget(self.confirmDbPathButton)
|
||||
|
||||
|
||||
self.formLayout.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout)
|
||||
|
||||
self.dbVersionLabel = QLabel(DatabaseChecker)
|
||||
self.dbVersionLabel.setObjectName("dbVersionLabel")
|
||||
self.dbVersionLabel.setText("-")
|
||||
self.dbVersionLabel.setObjectName(u"dbVersionLabel")
|
||||
self.dbVersionLabel.setText(u"-")
|
||||
|
||||
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.dbVersionLabel)
|
||||
|
||||
self.verticalSpacer = QSpacerItem(
|
||||
20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
|
||||
)
|
||||
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
|
||||
self.formLayout.setItem(6, QFormLayout.FieldRole, self.verticalSpacer)
|
||||
|
||||
self.label_5 = QLabel(DatabaseChecker)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.label_5.setObjectName(u"label_5")
|
||||
|
||||
self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_5)
|
||||
|
||||
self.dbCheckConnLabel = QLabel(DatabaseChecker)
|
||||
self.dbCheckConnLabel.setObjectName("dbCheckConnLabel")
|
||||
self.dbCheckConnLabel.setText("...")
|
||||
self.dbCheckConnLabel.setObjectName(u"dbCheckConnLabel")
|
||||
self.dbCheckConnLabel.setText(u"...")
|
||||
|
||||
self.formLayout.setWidget(7, QFormLayout.FieldRole, self.dbCheckConnLabel)
|
||||
|
||||
self.continueButton = QPushButton(DatabaseChecker)
|
||||
self.continueButton.setObjectName("continueButton")
|
||||
self.continueButton.setObjectName(u"continueButton")
|
||||
self.continueButton.setEnabled(False)
|
||||
|
||||
self.formLayout.setWidget(8, QFormLayout.SpanningRole, self.continueButton)
|
||||
|
||||
self.label_2 = QLabel(DatabaseChecker)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.label_2.setObjectName(u"label_2")
|
||||
|
||||
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_2)
|
||||
|
||||
self.line = QFrame(DatabaseChecker)
|
||||
self.line.setObjectName("line")
|
||||
self.line.setObjectName(u"line")
|
||||
self.line.setFrameShape(QFrame.HLine)
|
||||
self.line.setFrameShadow(QFrame.Sunken)
|
||||
|
||||
self.formLayout.setWidget(3, QFormLayout.SpanningRole, self.line)
|
||||
|
||||
self.label_4 = QLabel(DatabaseChecker)
|
||||
self.label_4.setObjectName(u"label_4")
|
||||
|
||||
self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_4)
|
||||
|
||||
self.dbReInitButton = QPushButton(DatabaseChecker)
|
||||
self.dbReInitButton.setObjectName(u"dbReInitButton")
|
||||
|
||||
self.formLayout.setWidget(5, QFormLayout.FieldRole, self.dbReInitButton)
|
||||
|
||||
|
||||
self.retranslateUi(DatabaseChecker)
|
||||
|
||||
QMetaObject.connectSlotsByName(DatabaseChecker)
|
||||
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, DatabaseChecker):
|
||||
self.label.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "dbPathLabel", None)
|
||||
)
|
||||
self.label_3.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "dbFilenameLabel", None)
|
||||
)
|
||||
self.confirmDbPathButton.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "confirmDbPathButton", None)
|
||||
)
|
||||
self.label_5.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "dbCheckConnLabel", None)
|
||||
)
|
||||
self.continueButton.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "continueButton", None)
|
||||
)
|
||||
self.label_2.setText(
|
||||
QCoreApplication.translate("DatabaseChecker", "dbVersionLabel", None)
|
||||
)
|
||||
self.label.setText(QCoreApplication.translate("DatabaseChecker", u"dbPathLabel", None))
|
||||
self.label_3.setText(QCoreApplication.translate("DatabaseChecker", u"dbFilenameLabel", None))
|
||||
self.confirmDbPathButton.setText(QCoreApplication.translate("DatabaseChecker", u"confirmDbPathButton", None))
|
||||
self.label_5.setText(QCoreApplication.translate("DatabaseChecker", u"dbCheckConnLabel", None))
|
||||
self.continueButton.setText(QCoreApplication.translate("DatabaseChecker", u"continueButton", None))
|
||||
self.label_2.setText(QCoreApplication.translate("DatabaseChecker", u"dbVersionLabel", None))
|
||||
self.label_4.setText(QCoreApplication.translate("DatabaseChecker", u"dbReInitLabel", None))
|
||||
self.dbReInitButton.setText(QCoreApplication.translate("DatabaseChecker", u"dbReInitButton", None))
|
||||
pass
|
||||
|
||||
# retranslateUi
|
||||
|
||||
|
0
ui/widgets/__init__.py
Normal file
0
ui/widgets/__init__.py
Normal file
231
ui/widgets/slidingstackedwidget.py
Normal file
231
ui/widgets/slidingstackedwidget.py
Normal file
@ -0,0 +1,231 @@
|
||||
"""
|
||||
Adapted from https://github.com/Qt-Widgets/SlidingStackedWidget-1
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2020 Tim Schneeberger (ThePBone) <tim.schneeberger(at)outlook.de>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from PySide6.QtCore import (
|
||||
QAbstractAnimation,
|
||||
QEasingCurve,
|
||||
QParallelAnimationGroup,
|
||||
QPoint,
|
||||
QPropertyAnimation,
|
||||
Signal,
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QGraphicsEffect,
|
||||
QGraphicsOpacityEffect,
|
||||
QStackedWidget,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
|
||||
class SlidingDirection(IntEnum):
|
||||
Auto = 0
|
||||
LeftToRight = 1
|
||||
RightToLeft = 2
|
||||
TopToBottom = 3
|
||||
BottomToTop = 4
|
||||
|
||||
|
||||
class SlidingStackedWidget(QStackedWidget):
|
||||
slidingDirection = SlidingDirection
|
||||
|
||||
animationFinished = Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.vertical = False
|
||||
self.speedMs = 300
|
||||
self.animationEasingCurve = QEasingCurve.Type.OutQuart
|
||||
self.animationCurrentIndex = 0
|
||||
self.animationNextIndex = 0
|
||||
self.animationCurrentPoint = QPoint(0, 0)
|
||||
self.animationRunning = False
|
||||
|
||||
self.wrap = False
|
||||
self.opacityAnimation = False
|
||||
|
||||
def setVertical(self, vertical: bool):
|
||||
self.vertical = vertical
|
||||
|
||||
def setSpeedMs(self, speedMs: int):
|
||||
self.speedMs = speedMs
|
||||
|
||||
def setAnimationEasingCurve(self, easingCurve: QEasingCurve.Type):
|
||||
self.animationEasingCurve = easingCurve
|
||||
|
||||
def setWrap(self, wrap: bool):
|
||||
self.wrap = wrap
|
||||
|
||||
def setOpacityAnimation(self, value: bool):
|
||||
self.opacityAnimation = value
|
||||
|
||||
def slideInNext(self) -> bool:
|
||||
currentIndex = self.currentIndex()
|
||||
if self.wrap or (currentIndex < self.count() - 1):
|
||||
self.slideInIdx(currentIndex + 1)
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def slideInPrev(self) -> bool:
|
||||
currentIndex = self.currentIndex()
|
||||
if self.wrap or (currentIndex > 0):
|
||||
self.slideInIdx(currentIndex - 1)
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def slideInIdx(self, idx: int, direction: SlidingDirection = SlidingDirection.Auto):
|
||||
if idx > self.count() - 1:
|
||||
direction = (
|
||||
SlidingDirection.TopToBottom
|
||||
if self.vertical
|
||||
else SlidingDirection.RightToLeft
|
||||
)
|
||||
idx %= self.count()
|
||||
elif idx < 0:
|
||||
direction = (
|
||||
SlidingDirection.BottomToTop
|
||||
if self.vertical
|
||||
else SlidingDirection.LeftToRight
|
||||
)
|
||||
idx = (idx + self.count()) % self.count()
|
||||
|
||||
self.slideInWgt(self.widget(idx), direction)
|
||||
|
||||
def slideInWgt(self, newwidget: QWidget, direction: SlidingDirection):
|
||||
if self.animationRunning:
|
||||
return
|
||||
|
||||
self.animationRunning = True
|
||||
|
||||
autoDirection = SlidingDirection.LeftToRight
|
||||
|
||||
currentIndex = self.currentIndex()
|
||||
nextIndex = self.indexOf(newwidget)
|
||||
if currentIndex == nextIndex:
|
||||
self.animationRunning = False
|
||||
return
|
||||
elif currentIndex < nextIndex:
|
||||
autoDirection = (
|
||||
SlidingDirection.TopToBottom
|
||||
if self.vertical
|
||||
else SlidingDirection.RightToLeft
|
||||
)
|
||||
else:
|
||||
autoDirection = (
|
||||
SlidingDirection.BottomToTop
|
||||
if self.vertical
|
||||
else SlidingDirection.LeftToRight
|
||||
)
|
||||
|
||||
if direction == SlidingDirection.Auto:
|
||||
direction = autoDirection
|
||||
|
||||
offsetX = self.frameRect().width()
|
||||
offsetY = self.frameRect().height()
|
||||
|
||||
self.widget(nextIndex).setGeometry(0, 0, offsetX, offsetY)
|
||||
if direction == SlidingDirection.BottomToTop:
|
||||
offsetX = 0
|
||||
offsetY = -offsetY
|
||||
elif direction == SlidingDirection.TopToBottom:
|
||||
offsetX = 0
|
||||
elif direction == SlidingDirection.RightToLeft:
|
||||
offsetX = -offsetX
|
||||
offsetY = 0
|
||||
elif direction == SlidingDirection.LeftToRight:
|
||||
offsetY = 0
|
||||
|
||||
nextPoint = self.widget(nextIndex).pos()
|
||||
currentPoint = self.widget(currentIndex).pos()
|
||||
self.animationCurrentPoint = currentPoint
|
||||
self.widget(nextIndex).move(nextPoint.x() - offsetX, nextPoint.y() - offsetY)
|
||||
|
||||
self.widget(nextIndex).show()
|
||||
self.widget(nextIndex).raise_()
|
||||
|
||||
currentWidgetAnimation = self.widgetPosAnimation(currentIndex)
|
||||
currentWidgetAnimation.setStartValue(QPoint(currentPoint.x(), currentPoint.y()))
|
||||
currentWidgetAnimation.setEndValue(
|
||||
QPoint(offsetX + currentPoint.x(), offsetY + currentPoint.y())
|
||||
)
|
||||
|
||||
nextWidgetAnimation = self.widgetPosAnimation(nextIndex)
|
||||
nextWidgetAnimation.setStartValue(
|
||||
QPoint(-offsetX + nextPoint.x(), offsetY + nextPoint.y())
|
||||
)
|
||||
nextWidgetAnimation.setEndValue(QPoint(nextPoint.x(), nextPoint.y()))
|
||||
|
||||
animationGroup = QParallelAnimationGroup(self)
|
||||
animationGroup.addAnimation(currentWidgetAnimation)
|
||||
animationGroup.addAnimation(nextWidgetAnimation)
|
||||
|
||||
if self.opacityAnimation:
|
||||
currentWidgetOpacityEffect = QGraphicsOpacityEffect()
|
||||
currentWidgetOpacityEffectAnimation = self.widgetOpacityAnimation(
|
||||
currentIndex, currentWidgetOpacityEffect, 1, 0
|
||||
)
|
||||
|
||||
nextWidgetOpacityEffect = QGraphicsOpacityEffect()
|
||||
nextWidgetOpacityEffect.setOpacity(0)
|
||||
nextWidgetOpacityEffectAnimation = self.widgetOpacityAnimation(
|
||||
nextIndex, nextWidgetOpacityEffect, 0, 1
|
||||
)
|
||||
|
||||
animationGroup.addAnimation(currentWidgetOpacityEffectAnimation)
|
||||
animationGroup.addAnimation(nextWidgetOpacityEffectAnimation)
|
||||
|
||||
animationGroup.finished.connect(self.animationDoneSlot)
|
||||
self.animationNextIndex = nextIndex
|
||||
self.animationCurrentIndex = currentIndex
|
||||
self.animationRunning = True
|
||||
animationGroup.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||
|
||||
def widgetPosAnimation(self, widgetIndex: int):
|
||||
result = QPropertyAnimation(self.widget(widgetIndex), b"pos")
|
||||
result.setDuration(self.speedMs)
|
||||
result.setEasingCurve(self.animationEasingCurve)
|
||||
return result
|
||||
|
||||
def widgetOpacityAnimation(
|
||||
self, widgetIndex: int, graphicEffect: QGraphicsEffect, startValue, endValue
|
||||
):
|
||||
self.widget(widgetIndex).setGraphicsEffect(graphicEffect)
|
||||
result = QPropertyAnimation(graphicEffect, b"opacity")
|
||||
result.setDuration(round(self.speedMs / 2))
|
||||
result.setStartValue(startValue)
|
||||
result.setEndValue(endValue)
|
||||
result.finished.connect(
|
||||
lambda: graphicEffect.deleteLater() if graphicEffect is not None else ...
|
||||
)
|
||||
return result
|
||||
|
||||
def animationDoneSlot(self):
|
||||
self.setCurrentIndex(self.animationNextIndex)
|
||||
self.widget(self.animationCurrentIndex).hide()
|
||||
self.widget(self.animationCurrentIndex).move(self.animationCurrentPoint)
|
||||
self.animationRunning = False
|
||||
self.animationFinished.emit()
|
Reference in New Issue
Block a user