picodulce/picodulce.py
2024-04-25 08:41:49 -03:00

457 lines
20 KiB
Python

import sys
import subprocess
import threading
import logging
import os
from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QVBoxLayout, QPushButton, QMessageBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QCheckBox
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette
from PyQt5.QtCore import Qt
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class PicomcVersionSelector(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle('PicoDulce Launcher') # Change window title
self.setWindowIcon(QIcon('launcher_icon.ico')) # Set window icon
self.setGeometry(100, 100, 400, 250)
# Set application style and palette
app_style = QApplication.setStyle("Fusion")
dark_palette = self.create_dark_palette()
QApplication.instance().setPalette(dark_palette)
# Set window border color to dark mode on Windows
if sys.platform == 'win32':
self.setStyleSheet("QMainWindow { border: 2px solid rgb(53, 53, 53); }")
# Create title label
title_label = QLabel('PicoDulce Launcher') # Change label text
title_label.setFont(QFont("Arial", 24, QFont.Bold))
# Create installed versions section
installed_versions_label = QLabel('Installed Versions:')
installed_versions_label.setFont(QFont("Arial", 14))
self.installed_version_combo = QComboBox()
self.installed_version_combo.setMinimumWidth(200)
self.populate_installed_versions()
# Create buttons layout
buttons_layout = QVBoxLayout()
# Create play button for installed versions
self.play_button = QPushButton('Play')
self.play_button.setFocusPolicy(Qt.NoFocus) # Set focus policy to prevent highlighting
self.play_button.clicked.connect(self.play_instance)
self.play_button.setStyleSheet("background-color: #4bb679; color: white;")
buttons_layout.addWidget(self.play_button)
# Create button to open version download menu
self.version_download_button = QPushButton('Download Version')
self.version_download_button.clicked.connect(self.open_version_menu)
buttons_layout.addWidget(self.version_download_button)
# Create button to manage accounts
self.manage_accounts_button = QPushButton('Manage Accounts')
self.manage_accounts_button.clicked.connect(self.manage_accounts)
buttons_layout.addWidget(self.manage_accounts_button)
# Create button to install mod loader
self.install_mod_loader_button = QPushButton('Install Mod Loader')
self.install_mod_loader_button.clicked.connect(self.open_mod_loader_menu)
buttons_layout.addWidget(self.install_mod_loader_button)
# Create a button for the marroc mod loader
self.open_marroc_button = QPushButton('Marroc Mod Manager')
self.open_marroc_button.clicked.connect(self.open_marroc_script)
buttons_layout.addWidget(self.open_marroc_button)
# Create About button
self.about_button = QPushButton('About')
self.about_button.clicked.connect(self.show_about_dialog)
buttons_layout.addWidget(self.about_button)
# Set buttons layout alignment and spacing
buttons_layout.setAlignment(Qt.AlignTop)
buttons_layout.setSpacing(10)
# Set layout
main_layout = QVBoxLayout()
main_layout.addWidget(title_label, alignment=Qt.AlignCenter)
main_layout.addWidget(installed_versions_label)
main_layout.addWidget(self.installed_version_combo)
main_layout.addLayout(buttons_layout)
main_layout.setAlignment(Qt.AlignCenter)
main_layout.setSpacing(20)
self.setLayout(main_layout)
def populate_installed_versions(self):
# Run the command and get the output
try:
process = subprocess.Popen(['picomc', 'version', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, process.args, error)
except FileNotFoundError:
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
return
except subprocess.CalledProcessError as e:
logging.error("Error: %s", e.stderr)
return
# Parse the output and replace '[local]' with a space
versions = output.splitlines()
versions = [version.replace('[local]', ' ').strip() for version in versions]
# Populate installed versions combo box
self.installed_version_combo.clear()
self.installed_version_combo.addItems(versions)
def open_marroc_script(self):
try:
# Replace 'path_to_marroc.py' with the actual path to marroc.py
subprocess.Popen(['python', 'marroc.py'])
except FileNotFoundError:
logging.error("'marroc.py' not found.")
QMessageBox.critical(self, "Error", "'marroc.py' not found.")
def play_instance(self):
if self.installed_version_combo.count() == 0:
QMessageBox.warning(self, "No Version Available", "Please download a version first.")
return
selected_instance = self.installed_version_combo.currentText()
# Create a separate thread to run the game process
play_thread = threading.Thread(target=self.run_game, args=(selected_instance,))
play_thread.start()
def run_game(self, selected_instance):
try:
subprocess.run(['picomc', 'play', selected_instance], check=True)
except subprocess.CalledProcessError as e:
error_message = f"Error playing {selected_instance}: {e.stderr.decode()}"
logging.error(error_message)
QMessageBox.critical(self, "Error", error_message)
def open_version_menu(self):
dialog = QDialog(self)
dialog.setWindowTitle('Download Version')
dialog.setFixedSize(300, 250)
# Create title label
title_label = QLabel('Download Version')
title_label.setFont(QFont("Arial", 14))
# Create checkboxes for different version types
release_checkbox = QCheckBox('Releases')
snapshot_checkbox = QCheckBox('Snapshots')
alpha_checkbox = QCheckBox('Alpha')
beta_checkbox = QCheckBox('Beta')
# Create dropdown menu for versions
version_combo = QComboBox()
def update_versions():
version_combo.clear()
options = []
if release_checkbox.isChecked():
options.append('--release')
if snapshot_checkbox.isChecked():
options.append('--snapshot')
if alpha_checkbox.isChecked():
options.append('--alpha')
if beta_checkbox.isChecked():
options.append('--beta')
if options:
try:
process = subprocess.Popen(['picomc', 'version', 'list'] + options, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, process.args, error)
except FileNotFoundError:
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
return
except subprocess.CalledProcessError as e:
logging.error("Error: %s", e.stderr)
return
# Parse the output and replace '[local]' with a space
versions = output.splitlines()
versions = [version.replace('[local]', ' ').strip() for version in versions]
version_combo.addItems(versions)
release_checkbox.clicked.connect(update_versions)
snapshot_checkbox.clicked.connect(update_versions)
alpha_checkbox.clicked.connect(update_versions)
beta_checkbox.clicked.connect(update_versions)
# Set layout
layout = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(release_checkbox)
layout.addWidget(snapshot_checkbox)
layout.addWidget(alpha_checkbox)
layout.addWidget(beta_checkbox)
layout.addWidget(version_combo)
# Create download button
download_button = QPushButton('Download')
download_button.clicked.connect(lambda: self.prepare_version(dialog, version_combo.currentText()))
layout.addWidget(download_button)
dialog.setLayout(layout)
dialog.exec_()
def prepare_version(self, dialog, version):
dialog.close()
try:
subprocess.run(['picomc', 'version', 'prepare', version], check=True)
QMessageBox.information(self, "Success", f"Version {version} prepared successfully!")
self.populate_installed_versions() # Refresh the installed versions list after downloading
except subprocess.CalledProcessError as e:
error_message = f"Error preparing {version}: {e.stderr.decode()}"
logging.error(error_message)
QMessageBox.critical(self, "Error", error_message)
def manage_accounts(self):
dialog = QDialog(self)
dialog.setWindowTitle('Manage Accounts')
dialog.setFixedSize(300, 200)
# Create title label
title_label = QLabel('Manage Accounts')
title_label.setFont(QFont("Arial", 14))
# Create dropdown menu for accounts
account_combo = QComboBox()
self.populate_accounts(account_combo)
# Create select button
select_button = QPushButton('Select')
select_button.clicked.connect(lambda: self.set_default_account(dialog, account_combo.currentText()))
# Set layout
layout = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(account_combo)
layout.addWidget(select_button)
# Create a separate section for creating a new account
create_account_layout = QHBoxLayout()
new_account_input = QLineEdit()
create_account_button = QPushButton('Create')
create_account_button.clicked.connect(lambda: self.create_account(dialog, new_account_input.text()))
create_account_layout.addWidget(new_account_input)
create_account_layout.addWidget(create_account_button)
layout.addLayout(create_account_layout)
# Create a separate section for removing an account
remove_account_layout = QHBoxLayout()
remove_account_button = QPushButton('Remove Account')
remove_account_button.clicked.connect(lambda: self.remove_account(dialog, account_combo.currentText()))
remove_account_layout.addWidget(remove_account_button)
layout.addLayout(remove_account_layout)
dialog.setLayout(layout)
dialog.exec_()
def populate_accounts(self, account_combo):
# Run the command and get the output
try:
process = subprocess.Popen(['picomc', 'account', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, process.args, error)
except FileNotFoundError:
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
return
except subprocess.CalledProcessError as e:
logging.error("Error: %s", e.stderr)
return
# Parse the output and remove ' *' from account names
accounts = output.splitlines()
accounts = [account.replace(' *', '').strip() for account in accounts]
# Populate accounts combo box
account_combo.clear()
account_combo.addItems(accounts)
def create_account(self, dialog, username):
if username.strip() == '':
QMessageBox.warning(dialog, "Warning", "Username cannot be blank.")
return
try:
subprocess.run(['picomc', 'account', 'create', username], check=True)
QMessageBox.information(self, "Success", f"Account {username} created successfully!")
self.populate_accounts(dialog.findChild(QComboBox))
except subprocess.CalledProcessError as e:
error_message = f"Error creating account: {e.stderr.decode()}"
logging.error(error_message)
QMessageBox.critical(self, "Error", error_message)
def set_default_account(self, dialog, account):
dialog.close()
try:
subprocess.run(['picomc', 'account', 'setdefault', account], check=True)
QMessageBox.information(self, "Success", f"Default account set to {account}!")
except subprocess.CalledProcessError as e:
error_message = f"Error setting default account: {e.stderr.decode()}"
logging.error(error_message)
QMessageBox.critical(self, "Error", error_message)
def remove_account(self, dialog, username):
if username.strip() == '':
QMessageBox.warning(dialog, "Warning", "Please select an account to remove.")
return
# Remove any leading " * " from the username
username = username.strip().lstrip(" * ")
# Ask for confirmation twice before removing the account
confirm_message = f"Are you sure you want to remove the account '{username}'?\nThis action cannot be undone."
confirm_dialog = QMessageBox.question(self, "Confirm Removal", confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if confirm_dialog == QMessageBox.Yes:
confirm_message_again = "This action is irreversible. Are you absolutely sure?"
confirm_dialog_again = QMessageBox.question(self, "Confirm Removal", confirm_message_again, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if confirm_dialog_again == QMessageBox.Yes:
try:
subprocess.run(['picomc', 'account', 'remove', username], check=True)
QMessageBox.information(self, "Success", f"Account '{username}' removed successfully!")
self.populate_accounts(dialog.findChild(QComboBox))
except subprocess.CalledProcessError as e:
error_message = f"Error removing account: {e.stderr.decode()}"
logging.error(error_message)
QMessageBox.critical(self, "Error", error_message)
def show_about_dialog(self):
about_message = "PicoDulce Launcher\n\nA simple GUI for the picomc project."
QMessageBox.about(self, "About", about_message)
def create_dark_palette(self):
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(25, 25, 25))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.white)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Link, QColor(42, 130, 218))
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.white)
return palette
def open_mod_loader_menu(self):
dialog = QDialog(self)
dialog.setWindowTitle('Mod Loader Installer')
dialog.setFixedSize(300, 200)
# Create title label
title_label = QLabel('Mod Loader Installer')
title_label.setFont(QFont("Arial", 14))
# Create checkboxes for mod loaders
forge_checkbox = QCheckBox('Forge')
fabric_checkbox = QCheckBox('Fabric')
# Create dropdown menu for versions
version_combo = QComboBox()
def update_versions():
version_combo.clear()
if forge_checkbox.isChecked():
self.populate_available_releases(version_combo, True, False)
elif fabric_checkbox.isChecked():
self.populate_available_releases(version_combo, False, True)
forge_checkbox.clicked.connect(update_versions)
fabric_checkbox.clicked.connect(update_versions)
# Set layout
layout = QVBoxLayout()
layout.addWidget(title_label)
layout.addWidget(forge_checkbox)
layout.addWidget(fabric_checkbox)
layout.addWidget(version_combo)
# Create install button
install_button = QPushButton('Install')
install_button.clicked.connect(lambda: self.install_mod_loader(dialog, version_combo.currentText(), forge_checkbox.isChecked(), fabric_checkbox.isChecked()))
layout.addWidget(install_button)
dialog.setLayout(layout)
dialog.exec_()
def populate_available_releases(self, version_combo, install_forge, install_fabric):
try:
process = subprocess.Popen(['picomc', 'version', 'list', '--release'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, process.args, error)
except FileNotFoundError:
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
return
except subprocess.CalledProcessError as e:
logging.error("Error: %s", e.stderr)
return
if install_fabric:
releases = [version for version in output.splitlines() if version.startswith("1.") and int(version.split('.')[1]) >= 14]
elif install_forge:
releases = [version for version in output.splitlines() if version.startswith("1.") and float(version.split('.')[1]) >= 5]
else:
releases = output.splitlines()
version_combo.clear()
version_combo.addItems(releases)
def install_mod_loader(self, dialog, version, install_forge, install_fabric):
if not install_forge and not install_fabric:
QMessageBox.warning(dialog, "Select Mod Loader", "Please select at least one mod loader.")
return
mod_loader = None
if install_forge:
mod_loader = 'forge'
elif install_fabric:
mod_loader = 'fabric'
if not mod_loader:
QMessageBox.warning(dialog, "Select Mod Loader", "Please select at least one mod loader.")
return
try:
if mod_loader == 'forge':
subprocess.run(['picomc', 'mod', 'loader', 'forge', 'install', '--game', version], check=True)
elif mod_loader == 'fabric':
subprocess.run(['picomc', 'mod', 'loader', 'fabric', 'install', version], check=True)
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
self.populate_installed_versions() # Refresh the installed versions list after installation
except subprocess.CalledProcessError as e:
error_message = f"Error installing {mod_loader} for version {version}: {e.stderr.decode()}"
QMessageBox.critical(self, "Error", error_message)
logging.error(error_message)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = PicomcVersionSelector()
window.show()
sys.exit(app.exec_())