1
0
Fork 0
mirror of synced 2024-05-21 13:02:46 +12:00

SelectiveWidget: Create specialized widget for selecting optional downloads

Because there are two dialogs for editing optional downloads, refactor
the separate implementations to select them into a common reusable widget
and use it in SelectiveDialog and InstallDialog.
This commit is contained in:
loathingKernel 2024-01-09 16:32:53 +02:00
parent 9d3e9fecea
commit 971c31e8f4
9 changed files with 139 additions and 161 deletions

View file

@ -6,8 +6,7 @@ from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QVBoxLayout, QFormLayout
from legendary.utils.selective_dl import get_sdl_appname
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QFormLayout
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
@ -15,18 +14,46 @@ from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils.misc import format_size, icon
from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.collapsible_widget import CollapsibleFrame
from rare.widgets.dialogs import ActionDialog, dialog_title_game
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
from rare.widgets.selective_widget import SelectiveWidget
class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None):
widget = QWidget(parent)
title = widget.tr("Advanced options")
super(InstallDialogAdvanced, self).__init__(parent=parent)
title = self.tr("Advanced options")
self.setTitle(title)
widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(widget)
super(InstallDialogAdvanced, self).__init__(widget=widget, title=title, parent=parent)
self.setWidget(widget)
class InstallDialogSelective(CollapsibleFrame):
stateChanged: pyqtSignal = pyqtSignal()
def __init__(self, rgame: RareGame, parent=None):
super(InstallDialogSelective, self).__init__(parent=parent)
title = self.tr("Optional downloads")
self.setTitle(title)
self.setEnabled(bool(rgame.sdl_name))
self.selective_widget: SelectiveWidget = None
self.rgame = rgame
def update_list(self, platform: str):
if self.selective_widget is not None:
self.selective_widget.deleteLater()
self.selective_widget = SelectiveWidget(self.rgame, platform, parent=self)
self.selective_widget.stateChanged.connect(self.stateChanged)
self.setWidget(self.selective_widget)
def install_tags(self):
return self.selective_widget.install_tags()
class InstallDialog(ActionDialog):
@ -64,7 +91,8 @@ class InstallDialog(ActionDialog):
self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced)
self.selectable = CollapsibleFrame(widget=None, title=self.tr("Optional downloads"), parent=self)
self.selectable = InstallDialogSelective(rgame, parent=self)
self.selectable.stateChanged.connect(self.option_changed)
self.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False
@ -82,13 +110,14 @@ class InstallDialog(ActionDialog):
self.install_dir_edit = PathEdit(
path=base_path,
file_mode=QFileDialog.DirectoryOnly,
edit_func=self.option_changed,
save_func=self.save_install_edit,
edit_func=self.install_dir_edit_callback,
save_func=self.install_dir_save_callback,
parent=self,
)
self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0],
QFormLayout.FieldRole, self.install_dir_edit
QFormLayout.FieldRole,
self.install_dir_edit,
)
self.install_dir_edit.setDisabled(rgame.is_installed)
@ -102,10 +131,10 @@ class InstallDialog(ActionDialog):
self.ui.platform_combo.addItems(reversed(rgame.platforms))
combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform
self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(combo_text))
self.ui.platform_combo.currentIndexChanged.connect(lambda i: self.option_changed(None))
self.ui.platform_combo.currentIndexChanged.connect(self.option_changed)
self.ui.platform_combo.currentIndexChanged.connect(self.check_incompatible_platform)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_sdl_list)
self.ui.platform_combo.currentTextChanged.connect(self.selectable.update_list)
self.ui.platform_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.setDisabled(rgame.is_installed)
@ -131,11 +160,8 @@ class InstallDialog(ActionDialog):
lambda: self.non_reload_option_changed("shortcut")
)
self.selectable_checks: List[TagCheckBox] = []
self.config_tags: Optional[List[str]] = None
self.reset_install_dir(self.ui.platform_combo.currentIndex())
self.reset_sdl_list(self.ui.platform_combo.currentIndex())
self.selectable.update_list(self.ui.platform_combo.currentText())
self.check_incompatible_platform(self.ui.platform_combo.currentIndex())
self.accept_button.setEnabled(False)
@ -180,7 +206,7 @@ class InstallDialog(ActionDialog):
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
self.save_install_edit(self.install_dir_edit.text())
self.install_dir_save_callback(self.install_dir_edit.text())
super().showEvent(a0)
def execute(self):
@ -197,68 +223,30 @@ class InstallDialog(ActionDialog):
default_dir = self.core.get_default_install_dir(platform)
self.install_dir_edit.setText(default_dir)
@pyqtSlot(int)
def reset_sdl_list(self, index: int):
platform = self.ui.platform_combo.itemText(index)
for cb in self.selectable_checks:
cb.disconnect()
cb.deleteLater()
self.selectable_checks.clear()
if config_tags := self.core.lgd.config.get(self.rgame.app_name, 'install_tags', fallback=None):
self.config_tags = config_tags.split(",")
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, 'disable_sdl', fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
if sdl_data:
widget = QWidget(self.selectable)
layout = QVBoxLayout(widget)
layout.setSpacing(0)
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
if self.config_tags is not None:
if all(elem in self.config_tags for elem in info["tags"]):
cb.setChecked(True)
layout.addWidget(cb)
self.selectable_checks.append(cb)
for cb in self.selectable_checks:
cb.stateChanged.connect(self.option_changed)
self.selectable.setWidget(widget)
else:
self.selectable.setDisabled(True)
@pyqtSlot(int)
def check_incompatible_platform(self, index: int):
platform = self.ui.platform_combo.itemText(index)
if platform == "Mac" and pf.system() != "Darwin":
self.error_box(
self.tr("Warning"),
self.tr("You will not be able to run the game if you select <b>{}</b> as platform").format(platform)
self.tr("You will not be able to run the game if you select <b>{}</b> as platform").format(platform),
)
else:
self.error_box()
def get_options(self):
self.__options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text()
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
self.__options.force = self.advanced.ui.force_download_check.isChecked()
self.__options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
if self.selectable_checks:
self.__options.install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
self.__options.install_tag.extend(data)
self.__options.install_tag = self.selectable.install_tags()
self.__options.reset_sdl = True
def get_download_info(self):
self.__download = None
@ -279,13 +267,17 @@ class InstallDialog(ActionDialog):
self.get_options()
self.get_download_info()
def option_changed(self, path) -> Tuple[bool, str, int]:
@pyqtSlot()
def option_changed(self):
self.options_changed = True
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
def install_dir_edit_callback(self, path: str) -> Tuple[bool, str, int]:
self.option_changed()
return True, path, IndicatorReasonsCommon.VALID
def save_install_edit(self, path: str):
def install_dir_save_callback(self, path: str):
if not os.path.exists(path):
return
_, _, free_space = shutil.disk_usage(path)
@ -369,12 +361,6 @@ class InstallDialog(ActionDialog):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def reject_handler(self):
# FIXME: This is implemented through the selective downloads dialog now. remove soon
# if self.config_tags is not None:
# config_helper.set_option(self.rgame.app_name, 'install_tags', ','.join(self.config_tags))
# else:
# # lk: this is purely for cleaning any install tags we might have added erroneously to the config
# config_helper.remove_option(self.rgame.app_name, 'install_tags')
self.__queue_item = InstallQueueItemModel(options=self.__options, download=None)
@ -387,6 +373,3 @@ class TagCheckBox(QCheckBox):
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False

