mirror of
https://github.com/283375/arcaea-offline-pyside-ui.git
synced 2025-04-18 16:50:17 +00:00
220 lines
8.0 KiB
Python
220 lines
8.0 KiB
Python
import logging
|
|
|
|
from arcaea_offline.calculate import calculate_play_rating
|
|
from arcaea_offline.models import Chart, Score
|
|
from PySide6.QtCore import QCoreApplication, QModelIndex, QSortFilterProxyModel, Qt
|
|
|
|
from .base import DbTableModel
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DbScoreTableModel(DbTableModel):
|
|
IdRole = Qt.ItemDataRole.UserRole + 10
|
|
ChartRole = Qt.ItemDataRole.UserRole + 11
|
|
ScoreRole = Qt.ItemDataRole.UserRole + 12
|
|
PttRole = Qt.ItemDataRole.UserRole + 13
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.__items = []
|
|
|
|
def retranslateHeaders(self):
|
|
self._horizontalHeaders = [
|
|
# fmt: off
|
|
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.id"),
|
|
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.chart"),
|
|
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.score"),
|
|
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.potential"),
|
|
# fmt: on
|
|
]
|
|
|
|
def syncDb(self):
|
|
newScores = self._db.get_scores()
|
|
newScores = sorted(newScores, key=lambda x: x.id)
|
|
newCharts = []
|
|
for score in newScores:
|
|
dbChart = self._db.get_chart(score.song_id, score.rating_class)
|
|
newCharts.append(
|
|
dbChart
|
|
if isinstance(dbChart, Chart)
|
|
else Chart(
|
|
song_id=score.song_id,
|
|
rating_class=score.rating_class,
|
|
title=score.song_id,
|
|
set="unknown",
|
|
)
|
|
)
|
|
newPtts = []
|
|
for chart, score in zip(newCharts, newScores):
|
|
if (
|
|
isinstance(chart, Chart)
|
|
and chart.constant is not None
|
|
and isinstance(score, Score)
|
|
):
|
|
newPtts.append(calculate_play_rating(chart.constant, score.score))
|
|
else:
|
|
newPtts.append(None)
|
|
|
|
newScoreIds = [score.id for score in newScores]
|
|
oldScoreIds = [item[self.ScoreRole].id for item in self.__items]
|
|
|
|
deleteIds = list(set(oldScoreIds) - set(newScoreIds))
|
|
newIds = list(set(newScoreIds) - set(oldScoreIds))
|
|
deleteRowIndexes = [oldScoreIds.index(deleteId) for deleteId in deleteIds]
|
|
|
|
# first delete rows
|
|
for deleteRowIndex in sorted(deleteRowIndexes, reverse=True):
|
|
self.beginRemoveRows(QModelIndex(), deleteRowIndex, deleteRowIndex)
|
|
self.__items.pop(deleteRowIndex)
|
|
self.endRemoveRows()
|
|
|
|
# now update existing datas
|
|
for oldItem, newChart, newScore, newPtt in zip(
|
|
self.__items, newCharts, newScores, newPtts
|
|
):
|
|
oldItem[self.IdRole] = newScore.id
|
|
oldItem[self.ChartRole] = newChart
|
|
oldItem[self.ScoreRole] = newScore
|
|
oldItem[self.PttRole] = newPtt
|
|
|
|
# finally insert new rows
|
|
for newId in newIds:
|
|
insertRowIndex = self.rowCount()
|
|
itemListIndex = newScoreIds.index(newId)
|
|
score = newScores[itemListIndex]
|
|
chart = newCharts[itemListIndex]
|
|
ptt = newPtts[itemListIndex]
|
|
self.beginInsertRows(QModelIndex(), insertRowIndex, insertRowIndex)
|
|
self.__items.append(
|
|
{
|
|
self.IdRole: score.id,
|
|
self.ChartRole: chart,
|
|
self.ScoreRole: score,
|
|
self.PttRole: ptt,
|
|
}
|
|
)
|
|
self.endInsertRows()
|
|
|
|
# trigger view update
|
|
topLeft = self.index(0, 0)
|
|
bottomRight = self.index(self.rowCount() - 1, self.columnCount() - 1)
|
|
self.dataChanged.emit(
|
|
topLeft,
|
|
bottomRight,
|
|
[Qt.ItemDataRole.DisplayRole, self.IdRole, self.ChartRole, self.ScoreRole],
|
|
)
|
|
|
|
def rowCount(self, *args):
|
|
return len(self.__items)
|
|
|
|
def data(self, index, role):
|
|
if index.isValid() and self.checkIndex(index):
|
|
if index.column() == 0 and role in [
|
|
Qt.ItemDataRole.DisplayRole,
|
|
self.IdRole,
|
|
]:
|
|
return self.__items[index.row()][self.IdRole]
|
|
elif index.column() == 1 and role == self.ChartRole:
|
|
return self.__items[index.row()][self.ChartRole]
|
|
elif index.column() == 2 and role in [self.ChartRole, self.ScoreRole]:
|
|
return self.__items[index.row()][role]
|
|
elif index.column() == 3:
|
|
potential = self.__items[index.row()][self.PttRole]
|
|
if role == Qt.ItemDataRole.DisplayRole:
|
|
return f"{potential:.3f}" if potential is not None else "-"
|
|
elif role == self.PttRole:
|
|
return potential
|
|
return None
|
|
|
|
def setData(self, index, value, role):
|
|
if not (index.isValid() and self.checkIndex(index)):
|
|
return False
|
|
|
|
if index.column() == 2 and isinstance(value, Score) and role == self.ScoreRole:
|
|
self._db.update_score(self.__items[index.row()][self.IdRole], value)
|
|
self.syncDb()
|
|
return True
|
|
|
|
return False
|
|
|
|
def flags(self, index) -> Qt.ItemFlag:
|
|
flags = super().flags(index)
|
|
flags |= Qt.ItemFlag.ItemIsSelectable
|
|
if index.column() in [1, 2]:
|
|
flags |= Qt.ItemFlag.ItemIsEditable
|
|
return flags
|
|
|
|
def _removeRow(self, row: int, syncDb: bool = True):
|
|
if not 0 <= row < self.rowCount():
|
|
return False
|
|
|
|
try:
|
|
self._db.delete_score(self.__items[row][self.ScoreRole])
|
|
if syncDb:
|
|
self.syncDb()
|
|
return True
|
|
except Exception:
|
|
logger.exception(f"Table[Score]: Cannot remove row {row}")
|
|
return False
|
|
|
|
def removeRow(self, row: int, parent=...):
|
|
return self._removeRow(row)
|
|
|
|
def removeRows(self, row: int, count: int, parent=...):
|
|
maxRow = min(self.rowCount() - 1, row + count - 1)
|
|
if row > maxRow:
|
|
return False
|
|
|
|
result = all(
|
|
self._removeRow(row, syncDb=False) for row in range(row, row + count)
|
|
)
|
|
self.syncDb()
|
|
return result
|
|
|
|
def removeRowList(self, rowList: list[int]):
|
|
result = all(
|
|
self._removeRow(row, syncDb=False) for row in sorted(rowList, reverse=True)
|
|
)
|
|
self.syncDb()
|
|
return result
|
|
|
|
|
|
class DbScoreTableSortFilterProxyModel(QSortFilterProxyModel):
|
|
Sort_C2_ScoreRole = Qt.ItemDataRole.UserRole + 75
|
|
Sort_C2_TimeRole = Qt.ItemDataRole.UserRole + 76
|
|
|
|
def lessThan(self, sourceLeft, sourceRight) -> bool:
|
|
if sourceLeft.column() != sourceRight.column():
|
|
return
|
|
|
|
column = sourceLeft.column()
|
|
if column == 0:
|
|
return sourceLeft.data(DbScoreTableModel.IdRole) < sourceRight.data(
|
|
DbScoreTableModel.IdRole
|
|
)
|
|
elif column == 2:
|
|
scoreLeft = sourceLeft.data(DbScoreTableModel.ScoreRole)
|
|
scoreRight = sourceRight.data(DbScoreTableModel.ScoreRole)
|
|
if isinstance(scoreLeft, Score) and isinstance(scoreRight, Score):
|
|
if self.sortRole() == self.Sort_C2_ScoreRole:
|
|
return scoreLeft.score < scoreRight.score
|
|
elif self.sortRole() == self.Sort_C2_TimeRole:
|
|
if scoreLeft.date and scoreRight.date:
|
|
return scoreLeft.date < scoreRight.date
|
|
elif scoreLeft.date:
|
|
return False
|
|
else:
|
|
return True
|
|
elif column == 3:
|
|
pttLeft = sourceLeft.data(DbScoreTableModel.PttRole)
|
|
pttRight = sourceRight.data(DbScoreTableModel.PttRole)
|
|
if pttLeft and pttRight:
|
|
return pttLeft < pttRight
|
|
elif pttLeft:
|
|
return False
|
|
else:
|
|
return True
|
|
return super().lessThan(sourceLeft, sourceRight)
|