diff --git a/kde-display-profile-manager.py b/kde-display-profile-manager.py index c35f13d..4b39422 100755 --- a/kde-display-profile-manager.py +++ b/kde-display-profile-manager.py @@ -5,16 +5,18 @@ import json import subprocess import sys import os +import shlex from pathlib import Path # Try to import PySide6 for the GUI try: from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QListWidget, QPushButton, QInputDialog, QMessageBox, QLabel, + QListWidget, QListWidgetItem, QPushButton, QInputDialog, QMessageBox, QLabel, QFileDialog ) - from PySide6.QtCore import Qt + from PySide6.QtGui import QIcon + from PySide6.QtCore import Qt, QSize PYSIDE_AVAILABLE = True except ImportError: PYSIDE_AVAILABLE = False @@ -206,7 +208,7 @@ class DisplayProfileManagerGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("KDE Display Profile Manager") - self.setMinimumSize(400, 300) + self.setMinimumSize(420, 300) # Ensure default directory exists DEFAULT_PROFILE_DIR.mkdir(parents=True, exist_ok=True) @@ -219,23 +221,35 @@ class DisplayProfileManagerGUI(QMainWindow): self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) - layout.addWidget(QLabel("Available Profiles:")) + # Header layout + header_layout = QHBoxLayout() + header_layout.addWidget(QLabel("Available Profiles:")) + header_layout.addStretch() + + self.refresh_btn = QPushButton() + self.refresh_btn.setIcon(QIcon.fromTheme("view-refresh")) + self.refresh_btn.setIconSize(QSize(20, 20)) + self.refresh_btn.setToolTip("Refresh Profiles") + self.refresh_btn.setFlat(True) + self.refresh_btn.clicked.connect(self.refresh_profiles) + header_layout.addWidget(self.refresh_btn) + + layout.addLayout(header_layout) self.profile_list = QListWidget() + self.profile_list.itemDoubleClicked.connect(self.on_load_clicked) layout.addWidget(self.profile_list) btn_layout = QHBoxLayout() - self.load_btn = QPushButton("Load Profile") - self.load_btn.clicked.connect(self.on_load_clicked) - btn_layout.addWidget(self.load_btn) - self.save_btn = QPushButton("Save Current") self.save_btn.clicked.connect(self.on_save_clicked) btn_layout.addWidget(self.save_btn) + + self.load_btn = QPushButton("Load Selected") + self.load_btn.clicked.connect(self.on_load_clicked) + btn_layout.addWidget(self.load_btn) - self.refresh_btn = QPushButton("Refresh") - self.refresh_btn.clicked.connect(self.refresh_profiles) - btn_layout.addWidget(self.refresh_btn) + layout.addLayout(btn_layout) @@ -244,7 +258,44 @@ class DisplayProfileManagerGUI(QMainWindow): if DEFAULT_PROFILE_DIR.exists(): profiles = sorted(DEFAULT_PROFILE_DIR.glob("*.json")) for profile in profiles: - self.profile_list.addItem(profile.stem) + name = profile.stem + # Create item without text to prevent double-rendering/blurriness + item = QListWidgetItem(self.profile_list) + item.setData(Qt.UserRole, name) + + # Custom widget for the row + widget = QWidget() + row_layout = QHBoxLayout(widget) + row_layout.setContentsMargins(5, 2, 5, 2) + row_layout.setSpacing(5) + + label = QLabel(name) + # Ensure the label doesn't inherit transparency issues + label.setStyleSheet("background: transparent;") + row_layout.addWidget(label) + row_layout.addStretch() + + # Copy button + copy_btn = QPushButton() + copy_btn.setIcon(QIcon.fromTheme("edit-copy")) + copy_btn.setIconSize(QSize(16, 16)) + copy_btn.setFlat(True) + copy_btn.setToolTip("Copy Load Command") + copy_btn.clicked.connect(lambda checked=False, n=name: self.copy_profile_cmd(n)) + row_layout.addWidget(copy_btn) + + # Delete button + del_btn = QPushButton() + del_btn.setIcon(QIcon.fromTheme("user-trash")) + del_btn.setIconSize(QSize(16, 16)) + del_btn.setFlat(True) + del_btn.setToolTip("Delete Profile") + del_btn.clicked.connect(lambda checked=False, n=name: self.delete_profile(n)) + row_layout.addWidget(del_btn) + + item.setSizeHint(widget.sizeHint()) + self.profile_list.addItem(item) + self.profile_list.setItemWidget(item, widget) def get_default_profile_name(self): existing_names = [] @@ -276,13 +327,16 @@ class DisplayProfileManagerGUI(QMainWindow): except Exception as e: QMessageBox.critical(self, "Error", f"Failed to save profile: {e}") - def on_load_clicked(self): - selected_item = self.profile_list.currentItem() - if not selected_item: + def on_load_clicked(self, item=None): + if not isinstance(item, QListWidgetItem): + item = self.profile_list.currentItem() + + if not item: QMessageBox.warning(self, "No Selection", "Please select a profile to load.") return - profile_name = selected_item.text() + # Retrieve name from UserRole data instead of item.text() + profile_name = item.data(Qt.UserRole) profile_path = DEFAULT_PROFILE_DIR / f"{profile_name}.json" try: @@ -291,11 +345,40 @@ class DisplayProfileManagerGUI(QMainWindow): except Exception as e: QMessageBox.critical(self, "Error", f"Failed to load profile: {e}") + def copy_profile_cmd(self, profile_name): + profile_path = DEFAULT_PROFILE_DIR / f"{profile_name}.json" + script_path = os.path.abspath(__file__) + cmd = f"python3 {shlex.quote(script_path)} load {shlex.quote(str(profile_path))}" + + clipboard = QApplication.clipboard() + clipboard.setText(cmd) + + QMessageBox.information(self, "Command Copied", f"The following command has been copied to your clipboard:\n\n{cmd}") + + def delete_profile(self, profile_name): + profile_path = DEFAULT_PROFILE_DIR / f"{profile_name}.json" + + reply = QMessageBox.question(self, "Confirm Delete", + f"Are you sure you want to delete profile '{profile_name}'?", + QMessageBox.Yes | QMessageBox.No) + + if reply == QMessageBox.Yes: + try: + os.remove(profile_path) + self.refresh_profiles() + QMessageBox.information(self, "Success", f"Profile '{profile_name}' deleted.") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to delete profile: {e}") + def show_gui(): if not PYSIDE_AVAILABLE: print("Error: PySide6 is not installed. Please install it to use the GUI.", file=sys.stderr) sys.exit(1) + # Use 'Round' instead of 'PassThrough' as it is often more stable for font rendering + if hasattr(Qt, "HighDpiScaleFactorRoundingPolicy"): + QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.Round) + app = QApplication(sys.argv) window = DisplayProfileManagerGUI() window.show()