View file

@ -1,18 +1,11 @@
from typing import List, Union, Optional
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (
QLabel,
QVBoxLayout,
QCheckBox,
QLayout, QGroupBox,
)
from legendary.utils.selective_dl import get_sdl_appname
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLayout, QGroupBox
from rare.models.game import RareGame
from rare.models.install import SelectiveDownloadsModel
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.utils.misc import icon
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
from rare.widgets.selective_widget import SelectiveWidget
class SelectiveDialog(ButtonDialog):
@ -25,20 +18,18 @@ class SelectiveDialog(ButtonDialog):
title_label = QLabel(f"<h4>{dialog_title_game(header, rgame.app_title)}</h4>", self)
self.core = rgame.core
self.rgame = rgame
self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
selectable_group = QGroupBox(self.tr("Optional downloads"), self)
self.selectable_layout = QVBoxLayout(selectable_group)
self.selectable_layout.setSpacing(0)
self.selectable_checks: List[TagCheckBox] = []
self.config_tags: Optional[List[str]] = None
container = QGroupBox(self.tr("Optional downloads"), self)
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(self.selective_widget)
layout = QVBoxLayout()
layout.setSizeConstraint(QLayout.SetFixedSize)
layout.addWidget(title_label)
layout.addWidget(selectable_group)
layout.addWidget(container)
self.setCentralLayout(layout)
@ -47,62 +38,13 @@ class SelectiveDialog(ButtonDialog):
self.options: SelectiveDownloadsModel = SelectiveDownloadsModel(rgame.app_name)
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
self.create_sdl_list()
else:
self.options.accepted = True
self.accept()
def create_sdl_list(self):
platform = self.rgame.igame.platform
for cb in self.selectable_checks:
cb.disconnect()
cb.deleteLater()
self.selectable_checks.clear()
if config_tags := self.core.lgd.config.get(self.rgame.app_name, "install_tags", fallback=None):
self.config_tags = config_tags.split(",")
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, "disable_sdl", fallback=False)
sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
if sdl_data:
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
if self.config_tags is not None:
if all(elem in self.config_tags for elem in info["tags"]):
cb.setChecked(True)
self.selectable_layout.addWidget(cb)
self.selectable_checks.append(cb)
def done_handler(self):
self.result_ready.emit(self.rgame, self.options)
def accept_handler(self):
install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
install_tag.extend(data)
self.options.accepted = True
self.options.install_tag = install_tag
self.options.install_tag = self.selective_widget.install_tags()
def reject_handler(self):
self.options.accepted = False
self.options.install_tag = None
class TagCheckBox(QCheckBox):
def __init__(self, text, desc, tags: List[str], parent=None):
super(TagCheckBox, self).__init__(parent)
self.setText(text)
self.setToolTip(desc)
self.tags = tags
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False

