Merge pull request #233 from Dummerle/install_advanced_options
Install advanced options
This commit is contained in:
commit
159732b214
|
@ -10,17 +10,18 @@ from legendary.models.downloading import ConditionCheckResult
|
|||
from legendary.models.game import Game
|
||||
from legendary.utils.selective_dl import get_sdl_appname
|
||||
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.api_arguments import LgndrInstallGameArgs
|
||||
from rare.lgndr.api_exception import LgndrException
|
||||
from rare.lgndr.api_monkeys import LgndrIndirectStatus
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.models.install import InstallDownloadModel, InstallQueueItemModel
|
||||
from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
|
||||
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
from rare.models.install import InstallDownloadModel, InstallQueueItemModel
|
||||
from rare.utils.misc import get_size
|
||||
from rare.utils import config_helper
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
from rare.utils.misc import get_size
|
||||
from rare.widgets.collabsible_widget import CollabsibleWidget
|
||||
|
||||
|
||||
class InstallDialog(QDialog, Ui_InstallDialog):
|
||||
|
@ -41,6 +42,11 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
if not self.dl_item.options.overlay
|
||||
else Game(app_name=self.app_name, app_title="Epic Overlay")
|
||||
)
|
||||
self.advanced_layout.setParent(None)
|
||||
self.advanced_widget = CollabsibleWidget(
|
||||
self.advanced_layout, self.tr("Advanced options"), parent=self
|
||||
)
|
||||
self.advanced_placeholder_layout.addWidget(self.advanced_widget)
|
||||
|
||||
self.game_path = self.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value", "")
|
||||
|
||||
|
@ -147,6 +153,8 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
self.verify_button.clicked.connect(self.verify_clicked)
|
||||
self.install_button.clicked.connect(self.install_clicked)
|
||||
|
||||
self.install_preqs_check.setChecked(self.dl_item.options.install_preqs)
|
||||
|
||||
self.install_dialog_layout.setSizeConstraint(self.install_dialog_layout.SetFixedSize)
|
||||
|
||||
def execute(self):
|
||||
|
@ -199,6 +207,8 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
self.dl_item.options.ignore_space = self.ignore_space_check.isChecked()
|
||||
self.dl_item.options.no_install = self.download_only_check.isChecked()
|
||||
self.dl_item.options.platform = self.platform_combo_box.currentText()
|
||||
self.dl_item.options.install_preqs = self.install_preqs_check.isChecked()
|
||||
self.dl_item.options.create_shortcut = self.shortcut_cb.isChecked()
|
||||
if self.sdl_list_cbs:
|
||||
self.dl_item.options.install_tag = [""]
|
||||
for cb in self.sdl_list_cbs:
|
||||
|
@ -280,6 +290,7 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
if dl_item.igame.prereq_info and not dl_item.igame.prereq_info.get("installed", False):
|
||||
self.install_preqs_check.setVisible(True)
|
||||
self.install_preqs_lbl.setVisible(True)
|
||||
self.install_preqs_check.setChecked(True)
|
||||
self.install_preqs_check.setText(
|
||||
self.tr("Also install: {}").format(dl_item.igame.prereq_info.get("name", ""))
|
||||
)
|
||||
|
|
|
@ -3,31 +3,25 @@ import platform
|
|||
import shutil
|
||||
from logging import getLogger
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QObject,
|
||||
QRunnable,
|
||||
Qt,
|
||||
pyqtSignal,
|
||||
QThreadPool,
|
||||
pyqtSlot,
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMenu,
|
||||
QProgressBar,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
QMessageBox,
|
||||
QWidgetAction,
|
||||
)
|
||||
from legendary.models.game import Game, InstalledGame, VerifyResult
|
||||
from legendary.utils.lfs import validate_files
|
||||
from legendary.models.game import Game, InstalledGame
|
||||
|
||||
from rare.models.install import InstallOptionsModel
|
||||
from rare.shared import (
|
||||
LegendaryCoreSingleton,
|
||||
GlobalSignalsSingleton,
|
||||
|
@ -35,11 +29,9 @@ from rare.shared import (
|
|||
)
|
||||
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
|
||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
from rare.utils.legendary_utils import VerifyWorker
|
||||
from rare.models.install import InstallOptionsModel
|
||||
from rare.utils.steam_grades import SteamWorker
|
||||
from rare.utils.misc import get_size
|
||||
from rare.utils.steam_grades import SteamWorker
|
||||
from rare.widgets.image_widget import ImageWidget
|
||||
|
||||
logger = getLogger("GameInfo")
|
||||
|
@ -401,245 +393,3 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
|
||||
self.move_game_pop_up.update_game(app_name)
|
||||
|
||||
|
||||
class MoveGamePopUp(QWidget):
|
||||
move_clicked = pyqtSignal(str)
|
||||
browse_done = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(MoveGamePopUp, self).__init__()
|
||||
layout: QVBoxLayout = QVBoxLayout()
|
||||
self.install_path = str()
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.move_path_edit = PathEdit(str(), QFileDialog.Directory, edit_func=self.edit_func_move_game)
|
||||
self.move_path_edit.path_select.clicked.connect(self.emit_browse_done_signal)
|
||||
|
||||
self.move_game = QPushButton(self.tr("Move"))
|
||||
self.move_game.setMaximumWidth(50)
|
||||
self.move_game.clicked.connect(self.emit_move_game_signal)
|
||||
|
||||
self.warn_overwriting = QLabel()
|
||||
|
||||
middle_layout = QHBoxLayout()
|
||||
middle_layout.setAlignment(Qt.AlignRight)
|
||||
middle_layout.addWidget(self.warn_overwriting, stretch=1)
|
||||
middle_layout.addWidget(self.move_game)
|
||||
|
||||
bottom_layout = QVBoxLayout()
|
||||
self.aval_space_label = QLabel()
|
||||
self.req_space_label = QLabel()
|
||||
bottom_layout.addWidget(self.aval_space_label)
|
||||
bottom_layout.addWidget(self.req_space_label)
|
||||
|
||||
layout.addWidget(self.move_path_edit)
|
||||
layout.addLayout(middle_layout)
|
||||
layout.addLayout(bottom_layout)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def emit_move_game_signal(self):
|
||||
self.move_clicked.emit(self.move_path_edit.text())
|
||||
|
||||
def emit_browse_done_signal(self):
|
||||
self.browse_done.emit()
|
||||
|
||||
def refresh_indicator(self):
|
||||
# needed so the edit_func gets run again
|
||||
text = self.move_path_edit.text()
|
||||
self.move_path_edit.setText(str())
|
||||
self.move_path_edit.setText(text)
|
||||
|
||||
# Thanks to lk.
|
||||
@staticmethod
|
||||
def find_mount(path):
|
||||
mount_point = path
|
||||
while path != path.anchor:
|
||||
if path.is_mount():
|
||||
return path
|
||||
else:
|
||||
path = path.parent
|
||||
return mount_point
|
||||
|
||||
def edit_func_move_game(self, dir_selected):
|
||||
self.move_game.setEnabled(True)
|
||||
self.warn_overwriting.setHidden(True)
|
||||
|
||||
def helper_func(reason: str) -> Tuple[bool, str, str]:
|
||||
self.move_game.setEnabled(False)
|
||||
return False, dir_selected, self.tr(reason)
|
||||
|
||||
if not self.install_path or not dir_selected:
|
||||
return helper_func("You need to provide a directory.")
|
||||
|
||||
install_path = Path(self.install_path).resolve()
|
||||
dest_path = Path(dir_selected).resolve()
|
||||
dest_path_with_suffix = dest_path.joinpath(install_path.stem).resolve()
|
||||
|
||||
if not dest_path.is_dir():
|
||||
return helper_func("Directory doesn't exist or file selected.")
|
||||
|
||||
# Get free space on drive and size of game folder
|
||||
_, _, free_space = shutil.disk_usage(dest_path)
|
||||
source_size = sum(f.stat().st_size for f in install_path.glob("**/*") if f.is_file())
|
||||
|
||||
# Calculate from bytes to gigabytes
|
||||
free_space_dest_drive = round(free_space / 1000**3, 2)
|
||||
source_size = round(source_size / 1000**3, 2)
|
||||
self.aval_space_label.setText(self.tr("Available space: {}GB".format(free_space_dest_drive)))
|
||||
self.req_space_label.setText(self.tr("Required space: {}GB").format(source_size))
|
||||
|
||||
if not os.access(dir_selected, os.W_OK) or not os.access(self.install_path, os.W_OK):
|
||||
return helper_func("No write permission on destination path/current install path.")
|
||||
|
||||
if install_path == dest_path or install_path == dest_path_with_suffix:
|
||||
return helper_func("Same directory or parent directory selected.")
|
||||
|
||||
if str(install_path) in str(dest_path):
|
||||
return helper_func("You can't select a directory that is inside the current install path.")
|
||||
|
||||
if str(dest_path_with_suffix) in str(install_path):
|
||||
return helper_func("You can't select a directory which contains the game installation.")
|
||||
|
||||
for game in self.core.get_installed_list():
|
||||
if game.install_path in dir_selected:
|
||||
return helper_func("Game installations cannot be nested due to unintended sideeffects.")
|
||||
|
||||
is_existing_dir = is_game_dir(install_path, dest_path_with_suffix)
|
||||
|
||||
for i in dest_path.iterdir():
|
||||
if install_path.stem in i.stem:
|
||||
if dest_path_with_suffix.is_dir():
|
||||
if not is_existing_dir:
|
||||
self.warn_overwriting.setHidden(False)
|
||||
elif dest_path_with_suffix.is_file():
|
||||
self.warn_overwriting.setHidden(False)
|
||||
|
||||
if free_space_dest_drive <= source_size and not is_existing_dir:
|
||||
return helper_func("Not enough space available on drive.")
|
||||
|
||||
# Fallback
|
||||
self.move_game.setEnabled(True)
|
||||
return True, dir_selected, str()
|
||||
|
||||
def update_game(self, app_name):
|
||||
igame = self.core.get_installed_game(app_name, False)
|
||||
if igame is None:
|
||||
return
|
||||
self.install_path = igame.install_path
|
||||
self.move_path_edit.setText(igame.install_path)
|
||||
self.warn_overwriting.setText(
|
||||
self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem)
|
||||
)
|
||||
|
||||
|
||||
class CopyGameInstallation(QRunnable):
|
||||
class Signals(QObject):
|
||||
progress = pyqtSignal(int)
|
||||
finished = pyqtSignal(str)
|
||||
no_space_left = pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
install_path: str,
|
||||
dest_path: Path,
|
||||
is_existing_dir: bool,
|
||||
igame: InstalledGame,
|
||||
):
|
||||
super(CopyGameInstallation, self).__init__()
|
||||
self.signals = self.Signals()
|
||||
self.install_path = install_path
|
||||
self.dest_path = dest_path
|
||||
self.source_size = 0
|
||||
self.dest_size = 0
|
||||
self.is_existing_dir = is_existing_dir
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.igame = igame
|
||||
self.file_list = None
|
||||
self.total: int = 0
|
||||
|
||||
def run(self):
|
||||
root_directory = Path(self.install_path)
|
||||
self.source_size = sum(f.stat().st_size for f in root_directory.glob("**/*") if f.is_file())
|
||||
|
||||
# if game dir is not existing, just copying:
|
||||
if not self.is_existing_dir:
|
||||
shutil.copytree(
|
||||
self.install_path,
|
||||
self.dest_path,
|
||||
copy_function=self.copy_each_file_with_progress,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
else:
|
||||
manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
|
||||
manifest = self.core.load_manifest(manifest_data)
|
||||
files = sorted(
|
||||
manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower(),
|
||||
)
|
||||
self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
self.total = len(self.file_list)
|
||||
|
||||
# recreate dir structure
|
||||
shutil.copytree(
|
||||
self.install_path,
|
||||
self.dest_path,
|
||||
copy_function=self.copy_dir_structure,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
|
||||
for i, (result, relative_path, _, _) in enumerate(
|
||||
validate_files(str(self.dest_path), self.file_list)
|
||||
):
|
||||
dst_path = f"{self.dest_path}/{relative_path}"
|
||||
src_path = f"{self.install_path}/{relative_path}"
|
||||
if Path(src_path).is_file():
|
||||
if result == VerifyResult.HASH_MISMATCH:
|
||||
try:
|
||||
shutil.copy(src_path, dst_path)
|
||||
except IOError:
|
||||
self.signals.no_space_left.emit()
|
||||
return
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
try:
|
||||
shutil.copy(src_path, dst_path)
|
||||
except (IOError, OSError):
|
||||
self.signals.no_space_left.emit()
|
||||
return
|
||||
elif result == VerifyResult.OTHER_ERROR:
|
||||
logger.warning(f"Copying file {src_path} to {dst_path} failed")
|
||||
self.signals.progress.emit(int(i * 10 / self.total * 10))
|
||||
else:
|
||||
logger.warning(
|
||||
f"Source dir does not have file {src_path}. File will be missing in the destination "
|
||||
f"dir. "
|
||||
)
|
||||
|
||||
shutil.rmtree(self.install_path)
|
||||
self.signals.finished.emit(str(self.dest_path))
|
||||
|
||||
def copy_each_file_with_progress(self, src, dst):
|
||||
shutil.copy(src, dst)
|
||||
self.dest_size += Path(src).stat().st_size
|
||||
self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
|
||||
|
||||
# This method is a copy_func, and only copies the src if it's a dir.
|
||||
# Thus, it can be used to re-create the dir strucute.
|
||||
@staticmethod
|
||||
def copy_dir_structure(src, dst):
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
if os.path.isdir(src):
|
||||
shutil.copyfile(src, dst)
|
||||
shutil.copystat(src, dst)
|
||||
return dst
|
||||
|
||||
|
||||
def is_game_dir(install_path: Path, dest_path: Path):
|
||||
# This iterates over the destination dir, then iterates over the current install dir and if the file names
|
||||
# matches, we have an exisiting dir
|
||||
if dest_path.is_dir():
|
||||
for file in dest_path.iterdir():
|
||||
for install_file in install_path.iterdir():
|
||||
if file.name == install_file.name:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -30,54 +30,37 @@ class GameSettings(DefaultGameSettings):
|
|||
"",
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
placeholder=self.tr("Cloud save path"),
|
||||
edit_func=lambda text: (os.path.exists(text), text, PathEdit.reasons.dir_not_exist),
|
||||
edit_func=lambda text: (True, text, None)
|
||||
if os.path.exists(text)
|
||||
else (False, text, PathEdit.reasons.dir_not_exist),
|
||||
save_func=self.save_save_path,
|
||||
)
|
||||
self.cloud_layout.addRow(
|
||||
QLabel(self.tr("Save path")), self.cloud_save_path_edit
|
||||
)
|
||||
self.cloud_layout.addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit)
|
||||
|
||||
self.compute_save_path_button = QPushButton(
|
||||
icon("fa.magic"), self.tr("Auto compute save path")
|
||||
)
|
||||
self.compute_save_path_button.setSizePolicy(
|
||||
QSizePolicy.Maximum, QSizePolicy.Fixed
|
||||
)
|
||||
self.compute_save_path_button = QPushButton(icon("fa.magic"), self.tr("Auto compute save path"))
|
||||
self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
self.compute_save_path_button.clicked.connect(self.compute_save_path)
|
||||
self.cloud_layout.addRow(None, self.compute_save_path_button)
|
||||
|
||||
self.offline.currentIndexChanged.connect(
|
||||
lambda x: self.update_combobox(x, "offline")
|
||||
)
|
||||
self.skip_update.currentIndexChanged.connect(
|
||||
lambda x: self.update_combobox(x, "skip_update_check")
|
||||
)
|
||||
self.offline.currentIndexChanged.connect(lambda x: self.update_combobox(x, "offline"))
|
||||
self.skip_update.currentIndexChanged.connect(lambda x: self.update_combobox(x, "skip_update_check"))
|
||||
self.cloud_sync.stateChanged.connect(
|
||||
lambda: self.settings.setValue(
|
||||
f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked()
|
||||
)
|
||||
)
|
||||
self.override_exe_edit.textChanged.connect(
|
||||
lambda text: self.save_line_edit("override_exe", text)
|
||||
)
|
||||
self.launch_params.textChanged.connect(
|
||||
lambda x: self.save_line_edit("start_params", x)
|
||||
)
|
||||
self.override_exe_edit.textChanged.connect(lambda text: self.save_line_edit("override_exe", text))
|
||||
self.launch_params.textChanged.connect(lambda x: self.save_line_edit("start_params", x))
|
||||
|
||||
self.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
def compute_save_path(self):
|
||||
if (
|
||||
self.core.is_installed(self.game.app_name)
|
||||
and self.game.supports_cloud_saves
|
||||
):
|
||||
if self.core.is_installed(self.game.app_name) and self.game.supports_cloud_saves:
|
||||
try:
|
||||
new_path = self.core.get_save_path(self.game.app_name)
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
resolver = WineResolver(
|
||||
get_raw_save_path(self.game), self.game.app_name
|
||||
)
|
||||
resolver = WineResolver(get_raw_save_path(self.game), self.game.app_name)
|
||||
if not resolver.wine_env.get("WINEPREFIX"):
|
||||
self.cloud_save_path_edit.setText("")
|
||||
QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings")
|
||||
|
@ -87,18 +70,14 @@ class GameSettings(DefaultGameSettings):
|
|||
self.compute_save_path_button.setDisabled(True)
|
||||
|
||||
app_name = self.game.app_name[:]
|
||||
resolver.signals.result_ready.connect(
|
||||
lambda x: self.wine_resolver_finished(x, app_name)
|
||||
)
|
||||
resolver.signals.result_ready.connect(lambda x: self.wine_resolver_finished(x, app_name))
|
||||
QThreadPool.globalInstance().start(resolver)
|
||||
return
|
||||
else:
|
||||
self.cloud_save_path_edit.setText(new_path)
|
||||
|
||||
def wine_resolver_finished(self, path, app_name):
|
||||
logger.info(
|
||||
f"Wine resolver finished for {app_name}. Computed save path: {path}"
|
||||
)
|
||||
logger.info(f"Wine resolver finished for {app_name}. Computed save path: {path}")
|
||||
if app_name == self.game.app_name:
|
||||
self.cloud_save_path_edit.setDisabled(False)
|
||||
self.compute_save_path_button.setDisabled(False)
|
||||
|
@ -110,9 +89,9 @@ class GameSettings(DefaultGameSettings):
|
|||
QMessageBox.warning(
|
||||
None,
|
||||
"Error",
|
||||
self.tr(
|
||||
"Error while launching {}. No permission to create {}"
|
||||
).format(self.game.app_title, path),
|
||||
self.tr("Error while launching {}. No permission to create {}").format(
|
||||
self.game.app_title, path
|
||||
),
|
||||
)
|
||||
return
|
||||
if not path:
|
||||
|
@ -160,9 +139,7 @@ class GameSettings(DefaultGameSettings):
|
|||
self.igame = self.core.get_installed_game(self.game.app_name)
|
||||
if self.igame:
|
||||
if self.igame.can_run_offline:
|
||||
offline = self.core.lgd.config.get(
|
||||
self.game.app_name, "offline", fallback="unset"
|
||||
)
|
||||
offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset")
|
||||
if offline == "true":
|
||||
self.offline.setCurrentIndex(1)
|
||||
elif offline == "false":
|
||||
|
@ -176,9 +153,7 @@ class GameSettings(DefaultGameSettings):
|
|||
else:
|
||||
self.offline.setEnabled(False)
|
||||
|
||||
skip_update = self.core.lgd.config.get(
|
||||
self.game.app_name, "skip_update_check", fallback="unset"
|
||||
)
|
||||
skip_update = self.core.lgd.config.get(self.game.app_name, "skip_update_check", fallback="unset")
|
||||
if skip_update == "true":
|
||||
self.skip_update.setCurrentIndex(1)
|
||||
elif skip_update == "false":
|
||||
|
@ -198,18 +173,14 @@ class GameSettings(DefaultGameSettings):
|
|||
self.cloud_save_path_edit.setText("")
|
||||
else:
|
||||
self.cloud_group.setEnabled(True)
|
||||
sync_cloud = self.settings.value(
|
||||
f"{self.game.app_name}/auto_sync_cloud", True, bool
|
||||
)
|
||||
sync_cloud = self.settings.value(f"{self.game.app_name}/auto_sync_cloud", True, bool)
|
||||
self.cloud_sync.setChecked(sync_cloud)
|
||||
if self.igame.save_path:
|
||||
self.cloud_save_path_edit.setText(self.igame.save_path)
|
||||
else:
|
||||
self.cloud_save_path_edit.setText("")
|
||||
|
||||
self.launch_params.setText(
|
||||
self.core.lgd.config.get(self.game.app_name, "start_params", fallback="")
|
||||
)
|
||||
self.launch_params.setText(self.core.lgd.config.get(self.game.app_name, "start_params", fallback=""))
|
||||
self.override_exe_edit.setText(
|
||||
self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="")
|
||||
)
|
||||
|
|
258
rare/components/tabs/games/game_info/move_game.py
Normal file
258
rare/components/tabs/games/game_info/move_game.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
import os
|
||||
import shutil
|
||||
from logging import getLogger
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QRunnable, QObject, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
|
||||
from legendary.models.game import VerifyResult, InstalledGame
|
||||
from legendary.utils.lfs import validate_files
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
|
||||
logger = getLogger("MoveGame")
|
||||
|
||||
|
||||
class MoveGamePopUp(QWidget):
|
||||
move_clicked = pyqtSignal(str)
|
||||
browse_done = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super(MoveGamePopUp, self).__init__()
|
||||
layout: QVBoxLayout = QVBoxLayout()
|
||||
self.install_path = str()
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.move_path_edit = PathEdit(str(), QFileDialog.Directory, edit_func=self.edit_func_move_game)
|
||||
self.move_path_edit.path_select.clicked.connect(self.emit_browse_done_signal)
|
||||
|
||||
self.move_game = QPushButton(self.tr("Move"))
|
||||
self.move_game.setMaximumWidth(50)
|
||||
self.move_game.clicked.connect(self.emit_move_game_signal)
|
||||
|
||||
self.warn_overwriting = QLabel()
|
||||
|
||||
middle_layout = QHBoxLayout()
|
||||
middle_layout.setAlignment(Qt.AlignRight)
|
||||
middle_layout.addWidget(self.warn_overwriting, stretch=1)
|
||||
middle_layout.addWidget(self.move_game)
|
||||
|
||||
bottom_layout = QVBoxLayout()
|
||||
self.aval_space_label = QLabel()
|
||||
self.req_space_label = QLabel()
|
||||
bottom_layout.addWidget(self.aval_space_label)
|
||||
bottom_layout.addWidget(self.req_space_label)
|
||||
|
||||
layout.addWidget(self.move_path_edit)
|
||||
layout.addLayout(middle_layout)
|
||||
layout.addLayout(bottom_layout)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def emit_move_game_signal(self):
|
||||
self.move_clicked.emit(self.move_path_edit.text())
|
||||
|
||||
def emit_browse_done_signal(self):
|
||||
self.browse_done.emit()
|
||||
|
||||
def refresh_indicator(self):
|
||||
# needed so the edit_func gets run again
|
||||
text = self.move_path_edit.text()
|
||||
self.move_path_edit.setText(str())
|
||||
self.move_path_edit.setText(text)
|
||||
|
||||
# Thanks to lk.
|
||||
@staticmethod
|
||||
def find_mount(path):
|
||||
mount_point = path
|
||||
while path != path.anchor:
|
||||
if path.is_mount():
|
||||
return path
|
||||
else:
|
||||
path = path.parent
|
||||
return mount_point
|
||||
|
||||
def edit_func_move_game(self, dir_selected):
|
||||
self.move_game.setEnabled(True)
|
||||
self.warn_overwriting.setHidden(True)
|
||||
|
||||
def helper_func(reason: str) -> Tuple[bool, str, str]:
|
||||
self.move_game.setEnabled(False)
|
||||
return False, dir_selected, self.tr(reason)
|
||||
|
||||
if not self.install_path or not dir_selected:
|
||||
return helper_func("You need to provide a directory.")
|
||||
|
||||
install_path = Path(self.install_path).resolve()
|
||||
dest_path = Path(dir_selected).resolve()
|
||||
dest_path_with_suffix = dest_path.joinpath(install_path.stem).resolve()
|
||||
|
||||
if not dest_path.is_dir():
|
||||
return helper_func("Directory doesn't exist or file selected.")
|
||||
|
||||
# Get free space on drive and size of game folder
|
||||
_, _, free_space = shutil.disk_usage(dest_path)
|
||||
source_size = sum(f.stat().st_size for f in install_path.glob("**/*") if f.is_file())
|
||||
|
||||
# Calculate from bytes to gigabytes
|
||||
free_space_dest_drive = round(free_space / 1000 ** 3, 2)
|
||||
source_size = round(source_size / 1000 ** 3, 2)
|
||||
self.aval_space_label.setText(self.tr("Available space: {}GB".format(free_space_dest_drive)))
|
||||
self.req_space_label.setText(self.tr("Required space: {}GB").format(source_size))
|
||||
|
||||
if not os.access(dir_selected, os.W_OK) or not os.access(self.install_path, os.W_OK):
|
||||
return helper_func("No write permission on destination path/current install path.")
|
||||
|
||||
if install_path == dest_path or install_path == dest_path_with_suffix:
|
||||
return helper_func("Same directory or parent directory selected.")
|
||||
|
||||
if str(install_path) in str(dest_path):
|
||||
return helper_func("You can't select a directory that is inside the current install path.")
|
||||
|
||||
if str(dest_path_with_suffix) in str(install_path):
|
||||
return helper_func("You can't select a directory which contains the game installation.")
|
||||
|
||||
for game in self.core.get_installed_list():
|
||||
if game.install_path in dir_selected:
|
||||
return helper_func("Game installations cannot be nested due to unintended sideeffects.")
|
||||
|
||||
is_existing_dir = is_game_dir(install_path, dest_path_with_suffix)
|
||||
|
||||
for i in dest_path.iterdir():
|
||||
if install_path.stem in i.stem:
|
||||
if dest_path_with_suffix.is_dir():
|
||||
if not is_existing_dir:
|
||||
self.warn_overwriting.setHidden(False)
|
||||
elif dest_path_with_suffix.is_file():
|
||||
self.warn_overwriting.setHidden(False)
|
||||
|
||||
if free_space_dest_drive <= source_size and not is_existing_dir:
|
||||
return helper_func("Not enough space available on drive.")
|
||||
|
||||
# Fallback
|
||||
self.move_game.setEnabled(True)
|
||||
return True, dir_selected, str()
|
||||
|
||||
def update_game(self, app_name):
|
||||
igame = self.core.get_installed_game(app_name, False)
|
||||
if igame is None:
|
||||
return
|
||||
self.install_path = igame.install_path
|
||||
self.move_path_edit.setText(igame.install_path)
|
||||
self.warn_overwriting.setText(
|
||||
self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem)
|
||||
)
|
||||
|
||||
|
||||
class CopyGameInstallation(QRunnable):
|
||||
class Signals(QObject):
|
||||
progress = pyqtSignal(int)
|
||||
finished = pyqtSignal(str)
|
||||
no_space_left = pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
install_path: str,
|
||||
dest_path: Path,
|
||||
is_existing_dir: bool,
|
||||
igame: InstalledGame,
|
||||
):
|
||||
super(CopyGameInstallation, self).__init__()
|
||||
self.signals = CopyGameInstallation.Signals()
|
||||
self.install_path = install_path
|
||||
self.dest_path = dest_path
|
||||
self.source_size = 0
|
||||
self.dest_size = 0
|
||||
self.is_existing_dir = is_existing_dir
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.igame = igame
|
||||
self.file_list = None
|
||||
self.total: int = 0
|
||||
|
||||
def run(self):
|
||||
root_directory = Path(self.install_path)
|
||||
self.source_size = sum(f.stat().st_size for f in root_directory.glob("**/*") if f.is_file())
|
||||
|
||||
# if game dir is not existing, just copying:
|
||||
if not self.is_existing_dir:
|
||||
shutil.copytree(
|
||||
self.install_path,
|
||||
self.dest_path,
|
||||
copy_function=self.copy_each_file_with_progress,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
else:
|
||||
manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
|
||||
manifest = self.core.load_manifest(manifest_data)
|
||||
files = sorted(
|
||||
manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower(),
|
||||
)
|
||||
self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
self.total = len(self.file_list)
|
||||
|
||||
# recreate dir structure
|
||||
shutil.copytree(
|
||||
self.install_path,
|
||||
self.dest_path,
|
||||
copy_function=self.copy_dir_structure,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
|
||||
for i, (result, relative_path, _, _) in enumerate(
|
||||
validate_files(str(self.dest_path), self.file_list)
|
||||
):
|
||||
dst_path = f"{self.dest_path}/{relative_path}"
|
||||
src_path = f"{self.install_path}/{relative_path}"
|
||||
if Path(src_path).is_file():
|
||||
if result == VerifyResult.HASH_MISMATCH:
|
||||
try:
|
||||
shutil.copy(src_path, dst_path)
|
||||
except IOError:
|
||||
self.signals.no_space_left.emit()
|
||||
return
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
try:
|
||||
shutil.copy(src_path, dst_path)
|
||||
except (IOError, OSError):
|
||||
self.signals.no_space_left.emit()
|
||||
return
|
||||
elif result == VerifyResult.OTHER_ERROR:
|
||||
logger.warning(f"Copying file {src_path} to {dst_path} failed")
|
||||
self.signals.progress.emit(int(i * 10 / self.total * 10))
|
||||
else:
|
||||
logger.warning(
|
||||
f"Source dir does not have file {src_path}. File will be missing in the destination "
|
||||
f"dir. "
|
||||
)
|
||||
|
||||
shutil.rmtree(self.install_path)
|
||||
self.signals.finished.emit(str(self.dest_path))
|
||||
|
||||
def copy_each_file_with_progress(self, src, dst):
|
||||
shutil.copy(src, dst)
|
||||
self.dest_size += Path(src).stat().st_size
|
||||
self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
|
||||
|
||||
# This method is a copy_func, and only copies the src if it's a dir.
|
||||
# Thus, it can be used to re-create the dir strucute.
|
||||
@staticmethod
|
||||
def copy_dir_structure(src, dst):
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
if os.path.isdir(src):
|
||||
shutil.copyfile(src, dst)
|
||||
shutil.copystat(src, dst)
|
||||
return dst
|
||||
|
||||
|
||||
def is_game_dir(install_path: Path, dest_path: Path):
|
||||
# This iterates over the destination dir, then iterates over the current install dir and if the file names
|
||||
# matches, we have an exisiting dir
|
||||
if dest_path.is_dir():
|
||||
for file in dest_path.iterdir():
|
||||
for install_file in install_path.iterdir():
|
||||
if file.name == install_file.name:
|
||||
return True
|
||||
return False
|
|
@ -40,7 +40,7 @@ class PreLaunchThread(QRunnable):
|
|||
def prepare_launch(self, app_name) -> Union[LaunchArgs, None]:
|
||||
try:
|
||||
args = get_launch_args(self.core, InitArgs(app_name))
|
||||
except GameArgsError as e:
|
||||
except Exception as e:
|
||||
self.signals.error_occurred.emit(str(e))
|
||||
return None
|
||||
if not args:
|
||||
|
@ -196,8 +196,11 @@ class GameProcessApp(RareApp):
|
|||
|
||||
def stop(self):
|
||||
self.logger.info("Stopping server")
|
||||
self.server.close()
|
||||
self.server.deleteLater()
|
||||
try:
|
||||
self.server.close()
|
||||
self.server.deleteLater()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.exit_app.emit()
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/dialogs/install_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
@ -14,11 +14,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
class Ui_InstallDialog(object):
|
||||
def setupUi(self, InstallDialog):
|
||||
InstallDialog.setObjectName("InstallDialog")
|
||||
InstallDialog.resize(406, 447)
|
||||
InstallDialog.resize(530, 489)
|
||||
InstallDialog.setWindowTitle("Rare")
|
||||
self.install_dialog_layout = QtWidgets.QFormLayout(InstallDialog)
|
||||
self.install_dialog_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
|
||||
self.install_dialog_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.install_dialog_layout.setLabelAlignment(
|
||||
QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
|
||||
self.install_dialog_layout.setObjectName("install_dialog_layout")
|
||||
self.install_dialog_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.install_dialog_label.setObjectName("install_dialog_label")
|
||||
|
@ -29,38 +30,6 @@ class Ui_InstallDialog(object):
|
|||
self.install_dir_layout = QtWidgets.QHBoxLayout()
|
||||
self.install_dir_layout.setObjectName("install_dir_layout")
|
||||
self.install_dialog_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.install_dir_layout)
|
||||
self.max_workers_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.max_workers_label.setObjectName("max_workers_label")
|
||||
self.install_dialog_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.max_workers_label)
|
||||
self.max_workers_layout = QtWidgets.QHBoxLayout()
|
||||
self.max_workers_layout.setObjectName("max_workers_layout")
|
||||
self.max_workers_spin = QtWidgets.QSpinBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.max_workers_spin.sizePolicy().hasHeightForWidth())
|
||||
self.max_workers_spin.setSizePolicy(sizePolicy)
|
||||
self.max_workers_spin.setObjectName("max_workers_spin")
|
||||
self.max_workers_layout.addWidget(self.max_workers_spin)
|
||||
self.max_workers_info_label = QtWidgets.QLabel(InstallDialog)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.max_workers_info_label.setFont(font)
|
||||
self.max_workers_info_label.setObjectName("max_workers_info_label")
|
||||
self.max_workers_layout.addWidget(self.max_workers_info_label)
|
||||
self.install_dialog_layout.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.max_workers_layout)
|
||||
self.force_download_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.force_download_label.setObjectName("force_download_label")
|
||||
self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
|
||||
self.force_download_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.force_download_check.sizePolicy().hasHeightForWidth())
|
||||
self.force_download_check.setSizePolicy(sizePolicy)
|
||||
self.force_download_check.setText("")
|
||||
self.force_download_check.setObjectName("force_download_check")
|
||||
self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
|
||||
self.platform_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.platform_label.setObjectName("platform_label")
|
||||
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label)
|
||||
|
@ -72,17 +41,13 @@ class Ui_InstallDialog(object):
|
|||
self.platform_combo_box.setSizePolicy(sizePolicy)
|
||||
self.platform_combo_box.setObjectName("platform_combo_box")
|
||||
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.platform_combo_box)
|
||||
self.ignore_space_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.ignore_space_label.setObjectName("ignore_space_label")
|
||||
self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
|
||||
self.download_only_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.download_only_label.setObjectName("download_only_label")
|
||||
self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
|
||||
self.shortcut_lbl = QtWidgets.QLabel(InstallDialog)
|
||||
self.shortcut_lbl.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.shortcut_lbl.setObjectName("shortcut_lbl")
|
||||
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.shortcut_lbl)
|
||||
self.shortcut_cb = QtWidgets.QCheckBox(InstallDialog)
|
||||
self.shortcut_cb.setText("")
|
||||
self.shortcut_cb.setChecked(True)
|
||||
self.shortcut_cb.setObjectName("shortcut_cb")
|
||||
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.shortcut_cb)
|
||||
self.sdl_list_label = QtWidgets.QLabel(InstallDialog)
|
||||
|
@ -115,6 +80,9 @@ class Ui_InstallDialog(object):
|
|||
self.install_size_info_label.setWordWrap(True)
|
||||
self.install_size_info_label.setObjectName("install_size_info_label")
|
||||
self.install_dialog_layout.setWidget(14, QtWidgets.QFormLayout.FieldRole, self.install_size_info_label)
|
||||
self.warn_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.warn_label.setObjectName("warn_label")
|
||||
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.LabelRole, self.warn_label)
|
||||
self.warn_message = QtWidgets.QLabel(InstallDialog)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
|
@ -124,9 +92,6 @@ class Ui_InstallDialog(object):
|
|||
self.warn_message.setWordWrap(True)
|
||||
self.warn_message.setObjectName("warn_message")
|
||||
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.FieldRole, self.warn_message)
|
||||
self.warn_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.warn_label.setObjectName("warn_label")
|
||||
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.LabelRole, self.warn_label)
|
||||
self.button_layout = QtWidgets.QHBoxLayout()
|
||||
self.button_layout.setObjectName("button_layout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
|
@ -140,42 +105,35 @@ class Ui_InstallDialog(object):
|
|||
self.install_button = QtWidgets.QPushButton(InstallDialog)
|
||||
self.install_button.setObjectName("install_button")
|
||||
self.button_layout.addWidget(self.install_button)
|
||||
self.install_dialog_layout.setLayout(16, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
|
||||
self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
|
||||
self.install_preqs_lbl.setObjectName("install_preqs_lbl")
|
||||
self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
|
||||
self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.install_preqs_check.setFont(font)
|
||||
self.install_preqs_check.setText("")
|
||||
self.install_preqs_check.setObjectName("install_preqs_check")
|
||||
self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
|
||||
self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
self.install_dialog_layout.setLayout(17, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
|
||||
self.advanced_layout = QtWidgets.QFormLayout()
|
||||
self.advanced_layout.setLabelAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
|
||||
self.advanced_layout.setObjectName("advanced_layout")
|
||||
self.max_workers_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.max_workers_label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.max_workers_label.setObjectName("max_workers_label")
|
||||
self.advanced_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.max_workers_label)
|
||||
self.max_workers_layout = QtWidgets.QHBoxLayout()
|
||||
self.max_workers_layout.setObjectName("max_workers_layout")
|
||||
self.max_workers_spin = QtWidgets.QSpinBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth())
|
||||
self.ignore_space_check.setSizePolicy(sizePolicy)
|
||||
sizePolicy.setHeightForWidth(self.max_workers_spin.sizePolicy().hasHeightForWidth())
|
||||
self.max_workers_spin.setSizePolicy(sizePolicy)
|
||||
self.max_workers_spin.setObjectName("max_workers_spin")
|
||||
self.max_workers_layout.addWidget(self.max_workers_spin)
|
||||
self.max_workers_info_label = QtWidgets.QLabel(InstallDialog)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.ignore_space_check.setFont(font)
|
||||
self.ignore_space_check.setObjectName("ignore_space_check")
|
||||
self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
|
||||
self.download_only_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth())
|
||||
self.download_only_check.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.download_only_check.setFont(font)
|
||||
self.download_only_check.setObjectName("download_only_check")
|
||||
self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
|
||||
self.max_workers_info_label.setFont(font)
|
||||
self.max_workers_info_label.setObjectName("max_workers_info_label")
|
||||
self.max_workers_layout.addWidget(self.max_workers_info_label)
|
||||
self.advanced_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.max_workers_layout)
|
||||
self.max_memory_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.max_memory_label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.max_memory_label.setObjectName("max_memory_label")
|
||||
self.install_dialog_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.max_memory_label)
|
||||
self.advanced_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.max_memory_label)
|
||||
self.max_memory_layout = QtWidgets.QHBoxLayout()
|
||||
self.max_memory_layout.setObjectName("max_memory_layout")
|
||||
self.max_memory_spin = QtWidgets.QSpinBox(InstallDialog)
|
||||
|
@ -196,15 +154,70 @@ class Ui_InstallDialog(object):
|
|||
self.max_memory_info_label.setFont(font)
|
||||
self.max_memory_info_label.setObjectName("max_memory_info_label")
|
||||
self.max_memory_layout.addWidget(self.max_memory_info_label)
|
||||
self.install_dialog_layout.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout)
|
||||
self.advanced_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout)
|
||||
self.dl_optimizations_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.dl_optimizations_label.setObjectName("dl_optimizations_label")
|
||||
self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
|
||||
self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
|
||||
self.dl_optimizations_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
self.dl_optimizations_check.setText("")
|
||||
self.dl_optimizations_check.setChecked(False)
|
||||
self.dl_optimizations_check.setObjectName("dl_optimizations_check")
|
||||
self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
|
||||
self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
|
||||
self.force_download_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.force_download_label.setObjectName("force_download_label")
|
||||
self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
|
||||
self.force_download_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.force_download_check.sizePolicy().hasHeightForWidth())
|
||||
self.force_download_check.setSizePolicy(sizePolicy)
|
||||
self.force_download_check.setText("")
|
||||
self.force_download_check.setObjectName("force_download_check")
|
||||
self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
|
||||
self.ignore_space_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.ignore_space_label.setObjectName("ignore_space_label")
|
||||
self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
|
||||
self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth())
|
||||
self.ignore_space_check.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.ignore_space_check.setFont(font)
|
||||
self.ignore_space_check.setObjectName("ignore_space_check")
|
||||
self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
|
||||
self.download_only_label = QtWidgets.QLabel(InstallDialog)
|
||||
self.download_only_label.setObjectName("download_only_label")
|
||||
self.advanced_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
|
||||
self.download_only_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth())
|
||||
self.download_only_check.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.download_only_check.setFont(font)
|
||||
self.download_only_check.setObjectName("download_only_check")
|
||||
self.advanced_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
|
||||
self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
|
||||
self.install_preqs_lbl.setObjectName("install_preqs_lbl")
|
||||
self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
|
||||
self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
|
||||
font = QtGui.QFont()
|
||||
font.setItalic(True)
|
||||
self.install_preqs_check.setFont(font)
|
||||
self.install_preqs_check.setText("")
|
||||
self.install_preqs_check.setChecked(False)
|
||||
self.install_preqs_check.setObjectName("install_preqs_check")
|
||||
self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
|
||||
self.install_dialog_layout.setLayout(16, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
|
||||
self.advanced_placeholder_layout = QtWidgets.QVBoxLayout()
|
||||
self.advanced_placeholder_layout.setObjectName("advanced_placeholder_layout")
|
||||
self.install_dialog_layout.setLayout(12, QtWidgets.QFormLayout.SpanningRole, self.advanced_placeholder_layout)
|
||||
|
||||
self.retranslateUi(InstallDialog)
|
||||
QtCore.QMetaObject.connectSlotsByName(InstallDialog)
|
||||
|
@ -213,30 +226,30 @@ class Ui_InstallDialog(object):
|
|||
_translate = QtCore.QCoreApplication.translate
|
||||
self.install_dialog_label.setText(_translate("InstallDialog", "error"))
|
||||
self.install_dir_label.setText(_translate("InstallDialog", "Install directory"))
|
||||
self.max_workers_label.setText(_translate("InstallDialog", "Max workers"))
|
||||
self.max_workers_info_label.setText(_translate("InstallDialog", "Less is slower. (0: Default)"))
|
||||
self.force_download_label.setText(_translate("InstallDialog", "Force redownload"))
|
||||
self.platform_label.setText(_translate("InstallDialog", "Platform"))
|
||||
self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space"))
|
||||
self.download_only_label.setText(_translate("InstallDialog", "Download only"))
|
||||
self.shortcut_lbl.setText(_translate("InstallDialog", "Create shortcut"))
|
||||
self.sdl_list_label.setText(_translate("InstallDialog", "Optional packs"))
|
||||
self.download_size_label.setText(_translate("InstallDialog", "Download size"))
|
||||
self.download_size_info_label.setText(_translate("InstallDialog", "Click verify..."))
|
||||
self.install_size_label.setText(_translate("InstallDialog", "Total install size"))
|
||||
self.install_size_info_label.setText(_translate("InstallDialog", "Click verify..."))
|
||||
self.warn_message.setText(_translate("InstallDialog", "None"))
|
||||
self.warn_label.setText(_translate("InstallDialog", "Warning"))
|
||||
self.warn_message.setText(_translate("InstallDialog", "None"))
|
||||
self.cancel_button.setText(_translate("InstallDialog", "Cancel"))
|
||||
self.verify_button.setText(_translate("InstallDialog", "Verify"))
|
||||
self.install_button.setText(_translate("InstallDialog", "Install"))
|
||||
self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
|
||||
self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!"))
|
||||
self.download_only_check.setText(_translate("InstallDialog", "Do not try to install."))
|
||||
self.max_workers_label.setText(_translate("InstallDialog", "Max workers"))
|
||||
self.max_workers_info_label.setText(_translate("InstallDialog", "Less is slower. (0: Default)"))
|
||||
self.max_memory_label.setText(_translate("InstallDialog", "Max shared memory"))
|
||||
self.max_memory_spin.setSuffix(_translate("InstallDialog", "MiB"))
|
||||
self.max_memory_info_label.setText(_translate("InstallDialog", "Less is slower (0: Default)"))
|
||||
self.dl_optimizations_label.setText(_translate("InstallDialog", "Enable reordering"))
|
||||
self.force_download_label.setText(_translate("InstallDialog", "Force redownload"))
|
||||
self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space"))
|
||||
self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!"))
|
||||
self.download_only_label.setText(_translate("InstallDialog", "Download only"))
|
||||
self.download_only_check.setText(_translate("InstallDialog", "Do not try to install."))
|
||||
self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>406</width>
|
||||
<height>447</height>
|
||||
<width>530</width>
|
||||
<height>489</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -37,59 +37,6 @@
|
|||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="install_dir_layout"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="max_workers_label">
|
||||
<property name="text">
|
||||
<string>Max workers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="max_workers_layout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="max_workers_spin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="max_workers_info_label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Less is slower. (0: Default)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="force_download_label">
|
||||
<property name="text">
|
||||
<string>Force redownload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="force_download_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="platform_label">
|
||||
<property name="text">
|
||||
|
@ -107,25 +54,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="ignore_space_label">
|
||||
<property name="text">
|
||||
<string>Ignore free space</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="download_only_label">
|
||||
<property name="text">
|
||||
<string>Download only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="shortcut_lbl">
|
||||
<property name="text">
|
||||
<string>Create shortcut</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
|
@ -133,6 +69,9 @@
|
|||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
|
@ -198,6 +137,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="warn_label">
|
||||
<property name="text">
|
||||
<string>Warning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QLabel" name="warn_message">
|
||||
<property name="font">
|
||||
|
@ -219,14 +165,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="warn_label">
|
||||
<property name="text">
|
||||
<string>Warning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0" colspan="2">
|
||||
<item row="17" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="button_layout">
|
||||
<item>
|
||||
<spacer name="button_hspacer">
|
||||
|
@ -264,125 +203,211 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="install_preqs_lbl">
|
||||
<property name="text">
|
||||
<string>Install prerequisites</string>
|
||||
<item row="16" column="0" colspan="2">
|
||||
<layout class="QFormLayout" name="advanced_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QCheckBox" name="install_preqs_check">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="ignore_space_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use with caution!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="download_only_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Do not try to install.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="max_memory_label">
|
||||
<property name="text">
|
||||
<string>Max shared memory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="max_memory_layout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="max_memory_spin">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="max_workers_label">
|
||||
<property name="text">
|
||||
<string>Max workers</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="max_workers_layout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="max_workers_spin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="max_workers_info_label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Less is slower. (0: Default)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="max_memory_label">
|
||||
<property name="text">
|
||||
<string>Max shared memory</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="max_memory_layout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="max_memory_spin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10240</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="max_memory_info_label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Less is slower (0: Default)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="dl_optimizations_label">
|
||||
<property name="text">
|
||||
<string>Enable reordering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="dl_optimizations_check">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="force_download_label">
|
||||
<property name="text">
|
||||
<string>Force redownload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="force_download_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10240</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="max_memory_info_label">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="ignore_space_label">
|
||||
<property name="text">
|
||||
<string>Ignore free space</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="ignore_space_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Less is slower (0: Default)</string>
|
||||
<string>Use with caution!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="download_only_label">
|
||||
<property name="text">
|
||||
<string>Download only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="download_only_check">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Do not try to install.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="install_preqs_lbl">
|
||||
<property name="text">
|
||||
<string>Install prerequisites</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="install_preqs_check">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="dl_optimizations_label">
|
||||
<property name="text">
|
||||
<string>Enable reordering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="dl_optimizations_check">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="12" column="0" colspan="2">
|
||||
<layout class="QVBoxLayout" name="advanced_placeholder_layout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
86
rare/widgets/collabsible_widget.py
Normal file
86
rare/widgets/collabsible_widget.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from PyQt5.QtCore import QParallelAnimationGroup, Qt, QPropertyAnimation, QAbstractAnimation
|
||||
from PyQt5.QtWidgets import QWidget, QFrame, QToolButton, QGridLayout, QSizePolicy, QLayout
|
||||
|
||||
|
||||
# https://newbedev.com/how-to-make-an-expandable-collapsable-section-widget-in-qt
|
||||
|
||||
|
||||
class CollabsibleWidget(QWidget):
|
||||
def __init__(
|
||||
self, child_layout: QLayout = None, title: str = "", animation_duration: int = 200, parent=None
|
||||
):
|
||||
"""
|
||||
References:
|
||||
# Adapted from c++ version
|
||||
https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
|
||||
"""
|
||||
super(CollabsibleWidget, self).__init__(parent=parent)
|
||||
|
||||
self.animationDuration = animation_duration
|
||||
self.toggleAnimation = QParallelAnimationGroup()
|
||||
self.contentArea = QWidget()
|
||||
self.headerLine = QFrame()
|
||||
self.toggleButton = QToolButton()
|
||||
self.mainLayout = QGridLayout()
|
||||
|
||||
toggleButton = self.toggleButton
|
||||
toggleButton.setStyleSheet("QToolButton { border: none; }")
|
||||
toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
|
||||
toggleButton.setArrowType(Qt.RightArrow)
|
||||
toggleButton.setText(str(title))
|
||||
toggleButton.setCheckable(True)
|
||||
toggleButton.setChecked(False)
|
||||
|
||||
headerLine = self.headerLine
|
||||
headerLine.setFrameShape(QFrame.HLine)
|
||||
headerLine.setFrameShadow(QFrame.Sunken)
|
||||
headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
|
||||
|
||||
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
|
||||
self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
# start out collapsed
|
||||
self.contentArea.setMaximumHeight(0)
|
||||
self.contentArea.setMinimumHeight(0)
|
||||
# let the entire widget grow and shrink with its content
|
||||
toggleAnimation = self.toggleAnimation
|
||||
toggleAnimation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
|
||||
toggleAnimation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
|
||||
toggleAnimation.addAnimation(QPropertyAnimation(self.contentArea, b"maximumHeight"))
|
||||
# don't waste space
|
||||
mainLayout = self.mainLayout
|
||||
mainLayout.setVerticalSpacing(0)
|
||||
mainLayout.setContentsMargins(0, 0, 0, 0)
|
||||
row = 0
|
||||
mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, Qt.AlignLeft)
|
||||
mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
|
||||
row += 1
|
||||
mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
|
||||
self.setLayout(self.mainLayout)
|
||||
|
||||
def start_animation(checked):
|
||||
arrow_type = Qt.DownArrow if checked else Qt.RightArrow
|
||||
direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
|
||||
toggleButton.setArrowType(arrow_type)
|
||||
self.toggleAnimation.setDirection(direction)
|
||||
self.toggleAnimation.start()
|
||||
|
||||
self.toggleButton.clicked.connect(start_animation)
|
||||
|
||||
if child_layout:
|
||||
self.setContentLayout(child_layout)
|
||||
|
||||
def setContentLayout(self, content_layout: QLayout):
|
||||
# Not sure if this is equivalent to self.contentArea.destroy()
|
||||
self.contentArea.destroy()
|
||||
self.contentArea.setLayout(content_layout)
|
||||
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
|
||||
contentHeight = content_layout.sizeHint().height()
|
||||
for i in range(self.toggleAnimation.animationCount() - 1):
|
||||
spoilerAnimation = self.toggleAnimation.animationAt(i)
|
||||
spoilerAnimation.setDuration(self.animationDuration)
|
||||
spoilerAnimation.setStartValue(collapsedHeight)
|
||||
spoilerAnimation.setEndValue(collapsedHeight + contentHeight)
|
||||
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
|
||||
contentAnimation.setDuration(self.animationDuration)
|
||||
contentAnimation.setStartValue(0)
|
||||
contentAnimation.setEndValue(contentHeight)
|
Loading…
Reference in a new issue