From b48e177ae817b9be746b5f64ad08f62e0edd7c7b Mon Sep 17 00:00:00 2001 From: 283375 Date: Wed, 25 Oct 2023 17:41:40 +0800 Subject: [PATCH] feat: `TabDb_RemoveDuplicateScores` --- .../tabs/tabDb/tabDb_RemoveDuplicateScores.ui | 376 +++++++++++------- .../tabDb/tabDb_RemoveDuplicateScores_ui.py | 179 ++++++--- ui/designer/tabs/tabDbEntry.ui | 11 + ui/designer/tabs/tabDbEntry_ui.py | 5 + .../tabs/tabDb/tabDb_RemoveDuplicateScores.py | 211 +++++++++- ui/resources/lang/en_US.ts | 115 +++++- ui/resources/lang/zh_CN.ts | 115 +++++- 7 files changed, 771 insertions(+), 241 deletions(-) diff --git a/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui b/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui index 6955e94..edfa693 100644 --- a/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui +++ b/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores.ui @@ -6,31 +6,31 @@ 0 0 - 700 + 600 500 TabDb_RemoveDuplicateScores - - - - - - - scan.title - - - - - - scan.option.score - - - + + + + + scan.title + + + + + + + + scan.option.score + + + @@ -52,155 +52,235 @@ - - - - - - MAX RECALL - - - - - - + - scan.option.clearType - - - - - - - scan.option.modifier + MAX RECALL + + + + - + - scan.scanButton + scan.option.date + + + + + + + scan.option.modifier + + + + + + + scan.option.clearType - - - - - - quickSelect.title - - - - - - quickSelect.keepSingleLabel - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 91 - - - - - - - - quickSelect.selectButton - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - collapseAllButton - - - - - - - expandAllButton - - - - - - - resetModelButton - - - - - - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::NoSelection - - - QAbstractItemView::ScrollPerPixel - + + + + + scan.scanButton + + + + - - - - - true - - - - QPushButton { color: red }; - - - deleteSelectedButton - - + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + quickSelect.title + + + + + + quickSelect.description + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + quickSelect.selectButton + + + + + + + + + + + + + + + + deselectAllButton + + + + + + + reverseSelectionButton + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + collapseAllButton + + + + + + + expandAllButton + + + + + + + resetModelButton + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + true + + + + QPushButton { color: red }; + + + deleteSelectionButton + + + + + + + + + + + scan_option_scoreCheckBox + scan_option_pureCheckBox + scan_option_farCheckBox + scan_option_lostCheckBox + scan_option_maxRecallCheckBox + scan_option_dateCheckBox + scan_option_modifierCheckBox + scan_option_clearTypeCheckBox + scan_scanButton + treeView + quickSelect_comboBox + quickSelect_selectButton + deselectAllButton + reverseSelectionButton + collapseAllButton + expandAllButton + resetModelButton + deleteSelectionButton + diff --git a/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py b/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py index cae37a9..f7247e3 100644 --- a/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py +++ b/ui/designer/tabs/tabDb/tabDb_RemoveDuplicateScores_ui.py @@ -16,31 +16,31 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox, - QGridLayout, QGroupBox, QHBoxLayout, QHeaderView, - QLabel, QPushButton, QSizePolicy, QSpacerItem, - QTreeView, QVBoxLayout, QWidget) + 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(700, 500) + TabDb_RemoveDuplicateScores.resize(600, 500) TabDb_RemoveDuplicateScores.setWindowTitle(u"TabDb_RemoveDuplicateScores") - self.gridLayout = QGridLayout(TabDb_RemoveDuplicateScores) - self.gridLayout.setObjectName(u"gridLayout") - self.verticalLayout = QVBoxLayout() - self.verticalLayout.setObjectName(u"verticalLayout") + 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_2 = QVBoxLayout(self.groupBox_2) - self.verticalLayout_2.setObjectName(u"verticalLayout_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.verticalLayout_2.addWidget(self.scan_option_scoreCheckBox) + self.horizontalLayout_2.addWidget(self.scan_option_scoreCheckBox) - self.horizontalLayout_2 = QHBoxLayout() - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") self.scan_option_pureCheckBox = QCheckBox(self.groupBox_2) self.scan_option_pureCheckBox.setObjectName(u"scan_option_pureCheckBox") self.scan_option_pureCheckBox.setText(u"PURE") @@ -59,38 +59,59 @@ class Ui_TabDb_RemoveDuplicateScores(object): self.horizontalLayout_2.addWidget(self.scan_option_lostCheckBox) - - self.verticalLayout_2.addLayout(self.horizontalLayout_2) - 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.verticalLayout_2.addWidget(self.scan_option_maxRecallCheckBox) + 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_clearTypeCheckBox = QCheckBox(self.groupBox_2) - self.scan_option_clearTypeCheckBox.setObjectName(u"scan_option_clearTypeCheckBox") + self.scan_option_dateCheckBox = QCheckBox(self.groupBox_2) + self.scan_option_dateCheckBox.setObjectName(u"scan_option_dateCheckBox") - self.horizontalLayout_3.addWidget(self.scan_option_clearTypeCheckBox) + 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.verticalLayout_2.addLayout(self.horizontalLayout_3) + 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_2.addWidget(self.scan_scanButton) + self.verticalLayout.addWidget(self.scan_scanButton) - self.verticalLayout.addWidget(self.groupBox_2) + 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) @@ -100,73 +121,103 @@ class Ui_TabDb_RemoveDuplicateScores(object): self.verticalLayout_3.addWidget(self.label) - self.quickSelect_timeComboBox = QComboBox(self.groupBox) - self.quickSelect_timeComboBox.setObjectName(u"quickSelect_timeComboBox") + 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_timeComboBox) + self.verticalLayout_3.addWidget(self.quickSelect_comboBox) - self.quickSelect_ColumnComboBox = QComboBox(self.groupBox) - self.quickSelect_ColumnComboBox.setObjectName(u"quickSelect_ColumnComboBox") + self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) - self.verticalLayout_3.addWidget(self.quickSelect_ColumnComboBox) - - self.verticalSpacer = QSpacerItem(20, 91, QSizePolicy.Minimum, QSizePolicy.Expanding) - - self.verticalLayout_3.addItem(self.verticalSpacer) + 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.addWidget(self.groupBox) + 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.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1) + self.verticalLayout_5.addWidget(self.deselectAllButton) - self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.reverseSelectionButton = QPushButton(self.groupBox_3) + self.reverseSelectionButton.setObjectName(u"reverseSelectionButton") - self.horizontalLayout_4.addItem(self.horizontalSpacer) + self.verticalLayout_5.addWidget(self.reverseSelectionButton) - self.collapseAllButton = QPushButton(TabDb_RemoveDuplicateScores) + 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.horizontalLayout_4.addWidget(self.collapseAllButton) + self.verticalLayout_5.addWidget(self.collapseAllButton) - self.expandAllButton = QPushButton(TabDb_RemoveDuplicateScores) + self.expandAllButton = QPushButton(self.groupBox_3) self.expandAllButton.setObjectName(u"expandAllButton") - self.horizontalLayout_4.addWidget(self.expandAllButton) + self.verticalLayout_5.addWidget(self.expandAllButton) - self.resetModelButton = QPushButton(TabDb_RemoveDuplicateScores) + self.resetModelButton = QPushButton(self.groupBox_3) self.resetModelButton.setObjectName(u"resetModelButton") - self.horizontalLayout_4.addWidget(self.resetModelButton) + self.verticalLayout_5.addWidget(self.resetModelButton) + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) - self.gridLayout.addLayout(self.horizontalLayout_4, 1, 0, 1, 1) + self.verticalLayout_5.addItem(self.verticalSpacer) - 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.gridLayout.addWidget(self.treeView, 0, 0, 1, 1) - - self.deleteSelectedButton = QPushButton(TabDb_RemoveDuplicateScores) - self.deleteSelectedButton.setObjectName(u"deleteSelectedButton") + self.deleteSelectionButton = QPushButton(self.groupBox_3) + self.deleteSelectionButton.setObjectName(u"deleteSelectionButton") font = QFont() font.setBold(True) - self.deleteSelectedButton.setFont(font) - self.deleteSelectedButton.setStyleSheet(u"QPushButton { color: red };") + self.deleteSelectionButton.setFont(font) + self.deleteSelectionButton.setStyleSheet(u"QPushButton { color: red };") - self.gridLayout.addWidget(self.deleteSelectedButton, 1, 1, 1, 1) + 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) @@ -175,16 +226,20 @@ class Ui_TabDb_RemoveDuplicateScores(object): 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_clearTypeCheckBox.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"scan.option.clearType", 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.keepSingleLabel", 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.deleteSelectedButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"deleteSelectedButton", None)) + self.deleteSelectionButton.setText(QCoreApplication.translate("TabDb_RemoveDuplicateScores", u"deleteSelectionButton", None)) pass # retranslateUi diff --git a/ui/designer/tabs/tabDbEntry.ui b/ui/designer/tabs/tabDbEntry.ui index 89befa5..de9b19c 100644 --- a/ui/designer/tabs/tabDbEntry.ui +++ b/ui/designer/tabs/tabDbEntry.ui @@ -29,6 +29,11 @@ tab.chartInfoEditor + + + tab.removeDuplicateScores + + @@ -46,6 +51,12 @@
ui.implements.tabs.tabDb.tabDb_ChartInfoEditor
1 + + TabDb_RemoveDuplicateScores + QWidget +
ui.implements.tabs.tabDb.tabDb_RemoveDuplicateScores
+ 1 +
diff --git a/ui/designer/tabs/tabDbEntry_ui.py b/ui/designer/tabs/tabDbEntry_ui.py index 423da36..37ec6f4 100644 --- a/ui/designer/tabs/tabDbEntry_ui.py +++ b/ui/designer/tabs/tabDbEntry_ui.py @@ -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 diff --git a/ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py b/ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py index 4702681..8d65a9d 100644 --- a/ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py +++ b/ui/implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py @@ -1,9 +1,11 @@ +from enum import IntEnum + from arcaea_offline.database import Database from arcaea_offline.models import Chart, Difficulty, Score, Song -from PySide6.QtCore import QModelIndex, Qt, Slot +from PySide6.QtCore import QCoreApplication, QModelIndex, Qt, Slot from PySide6.QtGui import QStandardItem, QStandardItemModel -from PySide6.QtWidgets import QStyledItemDelegate, QWidget -from sqlalchemy import func, select +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 ( @@ -11,6 +13,7 @@ from ui.designer.tabs.tabDb.tabDb_RemoveDuplicateScores_ui import ( ) 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): @@ -40,12 +43,19 @@ class RemoveDuplicateScoresModel(QStandardItemModel): item.setData(song, self.SongRole) item.setData(difficulty, self.DifficultyRole) - def setScores(self, scores: list[Score]): + 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[tuple[str, int], list[Score]] = {} + scoreKeyMap: dict[str, list[Score]] = {} for score in scores: - key = (score.song_id, score.rating_class) + key = self.getGroupKey(score, columns) if scoreKeyMap.get(key) is None: scoreKeyMap[key] = [score] else: @@ -54,7 +64,8 @@ class RemoveDuplicateScoresModel(QStandardItemModel): db = Database() with db.sessionmaker() as session: for key, scores in scoreKeyMap.items(): - songId, ratingClass = key + songId, ratingClass = key.split("||")[:2] + ratingClass = int(ratingClass) parentCheckBoxItem = QStandardItem(f"{len(scores)} items") parentChartItem = QStandardItem() @@ -112,26 +123,52 @@ class TreeViewProxyDelegate(QStyledItemDelegate): 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.scan_scanButton.clicked.connect(self.fillModel) - - self.model = RemoveDuplicateScoresModel(self) - self.treeView.setModel(self.model) + self.removeDuplicateScoresModel = RemoveDuplicateScoresModel(self) + self.treeView.setModel(self.removeDuplicateScoresModel) self.treeViewChartDelegate = TreeViewChartDelegate(self.treeView) self.treeViewScoreDelegate = TreeViewScoreDelegate(self.treeView) - self.treeViewDelegate = TreeViewProxyDelegate( + self.treeViewProxyDelegate = TreeViewProxyDelegate( self.treeViewChartDelegate, self.treeViewScoreDelegate, self.treeView ) - self.treeView.setItemDelegateForColumn(1, self.treeViewDelegate) + self.treeView.setItemDelegateForColumn(1, self.treeViewProxyDelegate) - def getGroupByColumns(self): + 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(): @@ -144,15 +181,17 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget): columns.append(Score.lost) if self.scan_option_maxRecallCheckBox.isChecked(): columns.append(Score.max_recall) - if self.scan_option_clearTypeCheckBox.isChecked(): - columns.append(Score.clear_type) + 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.getGroupByColumns() + columns = self.getQueryColumns() with self.db.sessionmaker() as session: groupBySubquery = ( select(*columns).group_by(*columns).having(func.count() > 1).subquery() @@ -162,11 +201,137 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget): ] return session.query(Score).where(*selectInClause).all() - def fillModel(self): + def scan(self): scores = self.getQueryScores() - self.model.setScores(scores) + 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() @@ -174,3 +339,11 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget): @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() diff --git a/ui/resources/lang/en_US.ts b/ui/resources/lang/en_US.ts index f7ce145..86ecfc3 100644 --- a/ui/resources/lang/en_US.ts +++ b/ui/resources/lang/en_US.ts @@ -599,6 +599,11 @@ validation tab.chartInfoEditor Chart Info Editor + + + tab.removeDuplicateScores + Remove Duplicate Scores + tab.scoreTableViewer @@ -757,6 +762,109 @@ validation Export all your scores to <a href="https://smartrte.github.io/b30gen.html">smartrte.github.io</a> compatible CSV file + + TabDb_RemoveDuplicateScores + + + scan.title + Scan Options + + + + scan.option.score + Score + + + + scan.option.date + Date + + + + scan.option.modifier + Modifier + + + + scan.option.clearType + Clear Type + + + + scan.scanButton + Scan + + + + quickSelect.title + Quick Select + + + + quickSelect.description + Keep the first score item<br>that matches: + + + + quickSelect.selectButton + Select + + + + deselectAllButton + Clear Selection + + + + reverseSelectionButton + Reverse Selection + + + + collapseAllButton + Collapse All Groups + + + + expandAllButton + Expand All Groups + + + + resetModelButton + Reset Model + + + + deleteSelectionButton + Delete Selected Scores + + + + quickSelectComboBox.idEarlier + Earlier ID + + + + quickSelectComboBox.dateEarlier + Earlier date + + + + quickSelectComboBox.columnsIntegral + More complete data + + + + deleteSelectionDialog.content {} + Deleting {} scores from database, this cannot be undone!<br>Confirm? + + + + scan_noColumnsDialog.content + You haven't selected any column! Are you sure to continue? + + TabOcrDisabled @@ -911,7 +1019,7 @@ validation TabOverview - + databaseDescribeLabel {} {} {} {} {} {} There are {} packs, {} songs, {} difficulties, {} chart info ({} complete) and {} scores in database. @@ -1006,11 +1114,6 @@ validation sourceCode Source code - - - <a href="https://github.com/283375/AndrealImageGenerator">283375/AndrealImageGenerator</a><br>(forked from <a href="https://github.com/Awbugl/AndrealImageGenerator">Awbugl/AndrealImageGenerator</a>) - - imageWhatIsThisDialog.description diff --git a/ui/resources/lang/zh_CN.ts b/ui/resources/lang/zh_CN.ts index 52f41b8..08e3d24 100644 --- a/ui/resources/lang/zh_CN.ts +++ b/ui/resources/lang/zh_CN.ts @@ -598,6 +598,11 @@ tab.chartInfoEditor 谱面信息编辑器 + + + tab.removeDuplicateScores + 移除重复分数 + tab.scoreTableViewer @@ -756,6 +761,109 @@ 将所有分数导出为兼容 <a href="https://smartrte.github.io/b30gen.html">smartrte.github.io</a> 的 CSV 文件 + + TabDb_RemoveDuplicateScores + + + scan.title + 扫描选项 + + + + scan.option.score + 分数 + + + + scan.option.date + 时间 + + + + scan.option.modifier + Modifier + + + + scan.option.clearType + Clear Type + + + + scan.scanButton + 扫描 + + + + quickSelect.title + 快速选择 + + + + quickSelect.description + 仅保留第一个<br>符合以下条件的分数: + + + + quickSelect.selectButton + 选择 + + + + deselectAllButton + 清空选择 + + + + reverseSelectionButton + 反选 + + + + collapseAllButton + 折叠所有 + + + + expandAllButton + 展开所有 + + + + resetModelButton + 重置模型 + + + + deleteSelectionButton + 删除已选分数 + + + + quickSelectComboBox.idEarlier + ID 更早 + + + + quickSelectComboBox.dateEarlier + 时间更早 + + + + quickSelectComboBox.columnsIntegral + 数据更完整 + + + + deleteSelectionDialog.content {} + 将从数据库中删除 {} 个分数。此操作无法撤销!<br>确认吗? + + + + scan_noColumnsDialog.content + 还未选择任何字段!确定继续吗? + + TabOcrDisabled @@ -910,7 +1018,7 @@ TabOverview - + databaseDescribeLabel {} {} {} {} {} {} 数据库中有 {} 个曲包,{} 首歌曲,{} 个难度,{} 个谱面信息({} 个完整),{} 个分数记录。 @@ -1005,11 +1113,6 @@ sourceCode 源代码 - - - <a href="https://github.com/283375/AndrealImageGenerator">283375/AndrealImageGenerator</a><br>(forked from <a href="https://github.com/Awbugl/AndrealImageGenerator">Awbugl/AndrealImageGenerator</a>) - - imageWhatIsThisDialog.description