From 061535db7c6446c8764444bcc6cfd1ccd000aa55 Mon Sep 17 00:00:00 2001 From: Wei Hong Date: Wed, 29 Jul 2026 00:03:25 +0800 Subject: [PATCH] Add profile manager and fix some issues inside progress dialog. --- app.py | 14 + constant.py | 2 +- core_lib/game/profile.py | 60 +++- core_lib/qt/progress_dialog.py | 7 +- manager/profile.py | 621 ++++++++++++++++++++++++++++++--- pages/create_profile.py | 2 - window.py | 4 - 7 files changed, 658 insertions(+), 52 deletions(-) diff --git a/app.py b/app.py index a8b86e7..f66b2d8 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,6 @@ import logging +import os +import sys from pathlib import Path from typing import Callable @@ -12,6 +14,7 @@ from core_lib.qt.hack import move_window_to_center, is_light_theme, \ recolor_icon from core_lib.qt import messagebox from core_lib.launcher.object import Launcher as LauncherObject +from manager.profile import ProfileManager from manager.widgets import WidgetManager from window import LauncherMainWindow @@ -107,6 +110,9 @@ class Launcher(QApplication, LauncherObject): # Widget (Signal, Event) self.widget_manager = WidgetManager() + # Manager + self.profile_manager = ProfileManager(self, self.widget_manager) + # Window config self.setApplicationName("TestLauncher") self.main_window = LauncherMainWindow( @@ -125,6 +131,10 @@ class Launcher(QApplication, LauncherObject): def exec(self): self.main_window.show() self.main_window.toolbar.update_all() + + # init managers + self.profile_manager.profile_load.emit() + self.exec_() def get_icon(self, path: Path): @@ -192,6 +202,10 @@ class Launcher(QApplication, LauncherObject): def log_dir(self): return self.work_dir / "log" + @property + def default_game_dir(self) -> Path: + return self.work_dir / "minecraft" if sys.platform.lower() == "darwin" else self.work_dir / ".minecraft" + @property def resources_dir(self): return self.program_dir / "resources" diff --git a/constant.py b/constant.py index f8f5d42..13e758a 100644 --- a/constant.py +++ b/constant.py @@ -1,2 +1,2 @@ -LAUNCHER_VERSION = "alpha_0.0.2" +LAUNCHER_VERSION = "0.0.3-alpha" MAIN_REPO_URL = "https://repo.weispace.net/wei/Launcher/" \ No newline at end of file diff --git a/core_lib/game/profile.py b/core_lib/game/profile.py index cd7f75c..03bccb5 100644 --- a/core_lib/game/profile.py +++ b/core_lib/game/profile.py @@ -143,10 +143,10 @@ def create_profile(version_id: str, version_type: str, java_args: str="", game_d return profile, profile_id -def create_profile_file(profiles: dict, profile_filepath: Path, profile_file_bak_filepath: Path | None=None, +def create_or_save_profile_file(profiles: dict, profile_filepath: Path, profile_file_bak_filepath: Path | None=None, overwrite=True, create_backup=True) -> None: """ - Create a new profile + Create or save a new profile :param profiles: :param profile_filepath: :param profile_file_bak_filepath: @@ -298,6 +298,22 @@ def is_profiles_valid(profiles: dict, fix_wrong=False, ignore_invalid_profile=Fa return not invalid_ids +def is_profile_valid(profile_data: dict, fix_wrong=False): + return _check_profile(profile_data, fix_wrong=fix_wrong) + +def is_profile_exists(profiles: dict, profile_id: str) -> bool: + if not isinstance(profiles, dict): + logger.warning("Profile type is not valid: {}".format(type(profiles))) + return False + + profiles_map: dict = profiles.get("profiles") + + if not isinstance(profiles_map, dict): + logger.warning("Profile map type is not valid: {}".format(type(profiles_map))) + return False + + return profile_id in profiles_map + def list_profiles(profiles: dict) -> dict: """ List all profiles that inside profiles data @@ -316,6 +332,21 @@ def get_profile(profiles: dict, profile_id: str) -> dict: f"Specified profile id {profile_id} not found." ) from exc +def set_current_profile_id(profiles: dict, profile_id: str): + """ + Set current profile id + INFO: You should ensure profile id is existing in profiles before calling this function. + :param profiles: + :param profile_id: + :return: + """ + profiles["lastPlayedProfileID"] = profile_id + +def get_current_profile_id(profiles: dict) -> str | None: + return profiles.get("lastPlayedProfileID", None) + +get_last_played_profile_id = get_current_profile_id + def add_profile(profiles: dict, profile: dict, max_generate_retry=10) -> tuple[dict, str]: """ Add a new profile @@ -359,11 +390,12 @@ def remove_profile(profiles: dict, profile_id: str): except KeyError: raise ProfileKeyNotFoundException("Specified profile id {} not found.".format(profile_id)) -def duplicate_profile(profiles: dict, profile_id: str, max_generate_retry=10) -> str: +def duplicate_profile(profiles: dict, profile_id: str, new_name: str | None=None, max_generate_retry: int=10) -> str: """ Duplicate a profile :param profiles: :param profile_id: + :param new_name: :param max_generate_retry: :return: new_profile_id: str @@ -387,7 +419,14 @@ def duplicate_profile(profiles: dict, profile_id: str, max_generate_retry=10) -> for p_id in profiles_map: if p_id == profile_id: - profiles["profiles"][new_profile_id] = deepcopy(profiles_map[profile_id]) + new_profile = deepcopy(profiles_map[profile_id]) + + if new_name is not None: + new_profile["name"] = new_name + else: + new_profile["name"] = f'{new_profile.get("name", "")} Copy' + + profiles["profiles"][new_profile_id] = new_profile return new_profile_id raise ProfileKeyNotFoundException( @@ -426,5 +465,18 @@ def convert_profile_data_to_object(profiles: dict, profile_id: str) -> Profile: return item +def update_profile(profiles: dict, profile_id: str, profile_data: dict, fix_wrong: bool=False) -> tuple[dict, str]: + if not is_profile_exists(profiles, profile_id): + raise ProfileException( + "Specified profile id {} not found.".format(profile_id) + ) + if not is_profile_valid(profile_data, fix_wrong=fix_wrong): + raise ProfileException( + "Provided profile data is not valid." + ) + + profiles["profiles"][profile_id] = profile_data + + return profiles, profile_id diff --git a/core_lib/qt/progress_dialog.py b/core_lib/qt/progress_dialog.py index d4a4f74..ec2d8f3 100644 --- a/core_lib/qt/progress_dialog.py +++ b/core_lib/qt/progress_dialog.py @@ -89,6 +89,7 @@ class ProgressDialog(QDialog): @Slot(str, str) def add_task(self, task_id: str, name: str): + # Prevent new tasks from being deleted by cleanup timer if task_id in self.rows: return @@ -131,14 +132,16 @@ class ProgressDialog(QDialog): self.close_timer.start(2000) def all_tasks_finished(self): - return all(row.finished == True for task_id, row in self.rows.items()) + return bool(self.rows) and all( + row.finished + for row in self.rows.values() + ) def cleanup(self): """ If you want to use this function with a timer. use self.close_timer.start(TIMEOUT) :return: """ - QTimer.singleShot(2000, self.cleanup) for task_id in list(self.rows): self.remove_task(task_id) diff --git a/manager/profile.py b/manager/profile.py index 723dc33..cb5c970 100644 --- a/manager/profile.py +++ b/manager/profile.py @@ -1,16 +1,19 @@ """ IMPORTANT: Still under construction! """ +import copy from dataclasses import dataclass from pathlib import Path from PySide6.QtCore import QObject, Signal from datetime import datetime -from PySide6.QtWidgets import QApplication - -from core_lib.common.exception import ProfileException -from core_lib.game.profile import read_profile_file +from core_lib.common.exception import ProfileException, ProfileLoadException, ProfileSaveException +from core_lib.game.profile import read_profile_file, is_profiles_valid, \ + get_profile_sample, create_or_save_profile_file, list_profiles as list_profiles_raw, convert_profile_data_to_object, \ + is_profile_exists, create_profile as create_profile_raw, update_profile as update_profile_raw, is_profile_valid, \ + get_current_profile_id, set_current_profile_id, remove_profile, duplicate_profile +from manager.widgets import AskForRequest DEFAuLT_PROFILE_RELATIVE_PATH = Path("launcher_profiles.json") @@ -42,26 +45,40 @@ class CreateProfileRequest: icon: str = "" resolution: tuple[int, int] | None = None +class _Profile_UnsetType: + pass + +_UNSET = _Profile_UnsetType() @dataclass(frozen=True) class UpdateProfileRequest: name: str | None = None version_id: str | None = None version_type: str | None = None - game_dir: Path | None = None - java_args: str | None = None + game_dir: Path | None | _Profile_UnsetType = _UNSET + java_args: str | None | _Profile_UnsetType = _UNSET icon: str | None = None - resolution: tuple[int, int] | None = None + resolution: tuple[int, int] | None | _Profile_UnsetType = _UNSET @dataclass(frozen=True) class LaunchProfile: - pass + profile_id: str + version_id: str + version_type: str + game_dir: Path + java_args: str + resolution: tuple[int, int] | None @dataclass(frozen=True) class ValidationResult: - pass + errors: dict[str, str] + + @property + def is_valid(self) -> bool: + return not self.errors class ProfileManager(QObject): + profile_load = Signal() profiles_changed = Signal() profile_created = Signal(str) profile_updated = Signal(str) @@ -70,60 +87,586 @@ class ProfileManager(QObject): error_occurred = Signal(str, str) # error_code, message replace_relative_path = Signal(str) - def __init__(self, app): + def __init__(self, app, widget_manager): QObject.__init__(self, app) self.app = app - self.profiles = [] + self.widget_manager = widget_manager + self.profiles = {} + + + self._path: Path | None = None + self._is_modified = False + + self.profile_load.connect( + self.load, + ) @property - def profile_path(self): + def default_profile_path(self): return Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH) # File lifetime - def load(self, path: Path=None, return_value=False) -> None | dict: - if path is None: - path = self.profile_path + def create_profiles(self, path: str | Path | None=None) -> bool: + path = Path(path) if path is not None else self.default_profile_path try: + new_profiles = get_profile_sample() + create_or_save_profile_file(new_profiles, path, create_backup=False) + return True + except ProfileSaveException as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to create profile file: {}".format(e.user_message), + ) + return False + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to create profile file. An unexpected error occurred: {}".format(e), + ) + return False + + def load(self, path: str | Path | None=None, return_value: bool=False) -> tuple[bool, None | dict]: + """ + Load profiles + :param path: If provided, this path becomes the current profile path after loading succeeds. + :param return_value: + :return: + """ + path = Path(path) if path is not None else self.default_profile_path + + try: + # Prevent existing data from being overwritten + if self._is_modified: + request = AskForRequest("ProfileManager", "Would you like to save the existing profile" + " before reload? (If not existing data will be overwritten)") + + self.widget_manager.signal.askyesno.emit(request) + + if not request.wait(60): + self.widget_manager.signal.show_error_message.emit( + "Timed out while waiting for confirmation. Please try again." + ) + return False, None + + result = request.result() + + if result: + save_result = self.save() + + if not save_result: + self.widget_manager.signal.show_error_message.emit( + "Unable to load profile due to saving error." + ) + return False, None + + # Create new profile if the target profile file does not exist + if not path.exists() and not self.create_profiles(path): + return False, None + result = read_profile_file(path) - except ProfileException: - pass + # Profile validation + if not is_profiles_valid(result): + self.widget_manager.signal.show_error_message.emit( + "Invalid profile file. Try restarting the profile in settings page.", + ) + return False, None - # def load(self, path: str | Path) -> None: ... - def save(self) -> None: ... - def save_as(self, path: str | Path) -> None: ... - def reload(self) -> None: ... + self.profiles = result + self._path = path + self._is_modified = False + + self.profiles_changed.emit() + + if return_value: + return True, copy.deepcopy(result) + + return True, None + except (ProfileException, ProfileLoadException) as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to load profile file: {}".format(e.user_message), + ) + return False, None + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to load profile file. An unexpected error occurred: {}".format(e), + ) + return False, None + + def save(self, path: str | Path | None=None, create_backup: bool=True) -> bool: + path = Path(path) if path is not None else self.path + + try: + if not is_profiles_valid(self.profiles): + self.widget_manager.signal.show_error_message.emit( + "Current profile store in memory is not a valid profile.", + ) + return False + + create_or_save_profile_file( + self.profiles, + path, + create_backup=create_backup, + ) + + self._path = path + self._is_modified = False + return True + except (ProfileException, ProfileSaveException) as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to save profile file: {}".format(e.user_message), + ) + return False + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to save profile file. An unexpected error occurred: {}".format(e), + ) + return False + + def reload(self) -> tuple[bool, dict | None]: + return self.load(path=self.path) # Profile search - def list_profiles(self) -> list[ProfileSummary]: ... - def get_profile(self, profile_id: str) -> ProfileDetail: ... - def contains(self, profile_id: str) -> bool: ... + def list_profiles(self) -> list[ProfileSummary]: + result = [] + profiles = list_profiles_raw(self.profiles) + + for profile_id in profiles: + profile = convert_profile_data_to_object(self.profiles, profile_id) + profile_summary = ProfileSummary( + icon=str(profile.icon), + id=profile_id, + last_used=profile.last_used, + name=profile.name, + version_id=profile.version_id, + version_type=profile.type + ) + result.append(profile_summary) + + return result + + def get_profile(self, profile_id: str) -> ProfileDetail | None: + if not self.contains(profile_id, show_error=True): + return None + + profile = convert_profile_data_to_object( + self.profiles, + profile_id, + ) + + resolution = None + + if profile.resolution: + # Put resolution data to profile detail + resolution = ( + profile.resolution["width"], + profile.resolution["height"], + ) + + return ProfileDetail( + id=profile_id, + name=profile.name, + version_id=profile.version_id, + version_type=profile.type, + icon=str(profile.icon), + last_used=profile.last_used, + game_dir=Path(profile.game_dir) if profile.game_dir else None, + java_args=profile.java_args, + resolution=resolution, + created=profile.created, + ) + + def contains(self, profile_id: str, show_error: bool=False) -> bool: + result = is_profile_exists(self.profiles, profile_id) + + if not result and show_error: + self.widget_manager.signal.show_error_message.emit( + "Profile ID {} does not exist.".format(profile_id), + ) + + return result # CRUD - def create_profile(self, request: CreateProfileRequest) -> str: ... - def update_profile( - self, profile_id: str, changes: UpdateProfileRequest - ) -> None: ... - def remove_profile(self, profile_id: str) -> None: ... - def duplicate_profile( - self, profile_id: str, new_name: str | None = None - ) -> str: ... + def create_profile(self, request: CreateProfileRequest) -> str | None: + if not self.is_loaded: + result, _ = self.load() + + if not result: + return None + + validation: ValidationResult = self.validate(request) + + if not validation.is_valid: + message = "\n".join(validation.errors.values()) + + self.widget_manager.signal.show_error_message.emit(message) + return None + + # Check game dir + if request.game_dir is not None: + game_dir = Path(request.game_dir).as_posix() + else: + default_game_dir = self.app.default_game_dir + game_dir = default_game_dir.as_posix() + + try: + # Apply resolution + resolution = None + if request.resolution is not None: + width, height = request.resolution + resolution = { + "width": width, + "height": height, + } + + # Copy profiles before update + updated_profiles = copy.deepcopy(self.profiles) + + _, profile_id = create_profile_raw( + version_id=request.version_id, + version_type=request.version_type, + java_args=request.java_args, + game_dir=game_dir, + icon=request.icon, + name=request.name, + resolution=resolution, + profiles=updated_profiles, + ) + + if not is_profiles_valid(updated_profiles): + raise ProfileException("Created profile data is invalid.") + + self.profiles = updated_profiles + + # Update change event and flag + self._is_modified = True + self.profile_created.emit(profile_id) + self.profiles_changed.emit() + + return profile_id + + except ProfileException as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to create profile: {e.user_message}" + ) + return None + except (TypeError, ValueError) as e: + self.widget_manager.signal.show_error_message.emit( + f"Invalid profile request: {e}" + ) + return None + + def update_profile(self, profile_id: str, changes: UpdateProfileRequest) -> bool: + # Check profile exists + if not self.contains(profile_id, show_error=True): + return False + + try: + # Copy before the actual update. + updated_profiles = copy.deepcopy(self.profiles) + profile_data = copy.deepcopy( + list_profiles_raw(updated_profiles)[profile_id] + ) + + # Check profile value change + if changes.name is not None: + profile_data["name"] = changes.name + + if changes.version_id is not None: + profile_data["lastVersionId"] = changes.version_id + + if changes.version_type is not None: + profile_data["type"] = changes.version_type + + if changes.game_dir is not _UNSET: + if changes.game_dir is None: + profile_data["gameDir"] = "" + else: + profile_data["gameDir"] = Path( + changes.game_dir + ).as_posix() + + if changes.java_args is not _UNSET: + profile_data["javaArgs"] = ( + "" if changes.java_args is None + else changes.java_args + ) + + if changes.icon is not None: + profile_data["icon"] = changes.icon + + if changes.resolution is not _UNSET: + if changes.resolution is None: + profile_data.pop("resolution", None) + else: + width, height = changes.resolution + profile_data["resolution"] = { + "width": width, + "height": height, + } + + update_profile_raw( + updated_profiles, + profile_id, + profile_data, + ) + + if profile_data == list_profiles_raw(self.profiles)[profile_id]: + return True + + # Update event and flag + self.profiles = updated_profiles + self._is_modified = True + + self.profile_updated.emit(profile_id) + self.profiles_changed.emit() + + return True + + except (ProfileException, TypeError, ValueError) as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to update profile: {e}" + ) + return False + + def remove_profile(self, profile_id: str) -> bool: + if not self.contains(profile_id, show_error=True): + return False + + try: + updated_profiles = copy.deepcopy(self.profiles) + is_current_changed = get_current_profile_id(self.profiles) == profile_id + + # !!!COPY BEFORE REMOVE!!! + remove_profile(updated_profiles, profile_id) + + # Unset current profile id if is same as removed profile's id + if is_current_changed: + updated_profiles["lastPlayedProfileID"] = None + + self.profiles = updated_profiles + + # event + self._is_modified = True + self.profile_removed.emit(profile_id) + + if is_current_changed: + self.current_profile_changed.emit(None) + + self.profiles_changed.emit() + + return True + except ProfileException as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to remove profile: {e.user_message}" + ) + return False + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to remove profile. An unknown error occurred: {e}" + ) + return False + + def duplicate_profile(self, profile_id: str, new_name: str | None = None) -> str | None: + if not self.contains(profile_id, show_error=True): + return None + + try: + updated_profiles = copy.deepcopy(self.profiles) + new_profile_id = duplicate_profile( + updated_profiles, + profile_id, + new_name, + ) + + # Update event and flag + self.profiles = updated_profiles + self._is_modified = True + self.profile_created.emit(new_profile_id) + self.profiles_changed.emit() + + return new_profile_id + except ProfileException as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to duplicate profile: {e.user_message}" + ) + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to duplicate profile. An unknown error occurred: {e}" + ) # Profile switch - def set_current_profile(self, profile_id: str | None) -> None: ... - def get_current_profile_id(self) -> str | None: ... - def get_launch_profile(self, profile_id: str) -> LaunchProfile: ... + def set_current_profile(self, profile_id: str | None) -> bool: + """ + Set the current profile ID. + :param profile_id: INFO: Set the current profile ID to None if you want to unset it + :return: + """ + if not self.is_loaded: + success, _ = self.load() + + if not success: + return False + + if profile_id is not None and not self.contains(profile_id, show_error=True): + return False + + try: + existing_id = get_current_profile_id(self.profiles) + + # ignore same id request + if profile_id == existing_id: + return True + + if existing_id is not None: + self.app.logger.debug( + "Replacing current profile ID {} to {}".format(existing_id, profile_id) + ) + + set_current_profile_id(self.profiles, profile_id) + + # event and flag update + self._is_modified = True + self.current_profile_changed.emit(profile_id) + self.profiles_changed.emit() + + return True + except ProfileException as e: + self.widget_manager.signal.show_error_message.emit( + f"Unable to set current profile ID: {e.user_message}" + ) + return False + except Exception as e: + self.widget_manager.signal.show_error_message.emit( + "Unable to set current profile ID. An unknown error occurred {}".format(str(e)) + ) + return False + + def get_current_profile_id(self) -> str | None: + if not self.is_loaded: + success, _ = self.load() + + if not success: + return None + + return get_current_profile_id(self.profiles) + + def get_launch_profile(self, profile_id: str) -> LaunchProfile | None: + profile = self.get_profile(profile_id) + + if profile is None: + return None + + # Use default game_dir if is unset + game_dir = ( + profile.game_dir + if profile.game_dir is not None + else self.app.default_game_dir + ) + + return LaunchProfile( + profile_id=profile.id, + version_id=profile.version_id, + version_type=profile.version_type, + game_dir=game_dir, + java_args=profile.java_args, + resolution=profile.resolution, + ) # Validation - def validate(self, request: CreateProfileRequest) -> ValidationResult: ... + @staticmethod + def validate(request: CreateProfileRequest) -> ValidationResult: + errors = {} + + if not isinstance(request.name, str) or not request.name.strip(): + errors["name"] = "Profile name cannot be empty." + + if not isinstance(request.version_id, str) or not request.version_id.strip(): + errors["version_id"] = "Version ID cannot be empty." + + if not isinstance(request.version_type, str) or not request.version_type.strip(): + errors["version_type"] = "Version type cannot be empty." + + if not isinstance(request.java_args, str): + errors["java_args"] = "Java arguments must be a string." + + if not isinstance(request.icon, str): + errors["icon"] = "Icon must be a string." + + if request.game_dir is not None: + try: + Path(request.game_dir) + except (TypeError, ValueError): + errors["game_dir"] = "Game directory is invalid." + + if request.resolution is not None: + if not isinstance(request.resolution, tuple) or len(request.resolution) != 2: + errors["resolution"] = ( + "Resolution must contain width and height." + ) + else: + width, height = request.resolution + + if (not isinstance(width, int) or isinstance(width, bool) or width <= 0 or + not isinstance(height, int) or isinstance(height, bool) or height <= 0): + errors["resolution"] = ( + "Resolution width and height must be positive integers." + ) + + return ValidationResult(errors=errors) + + @staticmethod + def validate_update(request: UpdateProfileRequest) -> ValidationResult: + errors = {} + + if not isinstance(request.name, str) or not request.name.strip(): + errors["name"] = "Profile name cannot be empty." + + if not isinstance(request.version_id, str) or not request.version_id.strip(): + errors["version_id"] = "Version ID cannot be empty." + + if not isinstance(request.version_type, str) or not request.version_type.strip(): + errors["version_type"] = "Version type cannot be empty." + + if not isinstance(request.java_args, str): + errors["java_args"] = "Java arguments must be a string." + + if not isinstance(request.icon, str): + errors["icon"] = "Icon must be a string." + + if request.game_dir is not _UNSET: + try: + Path(request.game_dir) + except (TypeError, ValueError): + errors["game_dir"] = "Game directory is invalid." + + if request.game_dir is not _UNSET: + if not isinstance(request.resolution, tuple) or len(request.resolution) != 2: + errors["resolution"] = ( + "Resolution must contain width and height." + ) + else: + width, height = request.resolution + + if (not isinstance(width, int) or isinstance(width, bool) or width <= 0 or + not isinstance(height, int) or isinstance(height, bool) or height <= 0): + errors["resolution"] = ( + "Resolution width and height must be positive integers." + ) + + return ValidationResult(errors=errors) # Property @property - def path(self) -> Path | None: ... + def path(self) -> Path | None: + if self._path is None: + self._path = Path(self.app.work_dir, DEFAuLT_PROFILE_RELATIVE_PATH) + + return self._path @property - def is_loaded(self) -> bool: ... + def is_loaded(self) -> bool: + return bool(self.profiles) and is_profiles_valid(self.profiles) @property - def is_dirty(self) -> bool: ... \ No newline at end of file + def is_modified(self) -> bool: + return self._is_modified \ No newline at end of file diff --git a/pages/create_profile.py b/pages/create_profile.py index 598535e..62ba92c 100644 --- a/pages/create_profile.py +++ b/pages/create_profile.py @@ -3,8 +3,6 @@ from pathlib import Path from PySide6.QtCore import QSize from PySide6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QListWidget, QPushButton, QLabel, QHBoxLayout -from core_lib.launcher.object import Launcher as LauncherObject - class CreateProfile(QWidget): def __init__(self, app, parent=None): super(CreateProfile, self).__init__(parent) diff --git a/window.py b/window.py index 5548c8c..a430a91 100644 --- a/window.py +++ b/window.py @@ -12,7 +12,6 @@ from core_lib.qt import messagebox from core_lib.qt.progress_dialog import ProgressDialog from core_lib.qt.messagebox import ask_yes_no_with_plain_text from constant import MAIN_REPO_URL -from manager.profile import ProfileManager from manager.widgets import AskForRequest, SelectPathRequest from pages.account import AccountPage from pages.create_profile import CreateProfile @@ -128,9 +127,6 @@ class LauncherMainWindow(QMainWindow): else: self.icon_label.setText("Ouch. The icon is missing.") - # Manager - self.profile_manager = ProfileManager(self) - # Dialog self.download_dialog = ProgressDialog(self)