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

257 lines
8.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.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import os
import shutil
import logging
import datetime
import functools
import darkdetect
__all__ = ['XrayAssetViewerQListWidget']
logger = logging.getLogger(__name__)
class AssetExistsMBox(AppQMessageBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStandardButtons(
AppQMessageBox.StandardButton.Yes | AppQMessageBox.StandardButton.No
)
def retranslate(self):
self.setWindowTitle(_(self.windowTitle()))
self.setText(_(self.text()))
# Ignore informative text, buttons
self.moveToCenter()
class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSelectionBehavior(AppQListWidget.SelectionBehavior.SelectRows)
self.setSelectionMode(AppQListWidget.SelectionMode.ExtendedSelection)
self.setIconSize(QtCore.QSize(64, 64))
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
self.initialTheme = darkdetect.theme()
else:
self.initialTheme = None
self.flushItem()
logger.info(f'Xray-core asset dir is \'{XRAY_ASSET_DIR}\'')
contextMenuActions = [
AppQAction(
_('Delete'),
callback=lambda: self.deleteSelectedItem(),
),
]
self.contextMenu = AppQMenu(*contextMenuActions)
# Add actions to self in order to activate shortcuts
self.addActions(self.contextMenu.actions())
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.handleCustomContextMenuRequested)
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
self.contextMenu.exec(self.mapToGlobal(point))
def flushItemByTheme(self, theme: str):
self.clear()
maxlen = max(
len(filename)
for filename in os.listdir(XRAY_ASSET_DIR)
if os.path.isfile(XRAY_ASSET_DIR / filename)
)
for filename in os.listdir(XRAY_ASSET_DIR):
if os.path.isfile(XRAY_ASSET_DIR / filename):
try:
# Exception may be raised
epoch = os.path.getmtime(XRAY_ASSET_DIR / filename)
except Exception:
# Any non-exit exceptions
mdate = ''
else:
mdate = datetime.datetime.fromtimestamp(epoch).strftime(
'%Y-%m-%d %H:%M:%S'
)
item = QListWidgetItem(f'{filename:{maxlen + 6}}{mdate}')
item.setFont(QFont(APP().customFontName))
if AppSettings.isStateON_('DarkMode'):
# Custom dark mode
item.setIcon(bootstrapIconWhite('file-earmark.svg'))
else:
if theme == 'Dark':
if PLATFORM == 'Windows':
# Windows
if versionToValue(PYSIDE6_VERSION) < versionToValue(
'6.7.0'
):
# PySide6 < 6.7.0 has no system theme handling on Windows.
# Always use black icon
item.setIcon(bootstrapIcon('file-earmark.svg'))
else:
# PySide6 has system theme handling.
item.setIcon(bootstrapIconWhite('file-earmark.svg'))
else:
item.setIcon(bootstrapIconWhite('file-earmark.svg'))
else:
item.setIcon(bootstrapIcon('file-earmark.svg'))
self.addItem(item)
def flushItem(self):
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
assert self.initialTheme is not None
# Ubuntu 20.04. Flush by initial theme(Ubuntu 20.04 theme changes bug)
self.flushItemByTheme(self.initialTheme)
else:
self.flushItemByTheme(darkdetect.theme())
def appendNewItem(self, filename: str):
def append(_filename):
try:
shutil.copy(_filename, XRAY_ASSET_DIR)
except shutil.SameFileError:
# Same file imported. Do nothing
pass
except Exception as ex:
# Any non-exit exception
_mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
_mbox.setWindowTitle(_('Import'))
_mbox.setText(_('Error import asset file'))
_mbox.setInformativeText(str(ex))
# Show the MessageBox asynchronously
_mbox.open()
else:
self.flushItem()
_mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Information)
_mbox.setWindowTitle(_('Import'))
_mbox.setText(_('Import asset file success'))
# Show the MessageBox asynchronously
_mbox.open()
basename = os.path.basename(filename)
if os.path.isfile(XRAY_ASSET_DIR / basename):
def handleResultCode(_filename, code):
if code == PySide6LegacyEnumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
append(_filename)
else:
# Do not overwrite
pass
mbox = AssetExistsMBox(icon=AppQMessageBox.Icon.Question)
mbox.setWindowTitle(_('Import'))
mbox.setText(_('Asset file already exists. Overwrite?'))
mbox.setInformativeText(basename)
mbox.finished.connect(functools.partial(handleResultCode, filename))
# dummy ref
setattr(self, '_assetExistsMBox', mbox)
# Show the MessageBox asynchronously
mbox.open()
else:
append(filename)
def deleteSelectedItem(self):
indexes = self.selectedIndex
if len(indexes) == 0:
# Nothing selected
return
def handleResultCode(_indexes, code):
if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes):
for index in _indexes:
os.remove(XRAY_ASSET_DIR / self.item(index).text())
self.flushItem()
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 = f'{self.item(indexes[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 keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key.Key_Delete:
self.deleteSelectedItem()
else:
super().keyPressEvent(event)
def themeChangedCallback(self, theme):
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
# Ubuntu 20.04 system dark theme does not
# change menu color. Do nothing
pass
else:
self.flushItemByTheme(theme)