mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-25 08:18:07 +03:00
280 lines
9.1 KiB
Python
280 lines
9.1 KiB
Python
# Copyright (C) 2023 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 Furious.Gui.Action import Action, Seperator
|
|
from Furious.Widget.Widget import ListWidget, MainWindow, Menu, MessageBox
|
|
from Furious.Utility.Constants import APPLICATION_NAME, PLATFORM, GOLDEN_RATIO, ROOT_DIR
|
|
from Furious.Utility.Utility import (
|
|
StateContext,
|
|
SupportConnectedCallback,
|
|
SupportThemeChangedCallback,
|
|
bootstrapIcon,
|
|
bootstrapIconWhite,
|
|
enumValueWrapper,
|
|
getUbuntuRelease,
|
|
moveToCenter,
|
|
)
|
|
from Furious.Utility.Translator import Translatable, gettext as _
|
|
|
|
from PySide6 import QtCore
|
|
from PySide6.QtGui import QFont
|
|
from PySide6.QtWidgets import QFileDialog, QListWidget, QListWidgetItem
|
|
|
|
import os
|
|
import shutil
|
|
import logging
|
|
import darkdetect
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class QuestionDeleteBox(MessageBox):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.isMulti = False
|
|
self.possibleRemark = ''
|
|
|
|
self.setWindowTitle(_('Delete'))
|
|
|
|
self.setStandardButtons(
|
|
MessageBox.StandardButton.Yes | MessageBox.StandardButton.No
|
|
)
|
|
|
|
def getText(self):
|
|
if self.isMulti:
|
|
return _('Delete these asset files?')
|
|
else:
|
|
return _('Delete this asset file?') + f'\n\n{self.possibleRemark}'
|
|
|
|
def retranslate(self):
|
|
with StateContext(self):
|
|
self.setWindowTitle(_(self.windowTitle()))
|
|
self.setText(self.getText())
|
|
|
|
# Ignore informative text, buttons
|
|
|
|
self.moveToCenter()
|
|
|
|
|
|
class AssetExistsBox(MessageBox):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.setWindowTitle(_('Import'))
|
|
|
|
self.setStandardButtons(
|
|
MessageBox.StandardButton.Yes | MessageBox.StandardButton.No
|
|
)
|
|
|
|
def retranslate(self):
|
|
with StateContext(self):
|
|
self.setWindowTitle(_(self.windowTitle()))
|
|
self.setText(_(self.text()))
|
|
|
|
# Ignore informative text, buttons
|
|
|
|
self.moveToCenter()
|
|
|
|
|
|
class ImportAction(Action):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(_('Import From File...'), **kwargs)
|
|
|
|
self.assetExistsBox = AssetExistsBox(
|
|
icon=MessageBox.Icon.Question, parent=self.parent()
|
|
)
|
|
self.importErrorBox = MessageBox(
|
|
icon=MessageBox.Icon.Critical, parent=self.parent()
|
|
)
|
|
self.importSuccessBox = MessageBox(
|
|
icon=MessageBox.Icon.Information, parent=self.parent()
|
|
)
|
|
|
|
def triggeredCallback(self, checked):
|
|
filename, selectedFilter = QFileDialog.getOpenFileName(
|
|
self.parent(), _('Import File'), filter=_('All files (*)')
|
|
)
|
|
|
|
if filename:
|
|
basename = os.path.basename(filename)
|
|
|
|
if os.path.isfile(AssetViewerWidget.AssetDir / os.path.basename(filename)):
|
|
self.assetExistsBox.setWindowTitle(_('Import'))
|
|
self.assetExistsBox.setText(_('Asset file already exists. Overwrite?'))
|
|
self.assetExistsBox.setInformativeText(basename)
|
|
|
|
if self.assetExistsBox.exec() == enumValueWrapper(
|
|
MessageBox.StandardButton.No
|
|
):
|
|
# Do not overwrite
|
|
return
|
|
|
|
try:
|
|
shutil.copy(filename, AssetViewerWidget.AssetDir)
|
|
except shutil.SameFileError:
|
|
# Same file imported. Do nothing
|
|
pass
|
|
except Exception as ex:
|
|
# Any non-exit exception
|
|
|
|
self.importErrorBox.setWindowTitle(_('Import'))
|
|
self.importErrorBox.setText(_('Error import asset file.'))
|
|
self.importErrorBox.setInformativeText(str(ex))
|
|
|
|
# Show the MessageBox and wait for user to close it
|
|
self.importErrorBox.exec()
|
|
else:
|
|
self.parent().flushItem()
|
|
|
|
self.importSuccessBox.setWindowTitle(_('Import'))
|
|
self.importSuccessBox.setText(_('Import asset file success.'))
|
|
|
|
# Show the MessageBox and wait for user to close it
|
|
self.importSuccessBox.exec()
|
|
|
|
|
|
class ExitAction(Action):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(_('Exit'), **kwargs)
|
|
|
|
def triggeredCallback(self, checked):
|
|
self.parent().hide()
|
|
|
|
|
|
class AssetViewerWidget(SupportThemeChangedCallback, MainWindow):
|
|
AssetDir = ROOT_DIR / APPLICATION_NAME / 'Data' / 'xray'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.setWindowTitle(_('Asset File'))
|
|
self.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
|
|
|
|
self.questionDeleteBox = QuestionDeleteBox(
|
|
icon=MessageBox.Icon.Question, parent=self
|
|
)
|
|
|
|
self.listWidget = ListWidget(parent=self)
|
|
self.listWidget.setSelectionBehavior(QListWidget.SelectionBehavior.SelectRows)
|
|
self.listWidget.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
|
|
self.listWidget.setIconSize(QtCore.QSize(64, 64))
|
|
|
|
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
|
|
self.initialTheme = darkdetect.theme()
|
|
else:
|
|
self.initialTheme = None
|
|
|
|
self.flushItem()
|
|
self.setCentralWidget(self.listWidget)
|
|
|
|
logger.info(f'asset dir is \'{AssetViewerWidget.AssetDir}\'')
|
|
|
|
contextMenuActions = [
|
|
Action(
|
|
_('Delete'), callback=lambda: self.deleteSelectedItem(), parent=self
|
|
),
|
|
]
|
|
|
|
self.contextMenu = Menu(*contextMenuActions)
|
|
|
|
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
|
|
self.customContextMenuRequested.connect(self.handleCustomContextMenuRequested)
|
|
|
|
fileMenuActions = [
|
|
ImportAction(parent=self),
|
|
Seperator(),
|
|
ExitAction(parent=self),
|
|
]
|
|
|
|
for menu in (fileMenuActions,):
|
|
for action in menu:
|
|
if isinstance(action, Action):
|
|
if hasattr(self, f'{action}'):
|
|
logger.warning(f'{self} already has action {action}')
|
|
|
|
setattr(self, f'{action}', action)
|
|
|
|
self._fileMenu = Menu(*fileMenuActions, title=_('File'), parent=self)
|
|
self.menuBar().addMenu(self._fileMenu)
|
|
|
|
def setWidthAndHeight(self):
|
|
self.setGeometry(100, 100, 360, 360 * GOLDEN_RATIO)
|
|
|
|
@QtCore.Slot(QtCore.QPoint)
|
|
def handleCustomContextMenuRequested(self, point):
|
|
self.contextMenu.exec(self.mapToGlobal(point))
|
|
|
|
def deleteSelectedItem(self):
|
|
indexes = self.listWidget.selectedIndex
|
|
|
|
if len(indexes) == 0:
|
|
# Nothing selected
|
|
return
|
|
|
|
self.questionDeleteBox.isMulti = bool(len(indexes) > 1)
|
|
self.questionDeleteBox.possibleRemark = (
|
|
f'{self.listWidget.item(indexes[0]).text()}'
|
|
)
|
|
self.questionDeleteBox.setText(self.questionDeleteBox.getText())
|
|
|
|
if self.questionDeleteBox.exec() == enumValueWrapper(
|
|
MessageBox.StandardButton.No
|
|
):
|
|
# Do not delete
|
|
return
|
|
|
|
for index in indexes:
|
|
os.remove(AssetViewerWidget.AssetDir / self.listWidget.item(index).text())
|
|
|
|
self.flushItem()
|
|
|
|
def flushItemByTheme(self, theme):
|
|
self.listWidget.clear()
|
|
|
|
for filename in os.listdir(AssetViewerWidget.AssetDir):
|
|
if os.path.isfile(AssetViewerWidget.AssetDir / filename):
|
|
item = QListWidgetItem(filename)
|
|
|
|
if theme == 'Dark':
|
|
if PLATFORM == 'Windows':
|
|
# Windows. Always use black icon
|
|
item.setIcon(bootstrapIcon('file-earmark.svg'))
|
|
else:
|
|
item.setIcon(bootstrapIconWhite('file-earmark.svg'))
|
|
else:
|
|
item.setIcon(bootstrapIcon('file-earmark.svg'))
|
|
|
|
self.listWidget.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
|
|
self.flushItemByTheme(self.initialTheme)
|
|
else:
|
|
self.flushItemByTheme(darkdetect.theme())
|
|
|
|
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)
|