From a4e6fca73d0a02f6fe5a3b0b93690eb758b09d7f Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Fri, 28 Feb 2025 22:18:57 -0600 Subject: [PATCH] More WIP for the Settings panel --- src/engines/engine_manager.py | 4 + src/ui/settings_window.py | 150 ++++++++++++++++++++++++++++------ 2 files changed, 129 insertions(+), 25 deletions(-) diff --git a/src/engines/engine_manager.py b/src/engines/engine_manager.py index 77018f8..25682e2 100644 --- a/src/engines/engine_manager.py +++ b/src/engines/engine_manager.py @@ -23,6 +23,10 @@ class EngineManager: def supported_engines(): return [Blender, FFMPEG] + @classmethod + def downloadable_engines(cls): + return [engine for engine in cls.supported_engines() if hasattr(engine, "downloader") and engine.downloader()] + @classmethod def engine_with_name(cls, engine_name): for obj in cls.supported_engines(): diff --git a/src/ui/settings_window.py b/src/ui/settings_window.py index 8e2df0e..5eafc44 100644 --- a/src/ui/settings_window.py +++ b/src/ui/settings_window.py @@ -1,19 +1,32 @@ +import humanize import socket +from datetime import datetime from PyQt6 import QtCore -from PyQt6.QtCore import Qt +from PyQt6.QtCore import Qt, QSettings from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QApplication, QMainWindow, QListWidget, QListWidgetItem, QStackedWidget, QVBoxLayout, \ QWidget, QLabel, QCheckBox, QLineEdit, \ QComboBox, QPushButton, QHBoxLayout, QGroupBox, QTableWidget, QAbstractItemView, QTableWidgetItem, QHeaderView from api.server_proxy import RenderServerProxy +from engines.engine_manager import EngineManager +from utilities.misc_helper import launch_url +from version import APP_AUTHOR, APP_NAME + +settings = QSettings(APP_AUTHOR, APP_NAME) class SettingsWindow(QMainWindow): def __init__(self): super().__init__() + # declare objects we need to store settings + self.check_for_engine_updates_checkbox = None + # todo: add all here + + self.installed_engines_table = None + self.setWindowTitle("Settings") # Create the main layout @@ -33,7 +46,7 @@ class SettingsWindow(QMainWindow): # Add items with icons to the sidebar self.add_sidebar_item("General", "../../resources/Gear.png") - self.add_sidebar_item("Server", "../../resources/Server.png") + self.add_sidebar_item("Network", "../../resources/Server.png") self.add_sidebar_item("Engines", "../../resources/Blender.png") self.sidebar.setCurrentRow(0) @@ -50,8 +63,6 @@ class SettingsWindow(QMainWindow): self.stacked_widget.addWidget(network_page) self.stacked_widget.addWidget(engines_page) - self.installed_engines_table = None - # Connect the sidebar to the stacked widget self.sidebar.currentRowChanged.connect(self.stacked_widget.setCurrentIndex) @@ -90,21 +101,33 @@ class SettingsWindow(QMainWindow): # Startup Settings Group startup_group = QGroupBox("Startup Settings") startup_layout = QVBoxLayout() - startup_layout.addWidget(QCheckBox("Start application on system startup")) - startup_layout.addWidget(QCheckBox("Check for updates automatically")) + # startup_layout.addWidget(QCheckBox("Start application on system startup")) + check_for_updates_checkbox = QCheckBox("Check for updates automatically") + check_for_updates_checkbox.setChecked(settings.value("auto_check_for_updates", True)) + check_for_updates_checkbox.stateChanged.connect(lambda state: settings.setValue("auto_check_for_updates", bool(state))) + startup_layout.addWidget(check_for_updates_checkbox) startup_group.setLayout(startup_layout) - # Language Settings Group - language_group = QGroupBox("Language Settings") - language_layout = QVBoxLayout() - language_layout.addWidget(QLabel("Language")) - language_combo = QComboBox() - language_combo.addItems(["English", "Spanish", "French", "German"]) - language_layout.addWidget(language_combo) - language_group.setLayout(language_layout) + # Render Settings Group + render_settings_group = QGroupBox("Render Settings") + render_settings_layout = QVBoxLayout() + render_settings_layout.addWidget(QLabel("Require same:")) + require_same_engine_checkbox = QCheckBox("Renderer Version") + require_same_engine_checkbox.setChecked(settings.value("render_require_same_engine_version")) + require_same_engine_checkbox.stateChanged.connect(lambda state: settings.setValue("render_require_same_engine_version", bool(state))) + render_settings_layout.addWidget(require_same_engine_checkbox) + require_same_cpu_checkbox = QCheckBox("CPU Architecture") + require_same_cpu_checkbox.setChecked(settings.value("render_require_same_cpu_type")) + require_same_cpu_checkbox.stateChanged.connect(lambda state: settings.setValue("render_require_same_cpu_type", bool(state))) + render_settings_layout.addWidget(require_same_cpu_checkbox) + require_same_os_checkbox = QCheckBox("Operating System") + require_same_os_checkbox.setChecked(settings.value("render_require_same_os")) + require_same_os_checkbox.stateChanged.connect(lambda state: settings.setValue("render_require_same_os", bool(state))) + render_settings_layout.addWidget(require_same_os_checkbox) + render_settings_group.setLayout(render_settings_layout) layout.addWidget(startup_group) - layout.addWidget(language_group) + layout.addWidget(render_settings_group) layout.addStretch() # Add a stretch to push content to the top page.setLayout(layout) @@ -142,31 +165,92 @@ class SettingsWindow(QMainWindow): self.installed_engines_table = EngineTableWidget() installed_layout.addWidget(self.installed_engines_table) installed_buttons_layout = QHBoxLayout() - check_engine_updates_button = QPushButton("Check for New Versions") launch_engine_button = QPushButton("Launch") + launch_engine_button.clicked.connect(self.launch_selected_engine) delete_engine_button = QPushButton("Delete") + delete_engine_button.clicked.connect(self.delete_selected_engine) - installed_buttons_layout.addWidget(check_engine_updates_button) installed_buttons_layout.addWidget(launch_engine_button) installed_buttons_layout.addWidget(delete_engine_button) - installed_layout.addLayout(installed_buttons_layout) installed_group.setLayout(installed_layout) - # Display Options Group - display_group = QGroupBox("Display Options") - display_layout = QVBoxLayout() - display_layout.addWidget(QCheckBox("Enable high DPI scaling")) - display_layout.addWidget(QCheckBox("Show status bar")) - display_group.setLayout(display_layout) + # Engine Updates Group + engine_updates_group = QGroupBox("Auto-Install") + engine_updates_layout = QVBoxLayout() + + engine_download_layout = QHBoxLayout() + engine_download_layout.addWidget(QLabel("Enable Downloads for:")) + + at_least_one_downloadable = False + for engine in EngineManager.downloadable_engines(): + engine_download_check = QCheckBox(engine.name()) + is_checked = settings.value(f"engine_download-{engine.name()}", False) + at_least_one_downloadable |= is_checked + engine_download_check.setChecked(is_checked) + # Capture the checkbox correctly using a default argument in lambda + engine_download_check.clicked.connect( + lambda state, checkbox=engine_download_check: self.engine_download_settings_changed(state, checkbox.text()) + ) + engine_download_layout.addWidget(engine_download_check) + + engine_updates_layout.addLayout(engine_download_layout) + + self.check_for_engine_updates_checkbox = QCheckBox("Check for new versions on launch") + self.check_for_engine_updates_checkbox.setChecked(settings.value('check_for_engine_updates_on_launch', True)) + self.check_for_engine_updates_checkbox.setEnabled(at_least_one_downloadable) + self.check_for_engine_updates_checkbox.stateChanged.connect( + lambda state: settings.setValue("check_for_engine_updates_on_launch", bool(state))) + engine_updates_layout.addWidget(self.check_for_engine_updates_checkbox) + self.engines_last_update_label = QLabel() + self.update_last_checked_label() + self.engines_last_update_label.setEnabled(at_least_one_downloadable) + engine_updates_layout.addWidget(self.engines_last_update_label) + self.check_for_new_engines_button = QPushButton("Check for New Versions") + self.check_for_new_engines_button.setEnabled(at_least_one_downloadable) + self.check_for_new_engines_button.clicked.connect(self.check_for_new_engines) + engine_updates_layout.addWidget(self.check_for_new_engines_button) + engine_updates_group.setLayout(engine_updates_layout) layout.addWidget(installed_group) - layout.addWidget(display_group) + layout.addWidget(engine_updates_group) layout.addStretch() # Add a stretch to push content to the top page.setLayout(layout) return page + def update_last_checked_label(self): + """Retrieve the last check timestamp and return a human-friendly string.""" + last_checked_str = settings.value("engines_last_update_time", None) + if not last_checked_str: + time_string = "Never" + else: + last_checked_dt = datetime.fromisoformat(last_checked_str) + now = datetime.now() + time_string = humanize.naturaltime(now - last_checked_dt) + self.engines_last_update_label.setText(f"Last Updated: {time_string}") + + def engine_download_settings_changed(self, state, engine_name): + settings.setValue(f"engine_download-{engine_name}", state) + + at_least_one_downloadable = False + for engine in EngineManager.downloadable_engines(): + at_least_one_downloadable |= settings.value(f"engine_download-{engine.name()}", False) + self.check_for_new_engines_button.setEnabled(at_least_one_downloadable) + self.check_for_engine_updates_checkbox.setEnabled(at_least_one_downloadable) + self.engines_last_update_label.setEnabled(at_least_one_downloadable) + + def delete_selected_engine(self): + pass + + def launch_selected_engine(self): + engine_info = self.installed_engines_table.selected_engine_data() + if engine_info: + launch_url(engine_info['path']) + + def check_for_new_engines(self): + settings.setValue("engines_last_update_time", datetime.now().isoformat()) + self.update_last_checked_label() class EngineTableWidget(QWidget): def __init__(self): @@ -214,6 +298,22 @@ class EngineTableWidget(QWidget): self.table.selectRow(0) + def selected_engine_data(self): + """Returns the data from the selected row as a dictionary.""" + row = self.table.currentRow() # Get the selected row index + + if row < 0: # No row selected + return None + + data = { + "engine": self.table.item(row, 0).text(), + "version": self.table.item(row, 1).text(), + "type": self.table.item(row, 2).text(), + "path": self.table.item(row, 3).text(), + } + + return data + if __name__ == "__main__": app = QApplication([])