diff --git a/ui/extends/shared/models/tables/b30.py b/ui/extends/shared/models/tables/b30.py new file mode 100644 index 0000000..e6eebdc --- /dev/null +++ b/ui/extends/shared/models/tables/b30.py @@ -0,0 +1,139 @@ +from arcaea_offline.models import Chart, Score +from PySide6.QtCore import QCoreApplication, QModelIndex, QSortFilterProxyModel, Qt + +from .base import DbTableModel + + +class DbB30TableModel(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("DB30TableModel", "horizontalHeader.id"), + QCoreApplication.translate("DB30TableModel", "horizontalHeader.chart"), + QCoreApplication.translate("DB30TableModel", "horizontalHeader.score"), + QCoreApplication.translate("DB30TableModel", "horizontalHeader.potential"), + # fmt: on + ] + + def syncDb(self): + self.__items.clear() + + results = self._db.conn.execute( + 'SELECT * FROM calculated ORDER BY "potential" DESC LIMIT 40' + ).fetchall() + + songIds = [r[0] for r in results] + ptts = [r[-1] for r in results] + + for scoreId, ptt in zip(songIds, ptts): + score = Score.from_db_row(self._db.get_scores(score_id=scoreId)[0]) + chart = Chart.from_db_row( + self._db.get_chart(score.song_id, score.rating_class) + ) + + self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) + self.__items.append( + { + self.IdRole: score.id, + self.ChartRole: chart, + self.ScoreRole: score, + self.PttRole: ptt, + } + ) + self.endInsertRows() + + # trigger view update + topLeft = self.index(0, 0) + bottomRight = self.index(self.rowCount() - 1, self.columnCount() - 1) + self.dataChanged.emit( + topLeft, + bottomRight, + [Qt.ItemDataRole.DisplayRole, self.IdRole, self.ChartRole, self.ScoreRole], + ) + + def rowCount(self, *args): + return len(self.__items) + + def data(self, index, role): + if index.isValid() and self.checkIndex(index): + if index.column() == 0 and role in [ + Qt.ItemDataRole.DisplayRole, + self.IdRole, + ]: + return self.__items[index.row()][self.IdRole] + elif index.column() == 1 and role == self.ChartRole: + return self.__items[index.row()][self.ChartRole] + elif index.column() == 2 and role in [self.ChartRole, self.ScoreRole]: + return self.__items[index.row()][role] + elif index.column() == 3: + if role == Qt.ItemDataRole.DisplayRole: + return f"{self.__items[index.row()][self.PttRole]:.3f}" + elif role == self.PttRole: + return self.__items[index.row()][self.PttRole] + return None + + def setData(self, index, value, role): + return False + + def flags(self, index) -> Qt.ItemFlag: + flags = super().flags(index) + flags &= ~(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable) + return flags + + def _removeRow(self, row: int, syncDb: bool = True): + return False + + def removeRow(self, row: int, parent=...): + return False + + def removeRows(self, row: int, count: int, parent=...): + return False + + def removeRowList(self, rowList: list[int]): + return False + + +class DbB30TableSortFilterProxyModel(QSortFilterProxyModel): + Sort_C2_ScoreRole = Qt.ItemDataRole.UserRole + 75 + Sort_C2_TimeRole = Qt.ItemDataRole.UserRole + 76 + + def headerData(self, section: int, orientation: Qt.Orientation, role: int): + # always show not sorted row sequence + if ( + orientation != Qt.Orientation.Vertical + or role != Qt.ItemDataRole.DisplayRole + ): + return super().headerData(section, orientation, role) + return section + 1 + + def lessThan(self, source_left, source_right) -> bool: + if source_left.column() != source_right.column(): + return + + column = source_left.column() + if column == 0: + return source_left.data(DbB30TableModel.IdRole) < source_right.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): + if self.sortRole() == self.Sort_C2_ScoreRole: + return score_left.score < score_right.score + elif self.sortRole() == self.Sort_C2_TimeRole: + return score_left.time < score_right.time + elif column == 3: + return source_left.data(DbB30TableModel.PttRole) < source_right.data( + DbB30TableModel.PttRole + ) + return super().lessThan(source_left, source_right) diff --git a/ui/implements/tabs/tabDb/tabDb_B30TableViewer.py b/ui/implements/tabs/tabDb/tabDb_B30TableViewer.py new file mode 100644 index 0000000..b113d2d --- /dev/null +++ b/ui/implements/tabs/tabDb/tabDb_B30TableViewer.py @@ -0,0 +1,100 @@ +from arcaea_offline.models import ScoreInsert +from PySide6.QtCore import QModelIndex, Qt, Slot +from PySide6.QtGui import QColor, QPalette +from PySide6.QtWidgets import QMessageBox + +from ui.extends.shared.delegates.chartDelegate import ChartDelegate +from ui.extends.shared.delegates.scoreDelegate import ScoreDelegate +from ui.extends.shared.models.tables.b30 import ( + DbB30TableModel, + DbB30TableSortFilterProxyModel, +) +from ui.implements.components.dbTableViewer import DbTableViewer + + +class TableChartDelegate(ChartDelegate): + def getChart(self, index): + return index.data(DbB30TableModel.ChartRole) + + +class TableScoreDelegate(ScoreDelegate): + def getChart(self, index): + return index.data(DbB30TableModel.ChartRole) + + def getScoreInsert(self, index: QModelIndex) -> ScoreInsert | None: + return super().getScoreInsert(index) + + def getScore(self, index): + return index.data(DbB30TableModel.ScoreRole) + + def setModelData(self, editor, model, index): + QMessageBox.information(self, None, "Cannot edit read only table.") + return False + + +class DbB30TableViewer(DbTableViewer): + def __init__(self, parent=None): + super().__init__(parent) + self.tableView.verticalHeader().setVisible(True) + + self.tableModel = DbB30TableModel(self) + self.tableProxyModel = DbB30TableSortFilterProxyModel(self) + self.tableProxyModel.setSourceModel(self.tableModel) + self.tableView.setModel(self.tableProxyModel) + self.tableView.setItemDelegateForColumn(1, TableChartDelegate(self.tableView)) + self.tableView.setItemDelegateForColumn(2, TableScoreDelegate(self.tableView)) + + tableViewPalette = QPalette(self.tableView.palette()) + highlightColor = QColor(tableViewPalette.color(QPalette.ColorRole.Highlight)) + highlightColor.setAlpha(25) + tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor) + self.tableView.setPalette(tableViewPalette) + self.tableModel.dataChanged.connect(self.resizeTableView) + + self.fillSortComboBox() + + def fillSortComboBox(self): + self.sort_comboBox.addItem("ID", [0, 1]) + self.sort_comboBox.addItem( + "Score", [3, DbB30TableSortFilterProxyModel.Sort_C2_ScoreRole] + ) + self.sort_comboBox.addItem( + "Time", [3, DbB30TableSortFilterProxyModel.Sort_C2_TimeRole] + ) + self.sort_comboBox.addItem("Potential", [3, 1]) + self.sort_comboBox.setCurrentIndex(0) + self.on_sort_comboBox_activated() + + @Slot() + def resizeTableView(self): + self.tableView.resizeRowsToContents() + self.tableView.resizeColumnsToContents() + + @Slot() + def on_sort_comboBox_activated(self): + self.sortProxyModel() + + @Slot() + def on_sort_descendingCheckBox_toggled(self): + self.sortProxyModel() + + @Slot() + def sortProxyModel(self): + if self.sort_comboBox.currentIndex() > -1: + column, role = self.sort_comboBox.currentData() + self.tableProxyModel.setSortRole(role) + self.tableProxyModel.sort( + column, + Qt.SortOrder.DescendingOrder + if self.sort_descendingCheckBox.isChecked() + else Qt.SortOrder.AscendingOrder, + ) + + @Slot() + def on_action_removeSelectedButton_clicked(self): + QMessageBox.information(self, None, "Cannot edit read only table.") + + @Slot() + def on_refreshButton_clicked(self): + self.tableModel.syncDb() + self.resizeTableView() diff --git a/ui/implements/tabs/tabDbEntry.py b/ui/implements/tabs/tabDbEntry.py index 835bb5c..41d9094 100644 --- a/ui/implements/tabs/tabDbEntry.py +++ b/ui/implements/tabs/tabDbEntry.py @@ -2,6 +2,7 @@ from PySide6.QtCore import QCoreApplication from PySide6.QtWidgets import QWidget from ui.designer.tabs.tabDbEntry_ui import Ui_TabDbEntry +from ui.implements.tabs.tabDb.tabDb_B30TableViewer import DbB30TableViewer from ui.implements.tabs.tabDb.tabDb_ScoreTableViewer import DbScoreTableViewer @@ -14,3 +15,7 @@ class TabDbEntry(Ui_TabDbEntry, QWidget): DbScoreTableViewer(self), QCoreApplication.translate("TabDbEntry", "tab.scoreTableViewer"), ) + self.tabWidget.addTab( + DbB30TableViewer(self), + QCoreApplication.translate("TabDbEntry", "tab.b30TableViewer"), + ) diff --git a/ui/implements/tabs/tabOcr/tabOcr_B30.py b/ui/implements/tabs/tabOcr/tabOcr_B30.py index b63975e..c7a4a70 100644 --- a/ui/implements/tabs/tabOcr/tabOcr_B30.py +++ b/ui/implements/tabs/tabOcr/tabOcr_B30.py @@ -12,8 +12,8 @@ from PySide6.QtWidgets import QWidget from ui.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30 from ui.extends.components.ocrQueue import OcrQueueModel -from ui.extends.shared.settings import Settings from ui.extends.shared.cv2_utils import cv2BgrMatToQImage, qImageToCvMatBgr +from ui.extends.shared.settings import Settings from ui.extends.tabs.tabOcr.tabOcr_B30 import ( ChieriV4OcrRunnable, b30ResultToScoreInsert, diff --git a/ui/resources/translations/en_US.ts b/ui/resources/translations/en_US.ts index 6da6f8a..24bae0e 100644 --- a/ui/resources/translations/en_US.ts +++ b/ui/resources/translations/en_US.ts @@ -49,6 +49,34 @@ Reset + + DB30TableModel + + + horizontalHeader.tableId + No. + + + + horizontalHeader.id + ID + + + + horizontalHeader.chart + Chart + + + + horizontalHeader.score + Score + + + + horizontalHeader.potential + Potential + + DatabaseChecker @@ -260,22 +288,22 @@ validation OcrTableModel - + horizontalHeader.title.select Select - + horizontalHeader.title.imagePreview Image Preview - + horizontalHeader.title.chart Chart - + horizontalHeader.title.score Score @@ -412,10 +440,15 @@ validation Manage - + tab.scoreTableViewer TABLE [Score] + + + tab.b30TableViewer + TABLE [B30] + TabDb_Manage diff --git a/ui/resources/translations/zh_CN.ts b/ui/resources/translations/zh_CN.ts index 5d9d864..102d09d 100644 --- a/ui/resources/translations/zh_CN.ts +++ b/ui/resources/translations/zh_CN.ts @@ -49,6 +49,34 @@ 重置 + + DB30TableModel + + + horizontalHeader.tableId + 序号 + + + + horizontalHeader.id + ID + + + + horizontalHeader.chart + 谱面 + + + + horizontalHeader.score + 分数 + + + + horizontalHeader.potential + 单曲 PTT + + DatabaseChecker @@ -259,22 +287,22 @@ OcrTableModel - + horizontalHeader.title.select 选择 - + horizontalHeader.title.imagePreview 图像预览 - + horizontalHeader.title.chart 谱面 - + horizontalHeader.title.score 分数 @@ -411,10 +439,15 @@ 管理 - + tab.scoreTableViewer 表 [分数] + + + tab.b30TableViewer + 表 [B30] + TabDb_Manage