View file

@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
InstallDialog.resize(179, 204)
InstallDialog.resize(197, 216)
InstallDialog.setWindowTitle("InstallDialog")
self.main_layout = QtWidgets.QFormLayout(InstallDialog)
self.main_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>179</width>
<height>204</height>
<width>197</width>
<height>216</height>
</rect>
</property>
<property name="windowTitle">

View file

@ -101,9 +101,7 @@ class CollapsibleBase(object):
class CollapsibleFrame(QFrame, CollapsibleBase):
def __init__(
self, widget: QWidget = None, title: str = "", button_text: str = "", animation_duration: int = 200, parent=None
):
def __init__(self, animation_duration: int = 200, parent=None):
super(CollapsibleFrame, self).__init__(parent=parent)
self.setup(animation_duration)
self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
@ -111,11 +109,10 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button = QToolButton(self)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setIcon(icon("fa.arrow-right"))
self.toggle_button.setText(button_text)
self.toggle_button.setCheckable(True)
self.toggle_button.setChecked(False)
self.title_label = QLabel(title)
self.title_label = QLabel(self)
font = self.title_label.font()
font.setBold(True)
self.title_label.setFont(font)
@ -134,8 +131,11 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
self.toggle_button.clicked.connect(self.animationStart)
if widget is not None:
self.setWidget(widget)
def setTitle(self, title: str):
self.title_label.setText(title)
def setText(self, text: str):
self.toggle_button.setText(text)
def isChecked(self) -> bool:
return self.toggle_button.isChecked()
@ -156,12 +156,9 @@ class CollapsibleFrame(QFrame, CollapsibleBase):
class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
def __init__(
self, widget: QWidget = None, title: str = "", animation_duration: int = 200, parent=None
):
def __init__(self, animation_duration: int = 200, parent=None):
super(CollapsibleGroupBox, self).__init__(parent=parent)
self.setup(animation_duration)
self.setTitle(title)
self.setCheckable(True)
self.setChecked(False)
@ -173,9 +170,6 @@ class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
self.toggled.connect(self.animationStart)
if widget is not None:
self.setWidget(widget)
def isChecked(self) -> bool:
return super(CollapsibleGroupBox, self).isChecked()
@ -202,7 +196,9 @@ if __name__ == "__main__":
ui_frame = Ui_InstallDialogAdvanced()
widget_frame = QWidget()
ui_frame.setupUi(widget_frame)
collapsible_frame = CollapsibleFrame(widget_frame, title="Frame me!")
collapsible_frame = CollapsibleFrame()
collapsible_frame.setWidget(widget_frame)
collapsible_frame.setTitle("Frame me!")
collapsible_frame.setDisabled(False)
def replace_func_frame(state):
@ -217,7 +213,9 @@ if __name__ == "__main__":
ui_group = Ui_InstallDialogAdvanced()
widget_group = QWidget()
ui_group.setupUi(widget_group)
collapsible_group = CollapsibleGroupBox(widget_group, title="Group me!")
collapsible_group = CollapsibleGroupBox()
collapsible_group.setWidget(widget_group)
collapsible_group.setTitle("Group me!")
collapsible_group.setDisabled(False)
def replace_func_group(state):

