Files
LorenEteval_Furious/Furious/Widget/UserSubsQTableWidget.py
2025-03-20 00:22:32 +08:00

241 lines
7.7 KiB
Python

# Copyright (C) 2024 Loren Eteval <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from typing import Callable
import functools
__all__ = ['UserSubsQTableWidget']
registerAppSettings('SubscriptionWidgetSectionSizeTable')
class UserSubsQTableWidgetHorizontalHeader(AppQHeaderView):
def __init__(self, *args, **kwargs):
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserSubsQTableWidgetVerticalHeader(AppQHeaderView):
def __init__(self, *args, **kwargs):
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
class UserSubsQTableWidgetHeaders:
def __init__(self, name: str, func: Callable[[dict], str] = None):
self.name = name
self.func = func
def __call__(self, item: dict) -> str:
if callable(self.func):
return self.func(item)
else:
return ''
def __eq__(self, other):
return str(self) == str(other)
def __str__(self):
return self.name
class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
Headers = [
UserSubsQTableWidgetHeaders('Remark', lambda item: item.get('remark', '')),
UserSubsQTableWidgetHeaders('URL', lambda item: item.get('webURL', '')),
]
def __init__(self, *args, **kwargs):
self.deleteUniqueCallback = kwargs.pop('deleteUniqueCallback', None)
super().__init__(*args, **kwargs)
# Must set before flush all
self.setColumnCount(len(self.Headers))
# Flush all data to table
self.flushAll()
# Install custom header
self.setHorizontalHeader(
UserSubsQTableWidgetHorizontalHeader(
parent=self,
sectionSizeSettingsName='SubscriptionWidgetSectionSizeTable',
)
)
self.setVerticalHeader(UserSubsQTableWidgetVerticalHeader(self))
self.horizontalHeader().setCustomSectionResizeMode()
self.horizontalHeader().restoreSectionSize()
self.setHorizontalHeaderLabels(list(_(str(header)) for header in self.Headers))
# Selection
self.setSelectionColor(AppHue.disconnectedColor())
self.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self.setSelectionMode(QTableWidget.SelectionMode.ExtendedSelection)
# No drag and drop
self.setDragEnabled(False)
self.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
self.setDropIndicatorShown(False)
self.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction)
contextMenuActions = [
AppQAction(
_('Delete'),
callback=lambda: self.deleteSelectedItem(),
),
]
self.contextMenu = AppQMenu(*contextMenuActions)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
# Signals
self.itemChanged.connect(self.handleItemChanged)
self.customContextMenuRequested.connect(self.handleCustomContextMenuRequested)
@QtCore.Slot(QTableWidgetItem)
def handleItemChanged(self, item: QTableWidgetItem):
unique = list(AS_UserSubscription().keys())[item.row()]
keyMap = ['remark', 'webURL']
AS_UserSubscription()[unique][keyMap[item.column()]] = item.text()
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
self.contextMenu.exec(self.mapToGlobal(point))
def deleteSelectedItem(self):
indexes = self.selectedIndex
if len(indexes) == 0:
# Nothing to do
return
def handleResultCode(_indexes, code):
if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes):
for i in range(len(_indexes)):
deleteIndex = _indexes[i] - i
deleteUnique = list(AS_UserSubscription().keys())[deleteIndex]
self.removeRow(deleteIndex)
AS_UserSubscription().pop(deleteUnique)
if callable(self.deleteUniqueCallback):
self.deleteUniqueCallback(deleteUnique)
else:
# Do not delete
pass
if PLATFORM == 'Windows':
# Windows
mbox = QuestionDeleteMBox(icon=AppQMessageBox.Icon.Question)
else:
# macOS & linux
mbox = QuestionDeleteMBox(
icon=AppQMessageBox.Icon.Question, parent=self.parent()
)
mbox.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
mbox.isMulti = bool(len(indexes) > 1)
mbox.possibleRemark = self.item(indexes[0], 0).text()
mbox.setText(mbox.customText())
mbox.finished.connect(functools.partial(handleResultCode, indexes))
# dummy ref
setattr(self, '_questionDeleteMBox', mbox)
# Show the MessageBox asynchronously
mbox.open()
def flushItem(self, row, column, item):
header = self.Headers[column]
oldItem = self.item(row, column)
newItem = QTableWidgetItem(header(item))
if oldItem is None:
# Item does not exists
newItem.setFont(QFont(APP().customFontName))
else:
# Use existing
newItem.setFont(oldItem.font())
newItem.setForeground(oldItem.foreground())
if oldItem.textAlignment() != 0:
newItem.setTextAlignment(oldItem.textAlignment())
# Editable
newItem.setFlags(
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsSelectable
| QtCore.Qt.ItemFlag.ItemIsEditable
)
self.setItem(row, column, newItem)
def flushRow(self, row, item):
for column in list(range(self.columnCount())):
self.flushItem(row, column, item)
def flushAll(self):
if self.rowCount() == 0:
# Should insert row
for index, key in enumerate(AS_UserSubscription()):
self.insertRow(index)
self.flushRow(index, AS_UserSubscription()[key])
else:
for index, key in enumerate(AS_UserSubscription()):
self.flushRow(index, AS_UserSubscription()[key])
def appendNewItem(self, **kwargs):
unique = kwargs.pop('unique', '')
remark = kwargs.pop('remark', '')
webURL = kwargs.pop('webURL', '')
# The internal subs object
subs = {
unique: {
'remark': remark,
'webURL': webURL,
}
}
AS_UserSubscription().update(subs)
row = self.rowCount()
self.insertRow(row)
self.flushRow(row, subs[unique])
def retranslate(self):
self.setHorizontalHeaderLabels(list(_(str(header)) for header in self.Headers))