import threading from PySide6.QtCore import QObject, Signal from PySide6.QtWidgets import ( QComboBox, QDialog, QDialogButtonBox, QLabel, QListWidget, QPushButton, QVBoxLayout, ) from manager.game import InstallableJavaRuntime, JavaManager, JavaRuntime class InstallJavaDialog(QDialog): """Install and register launcher-managed Mojang Java runtimes.""" class WorkerSignals(QObject): components_loaded = Signal(object, object) installed = Signal(object, object) runtime_installed = Signal(object) def __init__(self, app, manifest: dict | None = None, parent=None): super().__init__(parent) self.app = app self.java_manager: JavaManager = app.game_manager.java self.manifest = manifest or {} self.worker_signals = self.WorkerSignals(self) self.worker_signals.components_loaded.connect(self._components_loaded) self.worker_signals.installed.connect(self._installed) self.setWindowTitle(self.app.tr_text("Install Java")) self.setMinimumWidth(480) layout = QVBoxLayout(self) self.requirement_label = QLabel(self._requirement_text()) self.runtime_list = QListWidget() self.component_dropdown = QComboBox() self.component_dropdown.setEditable(False) self.status_label = QLabel(self.app.tr_text("Loading available Mojang runtimes...")) self.install_button = QPushButton(self.app.tr_text("Install")) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) layout.addWidget(self.requirement_label) layout.addWidget(QLabel(self.app.tr_text("Detected Java runtimes"))) layout.addWidget(self.runtime_list) layout.addWidget(QLabel(self.app.tr_text("Mojang runtime component"))) layout.addWidget(self.component_dropdown) layout.addWidget(self.status_label) layout.addWidget(self.install_button) layout.addWidget(buttons) buttons.rejected.connect(self.reject) self.install_button.clicked.connect(self.install_selected) self._refresh_runtime_list() threading.Thread(target=self._load_components, daemon=True).start() def _requirement_text(self) -> str: requirement = self.manifest.get("javaVersion") or {} major = requirement.get("majorVersion") component = requirement.get("component") if major: return self.app.tr_text( "This version requires Java {version}.", version=major ) + f" ({component or 'component unknown'})" return self.app.tr_text("Choose a Mojang Java runtime component to install.") def _refresh_runtime_list(self) -> None: self.runtime_list.clear() for runtime in self.java_manager.list_runtimes(): state = "available" if runtime.valid else "missing" self.runtime_list.addItem( f"Java {runtime.major_version} | {runtime.source} | {state}\n{runtime.executable}" ) def _load_components(self) -> None: try: runtimes = self.java_manager.list_installable_runtimes() self.worker_signals.components_loaded.emit(runtimes, None) except Exception as exc: self.worker_signals.components_loaded.emit([], exc) def _components_loaded(self, runtimes: list[InstallableJavaRuntime], error) -> None: requirement = self.manifest.get("javaVersion") or {} required = requirement.get("component") required_major = requirement.get("majorVersion") self.component_dropdown.clear() for runtime in runtimes: major = runtime.major_version if runtime.component == required and required_major is not None: # The Minecraft version manifest is authoritative for its own # runtime requirement if Mojang's version name is unusual. major = int(required_major) label = f"Java {major} | {runtime.component}" if major is not None else runtime.component if runtime.raw_version: label += f" ({runtime.raw_version})" self.component_dropdown.addItem(label, runtime.component) if required: index = self.component_dropdown.findData(required) if index < 0: label = f"Java {required_major} | {required}" if required_major else required self.component_dropdown.addItem(label, required) index = self.component_dropdown.count() - 1 self.component_dropdown.setCurrentIndex(index) self.install_button.setEnabled(self.component_dropdown.count() > 0) self.status_label.setText( self.app.tr_text("Unable to load component list: {error}", error=error) if error else self.app.tr_text("Ready to install.") ) def install_selected(self) -> None: component = self.component_dropdown.currentData() if not component: self.status_label.setText(self.app.tr_text("Select a runtime component.")) return self.install_button.setEnabled(False) self.component_dropdown.setEnabled(False) self.status_label.setText(self.app.tr_text("Installing {component}...", component=component)) callback, task_id = self.app.game_manager.manifest.create_download_progress_callback( self.app.tr_text("Installing Java ({component})", component=component) ) def worker(): try: runtime = self.java_manager.install_runtime( component, progress_callback=callback, redownload_callback=self.app.game_manager.game_file.redownload_files, ) self.app.widget_manager.download_signal.finished.emit(task_id) self.worker_signals.installed.emit(runtime, None) except Exception as exc: self.app.widget_manager.download_signal.failed.emit(task_id, str(exc)) self.worker_signals.installed.emit(None, exc) threading.Thread(target=worker, daemon=True).start() def _installed(self, runtime: JavaRuntime | None, error) -> None: self.install_button.setEnabled(True) self.component_dropdown.setEnabled(True) if error: self.status_label.setText(self.app.tr_text("Installation failed: {error}", error=error)) return self._refresh_runtime_list() self.status_label.setText(self.app.tr_text( "Java {version} installed successfully.", version=runtime.major_version )) self.runtime_installed.emit(runtime)