View file

@ -0,0 +1,55 @@
from typing import List, Union
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import QCheckBox, QWidget, QVBoxLayout
from legendary.utils.selective_dl import get_sdl_appname
from rare.models.game import RareGame
class TagCheckBox(QCheckBox):
def __init__(self, text, desc, tags: List[str], parent=None):
super(TagCheckBox, self).__init__(parent)
self.setText(text)
self.setToolTip(desc)
self.tags = tags
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False
class SelectiveWidget(QWidget):
stateChanged: pyqtSignal = pyqtSignal()
def __init__(self, rgame: RareGame, platform: str, parent=None):
super().__init__(parent=parent)
main_layout = QVBoxLayout(self)
main_layout.setSpacing(0)
core = rgame.core
config_tags = core.lgd.config.get(rgame.app_name, "install_tags", fallback=None)
config_disable_sdl = core.lgd.config.getboolean(rgame.app_name, "disable_sdl", fallback=False)
sdl_name = get_sdl_appname(rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
sdl_data = core.get_sdl_data(sdl_name, platform=platform)
if sdl_data:
for tag, info in sdl_data.items():
cb = TagCheckBox(info["name"].strip(), info["description"].strip(), info["tags"])
if tag == "__required":
cb.setChecked(True)
cb.setDisabled(True)
if config_tags is not None:
if all(elem in config_tags for elem in info["tags"]):
cb.setChecked(True)
cb.stateChanged.connect(self.stateChanged)
main_layout.addWidget(cb)
def install_tags(self):
install_tags = [""]
for cb in self.findChildren(TagCheckBox, options=Qt.FindDirectChildrenOnly):
if data := cb.isChecked():
# noinspection PyTypeChecker
install_tags.extend(data)
return install_tags

View file

@ -1,4 +1,4 @@
requests
requests<3.0
QtAwesome
setuptools
legendary-gl>=0.20.34

View file

@ -1,4 +1,4 @@
requests
requests<3.0
PyQt5
QtAwesome
setuptools

View file

@ -1,4 +1,4 @@
requests
requests<3.0
PyQt5
QtAwesome
setuptools