feat: full implementation - switcher, device watcher, hotkeys, tray, settings UI
This commit is contained in:
342
ui/settings_window.py
Normal file
342
ui/settings_window.py
Normal file
@@ -0,0 +1,342 @@
|
||||
import copy
|
||||
import logging
|
||||
import sys
|
||||
import winreg
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow, QWidget, QTabWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QListWidget, QListWidgetItem, QLabel, QLineEdit,
|
||||
QFormLayout, QSpinBox, QCheckBox, QComboBox, QGroupBox,
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VCP_INPUT_OPTIONS = [
|
||||
(15, "DisplayPort-1 (0x0F)"),
|
||||
(16, "DisplayPort-2 (0x10)"),
|
||||
(17, "HDMI-1 (0x11)"),
|
||||
(18, "HDMI-2 (0x12)"),
|
||||
(1, "VGA-1 (0x01)"),
|
||||
(3, "DVI-1 (0x03)"),
|
||||
]
|
||||
|
||||
|
||||
class SettingsWindow(QMainWindow):
|
||||
def __init__(self, config_manager, switcher, hotkey_manager):
|
||||
super().__init__()
|
||||
self._config_manager = config_manager
|
||||
self._switcher = switcher
|
||||
self._hotkey_manager = hotkey_manager
|
||||
self._working_config = copy.deepcopy(config_manager.get())
|
||||
|
||||
self.setWindowTitle("MonitorSwitcher \u2014 Ustawienia")
|
||||
self.setMinimumSize(640, 520)
|
||||
|
||||
tabs = QTabWidget()
|
||||
tabs.addTab(self._build_profiles_tab(), "Profile")
|
||||
tabs.addTab(self._build_devices_tab(), "Urzadzenia BT")
|
||||
tabs.addTab(self._build_hotkeys_tab(), "Hotkeys")
|
||||
tabs.addTab(self._build_general_tab(), "Ogolne")
|
||||
|
||||
save_btn = QPushButton("Zapisz i zamknij")
|
||||
save_btn.clicked.connect(self._save_and_close)
|
||||
cancel_btn = QPushButton("Anuluj")
|
||||
cancel_btn.clicked.connect(self.close)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addStretch()
|
||||
btn_row.addWidget(cancel_btn)
|
||||
btn_row.addWidget(save_btn)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(tabs)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
central = QWidget()
|
||||
central.setLayout(layout)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
# ── Profiles tab ──────────────────────────────────────────────────────────
|
||||
|
||||
def _build_profiles_tab(self) -> QWidget:
|
||||
widget = QWidget()
|
||||
layout = QHBoxLayout(widget)
|
||||
|
||||
left = QVBoxLayout()
|
||||
self._profile_list = QListWidget()
|
||||
self._profile_list.currentRowChanged.connect(self._on_profile_selected)
|
||||
left.addWidget(QLabel("Profile:"))
|
||||
left.addWidget(self._profile_list)
|
||||
|
||||
add_btn = QPushButton("+ Dodaj")
|
||||
add_btn.clicked.connect(self._add_profile)
|
||||
del_btn = QPushButton("Usun")
|
||||
del_btn.clicked.connect(self._delete_profile)
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addWidget(add_btn)
|
||||
btn_row.addWidget(del_btn)
|
||||
left.addLayout(btn_row)
|
||||
|
||||
right = QVBoxLayout()
|
||||
self._profile_name_edit = QLineEdit()
|
||||
self._profile_name_edit.setPlaceholderText("Nazwa profilu")
|
||||
self._profile_name_edit.textChanged.connect(self._on_profile_name_changed)
|
||||
right.addWidget(QLabel("Nazwa:"))
|
||||
right.addWidget(self._profile_name_edit)
|
||||
|
||||
self._monitor_combos: list[QComboBox] = []
|
||||
monitor_group = QGroupBox("Wejscia monitorow")
|
||||
monitor_layout = QFormLayout()
|
||||
for i in range(2):
|
||||
combo = QComboBox()
|
||||
for vcp, label in VCP_INPUT_OPTIONS:
|
||||
combo.addItem(label, vcp)
|
||||
combo.currentIndexChanged.connect(
|
||||
lambda _, idx=i: self._on_monitor_input_changed(idx)
|
||||
)
|
||||
self._monitor_combos.append(combo)
|
||||
monitor_layout.addRow(f"Monitor {i}:", combo)
|
||||
monitor_group.setLayout(monitor_layout)
|
||||
right.addWidget(monitor_group)
|
||||
right.addStretch()
|
||||
|
||||
layout.addLayout(left, 1)
|
||||
layout.addLayout(right, 2)
|
||||
|
||||
self._refresh_profile_list()
|
||||
return widget
|
||||
|
||||
def _refresh_profile_list(self) -> None:
|
||||
self._profile_list.clear()
|
||||
for p in self._working_config.get("profiles", []):
|
||||
self._profile_list.addItem(QListWidgetItem(p["name"]))
|
||||
if self._profile_list.count() > 0:
|
||||
self._profile_list.setCurrentRow(0)
|
||||
|
||||
def _on_profile_selected(self, row: int) -> None:
|
||||
profiles = self._working_config.get("profiles", [])
|
||||
if row < 0 or row >= len(profiles):
|
||||
return
|
||||
profile = profiles[row]
|
||||
self._profile_name_edit.blockSignals(True)
|
||||
self._profile_name_edit.setText(profile["name"])
|
||||
self._profile_name_edit.blockSignals(False)
|
||||
for combo in self._monitor_combos:
|
||||
combo.blockSignals(True)
|
||||
for inp in profile.get("monitor_inputs", []):
|
||||
idx = inp["monitor_index"]
|
||||
vcp = inp["vcp_value"]
|
||||
if idx < len(self._monitor_combos):
|
||||
data_idx = self._monitor_combos[idx].findData(vcp)
|
||||
self._monitor_combos[idx].setCurrentIndex(
|
||||
data_idx if data_idx >= 0 else 0
|
||||
)
|
||||
for combo in self._monitor_combos:
|
||||
combo.blockSignals(False)
|
||||
|
||||
def _on_profile_name_changed(self, text: str) -> None:
|
||||
row = self._profile_list.currentRow()
|
||||
profiles = self._working_config.get("profiles", [])
|
||||
if 0 <= row < len(profiles):
|
||||
profiles[row]["name"] = text
|
||||
self._profile_list.item(row).setText(text)
|
||||
|
||||
def _on_monitor_input_changed(self, monitor_idx: int) -> None:
|
||||
row = self._profile_list.currentRow()
|
||||
profiles = self._working_config.get("profiles", [])
|
||||
if row < 0 or row >= len(profiles):
|
||||
return
|
||||
vcp = self._monitor_combos[monitor_idx].currentData()
|
||||
inputs = profiles[row].setdefault("monitor_inputs", [])
|
||||
for inp in inputs:
|
||||
if inp["monitor_index"] == monitor_idx:
|
||||
inp["vcp_value"] = vcp
|
||||
return
|
||||
inputs.append({"monitor_index": monitor_idx, "vcp_value": vcp})
|
||||
|
||||
def _add_profile(self) -> None:
|
||||
new_id = f"profile_{len(self._working_config['profiles'])}"
|
||||
new_profile = {
|
||||
"id": new_id,
|
||||
"name": "Nowy profil",
|
||||
"hotkey": "",
|
||||
"monitor_inputs": [
|
||||
{"monitor_index": 0, "vcp_value": 15},
|
||||
{"monitor_index": 1, "vcp_value": 15},
|
||||
],
|
||||
}
|
||||
self._working_config["profiles"].append(new_profile)
|
||||
self._profile_list.addItem(QListWidgetItem(new_profile["name"]))
|
||||
self._profile_list.setCurrentRow(self._profile_list.count() - 1)
|
||||
|
||||
def _delete_profile(self) -> None:
|
||||
row = self._profile_list.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
self._working_config["profiles"].pop(row)
|
||||
self._refresh_profile_list()
|
||||
|
||||
# ── Devices tab ───────────────────────────────────────────────────────────
|
||||
|
||||
def _build_devices_tab(self) -> QWidget:
|
||||
widget = QWidget()
|
||||
outer = QVBoxLayout(widget)
|
||||
outer.addWidget(QLabel("Urzadzenia BT wyzwalajace przelaczeanie profilu:"))
|
||||
|
||||
self._devices_container = QVBoxLayout()
|
||||
outer.addLayout(self._devices_container)
|
||||
|
||||
add_btn = QPushButton("+ Dodaj urzadzenie")
|
||||
add_btn.clicked.connect(lambda: self._add_device_row())
|
||||
outer.addWidget(add_btn)
|
||||
outer.addStretch()
|
||||
|
||||
for trigger in self._working_config.get("device_triggers", []):
|
||||
self._add_device_row(trigger)
|
||||
|
||||
return widget
|
||||
|
||||
def _add_device_row(self, trigger: dict | None = None) -> None:
|
||||
if trigger is None:
|
||||
trigger = {
|
||||
"name": "Nowe urzadzenie",
|
||||
"bt_device_id": "",
|
||||
"on_connect": self._first_profile_id(),
|
||||
"on_disconnect": self._first_profile_id(),
|
||||
"enabled": True,
|
||||
}
|
||||
self._working_config.setdefault("device_triggers", []).append(trigger)
|
||||
|
||||
group = QGroupBox(trigger.get("name", ""))
|
||||
form = QFormLayout(group)
|
||||
|
||||
name_edit = QLineEdit(trigger.get("name", ""))
|
||||
bt_id_edit = QLineEdit(trigger.get("bt_device_id", ""))
|
||||
connect_combo = self._profile_combo(trigger.get("on_connect", ""))
|
||||
disconnect_combo = self._profile_combo(trigger.get("on_disconnect", ""))
|
||||
enabled_cb = QCheckBox()
|
||||
enabled_cb.setChecked(trigger.get("enabled", True))
|
||||
|
||||
form.addRow("Nazwa:", name_edit)
|
||||
form.addRow("BT Device ID (fragment):", bt_id_edit)
|
||||
form.addRow("Przy podlaczeniu:", connect_combo)
|
||||
form.addRow("Przy odlaczeniu:", disconnect_combo)
|
||||
form.addRow("Aktywne:", enabled_cb)
|
||||
|
||||
def update_trigger():
|
||||
trigger["name"] = name_edit.text()
|
||||
trigger["bt_device_id"] = bt_id_edit.text()
|
||||
trigger["on_connect"] = connect_combo.currentData()
|
||||
trigger["on_disconnect"] = disconnect_combo.currentData()
|
||||
trigger["enabled"] = enabled_cb.isChecked()
|
||||
group.setTitle(trigger["name"])
|
||||
|
||||
name_edit.textChanged.connect(lambda _: update_trigger())
|
||||
bt_id_edit.textChanged.connect(lambda _: update_trigger())
|
||||
connect_combo.currentIndexChanged.connect(lambda _: update_trigger())
|
||||
disconnect_combo.currentIndexChanged.connect(lambda _: update_trigger())
|
||||
enabled_cb.stateChanged.connect(lambda _: update_trigger())
|
||||
|
||||
self._devices_container.addWidget(group)
|
||||
|
||||
def _profile_combo(self, selected_id: str) -> QComboBox:
|
||||
combo = QComboBox()
|
||||
for p in self._working_config.get("profiles", []):
|
||||
combo.addItem(p["name"], p["id"])
|
||||
idx = combo.findData(selected_id)
|
||||
if idx >= 0:
|
||||
combo.setCurrentIndex(idx)
|
||||
return combo
|
||||
|
||||
def _first_profile_id(self) -> str:
|
||||
profiles = self._working_config.get("profiles", [])
|
||||
return profiles[0]["id"] if profiles else ""
|
||||
|
||||
# ── Hotkeys tab ───────────────────────────────────────────────────────────
|
||||
|
||||
def _build_hotkeys_tab(self) -> QWidget:
|
||||
widget = QWidget()
|
||||
layout = QVBoxLayout(widget)
|
||||
layout.addWidget(QLabel("Przypisz skroty klawiszowe do profili:"))
|
||||
layout.addWidget(QLabel("Format: ctrl+alt+1, win+F1, itp."))
|
||||
|
||||
form = QFormLayout()
|
||||
for profile in self._working_config.get("profiles", []):
|
||||
edit = QLineEdit(profile.get("hotkey", ""))
|
||||
edit.setPlaceholderText("np. ctrl+alt+1")
|
||||
pid = profile["id"]
|
||||
edit.textChanged.connect(
|
||||
lambda text, p=pid: self._on_hotkey_changed(p, text)
|
||||
)
|
||||
form.addRow(f"{profile['name']}:", edit)
|
||||
|
||||
layout.addLayout(form)
|
||||
layout.addStretch()
|
||||
return widget
|
||||
|
||||
def _on_hotkey_changed(self, profile_id: str, text: str) -> None:
|
||||
for p in self._working_config.get("profiles", []):
|
||||
if p["id"] == profile_id:
|
||||
p["hotkey"] = text.strip()
|
||||
return
|
||||
|
||||
# ── General tab ───────────────────────────────────────────────────────────
|
||||
|
||||
def _build_general_tab(self) -> QWidget:
|
||||
widget = QWidget()
|
||||
layout = QFormLayout(widget)
|
||||
|
||||
self._autostart_cb = QCheckBox()
|
||||
self._autostart_cb.setChecked(
|
||||
self._working_config.get("general", {}).get("autostart", True)
|
||||
)
|
||||
self._minimize_cb = QCheckBox()
|
||||
self._minimize_cb.setChecked(
|
||||
self._working_config.get("general", {}).get("minimize_to_tray", True)
|
||||
)
|
||||
self._debounce_spin = QSpinBox()
|
||||
self._debounce_spin.setRange(100, 5000)
|
||||
self._debounce_spin.setSuffix(" ms")
|
||||
self._debounce_spin.setValue(
|
||||
self._working_config.get("general", {}).get("debounce_ms", 500)
|
||||
)
|
||||
|
||||
layout.addRow("Autostart z Windows:", self._autostart_cb)
|
||||
layout.addRow("Minimalizuj do tray przy zamknieciu:", self._minimize_cb)
|
||||
layout.addRow("Debounce (opoznienie przelaczenia):", self._debounce_spin)
|
||||
|
||||
return widget
|
||||
|
||||
# ── Save ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def _save_and_close(self) -> None:
|
||||
general = self._working_config.setdefault("general", {})
|
||||
general["autostart"] = self._autostart_cb.isChecked()
|
||||
general["minimize_to_tray"] = self._minimize_cb.isChecked()
|
||||
general["debounce_ms"] = self._debounce_spin.value()
|
||||
|
||||
self._config_manager.update(self._working_config)
|
||||
self._hotkey_manager.unregister_all()
|
||||
self._hotkey_manager.register_all()
|
||||
_set_autostart(general.get("autostart", False))
|
||||
self.close()
|
||||
|
||||
|
||||
def _set_autostart(enable: bool) -> None:
|
||||
key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
app_name = "MonitorSwitcher"
|
||||
exe_path = sys.executable if not getattr(sys, "frozen", False) else sys.executable
|
||||
try:
|
||||
with winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE
|
||||
) as key:
|
||||
if enable:
|
||||
winreg.SetValueEx(key, app_name, 0, winreg.REG_SZ, f'"{exe_path}"')
|
||||
else:
|
||||
try:
|
||||
winreg.DeleteValue(key, app_name)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
logger.warning("Autostart registry error: %s", exc)
|
||||
Reference in New Issue
Block a user