310 lines
12 KiB
Python

from enum import IntEnum
from typing import Any, Optional
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.models import Chart, Score
from PySide6.QtCore import QCoreApplication, QDateTime, Signal, Slot
from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
QDateTimeEdit,
QLineEdit,
QMessageBox,
QSpinBox,
QWidget,
)
from ui.designer.components.scoreEditor_ui import Ui_ScoreEditor
from ui.extends.shared.language import LanguageChangeEventFilter
class ScoreValidateResult(IntEnum):
Ok = 0
ScoreMismatch = 1
ScoreEmpty = 2
ChartInvalid = 50
ScoreIncomplete = 100
class ScoreEditor(Ui_ScoreEditor, QWidget):
valueChanged = Signal()
accepted = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.__validateBeforeAccept = True
self.__chart = None
self.__score_id = None
self.scoreLineEdit.textChanged.connect(self.valueChanged)
self.pureSpinBox.valueChanged.connect(self.valueChanged)
self.farSpinBox.valueChanged.connect(self.valueChanged)
self.lostSpinBox.valueChanged.connect(self.valueChanged)
self.dateTimeEdit.dateTimeChanged.connect(self.valueChanged)
self.maxRecallSpinBox.valueChanged.connect(self.valueChanged)
self.modifierComboBox.currentIndexChanged.connect(self.valueChanged)
self.clearTypeComboBox.currentIndexChanged.connect(self.valueChanged)
self.commentLineEdit.textChanged.connect(self.valueChanged)
self.pureNoneCheckBox.toggled.connect(self.valueChanged)
self.farNoneCheckBox.toggled.connect(self.valueChanged)
self.lostNoneCheckBox.toggled.connect(self.valueChanged)
self.dateNoneCheckBox.toggled.connect(self.valueChanged)
self.maxRecallNoneCheckBox.toggled.connect(self.valueChanged)
self.modifierNoneCheckBox.toggled.connect(self.valueChanged)
self.clearTypeNoneCheckBox.toggled.connect(self.valueChanged)
self.commentNoneCheckBox.toggled.connect(self.valueChanged)
self.valueChanged.connect(self.validateScore)
self.valueChanged.connect(self.updateValidateLabel)
self.modifierComboBox.addItem("NORMAL", 0)
self.modifierComboBox.addItem("EASY", 1)
self.modifierComboBox.addItem("HARD", 2)
self.modifierComboBox.setCurrentIndex(-1)
self.clearTypeComboBox.addItem("TRACK LOST", 0)
self.clearTypeComboBox.addItem("NORMAL CLEAR", 1)
self.clearTypeComboBox.addItem("FULL RECALL", 2)
self.clearTypeComboBox.addItem("PURE MEMORY", 3)
self.clearTypeComboBox.addItem("EASY CLEAR", 4)
self.clearTypeComboBox.addItem("HARD CLEAR", 5)
self.clearTypeComboBox.setCurrentIndex(-1)
self.dateTimeEdit.setDateTime(QDateTime.currentDateTime())
def setValidateBeforeAccept(self, __bool: bool):
self.__validateBeforeAccept = __bool
def triggerValidateMessageBox(self):
validate = self.validateScore()
if validate in [ScoreValidateResult.Ok, ScoreValidateResult.ScoreIncomplete]:
return True
if validate == ScoreValidateResult.ChartInvalid:
QMessageBox.critical(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "chartInvalidDialog.title"),
QCoreApplication.translate("ScoreEditor", "chartInvalidDialog.title"),
# fmt: on
)
return False
if validate == ScoreValidateResult.ScoreMismatch:
result = QMessageBox.warning(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "scoreMismatchDialog.title"),
QCoreApplication.translate("ScoreEditor", "scoreMismatchDialog.content"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
return result == QMessageBox.StandardButton.Yes
elif validate == ScoreValidateResult.ScoreEmpty:
result = QMessageBox.warning(
self,
# fmt: off
QCoreApplication.translate("ScoreEditor", "emptyScoreDialog.title"),
QCoreApplication.translate("ScoreEditor", "emptyScoreDialog.content"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
return result == QMessageBox.StandardButton.Yes
else:
return False
@Slot()
def on_commitButton_clicked(self):
userAccept = (
self.triggerValidateMessageBox() if self.__validateBeforeAccept else True
)
if userAccept:
self.accepted.emit()
def score(self):
score_text = self.scoreLineEdit.text().replace("'", "")
return int(score_text) if score_text else 0
def setMinimums(self):
self.pureSpinBox.setMinimum(0)
self.farSpinBox.setMinimum(0)
self.lostSpinBox.setMinimum(0)
self.maxRecallSpinBox.setMinimum(-1)
def setLimits(self, chart: Chart):
self.setMinimums()
self.pureSpinBox.setMaximum(chart.notes)
self.farSpinBox.setMaximum(chart.notes)
self.lostSpinBox.setMaximum(chart.notes)
self.maxRecallSpinBox.setMaximum(chart.notes)
def resetLimits(self):
self.setMinimums()
self.pureSpinBox.setMaximum(0)
self.farSpinBox.setMaximum(0)
self.lostSpinBox.setMaximum(0)
self.maxRecallSpinBox.setMaximum(0)
def setChart(self, chart: Optional[Chart]):
if isinstance(chart, Chart):
self.__chart = chart
self.setLimits(chart)
else:
self.__chart = None
self.resetLimits()
self.updateValidateLabel()
def validateScore(self) -> ScoreValidateResult:
if not isinstance(self.__chart, Chart):
return ScoreValidateResult.ChartInvalid
score = self.value()
if score.pure is None or score.far is None:
return ScoreValidateResult.ScoreIncomplete
score_range = calculate_score_range(self.__chart.notes, score.pure, score.far)
note_in_range = score.pure + score.far + score.lost <= self.__chart.notes
score_in_range = score_range[0] <= score.score <= score_range[1]
if not score_in_range or not note_in_range:
return ScoreValidateResult.ScoreMismatch
if score.score == 0:
return ScoreValidateResult.ScoreEmpty
return ScoreValidateResult.Ok
def updateValidateLabel(self):
validate = self.validateScore()
if validate == ScoreValidateResult.Ok:
text = QCoreApplication.translate("ScoreEditor", "validate.ok")
elif validate == ScoreValidateResult.ChartInvalid:
text = QCoreApplication.translate("ScoreEditor", "validate.chartInvalid")
elif validate == ScoreValidateResult.ScoreMismatch:
text = QCoreApplication.translate("ScoreEditor", "validate.scoreMismatch")
elif validate == ScoreValidateResult.ScoreEmpty:
text = QCoreApplication.translate("ScoreEditor", "validate.scoreEmpty")
elif validate == ScoreValidateResult.ScoreIncomplete:
text = QCoreApplication.translate("ScoreEditor", "validate.scoreIncomplete")
else:
text = QCoreApplication.translate("ScoreEditor", "validate.unknownState")
self.validateLabel.setText(text)
def __getItemBaseName(self, item: QLineEdit | QSpinBox | QDateTimeEdit | QComboBox):
if isinstance(item, QSpinBox):
return item.objectName().replace("SpinBox", "")
elif isinstance(item, QLineEdit):
if item.objectName() == "scoreLineEdit":
return "score"
return item.objectName().replace("LineEdit", "")
elif isinstance(item, QComboBox):
return item.objectName().replace("ComboBox", "")
elif isinstance(item, QDateTimeEdit):
return "date"
def __getItemNoneCheckBox(self, itemBaseName: str) -> QCheckBox | None:
return self.findChild(QCheckBox, f"{itemBaseName}NoneCheckBox")
def __getItemEnabled(self, itemBaseName: str):
return not self.__getItemNoneCheckBox(itemBaseName).isChecked()
def getItemValue(self, item: QLineEdit | QSpinBox | QDateTimeEdit | QComboBox):
if isinstance(item, QDateTimeEdit) and item.objectName() == "dateTimeEdit":
return (
None
if self.dateNoneCheckBox.isChecked()
else self.dateTimeEdit.dateTime().toSecsSinceEpoch()
)
itemBaseName = self.__getItemBaseName(item)
itemEnabled = self.__getItemEnabled(itemBaseName)
if isinstance(item, QSpinBox):
return item.value() if itemEnabled else None
elif isinstance(item, QLineEdit):
return item.text() if itemEnabled else None
elif isinstance(item, QComboBox):
return item.currentData() if itemEnabled else None
def value(self):
if not isinstance(self.__chart, Chart):
return
score = Score(
song_id=self.__chart.song_id, rating_class=self.__chart.rating_class
)
if self.__score_id is not None:
score.id = self.__score_id
score.score = self.score()
score.pure = self.getItemValue(self.pureSpinBox)
score.far = self.getItemValue(self.farSpinBox)
score.lost = self.getItemValue(self.lostSpinBox)
score.date = self.getItemValue(self.dateTimeEdit)
score.max_recall = self.getItemValue(self.maxRecallSpinBox)
score.modifier = self.getItemValue(self.modifierComboBox)
score.clear_type = self.getItemValue(self.clearTypeComboBox)
score.comment = self.getItemValue(self.commentLineEdit)
return score
def setItemValue(
self, item: QLineEdit | QSpinBox | QDateTimeEdit | QComboBox, value: Any
):
if isinstance(item, QDateTimeEdit) and item.objectName() == "dateTimeEdit":
if value is None:
self.dateNoneCheckBox.setChecked(True)
else:
self.dateNoneCheckBox.setChecked(False)
self.dateTimeEdit.setDateTime(QDateTime.fromSecsSinceEpoch(value))
itemBaseName = self.__getItemBaseName(item)
itemNoneCheckBox = self.__getItemNoneCheckBox(itemBaseName)
if value is None:
itemNoneCheckBox.setChecked(True)
return
else:
itemNoneCheckBox.setChecked(False)
if isinstance(item, QSpinBox):
item.setValue(value)
elif isinstance(item, QLineEdit):
item.setText(value)
elif isinstance(item, QComboBox):
item.setCurrentIndex(value)
def setValue(self, score: Score):
if not isinstance(score, Score):
return
if score.id is not None:
self.__score_id = score.id
self.idLabel.setText(str(self.__score_id))
scoreText = str(score.score)
scoreText = scoreText.rjust(8, "0")
self.scoreLineEdit.setText(scoreText)
self.setItemValue(self.pureSpinBox, score.pure)
self.setItemValue(self.farSpinBox, score.far)
self.setItemValue(self.lostSpinBox, score.lost)
self.setItemValue(self.dateTimeEdit, score.date)
self.setItemValue(self.maxRecallSpinBox, score.max_recall)
self.setItemValue(self.modifierComboBox, score.modifier)
self.setItemValue(self.clearTypeComboBox, score.clear_type)
self.setItemValue(self.commentLineEdit, score.comment)
def reset(self):
self.setChart(None)
self.scoreLineEdit.setText("''")
self.pureSpinBox.setValue(0)
self.farSpinBox.setValue(0)
self.lostSpinBox.setValue(0)
self.maxRecallSpinBox.setValue(0)
self.modifierComboBox.setCurrentIndex(-1)
self.clearTypeComboBox.setCurrentIndex(-1)
self.commentLineEdit.setText("")